#pragma semicolon 1
#define PLUGIN_AUTHOR "jenz"
#define g_dLength 400
#define PLUGIN_VERSION "1.1"
#pragma newdecls required

#include <sourcemod>
#include <sourcebanspp>
#include <clientprefs>
#include <cstrike>
#include <sdktools>

Database g_dDatabase;
int g_disable_html_motd[MAXPLAYERS + 1];
Handle g_hOnReportBanPostForward;

public Plugin myinfo = 
{
	name = "jenz ban detector",
	author = PLUGIN_AUTHOR,
	description = "my ban detector maybe catches you",
	version = PLUGIN_VERSION,
	url = "www.unloze.com"
};

public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max)
{
    RegPluginLibrary("jenz_ban_detector");
    return APLRes_Success;
}

public void OnPluginStart()
{
    g_hOnReportBanPostForward = CreateGlobalForward("BanDetectorPost", ET_Ignore, Param_Cell, Param_String);
    if (!g_dDatabase)
    {
        Database.Connect(SQL_OnDatabaseConnect, "jenz_ban_detector");
    }
}

public void OnMapStart()
{
    if (!g_dDatabase)
    {
        Database.Connect(SQL_OnDatabaseConnect, "jenz_ban_detector");
    }
}

public void SQL_OnDatabaseConnect(Database db, const char[] error, any data)
{
    if(!db || strlen(error))
    {
        LogError("Database error: %s", error);
        return;
    }
    g_dDatabase = db;
}

public void OnClientDisconnect(int client)
{
    g_disable_html_motd[client] = 0;
}

public void OnClientPostAdminCheck(int client)
{
    g_disable_html_motd[client] = 0;
    if (!IsFakeClient(client) && !IsClientSourceTV(client))
    {
        SQL_addEntry(client);
    }
}

public void SQL_addEntry(int client)
{
    char sQuery[g_dLength];
    char sSID[MAX_NAME_LENGTH];
    char sIP[MAX_NAME_LENGTH];
    char sName[MAX_NAME_LENGTH];
    GetClientName(client, sName, sizeof(sName));
    int size2 = 2 * strlen(sName) + 1;
    char[] sEscapedName = new char[size2 + 1];
    GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID));
    g_dDatabase.Escape(sName, sEscapedName, size2 + 1);
    GetClientIP(client, sIP, sizeof(sIP));
    //Format(sQuery, sizeof(sQuery), "insert ignore into `ban_detector` (`steamid`, `ip`, `name`) SELECT '%s', '%s','%s'", sSID, sIP,sEscapedName);
    Format(sQuery, sizeof(sQuery), "insert ignore into `ban_detector` (`steamid`, `ip`, `name`) SELECT '%s', '%s','%s'", sSID, sIP,sEscapedName);
    g_dDatabase.Query(SQL_callback_insert_ignore, sQuery, GetClientSerial(client), DBPrio_Low);
}

public void SQL_callback_insert_ignore(Database db, DBResultSet results, const char[] error, int Serial)
{
    if(!db || strlen(error))
    {
        LogError("Database error: %s", error);
        delete results;
        return;
    }
    delete results;

    int client; 
    if ((client = GetClientFromSerial(Serial)) == 0)
    {
        return;
    }
    if (IsValidClient(client))
    {
        QueryClientConVar(client, "cl_disablehtmlmotd", CvarQueryFinished);
    }
}

public void CvarQueryFinished(QueryCookie cookie, int client, ConVarQueryResult res, const char[] sCvarName, const char[] sCvarVal)
{
    if (res != ConVarQuery_Okay)
    {
        return;
    }

    int disable_html_motd = StringToInt(sCvarVal);
    if (IsValidClient(client))
    {
        g_disable_html_motd[client] = disable_html_motd;
        char sQuery[g_dLength];
        char sSID[MAX_NAME_LENGTH];
        GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID));
        Format(sQuery, sizeof(sQuery), "UPDATE ban_detector SET last_connect = now(), disable_html_motd = '%i' where steamid = '%s'", disable_html_motd, sSID);
        g_dDatabase.Query(SQL_callback_update, sQuery, GetClientSerial(client), DBPrio_Low);
    }
}

public void SQL_callback_update(Database db, DBResultSet results, const char[] error, int Serial)
{
    if(!db || strlen(error))
    {
        LogError("Database error: %s", error);
        delete results;
        return;
    }
    delete results;

    int client; 
    if ((client = GetClientFromSerial(Serial)) == 0)
    {
        return;
    }
    if (IsValidClient(client) && !g_disable_html_motd[client])
    {
        CreateTimer(7.0, re_open_motd, Serial);
    }
}

