<commit_before>/**
* sourcebans.sp
*
* This file contains all Source Server Plugin Functions
* @author SourceBans Development Team
* @version 0.0.0.$Rev: 108 $
* @copyright InterWave Studios (www.interwavestudios.com)
* @package SourceBans
* @link http://www.sourcebans.net
*/

#pragma semicolon 1
#include <sourcemod>
#include <sourcebans>

#undef REQUIRE_PLUGIN
#include <adminmenu>
#include "dbi.inc"

#define SB_VERSION "1.4.8"

//GLOBAL DEFINES
#define YELLOW				0x01
#define NAMECOLOR			0x02
#define TEAMCOLOR			0x03
#define GREEN				0x04

#define DISABLE_ADDBAN		1
#define DISABLE_UNBAN		2

//#define DEBUG

enum State /* ConfigState */
{
	ConfigStateNone = 0,
	ConfigStateConfig,
	ConfigStateReasons,
	ConfigStateHacking
}

new g_BanTarget[MAXPLAYERS+1] = {-1, ...};
new g_BanTime[MAXPLAYERS+1] = {-1, ...};

new State:ConfigState;
new Handle:ConfigParser;

new Handle:hTopMenu = INVALID_HANDLE;

new const String:BLANK[] = "";
new const String:Prefix[] = "[SourceBans] ";

new String:ServerIp[24];
new String:ServerPort[7];
new String:DatabasePrefix[10] = "sb";
new String:WebsiteAddress[128];

/* Admin Stuff*/
new AdminCachePart:loadPart;
new bool:loadAdmins;
new bool:loadGroups;
new bool:loadOverrides;
new curLoading=0;
new AdminFlag:g_FlagLetters[26];

/* Admin KeyValues */
new String:groupsLoc[128];
new String:adminsLoc[128];
	
/* Cvar handle*/
new Handle:CvarHostIp;
new Handle:CvarPort;

/* Database handle */
new Handle:Database;
new Handle:SQLiteDB;

/* Menu file globals */
new Handle:ReasonMenuHandle;
new Handle:HackingMenuHandle;

/* Datapack and Timer handles */
new Handle:PlayerRecheck[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
new Handle:PlayerDataPack[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};

/* Player ban check status */
new bool:PlayerStatus[MAXPLAYERS + 1];

/* Disable of addban and unban */
new CommandDisable;
new bool:backupConfig = true;
new bool:enableAdmins = true;

/* Require a lastvisited from SB site */
new bool:requireSiteLogin = false;

/* Log Stuff */
new String:logFile[256];

/* Own Chat Reason */
new g_ownReasons[MAXPLAYERS+1] = {false, ...};

new MaxSlots;
new Float:RetryTime = 15.0;
new ProcessQueueTime = 5;
new bool:LateLoaded;
new bool:AutoAdd;
new bool:g_bConnecting = false;

new serverID = -1;

public Plugin:myinfo =
{
	name = "SourceBans",
	author = "SourceBans Development Team",
	description = "Advanced ban management for the Source engine",
	version = SB_VERSION,
	url = "http://www.sourcebans.net"
};

#if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 3
public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
#else
public bool:AskPluginLoad(Handle:myself, bool:late, String:error[], err_max)
#endif
{
	RegPluginLibrary("sourcebans");
	CreateNative("SBBanPlayer", Native_SBBanPlayer);
	LateLoaded = late;
	
	#if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 3
		return APLRes_Success;
	#else
		return true;
	#endif
}

public OnPluginStart()
{	
	LoadTranslations("common.phrases");
	LoadTranslations("plugin.basecommands");
	LoadTranslations("sourcebans.phrases");
	LoadTranslations("basebans.phrases");
	loadAdmins = loadGroups = loadOverrides = false;
	
	CvarHostIp = FindConVar("hostip");
	CvarPort = FindConVar("hostport");
	CreateConVar("sb_version", SB_VERSION, _, FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);
	RegServerCmd("sm_rehash",sm_rehash,"Reload SQL admins");
	RegAdminCmd("sm_ban", CommandBan, ADMFLAG_BAN, "sm_ban <#userid|name> <minutes|0> [reason]");
	RegAdminCmd("sm_banip", CommandBanIp, ADMFLAG_BAN, "sm_banip <ip|#userid|name> <time> [reason]");
	RegAdminCmd("sm_addban", CommandAddBan, ADMFLAG_RCON, "sm_addban <time> <steamid> [reason]");
	RegAdminCmd("sm_unban", CommandUnban, ADMFLAG_UNBAN, "sm_unban <steamid|ip> [reason]");
	RegAdminCmd("sb_reload",
				_CmdReload,
				ADMFLAG_RCON,
				"Reload sourcebans config and ban reason menu options",
				BLANK);
	
	RegConsoleCmd("say", ChatHook);
	RegConsoleCmd("say_team", ChatHook);
	
	if((ReasonMenuHandle = CreateMenu(ReasonSelected)) != INVALID_HANDLE)
	{
		SetMenuPagination(ReasonMenuHandle, 8);
		SetMenuExitBackButton(ReasonMenuHandle, true);
	}

	if((HackingMenuHandle = CreateMenu(HackingSelected)) != INVALID_HANDLE)
	{
		SetMenuPagination(HackingMenuHandle, 8);
		SetMenuExitBackButton(HackingMenuHandle, true);
	}
	
	g_FlagLetters['a'-'a'] = Admin_Reservation;
	g_FlagLetters['b'-'a'] = Admin_Generic;
	g_FlagLetters['c'-'a'] = Admin_Kick;
	g_FlagLetters['d'-'a'] = Admin_Ban;
	g_FlagLetters['e'-'a'] = Admin_Unban;
	g_FlagLetters['f'-'a'] = Admin_Slay;
	g_FlagLetters['g'-'a'] = Admin_Changemap;
	g_FlagLetters['h'-'a'] = Admin_Convars;
	g_FlagLetters['i'-'a'] = Admin_Config;
	g_FlagLetters['j'-'a'] = Admin_Chat;
	g_FlagLetters['k'-'a'] = Admin_Vote;
	g_FlagLetters['l'-'a'] = Admin_Password;
	g_FlagLetters['m'-'a'] = Admin_RCON;
	g_FlagLetters['n'-'a'] = Admin_Cheats;
	g_FlagLetters['o'-'a'] = Admin_Custom1;
	g_FlagLetters['p'-'a'] = Admin_Custom2;
	g_FlagLetters['q'-'a'] = Admin_Custom3;
	g_FlagLetters['r'-'a'] = Admin_Custom4;
	g_FlagLetters['s'-'a'] = Admin_Custom5;
	g_FlagLetters['t'-'a'] = Admin_Custom6;
	g_FlagLetters['z'-'a'] = Admin_Root;
	
	
	BuildPath(Path_SM, logFile, sizeof(logFile), "logs/sourcebans.log");
	g_bConnecting = true;
	
	// Catch config error and show link to FAQ
	if(!SQL_CheckConfig("sourcebans"))
	{
		if(ReasonMenuHandle != INVALID_HANDLE)
			CloseHandle(ReasonMenuHandle);
		if(HackingMenuHandle != INVALID_HANDLE)
			CloseHandle(HackingMenuHandle);
		LogToFile(logFile, "Database failure: Could not find Database conf \"sourcebans\". See FAQ: http://sourcebans.net/node/19");
		SetFailState("Database failure: Could not find Database conf \"sourcebans\"");
		return;
	}
	SQL_TConnect(GotDatabase, "sourcebans");
	
	BuildPath(Path_SM,groupsLoc,sizeof(groupsLoc),"configs/admin_groups.cfg");
	
	BuildPath(Path_SM,adminsLoc,sizeof(adminsLoc),"configs/admins.cfg");
	
	InitializeBackupDB();
	
	// This timer is what processes the SQLite queue when the database is unavailable
	CreateTimer(float(ProcessQueueTime * 60), ProcessQueue);
	
	/* Account for late loading */
	if(LateLoaded)
	{
		decl String:auth[30];
		for(new i = 1; i <= GetMaxClients(); i++)
		{
			if(IsClientConnected(i) && !IsFakeClient(i))
			{
				PlayerStatus[i] = false;
			}
			if(IsClientInGame(i) && !IsFakeClient(i))
			{
				GetClientAuthString(i, auth, sizeof(auth));
				OnClientAuthorized(i, auth);
			}
		}
	}
}

public OnAllPluginsLoaded()
{
	new Handle:topmenu;
#if defined DEBUG
	LogToFile(logFile, "OnAllPluginsLoaded()");
#endif
		
	if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE))
	{
		OnAdminMenuReady(topmenu);
	}
}

