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
This commit is contained in:
ORelio 2021-05-22 15:27:28 +02:00
parent e6b2b87366
commit b9935ab8fa
4 changed files with 87 additions and 16 deletions

View file

@ -35,6 +35,8 @@ namespace MinecraftClient.ChatBots
private string actionPrivate; private string actionPrivate;
private string actionOther; private string actionOther;
private bool ownersOnly; private bool ownersOnly;
private TimeSpan cooldown;
private DateTime cooldownExpiration;
/// <summary> /// <summary>
/// Create a respond rule from a regex and a reponse message or command /// Create a respond rule from a regex and a reponse message or command
@ -44,7 +46,8 @@ namespace MinecraftClient.ChatBots
/// <param name="actionPrivate">Internal command to run for private messages</param> /// <param name="actionPrivate">Internal command to run for private messages</param>
/// <param name="actionOther">Internal command to run for any other messages</param> /// <param name="actionOther">Internal command to run for any other messages</param>
/// <param name="ownersOnly">Only match messages from bot owners</param> /// <param name="ownersOnly">Only match messages from bot owners</param>
public RespondRule(Regex regex, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly) /// <param name="cooldown">Minimal cooldown between two matches</param>
public RespondRule(Regex regex, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly, TimeSpan cooldown)
{ {
this.regex = regex; this.regex = regex;
this.match = null; this.match = null;
@ -52,6 +55,8 @@ namespace MinecraftClient.ChatBots
this.actionPrivate = actionPrivate; this.actionPrivate = actionPrivate;
this.actionOther = actionOther; this.actionOther = actionOther;
this.ownersOnly = ownersOnly; this.ownersOnly = ownersOnly;
this.cooldown = cooldown;
this.cooldownExpiration = DateTime.MinValue;
} }
/// <summary> /// <summary>
@ -61,7 +66,8 @@ namespace MinecraftClient.ChatBots
/// <param name="actionPublic">Internal command to run for public messages</param> /// <param name="actionPublic">Internal command to run for public messages</param>
/// <param name="actionPrivate">Internal command to run for private messages</param> /// <param name="actionPrivate">Internal command to run for private messages</param>
/// <param name="ownersOnly">Only match messages from bot owners</param> /// <param name="ownersOnly">Only match messages from bot owners</param>
public RespondRule(string match, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly) /// <param name="cooldown">Minimal cooldown between two matches</param>
public RespondRule(string match, string actionPublic, string actionPrivate, string actionOther, bool ownersOnly, TimeSpan cooldown)
{ {
this.regex = null; this.regex = null;
this.match = match; this.match = match;
@ -69,6 +75,8 @@ namespace MinecraftClient.ChatBots
this.actionPrivate = actionPrivate; this.actionPrivate = actionPrivate;
this.actionOther = actionOther; this.actionOther = actionOther;
this.ownersOnly = ownersOnly; this.ownersOnly = ownersOnly;
this.cooldown = cooldown;
this.cooldownExpiration = DateTime.MinValue;
} }
/// <summary> /// <summary>
@ -81,6 +89,9 @@ namespace MinecraftClient.ChatBots
/// <returns>Internal command to run as a response to this user, or null if no match has been detected</returns> /// <returns>Internal command to run as a response to this user, or null if no match has been detected</returns>
public string Match(string username, string message, MessageType msgType, Dictionary<string, object> localVars) public string Match(string username, string message, MessageType msgType, Dictionary<string, object> localVars)
{ {
if (DateTime.Now < cooldownExpiration)
return null;
string toSend = null; string toSend = null;
if (ownersOnly && (String.IsNullOrEmpty(username) || !Settings.Bots_Owners.Contains(username.ToLower()))) if (ownersOnly && (String.IsNullOrEmpty(username) || !Settings.Bots_Owners.Contains(username.ToLower())))
@ -100,6 +111,7 @@ namespace MinecraftClient.ChatBots
{ {
if (regex.IsMatch(message)) if (regex.IsMatch(message))
{ {
cooldownExpiration = DateTime.Now + cooldown;
Match regexMatch = regex.Match(message); Match regexMatch = regex.Match(message);
localVars["match_0"] = regexMatch.Groups[0].Value; localVars["match_0"] = regexMatch.Groups[0].Value;
for (int i = regexMatch.Groups.Count - 1; i >= 1; i--) for (int i = regexMatch.Groups.Count - 1; i >= 1; i--)
@ -116,6 +128,7 @@ namespace MinecraftClient.ChatBots
{ {
if (message.ToLower().Contains(match.ToLower())) if (message.ToLower().Contains(match.ToLower()))
{ {
cooldownExpiration = DateTime.Now + cooldown;
localVars["match_0"] = message; localVars["match_0"] = message;
localVars["match_u"] = username; localVars["match_u"] = username;
return toSend.Replace("$u", username); return toSend.Replace("$u", username);
@ -124,6 +137,24 @@ namespace MinecraftClient.ChatBots
return null; return null;
} }
/// <summary>
/// Get a string representation of the RespondRule
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Translations.Get(
"bot.autoRespond.match",
match,
regex,
actionPublic,
actionPrivate,
actionOther,
ownersOnly,
(int)cooldown.TotalSeconds
);
}
} }
/// <summary> /// <summary>
@ -139,10 +170,10 @@ namespace MinecraftClient.ChatBots
string matchActionPrivate = null; string matchActionPrivate = null;
string matchActionOther = null; string matchActionOther = null;
bool ownersOnly = false; bool ownersOnly = false;
TimeSpan cooldown = TimeSpan.Zero;
respondRules = new List<RespondRule>(); respondRules = new List<RespondRule>();
if (Settings.DebugMessages) LogDebugToConsoleTranslated("bot.autoRespond.loading", System.IO.Path.GetFullPath(matchesFile));
LogToConsole("Loading matches from file: " + System.IO.Path.GetFullPath(matchesFile));
foreach (string lineRAW in File.ReadAllLines(matchesFile, Encoding.UTF8)) foreach (string lineRAW in File.ReadAllLines(matchesFile, Encoding.UTF8))
{ {
@ -154,13 +185,14 @@ namespace MinecraftClient.ChatBots
switch (line.Substring(1, line.Length - 2).ToLower()) switch (line.Substring(1, line.Length - 2).ToLower())
{ {
case "match": case "match":
CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly); CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly, cooldown);
matchRegex = null; matchRegex = null;
matchString = null; matchString = null;
matchAction = null; matchAction = null;
matchActionPrivate = null; matchActionPrivate = null;
matchActionOther = null; matchActionOther = null;
ownersOnly = false; ownersOnly = false;
cooldown = TimeSpan.Zero;
break; break;
} }
} }
@ -178,16 +210,17 @@ namespace MinecraftClient.ChatBots
case "actionprivate": matchActionPrivate = argValue; break; case "actionprivate": matchActionPrivate = argValue; break;
case "actionother": matchActionOther = argValue; break; case "actionother": matchActionOther = argValue; break;
case "ownersonly": ownersOnly = Settings.str2bool(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 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 UnloadBot(); //No need to keep the bot active
} }
} }
@ -200,21 +233,32 @@ namespace MinecraftClient.ChatBots
/// <param name="matchAction">Action if the matching message is public</param> /// <param name="matchAction">Action if the matching message is public</param>
/// <param name="matchActionPrivate">Action if the matching message is private</param> /// <param name="matchActionPrivate">Action if the matching message is private</param>
/// <param name="ownersOnly">Only match messages from bot owners</param> /// <param name="ownersOnly">Only match messages from bot owners</param>
private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate, string matchActionOther, bool ownersOnly) /// <param name="cooldown">Minimal cooldown between two matches</param>
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)); if (matchRegex != null || matchString != null)
} {
else if (matchString != null) respondRules.Add(rule);
{ LogDebugToConsoleTranslated("bot.autoRespond.loaded_match", rule);
respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate, matchActionOther, ownersOnly)); }
else LogDebugToConsoleTranslated("bot.autoRespond.no_trigger", rule);
} }
else LogDebugToConsoleTranslated("bot.autoRespond.no_action", rule);
} }
} }
/// <summary>
/// Process messages from the server and test them against all matches
/// </summary>
/// <param name="text">Text from the server</param>
public override void GetText(string text) public override void GetText(string text)
{ {
//Remove colour codes //Remove colour codes
@ -239,7 +283,7 @@ namespace MinecraftClient.ChatBots
if (!String.IsNullOrEmpty(toPerform)) if (!String.IsNullOrEmpty(toPerform))
{ {
string response = null; string response = null;
LogToConsole(toPerform); LogToConsoleTranslated("bot.autoRespond.match_run", toPerform);
PerformInternalCommand(toPerform, ref response, localVars); PerformInternalCommand(toPerform, ref response, localVars);
if (!String.IsNullOrEmpty(response)) if (!String.IsNullOrEmpty(response))
LogToConsole(response); LogToConsole(response);

View file

@ -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.reconnect_ignore=Kick Nachricht enthält keine Schlüsselwörter. Wird ignoriert!
bot.autoRelog.wait=Warte {0} Sekunden vor erneuter Verbindung... 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 # ChatLog
bot.chatLog.invalid_file=Pfad '{0}' enthält ungültige Zeichen. bot.chatLog.invalid_file=Pfad '{0}' enthält ungültige Zeichen.

View file

@ -429,6 +429,15 @@ bot.autoRelog.reconnect=Message contains '{0}'. Reconnecting.
bot.autoRelog.reconnect_ignore=Message not containing any defined keywords. Ignoring. bot.autoRelog.reconnect_ignore=Message not containing any defined keywords. Ignoring.
bot.autoRelog.wait=Waiting {0} seconds before reconnecting... 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 # ChatLog
bot.chatLog.invalid_file=Path '{0}' contains invalid characters. bot.chatLog.invalid_file=Path '{0}' contains invalid characters.

View file

@ -9,6 +9,7 @@
# You can define an action if the match was not sent by a player # 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 # Regex matches are also supported eg $1, $2, $3.. in actions
# Matches can optionally be restricted to bot owners only # 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, # When running a script from an AutoRespond match,
# additional %variables% are available from within your script: # additional %variables% are available from within your script:
@ -23,6 +24,7 @@ action=send hi, $u!
actionprivate=send /tell $u Hello! actionprivate=send /tell $u Hello!
actionother=log detected "hi" message actionother=log detected "hi" message
ownersonly=false ownersonly=false
cooldown=0
# You do not need to specify all the "action" fields # You do not need to specify all the "action" fields
# Only one of them is required for each match # Only one of them is required for each match
@ -54,5 +56,12 @@ match=gohome
actionprivate=send /home actionprivate=send /home
ownersonly=true ownersonly=true
# Example of match with 1-minute cooldown
[Match]
match=hello
action=send hello!
cooldown=60
# Enjoy! # Enjoy!
# - ORelio # - ORelio