New plugin: NameFilter
Remove un-typable characters from players names.
This commit is contained in:
parent
8598179f26
commit
4c6a9e7f57
21
NameFilter/configs/NameFilter.cfg
Normal file
21
NameFilter/configs/NameFilter.cfg
Normal file
@ -0,0 +1,21 @@
|
||||
"filters"
|
||||
{
|
||||
// this is what the filtered characters are replaced by, do "" to just remove them
|
||||
"censor" ""
|
||||
|
||||
// regex expression for filtering, matches are replaced by the censor, here we are using a whitelist - this would strip unicode characters
|
||||
"filter" "[^A-Za-z0-9\s!@#$%^&*()-_+=-`~\\\]\[{}|';:/.,?><]"
|
||||
|
||||
// series of regex expressions for names POST FILTER, matches will get replacement names
|
||||
"banned"
|
||||
{
|
||||
"1" "[Aa4]+[Dd]+[Mm]+[IiL1]+[nN]+"
|
||||
"2" "@((!?)me|(c?)t(s?)|(!?)admins|(!?)friends|random((c?)t?)|humans|spec|alive|dead|aim|bots)"
|
||||
}
|
||||
|
||||
// replacement names, granted by banned filter, or if the name is too short (<2)
|
||||
"names"
|
||||
{
|
||||
"1" "BAD_NAME"
|
||||
}
|
||||
}
|
331
NameFilter/scripting/NameFilter.sp
Normal file
331
NameFilter/scripting/NameFilter.sp
Normal file
@ -0,0 +1,331 @@
|
||||
#include <sdktools>
|
||||
#include <regex>
|
||||
|
||||
#pragma semicolon 1
|
||||
#pragma newdecls required
|
||||
|
||||
Regex g_FilterExpr;
|
||||
char g_FilterChar[2] = "";
|
||||
ArrayList g_BannedExprs;
|
||||
ArrayList g_ReplacementNames;
|
||||
int g_iBlockNameChangeEvents[MAXPLAYERS + 1] = {0, ...};
|
||||
|
||||
public Plugin myinfo =
|
||||
{
|
||||
name = "NameFilter",
|
||||
author = "BotoX",
|
||||
description = "Filters player names",
|
||||
version = "1.0"
|
||||
}
|
||||
|
||||
public void OnPluginStart()
|
||||
{
|
||||
HookEvent("player_changename", Event_ChangeName, EventHookMode_Pre);
|
||||
HookUserMessage(GetUserMessageId("SayText2"), UserMessage_SayText2, true);
|
||||
|
||||
LoadConfig();
|
||||
|
||||
for(int i = 1; i <= MaxClients; i++)
|
||||
{
|
||||
if(IsClientConnected(i))
|
||||
OnClientConnected(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnClientConnected(int client)
|
||||
{
|
||||
g_iBlockNameChangeEvents[client] = 0;
|
||||
|
||||
if(IsFakeClient(client))
|
||||
return;
|
||||
|
||||
char sName[MAX_NAME_LENGTH];
|
||||
GetClientName(client, sName, sizeof(sName));
|
||||
|
||||
if(FilterName(client, sName))
|
||||
SetClientName(client, sName);
|
||||
}
|
||||
|
||||
public void OnClientPutInServer(int client)
|
||||
{
|
||||
if(IsFakeClient(client))
|
||||
return;
|
||||
|
||||
char sName[MAX_NAME_LENGTH];
|
||||
GetClientName(client, sName, sizeof(sName));
|
||||
|
||||
if(FilterName(client, sName))
|
||||
{
|
||||
g_iBlockNameChangeEvents[client] = 2;
|
||||
SetClientName(client, sName);
|
||||
}
|
||||
}
|
||||
|
||||
public Action Event_ChangeName(Event event, const char[] name, bool dontBroadcast)
|
||||
{
|
||||
int client = GetClientOfUserId(event.GetInt("userid"));
|
||||
if(g_iBlockNameChangeEvents[client])
|
||||
{
|
||||
g_iBlockNameChangeEvents[client]--;
|
||||
SetEventBroadcast(event, true);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
public Action UserMessage_SayText2(UserMsg msg_id, BfRead msg, const int[] players, int playersNum, bool reliable, bool init)
|
||||
{
|
||||
if(!reliable)
|
||||
return Plugin_Continue;
|
||||
|
||||
int client;
|
||||
char sMessage[32];
|
||||
char sOldName[MAX_NAME_LENGTH];
|
||||
char sNewName[MAX_NAME_LENGTH];
|
||||
|
||||
if(GetUserMessageType() == UM_Protobuf)
|
||||
{
|
||||
PbReadString(msg, "msg_name", sMessage, sizeof(sMessage));
|
||||
|
||||
if(!(sMessage[0] == '#' && StrContains(sMessage, "Name_Change")))
|
||||
return Plugin_Continue;
|
||||
|
||||
client = PbReadInt(msg, "ent_idx");
|
||||
PbReadString(msg, "params", sOldName, sizeof(sOldName), 1);
|
||||
PbReadString(msg, "params", sNewName, sizeof(sNewName), 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
client = BfReadByte(msg);
|
||||
BfReadByte(msg);
|
||||
BfReadString(msg, sMessage, sizeof(sMessage));
|
||||
|
||||
if(!(sMessage[0] == '#' && StrContains(sMessage, "Name_Change")))
|
||||
return Plugin_Continue;
|
||||
|
||||
BfReadString(msg, sOldName, sizeof(sOldName));
|
||||
BfReadString(msg, sNewName, sizeof(sNewName));
|
||||
}
|
||||
|
||||
if(g_iBlockNameChangeEvents[client])
|
||||
{
|
||||
g_iBlockNameChangeEvents[client]--;
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
if(FilterName(client, sNewName))
|
||||
{
|
||||
if(StrEqual(sOldName, sNewName))
|
||||
{
|
||||
g_iBlockNameChangeEvents[client] = 3;
|
||||
SetClientName(client, sOldName);
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
g_iBlockNameChangeEvents[client] = 3;
|
||||
SetClientName(client, sOldName);
|
||||
|
||||
DataPack pack = new DataPack();
|
||||
pack.WriteCell(client);
|
||||
pack.WriteString(sNewName);
|
||||
|
||||
CreateTimer(0.1, Timer_ChangeName, pack);
|
||||
|
||||
return Plugin_Handled;
|
||||
}
|
||||
|
||||
return Plugin_Continue;
|
||||
}
|
||||
|
||||
public Action Timer_ChangeName(Handle timer, any data)
|
||||
{
|
||||
DataPack pack = view_as<DataPack>(data);
|
||||
pack.Reset();
|
||||
|
||||
int client = pack.ReadCell();
|
||||
char sName[MAX_NAME_LENGTH];
|
||||
pack.ReadString(sName, sizeof(sName));
|
||||
|
||||
delete pack;
|
||||
|
||||
SetClientName(client, sName);
|
||||
|
||||
return Plugin_Stop;
|
||||
}
|
||||
|
||||
void LoadConfig()
|
||||
{
|
||||
if(g_FilterExpr != INVALID_HANDLE)
|
||||
CloseHandle(g_FilterExpr);
|
||||
|
||||
if(g_BannedExprs)
|
||||
{
|
||||
for(int i = 0; i < g_BannedExprs.Length; i++)
|
||||
{
|
||||
Handle hRegex = g_BannedExprs.Get(i);
|
||||
CloseHandle(hRegex);
|
||||
}
|
||||
}
|
||||
|
||||
delete g_BannedExprs;
|
||||
delete g_ReplacementNames;
|
||||
|
||||
static char sConfigFile[PLATFORM_MAX_PATH];
|
||||
BuildPath(Path_SM, sConfigFile, sizeof(sConfigFile), "configs/NameFilter.cfg");
|
||||
if(!FileExists(sConfigFile))
|
||||
SetFailState("Could not find config: \"%s\"", sConfigFile);
|
||||
|
||||
KeyValues Config = new KeyValues("NameFilter");
|
||||
if(!Config.ImportFromFile(sConfigFile))
|
||||
{
|
||||
delete Config;
|
||||
SetFailState("ImportFromFile() failed!");
|
||||
}
|
||||
|
||||
Config.GetString("censor", g_FilterChar, 2, "*");
|
||||
|
||||
static char sBuffer[256];
|
||||
Config.GetString("filter", sBuffer, 256);
|
||||
|
||||
char sError[256];
|
||||
RegexError iError;
|
||||
g_FilterExpr = CompileRegex(sBuffer, 0, sError, sizeof(sError), iError);
|
||||
if(iError != REGEX_ERROR_NONE)
|
||||
{
|
||||
delete Config;
|
||||
SetFailState(sError);
|
||||
}
|
||||
|
||||
g_BannedExprs = new ArrayList();
|
||||
if(Config.JumpToKey("banned"))
|
||||
{
|
||||
if(Config.GotoFirstSubKey(false))
|
||||
{
|
||||
do
|
||||
{
|
||||
Config.GetString(NULL_STRING, sBuffer, sizeof(sBuffer));
|
||||
|
||||
Handle hRegex = CompileRegex(sBuffer, 0, sError, sizeof(sError), iError);
|
||||
if(iError != REGEX_ERROR_NONE)
|
||||
LogError("Error parsing banned filter: %s", sError);
|
||||
else
|
||||
g_BannedExprs.Push(hRegex);
|
||||
} while(Config.GotoNextKey(false));
|
||||
Config.GoBack();
|
||||
}
|
||||
Config.GoBack();
|
||||
}
|
||||
|
||||
g_ReplacementNames = new ArrayList(ByteCountToCells(MAX_NAME_LENGTH));
|
||||
if(Config.JumpToKey("names"))
|
||||
{
|
||||
if(Config.GotoFirstSubKey(false))
|
||||
{
|
||||
do
|
||||
{
|
||||
Config.GetString(NULL_STRING, sBuffer, sizeof(sBuffer));
|
||||
|
||||
g_ReplacementNames.PushString(sBuffer);
|
||||
} while(Config.GotoNextKey(false));
|
||||
Config.GoBack();
|
||||
}
|
||||
Config.GoBack();
|
||||
}
|
||||
|
||||
if(!g_ReplacementNames.Length)
|
||||
{
|
||||
LogError("Warning, you didn't specify any replacement names!");
|
||||
g_ReplacementNames.PushString("BAD_NAME");
|
||||
}
|
||||
|
||||
delete Config;
|
||||
}
|
||||
|
||||
bool FilterName(int client, char[] sName, int Length = MAX_NAME_LENGTH)
|
||||
{
|
||||
bool bChanged = false;
|
||||
RegexError iError;
|
||||
|
||||
// SourceMod Regex bug
|
||||
int Guard;
|
||||
for(Guard = 0; Guard < 100; Guard++)
|
||||
{
|
||||
int Match = MatchRegex(g_FilterExpr, sName, iError);
|
||||
if(iError != REGEX_ERROR_NONE)
|
||||
{
|
||||
LogError("Regex Error: %d", iError);
|
||||
break;
|
||||
}
|
||||
|
||||
if(Match <= 0)
|
||||
break;
|
||||
|
||||
for(int i = 0; i < Match; i++)
|
||||
{
|
||||
char sMatch[MAX_NAME_LENGTH];
|
||||
if(GetRegexSubString(g_FilterExpr, i, sMatch, sizeof(sMatch)))
|
||||
{
|
||||
if(ReplaceStringEx(sName, Length, sMatch, g_FilterChar) != -1)
|
||||
bChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(Guard == 100)
|
||||
LogError("SourceMod Regex failed! \"%s\"", sName);
|
||||
|
||||
if(g_BannedExprs)
|
||||
{
|
||||
for(int i = 0; i < g_BannedExprs.Length; i++)
|
||||
{
|
||||
Handle hRegex = g_BannedExprs.Get(i);
|
||||
int Match = MatchRegex(hRegex, sName, iError);
|
||||
if(iError != REGEX_ERROR_NONE)
|
||||
{
|
||||
LogError("Regex Error: %d", iError);
|
||||
continue;
|
||||
}
|
||||
|
||||
if(Match <= 0)
|
||||
continue;
|
||||
|
||||
int RandomName = client % g_ReplacementNames.Length;
|
||||
g_ReplacementNames.GetString(RandomName, sName, Length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if(bChanged)
|
||||
{
|
||||
if(strlen(sName) < 2)
|
||||
{
|
||||
int RandomName = client % g_ReplacementNames.Length;
|
||||
g_ReplacementNames.GetString(RandomName, sName, Length);
|
||||
return true;
|
||||
}
|
||||
|
||||
TerminateNameUTF8(sName);
|
||||
}
|
||||
|
||||
return bChanged;
|
||||
}
|
||||
|
||||
// ensures that utf8 names are properly terminated
|
||||
stock void TerminateNameUTF8(char[] name)
|
||||
{
|
||||
int len = strlen(name);
|
||||
for(int i = 0; i < len; i++)
|
||||
{
|
||||
int bytes = IsCharMB(name[i]);
|
||||
if(bytes > 1)
|
||||
{
|
||||
if(len - i < bytes)
|
||||
{
|
||||
name[i] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
i += bytes - 1;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user