public OnConfigsExecuted()
{
	decl String:filename[200];
	BuildPath(Path_SM, filename, sizeof(filename), "plugins/basebans.smx");
	if(FileExists(filename))
	{
		decl String:newfilename[200];
		BuildPath(Path_SM, newfilename, sizeof(newfilename), "plugins/disabled/basebans.smx");
		ServerCommand("sm plugins unload basebans");
		if(FileExists(newfilename))
			DeleteFile(newfilename);
		RenameFile(newfilename, filename);
		LogToFile(logFile, "plugins/basebans.smx was unloaded and moved to plugins/disabled/basebans.smx");
	}
}

public OnMapStart()
{
	MaxSlots = GetMaxClients();
	ResetSettings();
}

public OnMapEnd()
{
	for(new i = 0; i <= MaxSlots; i++)
	{
		if(PlayerDataPack[i] != INVALID_HANDLE)
		{
			/* Need to close reason pack */
			CloseHandle(PlayerDataPack[i]);
			PlayerDataPack[i] = INVALID_HANDLE;
		}
	}
}

// CLIENT CONNECTION FUNCTIONS //

public Action:OnClientPreAdminCheck(client)
{
	if (curLoading > 0)
		return Plugin_Stop;
	return Plugin_Continue;
}

public OnClientDisconnect(client)
{
	if(PlayerRecheck[client] != INVALID_HANDLE)
	{
		KillTimer(PlayerRecheck[client]);
		PlayerRecheck[client] = INVALID_HANDLE;
	}
	g_ownReasons[client] = false;
}

public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
{
	PlayerStatus[client] = false;
	return true;
}

