From b9935ab8fa2d23e81090d475cfe884fcfd0891c2 Mon Sep 17 00:00:00 2001 From: ORelio Date: Sat, 22 May 2021 15:27:28 +0200 Subject: [PATCH] AutoRespond: Support for per-match cooldown (#593) Prevent a match from triggering too often using a cooldown By default, matches do not have a cooldown (it's opt-in) Also add translation support and more debug messages --- MinecraftClient/ChatBots/AutoRespond.cs | 76 ++++++++++++++++++----- MinecraftClient/Resources/lang/de.ini | 9 +++ MinecraftClient/Resources/lang/en.ini | 9 +++ MinecraftClient/config/sample-matches.ini | 9 +++ 4 files changed, 87 insertions(+), 16 deletions(-) diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index abe9814d..d827bc40 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -35,6 +35,8 @@ namespace MinecraftClient.ChatBots private string actionPrivate; private string actionOther; private bool ownersOnly; + private TimeSpan cooldown; + private DateTime cooldownExpiration; /// /// Create a respond rule from a regex and a reponse message or command @@ -44,7 +46,8 @@ namespace MinecraftClient.ChatBots /// Internal command to run for private messages /// Internal command to run for any other messages /// Only match messages from bot owners - public RespondRule(Regex regex, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly) + /// Minimal cooldown between two matches + public RespondRule(Regex regex, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly, TimeSpan cooldown) { this.regex = regex; this.match = null; @@ -52,6 +55,8 @@ namespace MinecraftClient.ChatBots this.actionPrivate = actionPrivate; this.actionOther = actionOther; this.ownersOnly = ownersOnly; + this.cooldown = cooldown; + this.cooldownExpiration = DateTime.MinValue; } /// @@ -61,7 +66,8 @@ namespace MinecraftClient.ChatBots /// Internal command to run for public messages /// Internal command to run for private messages /// Only match messages from bot owners - public RespondRule(string match, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly) + /// Minimal cooldown between two matches + public RespondRule(string match, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly, TimeSpan cooldown) { this.regex = null; this.match = match; @@ -69,6 +75,8 @@ namespace MinecraftClient.ChatBots this.actionPrivate = actionPrivate; this.actionOther = actionOther; this.ownersOnly = ownersOnly; + this.cooldown = cooldown; + this.cooldownExpiration = DateTime.MinValue; } /// @@ -81,6 +89,9 @@ namespace MinecraftClient.ChatBots /// Internal command to run as a response to this user, or null if no match has been detected public string Match(string username, string message, MessageType msgType, Dictionary localVars) { + if (DateTime.Now < cooldownExpiration) + return null; + string toSend = null; if (ownersOnly && (String.IsNullOrEmpty(username) || !Settings.Bots_Owners.Contains(username.ToLower()))) @@ -100,6 +111,7 @@ namespace MinecraftClient.ChatBots { if (regex.IsMatch(message)) { + cooldownExpiration = DateTime.Now + cooldown; Match regexMatch = regex.Match(message); localVars["match_0"] = regexMatch.Groups[0].Value; for (int i = regexMatch.Groups.Count - 1; i >= 1; i--) @@ -116,6 +128,7 @@ namespace MinecraftClient.ChatBots { if (message.ToLower().Contains(match.ToLower())) { + cooldownExpiration = DateTime.Now + cooldown; localVars["match_0"] = message; localVars["match_u"] = username; return toSend.Replace("$u", username); @@ -124,6 +137,24 @@ namespace MinecraftClient.ChatBots return null; } + + /// + /// Get a string representation of the RespondRule + /// + /// + public override string ToString() + { + return Translations.Get( + "bot.autoRespond.match", + match, + regex, + actionPublic, + actionPrivate, + actionOther, + ownersOnly, + (int)cooldown.TotalSeconds + ); + } } /// @@ -139,10 +170,10 @@ namespace MinecraftClient.ChatBots string matchActionPrivate = null; string matchActionOther = null; bool ownersOnly = false; + TimeSpan cooldown = TimeSpan.Zero; respondRules = new List(); - if (Settings.DebugMessages) - LogToConsole("Loading matches from file: " + System.IO.Path.GetFullPath(matchesFile)); + LogDebugToConsoleTranslated("bot.autoRespond.loading", System.IO.Path.GetFullPath(matchesFile)); foreach (string lineRAW in File.ReadAllLines(matchesFile, Encoding.UTF8)) { @@ -154,13 +185,14 @@ namespace MinecraftClient.ChatBots switch (line.Substring(1, line.Length - 2).ToLower()) { case "match": - CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly); + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly, cooldown); matchRegex = null; matchString = null; matchAction = null; matchActionPrivate = null; matchActionOther = null; ownersOnly = false; + cooldown = TimeSpan.Zero; break; } } @@ -178,16 +210,17 @@ namespace MinecraftClient.ChatBots case "actionprivate": matchActionPrivate = argValue; break; case "actionother": matchActionOther = argValue; break; case "ownersonly": ownersOnly = Settings.str2bool(argValue); break; + case "cooldown": cooldown = TimeSpan.FromSeconds(Settings.str2int(argValue)); break; } } } } } - CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly); + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly, cooldown); } else { - LogToConsole("File not found: '" + System.IO.Path.GetFullPath(matchesFile) + "'"); + LogToConsoleTranslated("bot.autoRespond.file_not_found", System.IO.Path.GetFullPath(matchesFile)); UnloadBot(); //No need to keep the bot active } } @@ -200,21 +233,32 @@ namespace MinecraftClient.ChatBots /// Action if the matching message is public /// Action if the matching message is private /// Only match messages from bot owners - private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate, string matchActionOther, bool ownersOnly) + /// Minimal cooldown between two matches + private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate, string matchActionOther, bool ownersOnly, TimeSpan cooldown) { - if (matchAction != null || matchActionPrivate != null || matchActionOther != null) + if (matchRegex != null || matchString != null || matchAction != null || matchActionPrivate != null || matchActionOther != null || ownersOnly || cooldown != TimeSpan.Zero) { - if (matchRegex != null) + RespondRule rule = matchRegex != null + ? new RespondRule(matchRegex, matchAction, matchActionPrivate, matchActionOther, ownersOnly, cooldown) + : new RespondRule(matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly, cooldown); + + if (matchAction != null || matchActionPrivate != null || matchActionOther != null) { - respondRules.Add(new RespondRule(matchRegex, matchAction, matchActionPrivate, matchActionOther, ownersOnly)); - } - else if (matchString != null) - { - respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly)); + if (matchRegex != null || matchString != null) + { + respondRules.Add(rule); + LogDebugToConsoleTranslated("bot.autoRespond.loaded_match", rule); + } + else LogDebugToConsoleTranslated("bot.autoRespond.no_trigger", rule); } + else LogDebugToConsoleTranslated("bot.autoRespond.no_action", rule); } } + /// + /// Process messages from the server and test them against all matches + /// + /// Text from the server public override void GetText(string text) { //Remove colour codes @@ -239,7 +283,7 @@ namespace MinecraftClient.ChatBots if (!String.IsNullOrEmpty(toPerform)) { string response = null; - LogToConsole(toPerform); + LogToConsoleTranslated("bot.autoRespond.match_run", toPerform); PerformInternalCommand(toPerform, ref response, localVars); if (!String.IsNullOrEmpty(response)) LogToConsole(response); diff --git a/MinecraftClient/Resources/lang/de.ini b/MinecraftClient/Resources/lang/de.ini index 27bb71b3..adda5cdf 100644 --- a/MinecraftClient/Resources/lang/de.ini +++ b/MinecraftClient/Resources/lang/de.ini @@ -429,6 +429,15 @@ bot.autoRelog.reconnect=Nachricht enhält '{0}'. Verbinde erneut. bot.autoRelog.reconnect_ignore=Kick Nachricht enthält keine Schlüsselwörter. Wird ignoriert! bot.autoRelog.wait=Warte {0} Sekunden vor erneuter Verbindung... +# AutoRespond +bot.autoRespond.loading=Loading matches from '{0}' +bot.autoRespond.file_not_found=File not found: '{0}' +bot.autoRespond.loaded_match=Loaded match:\n{0} +bot.autoRespond.no_trigger=This match will never trigger:\n{0} +bot.autoRespond.no_action=No action for match:\n{0} +bot.autoRespond.match_run=Running action: {0} +bot.autoRespond.match=match: {0}\nregex: {1}\naction: {2}\nactionPrivate: {3}\nactionOther: {4}\nownersOnly: {5}\ncooldown: {6} + # ChatLog bot.chatLog.invalid_file=Pfad '{0}' enthält ungültige Zeichen. diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index a65d2114..4ed57100 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -429,6 +429,15 @@ bot.autoRelog.reconnect=Message contains '{0}'. Reconnecting. bot.autoRelog.reconnect_ignore=Message not containing any defined keywords. Ignoring. bot.autoRelog.wait=Waiting {0} seconds before reconnecting... +# AutoRespond +bot.autoRespond.loading=Loading matches from '{0}' +bot.autoRespond.file_not_found=File not found: '{0}' +bot.autoRespond.loaded_match=Loaded match:\n{0} +bot.autoRespond.no_trigger=This match will never trigger:\n{0} +bot.autoRespond.no_action=No action for match:\n{0} +bot.autoRespond.match_run=Running action: {0} +bot.autoRespond.match=match: {0}\nregex: {1}\naction: {2}\nactionPrivate: {3}\nactionOther: {4}\nownersOnly: {5}\ncooldown: {6} + # ChatLog bot.chatLog.invalid_file=Path '{0}' contains invalid characters. diff --git a/MinecraftClient/config/sample-matches.ini b/MinecraftClient/config/sample-matches.ini index 3e8bac5a..09a4b888 100644 --- a/MinecraftClient/config/sample-matches.ini +++ b/MinecraftClient/config/sample-matches.ini @@ -9,6 +9,7 @@ # You can define an action if the match was not sent by a player # Regex matches are also supported eg $1, $2, $3.. in actions # Matches can optionally be restricted to bot owners only +# Matches can have a cooldown, specified in seconds # When running a script from an AutoRespond match, # additional %variables% are available from within your script: @@ -23,6 +24,7 @@ action=send hi, $u! actionprivate=send /tell $u Hello! actionother=log detected "hi" message ownersonly=false +cooldown=0 # You do not need to specify all the "action" fields # Only one of them is required for each match @@ -54,5 +56,12 @@ match=gohome actionprivate=send /home ownersonly=true +# Example of match with 1-minute cooldown + +[Match] +match=hello +action=send hello! +cooldown=60 + # Enjoy! # - ORelio \ No newline at end of file