Profanity deterrent kick players with offensive names

status parsing with Regex in IW4 is much cleaner
fixed tempban not always kicking
made plugin event tasks parallel
This commit is contained in:
RaidMax 2018-04-29 15:44:04 -05:00
parent 35e7f57156
commit 3a463be7f8
8 changed files with 94 additions and 51 deletions

View File

@ -192,6 +192,9 @@
"GLOBAL_ERROR": "Error", "GLOBAL_ERROR": "Error",
"GLOBAL_WARNING": "Warning", "GLOBAL_WARNING": "Warning",
"GLOBAL_INFO": "Info", "GLOBAL_INFO": "Info",
"GLOBAL_VERBOSE": "Verbose" "GLOBAL_VERBOSE": "Verbose",
"MANAGER_CONSOLE_NOSERV": "No servers are currently being monitored",
"SERVER_PLUGIN_ERROR": "A plugin generated an error"
} }
} }

View File

@ -40,7 +40,7 @@
"SERVER_WARNLIMT_REACHED": "Слишком много предупреждений", "SERVER_WARNLIMT_REACHED": "Слишком много предупреждений",
"SERVER_WARNING": "Предупреждение", "SERVER_WARNING": "Предупреждение",
"SERVER_WEBSITE_GENERIC": "веб-сайт этого сервера", "SERVER_WEBSITE_GENERIC": "веб-сайт этого сервера",
"BROADCAST_ONLINE": "^5IW4MADMIN ^7сейчас ^В СЕТИ", "BROADCAST_ONLINE": "^5IW4MADMIN ^7сейчас СЕТИ",
"BROADCAST_OFFLINE": "IW4MAdmin отключается", "BROADCAST_OFFLINE": "IW4MAdmin отключается",
"COMMAND_HELP_SYNTAX": "синтаксис:", "COMMAND_HELP_SYNTAX": "синтаксис:",
"COMMAND_HELP_OPTIONAL": "опционально", "COMMAND_HELP_OPTIONAL": "опционально",

View File

@ -23,6 +23,11 @@ namespace Application.Misc
string response = await RequestClient.GetStringAsync($"http://v2.api.iphub.info/ip/{ip}"); string response = await RequestClient.GetStringAsync($"http://v2.api.iphub.info/ip/{ip}");
var responseJson = JsonConvert.DeserializeObject<JObject>(response); var responseJson = JsonConvert.DeserializeObject<JObject>(response);
int blockType = Convert.ToInt32(responseJson["block"]); int blockType = Convert.ToInt32(responseJson["block"]);
if (responseJson.ContainsKey("isp"))
{
if (responseJson["isp"].ToString() == "TSF-IP-CORE")
return true;
}
return blockType == 1; return blockType == 1;
} }
} }

View File