public OnClientAuthorized(client, const String:auth[])
{
	/* Do not check bots nor check player with lan steamid. */
	if(auth[0] == 'B' || auth[9] == 'L' || Database == INVALID_HANDLE)
	{
		PlayerStatus[client] = true;
		return;
	}
	
	decl String:Query[256], String:ip[30];
	GetClientIP(client, ip, sizeof(ip));
	FormatEx(Query, sizeof(Query), "SELECT bid FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, auth[8], ip);
#if defined DEBUG
	LogToFile(logFile, "Checking ban for: %s", auth);
#endif
	
	SQL_TQuery(Database, VerifyBan, Query, GetClientUserId(client), DBPrio_High);
}

public OnRebuildAdminCache(AdminCachePart:part)
{
	loadPart = part;
	switch(loadPart)
	{
		case AdminCache_Overrides:
		loadOverrides = true;
		case AdminCache_Groups:
			loadGroups = true;
		case AdminCache_Admins:
			loadAdmins = true;
	}
	if(enableAdmins)
	{
		if(Database == INVALID_HANDLE) {
			if(!g_bConnecting) {
				g_bConnecting = true;
				SQL_TConnect(GotDatabase,"sourcebans");
			}
		}
		else {
			GotDatabase(Database,Database,"",0);
		}
	}
}

// COMMAND CODE //

public Action:ChatHook(client, args)
{
	// is this player preparing to ban someone
	if (g_ownReasons[client])
	{
		// get the reason
		new String:reason[512];
		GetCmdArgString(reason, sizeof(reason));
		StripQuotes(reason);
		
		g_ownReasons[client] = false;
		
		if(StrEqual(reason[0], "!noreason"))
		{
			PrintToChat(client, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Chat Reason Aborted");
			return Plugin_Handled;
		}
		
		// ban him!
		PrepareBan(client, g_BanTarget[client], g_BanTime[client], reason, sizeof(reason));
		
		// block the reason to be sent in chat
		return Plugin_Handled;
	}
	return Plugin_Continue;
}

public Action:_CmdReload(client, args)
{
	ResetSettings();
	return Plugin_Handled;
}

public Action:CommandBan(client, args)
{
	if(args < 2)
	{
		ReplyToCommand(client, "%sUsage: sm_ban <#userid|name> <time|0> [reason]", Prefix);
		return Plugin_Handled;
	}

	// This is mainly for me sanity since client used to be called admin and target used to be called client
	new admin = client;
	
	// Get the target, find target returns a message on failure so we do not
	decl String:buffer[100];
	GetCmdArg(1, buffer, sizeof(buffer));
	new target = FindTarget(client, buffer, true);
	if(target == -1)
	{
		return Plugin_Handled;
	}

	// Get the ban time
	GetCmdArg(2, buffer, sizeof(buffer));
	new time = StringToInt(buffer);
	if(!time && client && !(GetUserFlagBits(client) & (ADMFLAG_UNBAN|ADMFLAG_ROOT)))
	{
		ReplyToCommand(client, "You do not have Perm Ban Permission");
		return Plugin_Handled;
	}
	
	// Get the reason
	decl String:reason[128];
	if(args >= 3)
	{
		GetCmdArg(3, reason, sizeof(reason));
	}
	else
	{
		reason[0] = '\0';
	}
	
	g_BanTarget[client] = target;
	g_BanTime[client] = time;
	
	if(!PlayerStatus[target])
	{
		// The target has not been banned verify. It must be completed before you can ban anyone.
		ReplyToCommand(admin, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Ban Not Verified");
		return Plugin_Handled;
	}

	
	CreateBan(client, target, time, reason);
	return Plugin_Handled;
}

public Action:CommandBanIp(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "%sUsage: sm_banip <ip|#userid|name> <time> [reason]", Prefix);
		return Plugin_Handled;
	}

	decl len, next_len;
	decl String:Arguments[256];
	decl String:arg[50], String:time[20];
	
	GetCmdArgString(Arguments, sizeof(Arguments));
	len = BreakString(Arguments, arg, sizeof(arg));
	
	if ((next_len = BreakString(Arguments[len], time, sizeof(time))) != -1)
	{
		len += next_len;
	}
	else
	{
		len = 0;
		Arguments[0] = '\0';
	}
	
	decl String:target_name[MAX_TARGET_LENGTH];
	decl target_list[1], bool:tn_is_ml;
	new target = -1;
	
	if (ProcessTargetString(
			arg,
			client, 
			target_list, 
			1, 
			COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_MULTI,
			target_name,
			sizeof(target_name),
			tn_is_ml) > 0)
	{
		target = target_list[0];
		
		if (!IsFakeClient(target) && CanUserTarget(client, target))
			GetClientIP(target, arg, sizeof(arg));
	}
	
	decl String:adminIp[24], String:adminAuth[64];
	new minutes = StringToInt(time);
	if(!minutes && client && !(GetUserFlagBits(client) & (ADMFLAG_UNBAN|ADMFLAG_ROOT)))
	{
		ReplyToCommand(client, "You do not have Perm Ban Permission");
		return Plugin_Handled;
	}
	if(!client)
	{
		// setup dummy adminAuth and adminIp for server
		strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
		strcopy(adminIp, sizeof(adminIp), ServerIp);
	} else {
		GetClientIP(client, adminIp, sizeof(adminIp));
		GetClientAuthString(client, adminAuth, sizeof(adminAuth));
	}
	
	// Pack everything into a data pack so we can retain it
	new Handle:dataPack = CreateDataPack();
	WritePackCell(dataPack, client);
	WritePackCell(dataPack, minutes);
	WritePackString(dataPack, Arguments[len]);
	WritePackString(dataPack, arg);
	WritePackString(dataPack, adminAuth);
	WritePackString(dataPack, adminIp);
	
	decl String:Query[256];
	FormatEx(Query, sizeof(Query), "SELECT bid FROM %s_bans WHERE type = 1 AND ip     = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", 
																	DatabasePrefix, arg);
	
	SQL_TQuery(Database, SelectBanIpCallback,  Query, dataPack, DBPrio_High);
	return Plugin_Handled;
}

public Action:CommandUnban(client, args)
{
	if(args < 1)
	{
		ReplyToCommand(client, "%sUsage: sm_unban <steamid|ip> [reason]", Prefix);
		return Plugin_Handled;
	}
	
	if(CommandDisable & DISABLE_UNBAN)
	{
		// They must go to the website to unban people
		ReplyToCommand(client, "%s%t", Prefix, "Can Not Unban", WebsiteAddress);
		return Plugin_Handled;
	}
	
	decl len, String:Arguments[256], String:arg[50], String:adminAuth[64];
	GetCmdArgString(Arguments, sizeof(Arguments));
	
	if ((len = BreakString(Arguments, arg, sizeof(arg))) == -1)
	{
		len = 0;
		Arguments[0] = '\0';
	}
	if(!client)
	{
		// setup dummy adminAuth and adminIp for server
		strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
	} else {
		GetClientAuthString(client, adminAuth, sizeof(adminAuth));
	}
	
	// Pack everything into a data pack so we can retain it
	new Handle:dataPack = CreateDataPack();
	WritePackCell(dataPack, client);
	WritePackString(dataPack, Arguments[len]);
	WritePackString(dataPack, arg);
	WritePackString(dataPack, adminAuth);
	
	decl String:query[200];
	if (strncmp(arg, "STEAM_", 6) == 0)
	{
		Format(query, sizeof(query), "SELECT bid FROM %s_bans WHERE (type = 0 AND authid = '%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg);
	} else {
		Format(query, sizeof(query), "SELECT bid FROM %s_bans WHERE (type = 1 AND ip     = '%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg);
	}
	SQL_TQuery(Database, SelectUnbanCallback, query, dataPack);
	return Plugin_Handled;
}

public Action:CommandAddBan(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "%sUsage: sm_addban <time> <steamid> [reason]", Prefix);
		return Plugin_Handled;
	}
	
	if(CommandDisable & DISABLE_ADDBAN)
	{
		// They must go to the website to add bans
		ReplyToCommand(client, "%s%t", Prefix, "Can Not Add Ban", WebsiteAddress);
		return Plugin_Handled;
	}
	
	decl String:arg_string[256], String:time[50], String:authid[50];
	GetCmdArgString(arg_string, sizeof(arg_string));
	
	new len, total_len;
	
	/* Get time */
	if ((len = BreakString(arg_string, time, sizeof(time))) == -1)
	{
		ReplyToCommand(client, "%sUsage: sm_addban <time> <steamid> [reason]", Prefix);
		return Plugin_Handled;
	}	
	total_len += len;
	
	/* Get steamid */
	if ((len = BreakString(arg_string[total_len], authid, sizeof(authid))) != -1)
	{
		total_len += len;
	}
	else
	{
		total_len = 0;
		arg_string[0] = '\0';
	}
	
	decl String:adminIp[24], String:adminAuth[64];
	new minutes = StringToInt(time);
	if(!minutes && client && !(GetUserFlagBits(client) & (ADMFLAG_UNBAN|ADMFLAG_ROOT)))
	{
		ReplyToCommand(client, "You do not have Perm Ban Permission");
		return Plugin_Handled;
	}
	if(!client)
	{
		// setup dummy adminAuth and adminIp for server
		strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
		strcopy(adminIp, sizeof(adminIp), ServerIp);
	} else {
		GetClientIP(client, adminIp, sizeof(adminIp));
		GetClientAuthString(client, adminAuth, sizeof(adminAuth));
	}
	
	// Pack everything into a data pack so we can retain it
	new Handle:dataPack = CreateDataPack();
	WritePackCell(dataPack, client);
	WritePackCell(dataPack, minutes);
	WritePackString(dataPack, arg_string[total_len]);
	WritePackString(dataPack, authid);
	WritePackString(dataPack, adminAuth);
	WritePackString(dataPack, adminIp);
	
	decl String:Query[256];
	FormatEx(Query, sizeof(Query), "SELECT bid FROM %s_bans WHERE type = 0 AND authid = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", 
																	DatabasePrefix, authid);
	
	SQL_TQuery(Database, SelectAddbanCallback, Query, dataPack, DBPrio_High);
	return Plugin_Handled;
}

public Action:sm_rehash(args)
{
	if(enableAdmins)
		DumpAdminCache(AdminCache_Groups,true);
	return Plugin_Handled;   
}



// MENU CODE //

public OnAdminMenuReady(Handle:topmenu)
{
#if defined DEBUG
	LogToFile(logFile, "OnAdminMenuReady()");
#endif

	/* Block us from being called twice */
	if (topmenu == hTopMenu)
	{
		return;
	}
		
	/* Save the Handle */
	hTopMenu = topmenu;
	
	/* Find the "Player Commands" category */
	new TopMenuObject:player_commands = FindTopMenuCategory(hTopMenu, ADMINMENU_PLAYERCOMMANDS);

	if (player_commands != INVALID_TOPMENUOBJECT)
	{
// just to avoid "unused variable 'res'" warning
#if defined DEBUG
		new TopMenuObject:res = AddToTopMenu(hTopMenu,
							"sm_ban", 		// Name
							TopMenuObject_Item,	// We are a submenu
							AdminMenu_Ban,		// Handler function
							player_commands,	// We are a submenu of Player Commands
							"sm_ban",		// The command to be finally called (Override checks)
							ADMFLAG_BAN);		// What flag do we need to see the menu option
		decl String:temp[125];
		Format(temp, 125, "Result of AddToTopMenu: %d", res);
		LogToFile(logFile, temp);
		LogToFile(logFile, "Added Ban option to admin menu");
#else
		AddToTopMenu(hTopMenu,
							"sm_ban", 		// Name
							TopMenuObject_Item,	// We are a submenu
							AdminMenu_Ban,		// Handler function
							player_commands,	// We are a submenu of Player Commands
							"sm_ban",		// The command to be finally called (Override checks)
							ADMFLAG_BAN);		// What flag do we need to see the menu option
#endif
	}
}

public AdminMenu_Ban(Handle:topmenu, 
					TopMenuAction:action,	// Action being performed
					TopMenuObject:object_id,// The object ID (if used)
					param,			// client idx of admin who chose the option (if used)
					String:buffer[],	// Output buffer (if used)
					maxlength)		// Output buffer (if used)
{
	/* Clear the Ownreason bool, so he is able to chat again;) */
	g_ownReasons[param] = false;
	
#if defined DEBUG
	LogToFile(logFile, "AdminMenu_Ban()");
#endif
	if (action == TopMenuAction_DisplayOption)	// We are only being displayed, We only need to show the option name
	{
		Format(buffer, maxlength, "%T", "Ban player", param);
		//Format(buffer, maxlength, "Ban player", param);	// Show the menu option
#if defined DEBUG
	LogToFile(logFile, "AdminMenu_Ban() -> Formatted the Ban option text");
#endif
	}
	else if (action == TopMenuAction_SelectOption)
	{
		DisplayBanTargetMenu(param);	// Someone chose to ban someone, show the list of users menu
#if defined DEBUG
	LogToFile(logFile, "AdminMenu_Ban() -> DisplayBanTargetMenu()");
#endif
	}
}

public ReasonSelected(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		decl String:info[128];
		decl String:key[128];
		GetMenuItem(menu, param2, key, sizeof(key), _, info, sizeof(info));
			
		if(StrEqual("Hacking", key))
		{
			DisplayMenu(HackingMenuHandle, param1, MENU_TIME_FOREVER);
			return;
		}
		if(StrEqual("Own Reason", key))
		{
			// admin wants to use his own reason
			g_ownReasons[param1] = true;
			PrintToChat(param1, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Chat Reason");
			return;
		}
		if(g_BanTarget[param1] != -1 && g_BanTime[param1] != -1)
			PrepareBan(param1, g_BanTarget[param1], g_BanTime[param1], info, sizeof(info));	

	} 
	else if (action == MenuAction_Cancel && param2 == MenuCancel_Disconnected) 
	{
		if(PlayerDataPack[param1] != INVALID_HANDLE)
		{
			CloseHandle(PlayerDataPack[param1]);
			PlayerDataPack[param1] = INVALID_HANDLE;
		}

	}
	else if (action == MenuAction_Cancel)
	{
		DisplayBanTimeMenu(param1);
	}
}

public HackingSelected(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		decl String:info[128];
		decl String:key[128];
		GetMenuItem(menu, param2, key, sizeof(key), _, info, sizeof(info));
		
		if(g_BanTarget[param1] != -1 && g_BanTime[param1] != -1)
			PrepareBan(param1, g_BanTarget[param1], g_BanTime[param1], info, sizeof(info));
		
	} else if (action == MenuAction_Cancel && param2 == MenuCancel_Disconnected) {

		new Handle:Pack = PlayerDataPack[param1];

		if(Pack != INVALID_HANDLE)
		{
			SetPackPosition(Pack, 40);
			new Handle:ReasonPack = Handle:ReadPackCell(Pack);

			if(ReasonPack != INVALID_HANDLE)
			{
				CloseHandle(ReasonPack);
			}

			CloseHandle(Pack);
			PlayerDataPack[param1] = INVALID_HANDLE;
		}
	}
	else if (action == MenuAction_Cancel)
	{
		DisplayMenu(ReasonMenuHandle, param1, MENU_TIME_FOREVER);
	}
}