public Action re_open_motd(Handle hTimer, int Serial)
{
    int client; 
    if ((client = GetClientFromSerial(Serial)) == 0)
    {
        return Plugin_Handled;
    }
    if (IsValidClient(client))
    {
        //reopening the menu again after info got stored. it still generates the same fingerprint despite not showing the client any menu.
        Handle panel = CreateKeyValues("data");
        KvSetString(panel, "title", "");
        KvSetString(panel, "type", "2");
        KvSetString(panel, "msg", "https://unloze.com/motd/CSS_ZE_MOTD.html");
        ShowVGUIPanel(client, "info", panel, false);
        CloseHandle(panel);
        CreateTimer(5.0, SQL_Select_fingerprints, Serial);
    }
    return Plugin_Handled;
}

public Action SQL_Select_fingerprints(Handle hTimer, int Serial)
{
    int client; 
    if ((client = GetClientFromSerial(Serial)) == 0)
    {
        return Plugin_Handled;
    }
    if (IsValidClient(client))
    {
        char sQuery[g_dLength];
        char sSID[MAX_NAME_LENGTH];
        char sIP[MAX_NAME_LENGTH];
        GetClientIP(client, sIP, sizeof(sIP));
        GetClientAuthId(client, AuthId_Steam2, sSID, sizeof(sSID));
        //steamID is unique but fingerprint might occur multiple times.
        Format(sQuery, sizeof(sQuery), "select steamid from ban_detector.ban_detector where fingerprint = (select fingerprint from ban_detector.ban_detector where steamid = '%s' and fingerprint is not null and length(fingerprint) > 5)", sSID);
        //PrintToChatAll("sQuery: %s", sQuery);
        g_dDatabase.Query(SQL_FindFingerPrints, sQuery, Serial, DBPrio_Low);
    }
    return Plugin_Handled;
}

public void SQL_FindFingerPrints(Database db, DBResultSet results, const char[] error, int Serial)
{
    if (!db || strlen(error))
    {
        LogError("Database error: %s", error);
        delete results;
        return;
    }
    int client;
    if ((client = GetClientFromSerial(Serial)) == 0)
    {
        delete results;
        return;
    }
    if (!IsValidClient(client))
    {
        delete results;
        return;
    }

    //god knows how big this might need to be
    char sQuery[4344];
    Format(sQuery, sizeof(sQuery), "select authid from unloze_sourceban.sb_bans where authid in (");

    //cant rely on IP cause several chinese players share VPN and end up with same IP despite clearly being different people. 
    //now instead of fingerprint returns all steam IDs related to the specific fingerprint.
    bool first = true;
    while (results.RowCount > 0 && results.FetchRow())
    {
        char steamid[64];
        results.FetchString(0, steamid, sizeof(steamid));
        if (first)
        {
            Format(steamid, sizeof(steamid), "'%s'", steamid);
            StrCat(sQuery, sizeof(sQuery), steamid);
            first = false;
        }
        else
        {
            Format(steamid, sizeof(steamid), ",'%s'", steamid);
            StrCat(sQuery, sizeof(sQuery), steamid);
        }
    }
    StrCat(sQuery, sizeof(sQuery), ") and (RemoveType != 'U' or RemoveType is NULL) and (ends > UNIX_TIMESTAMP() + 3600 or ends = created) order by created desc limit 1");
    delete results;
    if (!first)
    {
        g_dDatabase.Query(sql_select_sb_bans, sQuery, Serial, DBPrio_Low);
    }
}

public void sql_select_sb_bans(Database db, DBResultSet results, const char[] error, int Serial)
{
    if (!db || strlen(error))
    {
        delete results;
        LogError("Database error 2: %s", error);
        return;
    }

    int client;
    if ((client = GetClientFromSerial(Serial)) == 0)
    {
        delete results;
        return;
    }
    if (!IsValidClient(client))
    {
        delete results;
        return;
    }

    //if we get a result its a steam ID that still has an active ban. the steam ID that is banned is confirmed to have the same fingerprint as the connecting client.
    if (results.RowCount && results.FetchRow())
    {
        char sSID[MAX_NAME_LENGTH];
        results.FetchString(0, sSID, sizeof(sSID));    
        Call_StartForward(g_hOnReportBanPostForward);
        Call_PushCell(client);
        Call_PushString(sSID);
        Call_Finish();
        //bans need to be over 1 hour long for getting detected
        char message[1024];
        Format(message, sizeof(message), "Ban avoiding (Jenz ban detector). SteamID avoiding ban: %s", sSID);
        //instead of permaban now just banning the alt account for circa 3 months.
        SBPP_BanPlayer(0, client, 131487, message);
        //first parameter is client doing the ban, should be 0 so its console.
        //second parameter is the target getting banned
        //third parameter was the actual timevalue.
    }
    delete results;
}

stock bool IsValidClient(int client)
{
    if (client > 0 && client <= MaxClients && IsClientConnected(client) && IsClientInGame(client) && !IsFakeClient(client))
        return true;
    return false;
}