@ -22,7 +22,9 @@ namespace Application.RconParsers
Ban = "clientkick {0} \"{1}\"", Ban = "clientkick {0} \"{1}\"",
TempBan = "tempbanclient {0} \"{1}\"" TempBan = "tempbanclient {0} \"{1}\""
}; };
private static string StatusRegex = @"^( *[0-9]+) +([0-9]+) +((?:[A-Z]+|[0-9]+)) +((?:[a-z]|[0-9]){16}|bot[0-9]+) +(.{0,20}) +([0-9]+) +(\d+\.\d+\.\d+.\d+\:\d{1,5}|0+.0+:\d{1,5}) +(-*[0-9]+) +([0-9]+) *$";
public async Task<string[]> ExecuteCommandAsync(Connection connection, string command) public async Task<string[]> ExecuteCommandAsync(Connection connection, string command)
{ {
return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command)).Skip(1).ToArray(); return (await connection.SendQueryAsync(StaticHelpers.QueryType.COMMAND, command)).Skip(1).ToArray();
@ -82,30 +84,33 @@ namespace Application.RconParsers
{ {
String responseLine = S.Trim(); String responseLine = S.Trim();
if (Regex.Matches(responseLine, @" *^\d+", RegexOptions.IgnoreCase).Count > 0) var regex = Regex.Match(responseLine, StatusRegex, RegexOptions.IgnoreCase);
if (regex.Success)
{ {
String[] playerInfo = responseLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); int clientNumber = int.Parse(regex.Groups[1].Value);
if (playerInfo.Length < 4) int score = int.Parse(regex.Groups[2].Value);
continue;
int cID = -1; int ping = 999;
int Ping = -1;
Int32.TryParse(playerInfo[2], out Ping); // their state can be CNCT, ZMBI etc
String cName = Encoding.UTF8.GetString(Encoding.Convert(Utilities.EncodingType, Encoding.UTF8, Utilities.EncodingType.GetBytes(responseLine.Substring(46, 18).StripColors().Trim()))); if (regex.Groups[3].Value.Length <= 3)
long npID = Regex.Match(responseLine, @"([a-z]|[0-9]){16}|bot[0-9]+", RegexOptions.IgnoreCase).Value.ConvertLong(); {
int.TryParse(playerInfo[0], out cID); ping = int.Parse(regex.Groups[3].Value);
var regex = Regex.Match(responseLine, @"\d+\.\d+\.\d+.\d+\:\d{1,5}"); }
int cIP = regex.Value.Split(':')[0].ConvertToIP();
regex = Regex.Match(responseLine, @"[0-9]{1,2}\s+[0-9]+\s+"); long networkId = regex.Groups[4].Value.ConvertLong();
int score = Int32.Parse(regex.Value.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries)[1]); string name = regex.Groups[5].Value.StripColors().Trim();
int ip = regex.Groups[7].Value.Split(':')[0].ConvertToIP();
Player P = new Player() Player P = new Player()
{ {
Name = cName, Name = name,
NetworkId = npID, NetworkId = networkId,
ClientNumber = cID, ClientNumber = clientNumber,
IPAddress = cIP, IPAddress = ip,
Ping = Ping, Ping = ping,
Score = score, Score = score,
IsBot = cIP == 0 IsBot = ip == 0
}; };
StatusPlayers.Add(P); StatusPlayers.Add(P);

View File

@ -178,6 +178,9 @@ namespace IW4MAdmin
if (player.Level != Player.Permission.Banned && currentBan.Type == Penalty.PenaltyType.Ban) if (player.Level != Player.Permission.Banned && currentBan.Type == Penalty.PenaltyType.Ban)
await player.Ban($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense}", autoKickClient); await player.Ban($"{loc["SERVER_BAN_PREV"]} {currentBan.Offense}", autoKickClient);
// they didn't fully connect so empty their slot
Players[player.ClientNumber] = null;
return true; return true;
} }
@ -186,7 +189,7 @@ namespace IW4MAdmin
var e = new GameEvent(GameEvent.EventType.Connect, "", player, null, this); var e = new GameEvent(GameEvent.EventType.Connect, "", player, null, this);
Manager.GetEventHandler().AddEvent(e); Manager.GetEventHandler().AddEvent(e);
// e.OnProcessed.Wait(); e.OnProcessed.Wait();
if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs && if (!Manager.GetApplicationSettings().Configuration().EnableClientVPNs &&
await VPNCheck.UsingVPN(player.IPAddressString, Manager.GetApplicationSettings().Configuration().IPHubAPIKey)) await VPNCheck.UsingVPN(player.IPAddressString, Manager.GetApplicationSettings().Configuration().IPHubAPIKey))
@ -362,14 +365,15 @@ namespace IW4MAdmin
await ProcessEvent(E); await ProcessEvent(E);
Manager.GetEventApi().OnServerEvent(this, E); Manager.GetEventApi().OnServerEvent(this, E);
foreach (IPlugin P in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) // this allows us to catch exceptions but still run it parallel
async Task pluginHandlingAsync(Task onEvent, string pluginName)
{ {
try try
{ {
if (cts.IsCancellationRequested) if (cts.IsCancellationRequested)
break; return;
await P.OnEventAsync(E, this); await onEvent;
} }
// this happens if a plugin (login) wants to stop commands from executing // this happens if a plugin (login) wants to stop commands from executing
@ -381,7 +385,7 @@ namespace IW4MAdmin
catch (Exception Except) catch (Exception Except)
{ {
Logger.WriteError(String.Format("The plugin \"{0}\" generated an error. ( see log )", P.Name)); Logger.WriteError($"{loc["SERVER_PLUGIN_ERROR"]} [{pluginName}]");
Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message)); Logger.WriteDebug(String.Format("Error Message: {0}", Except.Message));
Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace)); Logger.WriteDebug(String.Format("Error Trace: {0}", Except.StackTrace));
while (Except.InnerException != null) while (Except.InnerException != null)
@ -389,11 +393,15 @@ namespace IW4MAdmin
Except = Except.InnerException; Except = Except.InnerException;
Logger.WriteDebug($"Inner exception: {Except.Message}"); Logger.WriteDebug($"Inner exception: {Except.Message}");
} }
continue;
} }
} }
var pluginTasks = SharedLibraryCore.Plugins.PluginImporter.ActivePlugins.
Select(p => pluginHandlingAsync(p.OnEventAsync(E, this), p.Name));
// execute all the plugin updates simultaneously
await Task.WhenAll(pluginTasks);
// hack: this prevents commands from getting executing that 'shouldn't' be // hack: this prevents commands from getting executing that 'shouldn't' be
if (E.Type == GameEvent.EventType.Command && if (E.Type == GameEvent.EventType.Command &&
E.Extra != null && E.Extra != null &&
@ -402,7 +410,6 @@ namespace IW4MAdmin
{ {
await (((Command)E.Extra).ExecuteAsync(E)); await (((Command)E.Extra).ExecuteAsync(E));
} }
} }
/// <summary> /// <summary>
@ -599,27 +606,35 @@ namespace IW4MAdmin
override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts) override public async Task<bool> ProcessUpdatesAsync(CancellationToken cts)
{ {
// this isn't really used anymore
this.cts = cts; this.cts = cts;
try try
{ {
if (Manager.ShutdownRequested())
{
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
await plugin.OnUnloadAsync();
for (int i = 0; i < Players.Count; i++)
await RemovePlayer(i);
}
// only check every 2 minutes if the server doesn't seem to be responding
if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1) if ((DateTime.Now - LastPoll).TotalMinutes < 2 && ConnectionErrors >= 1)
return true; return true;
try try
{ {
// trying to reduce the polling rate as every 450ms is unnecessary int polledPlayerCount = await PollPlayersAsync();
if ((DateTime.Now - LastPoll).TotalSeconds >= 10)
{
int polledPlayerCount = await PollPlayersAsync();
if (ConnectionErrors > 0) if (ConnectionErrors > 0)
{ {
Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}"); Logger.WriteVerbose($"{loc["MANAGER_CONNECTION_REST"]} {IP}:{Port}");
Throttled = false; Throttled = false;
}
ConnectionErrors = 0;
LastPoll = DateTime.Now;
} }
ConnectionErrors = 0;
LastPoll = DateTime.Now;
} }
catch (NetworkException e) catch (NetworkException e)
@ -637,6 +652,8 @@ namespace IW4MAdmin
LastMessage = DateTime.Now - start; LastMessage = DateTime.Now - start;
lastCount = DateTime.Now; lastCount = DateTime.Now;
// todo: re-enable on tick
/*
if ((DateTime.Now - tickTime).TotalMilliseconds >= 1000) if ((DateTime.Now - tickTime).TotalMilliseconds >= 1000)
{ {
foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins) foreach (var Plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
@ -647,8 +664,9 @@ namespace IW4MAdmin
await Plugin.OnTickAsync(this); await Plugin.OnTickAsync(this);
} }
tickTime = DateTime.Now; tickTime = DateTime.Now;
} }*/
// update the player history
if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) if ((lastCount - playerCountStart).TotalMinutes >= SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval)
{ {
while (PlayerHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours while (PlayerHistory.Count > ((60 / SharedLibraryCore.Helpers.PlayerHistory.UpdateInterval) * 12)) // 12 times a hour for 12 hours
@ -657,6 +675,7 @@ namespace IW4MAdmin
playerCountStart = DateTime.Now; playerCountStart = DateTime.Now;
} }
// send out broadcast messages
if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod if (LastMessage.TotalSeconds > Manager.GetApplicationSettings().Configuration().AutoMessagePeriod
&& BroadcastMessages.Count > 0 && BroadcastMessages.Count > 0
&& ClientNum > 0) && ClientNum > 0)
@ -666,14 +685,6 @@ namespace IW4MAdmin
start = DateTime.Now; start = DateTime.Now;
} }
if (Manager.ShutdownRequested())
{
foreach (var plugin in SharedLibraryCore.Plugins.PluginImporter.ActivePlugins)
await plugin.OnUnloadAsync();
for (int i = 0; i < Players.Count; i++)
await RemovePlayer(i);
}
return true; return true;
} }