public MenuHandler_BanPlayerList(Handle:menu, MenuAction:action, param1, param2)
{
#if defined DEBUG
	LogToFile(logFile, "MenuHandler_BanPlayerList()");
#endif
	if (action == MenuAction_End)
	{
		CloseHandle(menu);				// Chose to close the menu
	}
	else if (action == MenuAction_Cancel)
	{
		if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
		{
			DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
		}
	}
	else if (action == MenuAction_Select)			// Chose someone!
	{
		decl String:info[32], String:name[32];
		new userid, target;
		
		GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name));
		userid = StringToInt(info);

		if ((target = GetClientOfUserId(userid)) == 0)
		{
			PrintToChat(param1, "%s%t", Prefix, "Player no longer available");
		}
		else if (!CanUserTarget(param1, target))
		{
			PrintToChat(param1, "%s%t", Prefix, "Unable to target");
		}
		else
		{
			g_BanTarget[param1] = target;
			DisplayBanTimeMenu(param1);
		}
	}
}

public MenuHandler_BanTimeList(Handle:menu, MenuAction:action, param1, param2)
{
#if defined DEBUG
	LogToFile(logFile, "MenuHandler_BanTimeList()");
#endif
	if (action == MenuAction_End)
	{
		CloseHandle(menu);
	}
	else if (action == MenuAction_Cancel)
	{
		if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
		{
			DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
		}
	}
	else if (action == MenuAction_Select)
	{
		decl String:info[32];
		
		GetMenuItem(menu, param2, info, sizeof(info));
		g_BanTime[param1] = StringToInt(info);
		
		//DisplayBanReasonMenu(param1);
		DisplayMenu(ReasonMenuHandle, param1, MENU_TIME_FOREVER);
	}
}

stock DisplayBanTargetMenu(client)
{
#if defined DEBUG
    LogToFile(logFile, "DisplayBanTargetMenu()");
#endif
	new Handle:menu = CreateMenu(MenuHandler_BanPlayerList);// Create a new menu, pass it the handler.
	
	decl String:title[100];
	Format(title, sizeof(title), "%T:", "Ban player", client);
	
	//Format(title, sizeof(title), "Ban player", client);	// Create the title of the menu
	SetMenuTitle(menu, title);				// Set the title
	SetMenuExitBackButton(menu, true);			// Yes we want back/exit
	
	AddTargetsToMenu(menu, 					// Add clients to our menu
			client, 				// The client that called the display
			false, 					// We want to see people connecting
			false);					// And dead people
	
	DisplayMenu(menu, client, MENU_TIME_FOREVER);		// Show the menu to the client FOREVER!
}

stock DisplayBanTimeMenu(client)
{
#if defined DEBUG
	LogToFile(logFile, "DisplayBanTimeMenu()");
#endif
		
	new Handle:menu = CreateMenu(MenuHandler_BanTimeList);
	
	decl String:title[100];
	Format(title, sizeof(title), "%T:", "Ban player", client);
	//Format(title, sizeof(title), "Ban player", client);
	SetMenuTitle(menu, title);
	SetMenuExitBackButton(menu, true);
	
	if(GetUserFlagBits(client) & (ADMFLAG_UNBAN|ADMFLAG_ROOT))
		AddMenuItem(menu, "0", "Permanent");
	AddMenuItem(menu, "10", "10 Minutes");
	AddMenuItem(menu, "30", "30 Minutes");
	AddMenuItem(menu, "60", "1 Hour");
	AddMenuItem(menu, "240", "4 Hours");
	AddMenuItem(menu, "1440", "1 Day");
	AddMenuItem(menu, "10080", "1 Week");
	
	DisplayMenu(menu, client, MENU_TIME_FOREVER);<commit_msg>Permission to permaban now follows sm_unban permission, even when sm_unban is overridden.<commit_after>/**
* sourcebans.sp
*
* This file contains all Source Server Plugin Functions
* @author SourceBans Development Team
* @version 0.0.0.$Rev: 108 $
* @copyright InterWave Studios (www.interwavestudios.com)
* @package SourceBans
* @link http://www.sourcebans.net
*/

#pragma semicolon 1
#include <sourcemod>
#include <sourcebans>

#undef REQUIRE_PLUGIN
#include <adminmenu>
#include "dbi.inc"

#define SB_VERSION "1.4.9"

//GLOBAL DEFINES
#define YELLOW				0x01
#define NAMECOLOR			0x02
#define TEAMCOLOR			0x03
#define GREEN				0x04

#define DISABLE_ADDBAN		1
#define DISABLE_UNBAN		2

//#define DEBUG

enum State /* ConfigState */
{
	ConfigStateNone = 0,
	ConfigStateConfig,
	ConfigStateReasons,
	ConfigStateHacking
}

new g_BanTarget[MAXPLAYERS+1] = {-1, ...};
new g_BanTime[MAXPLAYERS+1] = {-1, ...};

new State:ConfigState;
new Handle:ConfigParser;

new Handle:hTopMenu = INVALID_HANDLE;

new const String:BLANK[] = "";
new const String:Prefix[] = "[SourceBans] ";

new String:ServerIp[24];
new String:ServerPort[7];
new String:DatabasePrefix[10] = "sb";
new String:WebsiteAddress[128];

/* Admin Stuff*/
new AdminCachePart:loadPart;
new bool:loadAdmins;
new bool:loadGroups;
new bool:loadOverrides;
new curLoading=0;
new AdminFlag:g_FlagLetters[26];

/* Admin KeyValues */
new String:groupsLoc[128];
new String:adminsLoc[128];
	
/* Cvar handle*/
new Handle:CvarHostIp;
new Handle:CvarPort;

/* Database handle */
new Handle:Database;
new Handle:SQLiteDB;

/* Menu file globals */
new Handle:ReasonMenuHandle;
new Handle:HackingMenuHandle;

/* Datapack and Timer handles */
new Handle:PlayerRecheck[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
new Handle:PlayerDataPack[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};

/* Player ban check status */
new bool:PlayerStatus[MAXPLAYERS + 1];

/* Disable of addban and unban */
new CommandDisable;
new bool:backupConfig = true;
new bool:enableAdmins = true;

/* Require a lastvisited from SB site */
new bool:requireSiteLogin = false;

/* Log Stuff */
new String:logFile[256];

/* Own Chat Reason */
new g_ownReasons[MAXPLAYERS+1] = {false, ...};

new MaxSlots;
new Float:RetryTime = 15.0;
new ProcessQueueTime = 5;
new bool:LateLoaded;
new bool:AutoAdd;
new bool:g_bConnecting = false;

new serverID = -1;

public Plugin:myinfo =
{
	name = "SourceBans",
	author = "SourceBans Development Team",
	description = "Advanced ban management for the Source engine",
	version = SB_VERSION,
	url = "http://www.sourcebans.net"
};

#if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 3
public APLRes:AskPluginLoad2(Handle:myself, bool:late, String:error[], err_max)
#else
public bool:AskPluginLoad(Handle:myself, bool:late, String:error[], err_max)
#endif
{
	RegPluginLibrary("sourcebans");
	CreateNative("SBBanPlayer", Native_SBBanPlayer);
	LateLoaded = late;
	
	#if SOURCEMOD_V_MAJOR >= 1 && SOURCEMOD_V_MINOR >= 3
		return APLRes_Success;
	#else
		return true;
	#endif
}

public OnPluginStart()
{	
	LoadTranslations("common.phrases");
	LoadTranslations("plugin.basecommands");
	LoadTranslations("sourcebans.phrases");
	LoadTranslations("basebans.phrases");
	loadAdmins = loadGroups = loadOverrides = false;
	
	CvarHostIp = FindConVar("hostip");
	CvarPort = FindConVar("hostport");
	CreateConVar("sb_version", SB_VERSION, _, FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_REPLICATED|FCVAR_NOTIFY);
	RegServerCmd("sm_rehash",sm_rehash,"Reload SQL admins");
	RegAdminCmd("sm_ban", CommandBan, ADMFLAG_BAN, "sm_ban <#userid|name> <minutes|0> [reason]");
	RegAdminCmd("sm_banip", CommandBanIp, ADMFLAG_BAN, "sm_banip <ip|#userid|name> <time> [reason]");
	RegAdminCmd("sm_addban", CommandAddBan, ADMFLAG_RCON, "sm_addban <time> <steamid> [reason]");
	RegAdminCmd("sm_unban", CommandUnban, ADMFLAG_UNBAN, "sm_unban <steamid|ip> [reason]");
	RegAdminCmd("sb_reload",
				_CmdReload,
				ADMFLAG_RCON,
				"Reload sourcebans config and ban reason menu options",
				BLANK);
	
	RegConsoleCmd("say", ChatHook);
	RegConsoleCmd("say_team", ChatHook);
	
	if((ReasonMenuHandle = CreateMenu(ReasonSelected)) != INVALID_HANDLE)
	{
		SetMenuPagination(ReasonMenuHandle, 8);
		SetMenuExitBackButton(ReasonMenuHandle, true);
	}

	if((HackingMenuHandle = CreateMenu(HackingSelected)) != INVALID_HANDLE)
	{
		SetMenuPagination(HackingMenuHandle, 8);
		SetMenuExitBackButton(HackingMenuHandle, true);
	}
	
	g_FlagLetters['a'-'a'] = Admin_Reservation;
	g_FlagLetters['b'-'a'] = Admin_Generic;
	g_FlagLetters['c'-'a'] = Admin_Kick;
	g_FlagLetters['d'-'a'] = Admin_Ban;
	g_FlagLetters['e'-'a'] = Admin_Unban;
	g_FlagLetters['f'-'a'] = Admin_Slay;
	g_FlagLetters['g'-'a'] = Admin_Changemap;
	g_FlagLetters['h'-'a'] = Admin_Convars;
	g_FlagLetters['i'-'a'] = Admin_Config;
	g_FlagLetters['j'-'a'] = Admin_Chat;
	g_FlagLetters['k'-'a'] = Admin_Vote;
	g_FlagLetters['l'-'a'] = Admin_Password;
	g_FlagLetters['m'-'a'] = Admin_RCON;
	g_FlagLetters['n'-'a'] = Admin_Cheats;
	g_FlagLetters['o'-'a'] = Admin_Custom1;
	g_FlagLetters['p'-'a'] = Admin_Custom2;
	g_FlagLetters['q'-'a'] = Admin_Custom3;
	g_FlagLetters['r'-'a'] = Admin_Custom4;
	g_FlagLetters['s'-'a'] = Admin_Custom5;
	g_FlagLetters['t'-'a'] = Admin_Custom6;
	g_FlagLetters['z'-'a'] = Admin_Root;
	
	
	BuildPath(Path_SM, logFile, sizeof(logFile), "logs/sourcebans.log");
	g_bConnecting = true;
	
	// Catch config error and show link to FAQ
	if(!SQL_CheckConfig("sourcebans"))
	{
		if(ReasonMenuHandle != INVALID_HANDLE)
			CloseHandle(ReasonMenuHandle);
		if(HackingMenuHandle != INVALID_HANDLE)
			CloseHandle(HackingMenuHandle);
		LogToFile(logFile, "Database failure: Could not find Database conf \"sourcebans\". See FAQ: http://sourcebans.net/node/19");
		SetFailState("Database failure: Could not find Database conf \"sourcebans\"");
		return;
	}
	SQL_TConnect(GotDatabase, "sourcebans");
	
	BuildPath(Path_SM,groupsLoc,sizeof(groupsLoc),"configs/admin_groups.cfg");
	
	BuildPath(Path_SM,adminsLoc,sizeof(adminsLoc),"configs/admins.cfg");
	
	InitializeBackupDB();
	
	// This timer is what processes the SQLite queue when the database is unavailable
	CreateTimer(float(ProcessQueueTime * 60), ProcessQueue);
	
	/* Account for late loading */
	if(LateLoaded)
	{
		decl String:auth[30];
		for(new i = 1; i <= GetMaxClients(); i++)
		{
			if(IsClientConnected(i) && !IsFakeClient(i))
			{
				PlayerStatus[i] = false;
			}
			if(IsClientInGame(i) && !IsFakeClient(i))
			{
				GetClientAuthString(i, auth, sizeof(auth));
				OnClientAuthorized(i, auth);
			}
		}
	}
}

public OnAllPluginsLoaded()
{
	new Handle:topmenu;
#if defined DEBUG
	LogToFile(logFile, "OnAllPluginsLoaded()");
#endif
		
	if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != INVALID_HANDLE))
	{
		OnAdminMenuReady(topmenu);
	}
}