View File

@ -33,6 +33,16 @@ namespace IW4MAdmin.Plugins.ProfanityDeterment
S.Logger.WriteWarning("Could not add client to profanity tracking"); S.Logger.WriteWarning("Could not add client to profanity tracking");
} }
var objectionalWords = Settings.Configuration().OffensiveWords;
bool containsObjectionalWord = objectionalWords.FirstOrDefault(w => E.Origin.Name.ToLower().Contains(w)) != null;
if (containsObjectionalWord)
{
await E.Origin.Kick(Settings.Configuration().ProfanityKickMessage, new Player()
{
ClientId = 1
});
};
} }
if (E.Type == GameEvent.EventType.Disconnect) if (E.Type == GameEvent.EventType.Disconnect)

View File

@ -467,6 +467,14 @@ namespace IW4MAdmin.Plugins.Stats.Helpers
// calculate the new weight against average times the weight against play time // calculate the new weight against average times the weight against play time
clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight)); clientStats.SPM = (killSPM * SPMAgainstPlayWeight) + (clientStats.SPM * (1 - SPMAgainstPlayWeight));
if (clientStats.SPM < 0)
{
Log.WriteWarning("[StatManager:UpdateStats] clientStats SPM < 0");
Log.WriteDebug($"{scoreDifference}-{clientStats.RoundScore} - {clientStats.LastScore} - {clientStats.SessionScore}");
clientStats.SPM = 0;
}
clientStats.SPM = Math.Round(clientStats.SPM, 3); clientStats.SPM = Math.Round(clientStats.SPM, 3);
clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3); clientStats.Skill = Math.Round((clientStats.SPM * KDRWeight), 3);

View File

@ -37,6 +37,7 @@ namespace WebfrontCore.Controllers
CurrentServer = server, CurrentServer = server,
Name = Client.Name Name = Client.Name
}; };
var remoteEvent = new GameEvent() var remoteEvent = new GameEvent()
{ {
Type = GameEvent.EventType.Say, Type = GameEvent.EventType.Say,