public OnConfigsExecuted()
{
	decl String:filename[200];
	BuildPath(Path_SM, filename, sizeof(filename), "plugins/basebans.smx");
	if(FileExists(filename))
	{
		decl String:newfilename[200];
		BuildPath(Path_SM, newfilename, sizeof(newfilename), "plugins/disabled/basebans.smx");
		ServerCommand("sm plugins unload basebans");
		if(FileExists(newfilename))
			DeleteFile(newfilename);
		RenameFile(newfilename, filename);
		LogToFile(logFile, "plugins/basebans.smx was unloaded and moved to plugins/disabled/basebans.smx");
	}
}

public OnMapStart()
{
	MaxSlots = GetMaxClients();
	ResetSettings();
}

public OnMapEnd()
{
	for(new i = 0; i <= MaxSlots; i++)
	{
		if(PlayerDataPack[i] != INVALID_HANDLE)
		{
			/* Need to close reason pack */
			CloseHandle(PlayerDataPack[i]);
			PlayerDataPack[i] = INVALID_HANDLE;
		}
	}
}

// CLIENT CONNECTION FUNCTIONS //

public Action:OnClientPreAdminCheck(client)
{
	if (curLoading > 0)
		return Plugin_Stop;
	return Plugin_Continue;
}

public OnClientDisconnect(client)
{
	if(PlayerRecheck[client] != INVALID_HANDLE)
	{
		KillTimer(PlayerRecheck[client]);
		PlayerRecheck[client] = INVALID_HANDLE;
	}
	g_ownReasons[client] = false;
}

public bool:OnClientConnect(client, String:rejectmsg[], maxlen)
{
	PlayerStatus[client] = false;
	return true;
}

public OnClientAuthorized(client, const String:auth[])
{
	/* Do not check bots nor check player with lan steamid. */
	if(auth[0] == 'B' || auth[9] == 'L' || Database == INVALID_HANDLE)
	{
		PlayerStatus[client] = true;
		return;
	}
	
	decl String:Query[256], String:ip[30];
	GetClientIP(client, ip, sizeof(ip));
	FormatEx(Query, sizeof(Query), "SELECT bid FROM %s_bans WHERE ((type = 0 AND authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, auth[8], ip);
#if defined DEBUG
	LogToFile(logFile, "Checking ban for: %s", auth);
#endif
	
	SQL_TQuery(Database, VerifyBan, Query, GetClientUserId(client), DBPrio_High);
}

public OnRebuildAdminCache(AdminCachePart:part)
{
	loadPart = part;
	switch(loadPart)
	{
		case AdminCache_Overrides:
		loadOverrides = true;
		case AdminCache_Groups:
			loadGroups = true;
		case AdminCache_Admins:
			loadAdmins = true;
	}
	if(enableAdmins)
	{
		if(Database == INVALID_HANDLE) {
			if(!g_bConnecting) {
				g_bConnecting = true;
				SQL_TConnect(GotDatabase,"sourcebans");
			}
		}
		else {
			GotDatabase(Database,Database,"",0);
		}
	}
}

// COMMAND CODE //

public Action:ChatHook(client, args)
{
	// is this player preparing to ban someone
	if (g_ownReasons[client])
	{
		// get the reason
		new String:reason[512];
		GetCmdArgString(reason, sizeof(reason));
		StripQuotes(reason);
		
		g_ownReasons[client] = false;
		
		if(StrEqual(reason[0], "!noreason"))
		{
			PrintToChat(client, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Chat Reason Aborted");
			return Plugin_Handled;
		}
		
		// ban him!
		PrepareBan(client, g_BanTarget[client], g_BanTime[client], reason, sizeof(reason));
		
		// block the reason to be sent in chat
		return Plugin_Handled;
	}
	return Plugin_Continue;
}

public Action:_CmdReload(client, args)
{
	ResetSettings();
	return Plugin_Handled;
}

public Action:CommandBan(client, args)
{
	if(args < 2)
	{
		ReplyToCommand(client, "%sUsage: sm_ban <#userid|name> <time|0> [reason]", Prefix);
		return Plugin_Handled;
	}

	// This is mainly for me sanity since client used to be called admin and target used to be called client
	new admin = client;
	
	// Get the target, find target returns a message on failure so we do not
	decl String:buffer[100];
	GetCmdArg(1, buffer, sizeof(buffer));
	new target = FindTarget(client, buffer, true);
	if(target == -1)
	{
		return Plugin_Handled;
	}

	// Get the ban time
	GetCmdArg(2, buffer, sizeof(buffer));
	new time = StringToInt(buffer);
	if(!time && client && !(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN|ADMFLAG_ROOT)))
	{
		ReplyToCommand(client, "You do not have Perm Ban Permission");
		return Plugin_Handled;
	}
	
	// Get the reason
	decl String:reason[128];
	if(args >= 3)
	{
		GetCmdArg(3, reason, sizeof(reason));
	}
	else
	{
		reason[0] = '\0';
	}
	
	g_BanTarget[client] = target;
	g_BanTime[client] = time;
	
	if(!PlayerStatus[target])
	{
		// The target has not been banned verify. It must be completed before you can ban anyone.
		ReplyToCommand(admin, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Ban Not Verified");
		return Plugin_Handled;
	}

	
	CreateBan(client, target, time, reason);
	return Plugin_Handled;
}

public Action:CommandBanIp(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "%sUsage: sm_banip <ip|#userid|name> <time> [reason]", Prefix);
		return Plugin_Handled;
	}

	decl len, next_len;
	decl String:Arguments[256];
	decl String:arg[50], String:time[20];
	
	GetCmdArgString(Arguments, sizeof(Arguments));
	len = BreakString(Arguments, arg, sizeof(arg));
	
	if ((next_len = BreakString(Arguments[len], time, sizeof(time))) != -1)
	{
		len += next_len;
	}
	else
	{
		len = 0;
		Arguments[0] = '\0';
	}
	
	decl String:target_name[MAX_TARGET_LENGTH];
	decl target_list[1], bool:tn_is_ml;
	new target = -1;
	
	if (ProcessTargetString(
			arg,
			client, 
			target_list, 
			1, 
			COMMAND_FILTER_CONNECTED|COMMAND_FILTER_NO_MULTI,
			target_name,
			sizeof(target_name),
			tn_is_ml) > 0)
	{
		target = target_list[0];
		
		if (!IsFakeClient(target) && CanUserTarget(client, target))
			GetClientIP(target, arg, sizeof(arg));
	}
	
	decl String:adminIp[24], String:adminAuth[64];
	new minutes = StringToInt(time);
	if(!minutes && client && !(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN|ADMFLAG_ROOT)))
	{
		ReplyToCommand(client, "You do not have Perm Ban Permission");
		return Plugin_Handled;
	}
	if(!client)
	{
		// setup dummy adminAuth and adminIp for server
		strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
		strcopy(adminIp, sizeof(adminIp), ServerIp);
	} else {
		GetClientIP(client, adminIp, sizeof(adminIp));
		GetClientAuthString(client, adminAuth, sizeof(adminAuth));
	}
	
	// Pack everything into a data pack so we can retain it
	new Handle:dataPack = CreateDataPack();
	WritePackCell(dataPack, client);
	WritePackCell(dataPack, minutes);
	WritePackString(dataPack, Arguments[len]);
	WritePackString(dataPack, arg);
	WritePackString(dataPack, adminAuth);
	WritePackString(dataPack, adminIp);
	
	decl String:Query[256];
	FormatEx(Query, sizeof(Query), "SELECT bid FROM %s_bans WHERE type = 1 AND ip     = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", 
																	DatabasePrefix, arg);
	
	SQL_TQuery(Database, SelectBanIpCallback,  Query, dataPack, DBPrio_High);
	return Plugin_Handled;
}

public Action:CommandUnban(client, args)
{
	if(args < 1)
	{
		ReplyToCommand(client, "%sUsage: sm_unban <steamid|ip> [reason]", Prefix);
		return Plugin_Handled;
	}
	
	if(CommandDisable & DISABLE_UNBAN)
	{
		// They must go to the website to unban people
		ReplyToCommand(client, "%s%t", Prefix, "Can Not Unban", WebsiteAddress);
		return Plugin_Handled;
	}
	
	decl len, String:Arguments[256], String:arg[50], String:adminAuth[64];
	GetCmdArgString(Arguments, sizeof(Arguments));
	
	if ((len = BreakString(Arguments, arg, sizeof(arg))) == -1)
	{
		len = 0;
		Arguments[0] = '\0';
	}
	if(!client)
	{
		// setup dummy adminAuth and adminIp for server
		strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
	} else {
		GetClientAuthString(client, adminAuth, sizeof(adminAuth));
	}
	
	// Pack everything into a data pack so we can retain it
	new Handle:dataPack = CreateDataPack();
	WritePackCell(dataPack, client);
	WritePackString(dataPack, Arguments[len]);
	WritePackString(dataPack, arg);
	WritePackString(dataPack, adminAuth);
	
	decl String:query[200];
	if (strncmp(arg, "STEAM_", 6) == 0)
	{
		Format(query, sizeof(query), "SELECT bid FROM %s_bans WHERE (type = 0 AND authid = '%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg);
	} else {
		Format(query, sizeof(query), "SELECT bid FROM %s_bans WHERE (type = 1 AND ip     = '%s') AND (length = '0' OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", DatabasePrefix, arg);
	}
	SQL_TQuery(Database, SelectUnbanCallback, query, dataPack);
	return Plugin_Handled;
}

public Action:CommandAddBan(client, args)
{
	if (args < 2)
	{
		ReplyToCommand(client, "%sUsage: sm_addban <time> <steamid> [reason]", Prefix);
		return Plugin_Handled;
	}
	
	if(CommandDisable & DISABLE_ADDBAN)
	{
		// They must go to the website to add bans
		ReplyToCommand(client, "%s%t", Prefix, "Can Not Add Ban", WebsiteAddress);
		return Plugin_Handled;
	}
	
	decl String:arg_string[256], String:time[50], String:authid[50];
	GetCmdArgString(arg_string, sizeof(arg_string));
	
	new len, total_len;
	
	/* Get time */
	if ((len = BreakString(arg_string, time, sizeof(time))) == -1)
	{
		ReplyToCommand(client, "%sUsage: sm_addban <time> <steamid> [reason]", Prefix);
		return Plugin_Handled;
	}	
	total_len += len;
	
	/* Get steamid */
	if ((len = BreakString(arg_string[total_len], authid, sizeof(authid))) != -1)
	{
		total_len += len;
	}
	else
	{
		total_len = 0;
		arg_string[0] = '\0';
	}
	
	decl String:adminIp[24], String:adminAuth[64];
	new minutes = StringToInt(time);
	if(!minutes && client && !(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN|ADMFLAG_ROOT)))
	{
		ReplyToCommand(client, "You do not have Perm Ban Permission");
		return Plugin_Handled;
	}
	if(!client)
	{
		// setup dummy adminAuth and adminIp for server
		strcopy(adminAuth, sizeof(adminAuth), "STEAM_ID_SERVER");
		strcopy(adminIp, sizeof(adminIp), ServerIp);
	} else {
		GetClientIP(client, adminIp, sizeof(adminIp));
		GetClientAuthString(client, adminAuth, sizeof(adminAuth));
	}
	
	// Pack everything into a data pack so we can retain it
	new Handle:dataPack = CreateDataPack();
	WritePackCell(dataPack, client);
	WritePackCell(dataPack, minutes);
	WritePackString(dataPack, arg_string[total_len]);
	WritePackString(dataPack, authid);
	WritePackString(dataPack, adminAuth);
	WritePackString(dataPack, adminIp);
	
	decl String:Query[256];
	FormatEx(Query, sizeof(Query), "SELECT bid FROM %s_bans WHERE type = 0 AND authid = '%s' AND (length = 0 OR ends > UNIX_TIMESTAMP()) AND RemoveType IS NULL", 
																	DatabasePrefix, authid);
	
	SQL_TQuery(Database, SelectAddbanCallback, Query, dataPack, DBPrio_High);
	return Plugin_Handled;
}

public Action:sm_rehash(args)
{
	if(enableAdmins)
		DumpAdminCache(AdminCache_Groups,true);
	return Plugin_Handled;   
}



// MENU CODE //

public OnAdminMenuReady(Handle:topmenu)
{
#if defined DEBUG
	LogToFile(logFile, "OnAdminMenuReady()");
#endif

	/* Block us from being called twice */
	if (topmenu == hTopMenu)
	{
		return;
	}
		
	/* Save the Handle */
	hTopMenu = topmenu;
	
	/* Find the "Player Commands" category */
	new TopMenuObject:player_commands = FindTopMenuCategory(hTopMenu, ADMINMENU_PLAYERCOMMANDS);

	if (player_commands != INVALID_TOPMENUOBJECT)
	{
// just to avoid "unused variable 'res'" warning
#if defined DEBUG
		new TopMenuObject:res = AddToTopMenu(hTopMenu,
							"sm_ban", 		// Name
							TopMenuObject_Item,	// We are a submenu
							AdminMenu_Ban,		// Handler function
							player_commands,	// We are a submenu of Player Commands
							"sm_ban",		// The command to be finally called (Override checks)
							ADMFLAG_BAN);		// What flag do we need to see the menu option
		decl String:temp[125];
		Format(temp, 125, "Result of AddToTopMenu: %d", res);
		LogToFile(logFile, temp);
		LogToFile(logFile, "Added Ban option to admin menu");
#else
		AddToTopMenu(hTopMenu,
							"sm_ban", 		// Name
							TopMenuObject_Item,	// We are a submenu
							AdminMenu_Ban,		// Handler function
							player_commands,	// We are a submenu of Player Commands
							"sm_ban",		// The command to be finally called (Override checks)
							ADMFLAG_BAN);		// What flag do we need to see the menu option
#endif
	}
}

public AdminMenu_Ban(Handle:topmenu, 
					TopMenuAction:action,	// Action being performed
					TopMenuObject:object_id,// The object ID (if used)
					param,			// client idx of admin who chose the option (if used)
					String:buffer[],	// Output buffer (if used)
					maxlength)		// Output buffer (if used)
{
	/* Clear the Ownreason bool, so he is able to chat again;) */
	g_ownReasons[param] = false;
	
#if defined DEBUG
	LogToFile(logFile, "AdminMenu_Ban()");
#endif
	if (action == TopMenuAction_DisplayOption)	// We are only being displayed, We only need to show the option name
	{
		Format(buffer, maxlength, "%T", "Ban player", param);
		//Format(buffer, maxlength, "Ban player", param);	// Show the menu option
#if defined DEBUG
	LogToFile(logFile, "AdminMenu_Ban() -> Formatted the Ban option text");
#endif
	}
	else if (action == TopMenuAction_SelectOption)
	{
		DisplayBanTargetMenu(param);	// Someone chose to ban someone, show the list of users menu
#if defined DEBUG
	LogToFile(logFile, "AdminMenu_Ban() -> DisplayBanTargetMenu()");
#endif
	}
}

public ReasonSelected(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		decl String:info[128];
		decl String:key[128];
		GetMenuItem(menu, param2, key, sizeof(key), _, info, sizeof(info));
			
		if(StrEqual("Hacking", key))
		{
			DisplayMenu(HackingMenuHandle, param1, MENU_TIME_FOREVER);
			return;
		}
		if(StrEqual("Own Reason", key))
		{
			// admin wants to use his own reason
			g_ownReasons[param1] = true;
			PrintToChat(param1, "%c[%cSourceBans%c]%c %t", GREEN, NAMECOLOR, GREEN, NAMECOLOR, "Chat Reason");
			return;
		}
		if(g_BanTarget[param1] != -1 && g_BanTime[param1] != -1)
			PrepareBan(param1, g_BanTarget[param1], g_BanTime[param1], info, sizeof(info));	

	} 
	else if (action == MenuAction_Cancel && param2 == MenuCancel_Disconnected) 
	{
		if(PlayerDataPack[param1] != INVALID_HANDLE)
		{
			CloseHandle(PlayerDataPack[param1]);
			PlayerDataPack[param1] = INVALID_HANDLE;
		}

	}
	else if (action == MenuAction_Cancel)
	{
		DisplayBanTimeMenu(param1);
	}
}

public HackingSelected(Handle:menu, MenuAction:action, param1, param2)
{
	if (action == MenuAction_Select)
	{
		decl String:info[128];
		decl String:key[128];
		GetMenuItem(menu, param2, key, sizeof(key), _, info, sizeof(info));
		
		if(g_BanTarget[param1] != -1 && g_BanTime[param1] != -1)
			PrepareBan(param1, g_BanTarget[param1], g_BanTime[param1], info, sizeof(info));
		
	} else if (action == MenuAction_Cancel && param2 == MenuCancel_Disconnected) {

		new Handle:Pack = PlayerDataPack[param1];

		if(Pack != INVALID_HANDLE)
		{
			SetPackPosition(Pack, 40);
			new Handle:ReasonPack = Handle:ReadPackCell(Pack);

			if(ReasonPack != INVALID_HANDLE)
			{
				CloseHandle(ReasonPack);
			}

			CloseHandle(Pack);
			PlayerDataPack[param1] = INVALID_HANDLE;
		}
	}
	else if (action == MenuAction_Cancel)
	{
		DisplayMenu(ReasonMenuHandle, param1, MENU_TIME_FOREVER);
	}
}

public MenuHandler_BanPlayerList(Handle:menu, MenuAction:action, param1, param2)
{
#if defined DEBUG
	LogToFile(logFile, "MenuHandler_BanPlayerList()");
#endif
	if (action == MenuAction_End)
	{
		CloseHandle(menu);				// Chose to close the menu
	}
	else if (action == MenuAction_Cancel)
	{
		if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
		{
			DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
		}
	}
	else if (action == MenuAction_Select)			// Chose someone!
	{
		decl String:info[32], String:name[32];
		new userid, target;
		
		GetMenuItem(menu, param2, info, sizeof(info), _, name, sizeof(name));
		userid = StringToInt(info);

		if ((target = GetClientOfUserId(userid)) == 0)
		{
			PrintToChat(param1, "%s%t", Prefix, "Player no longer available");
		}
		else if (!CanUserTarget(param1, target))
		{
			PrintToChat(param1, "%s%t", Prefix, "Unable to target");
		}
		else
		{
			g_BanTarget[param1] = target;
			DisplayBanTimeMenu(param1);
		}
	}
}

public MenuHandler_BanTimeList(Handle:menu, MenuAction:action, param1, param2)
{
#if defined DEBUG
	LogToFile(logFile, "MenuHandler_BanTimeList()");
#endif
	if (action == MenuAction_End)
	{
		CloseHandle(menu);
	}
	else if (action == MenuAction_Cancel)
	{
		if (param2 == MenuCancel_ExitBack && hTopMenu != INVALID_HANDLE)
		{
			DisplayTopMenu(hTopMenu, param1, TopMenuPosition_LastCategory);
		}
	}
	else if (action == MenuAction_Select)
	{
		decl String:info[32];
		
		GetMenuItem(menu, param2, info, sizeof(info));
		g_BanTime[param1] = StringToInt(info);
		
		//DisplayBanReasonMenu(param1);
		DisplayMenu(ReasonMenuHandle, param1, MENU_TIME_FOREVER);
	}
}

stock DisplayBanTargetMenu(client)
{
#if defined DEBUG
    LogToFile(logFile, "DisplayBanTargetMenu()");
#endif
	new Handle:menu = CreateMenu(MenuHandler_BanPlayerList);// Create a new menu, pass it the handler.
	
	decl String:title[100];
	Format(title, sizeof(title), "%T:", "Ban player", client);
	
	//Format(title, sizeof(title), "Ban player", client);	// Create the title of the menu
	SetMenuTitle(menu, title);				// Set the title
	SetMenuExitBackButton(menu, true);			// Yes we want back/exit
	
	AddTargetsToMenu(menu, 					// Add clients to our menu
			client, 				// The client that called the display
			false, 					// We want to see people connecting
			false);					// And dead people
	
	DisplayMenu(menu, client, MENU_TIME_FOREVER);		// Show the menu to the client FOREVER!
}

stock DisplayBanTimeMenu(client)
{
#if defined DEBUG
	LogToFile(logFile, "DisplayBanTimeMenu()");
#endif
		
	new Handle:menu = CreateMenu(MenuHandler_BanTimeList);
	
	decl String:title[100];
	Format(title, sizeof(title), "%T:", "Ban player", client);
	//Format(title, sizeof(title), "Ban player", client);
	SetMenuTitle(menu, title);
	SetMenuExitBackButton(menu, true);
	
	if(CheckCommandAccess(client, "sm_unban", ADMFLAG_UNBAN|ADMFLAG_ROOT))
		AddMenuItem(menu, "0", "Permanent");
	AddMenuItem(menu, "10", "10 Minutes");
	AddMenuItem(menu, "30", "30 Minutes");
	AddMenuItem(menu, "60", "1 Hour");
	AddMenuItem(menu, "240", "4 Hours");
	AddMenuItem(menu, "1440", "1 Day");
	AddMenuItem(menu, "10080", "1 Week");
	
	DisplayMenu(menu, client, MENU_TIME_FOREVER);
{'fraction_non_alphanumeric': 0.13608728912509563, 'fraction_numerical': 0.012038490961066151, 'mean_word_length': 4.942696494796028, 'pattern_counts': {'":': 0, '<': 53, '<?xml version=': 0, '>': 69, 'https://': 0, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6955455', 'n_tokens_mistral': 19354, 'n_tokens_neox': 17628, 'n_words': 4074}