diff --git a/MinecraftClient/ChatBots/AutoRespond.cs b/MinecraftClient/ChatBots/AutoRespond.cs index 3a0d6575..52a14944 100644 --- a/MinecraftClient/ChatBots/AutoRespond.cs +++ b/MinecraftClient/ChatBots/AutoRespond.cs @@ -1,38 +1,209 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; +using System.Collections.Generic; +using System.Text.RegularExpressions; namespace MinecraftClient.ChatBots { + /// + /// This bot automatically runs actions when a user sends a message matching a specified rule + /// class AutoRespond : ChatBot { - private string[] respondon = new string[0]; - private string[] torespond = new string[0]; + private string matchesFile; + private List respondRules; + private static string header = "[AutoRespond] "; - //Initalize the bot + /// + /// Create a new AutoRespond bot + /// + /// INI File to load matches from + public AutoRespond(string matchesFile) + { + this.matchesFile = matchesFile; + } + + /// + /// Describe a respond rule based on a simple match or a regex + /// + private class RespondRule + { + private Regex regex; + private string match; + private string actionPublic; + private string actionPrivate; + + /// + /// Create a respond rule from a regex and a reponse message or command + /// + /// Regex + /// Internal command to run for public messages + /// Internal command to run for private messages + public RespondRule(Regex regex, string actionPublic, string actionPrivate) + { + this.regex = regex; + this.match = null; + this.actionPublic = actionPublic; + this.actionPrivate = actionPrivate; + } + + /// + /// Create a respond rule from a match string and a reponse message or command + /// + /// Match string + /// Internal command to run for public messages + /// Internal command to run for private messages + public RespondRule(string match, string actionPublic, string actionPrivate) + { + this.regex = null; + this.match = match; + this.actionPublic = actionPublic; + this.actionPrivate = actionPrivate; + } + + /// + /// Match the respond rule to the specified string and return a message or command to send if a match is detected + /// + /// Player who have sent the message + /// Message to match against the regex or match string + /// True if the provided message was sent privately eg with /tell + /// 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, bool privateMsg) + { + if (regex != null) + { + if (regex.IsMatch(message)) + { + Match regexMatch = regex.Match(message); + string toSend = privateMsg ? actionPrivate : actionPublic; + for (int i = regexMatch.Groups.Count - 1; i >= 1; i--) + toSend = toSend.Replace("$" + i, regexMatch.Groups[i].Value); + toSend = toSend.Replace("$u", username); + return toSend; + } + } + else if (!String.IsNullOrEmpty(match)) + { + if (message.Contains(match)) + { + return (privateMsg + ? actionPrivate + : actionPublic).Replace("$u", username); + } + } + return null; + } + } + + /// + /// Initialize the AutoRespond bot from the matches file + /// public override void Initialize() { - respondon = LoadDistinctEntriesFromFile(Settings.Respond_MatchesFile); - torespond = LoadDistinctEntriesFromFile(Settings.Respond_RespondFile); - ConsoleIO.WriteLine("Auto Respond Bot Sucessfully loaded!"); + if (File.Exists(matchesFile)) + { + Regex matchRegex = null; + string matchString = null; + string matchAction = null; + string matchActionPrivate = null; + respondRules = new List(); + + foreach (string lineRAW in File.ReadAllLines(matchesFile)) + { + string line = lineRAW.Split('#')[0].Trim(); + if (line.Length > 0) + { + if (line[0] == '[' && line[line.Length - 1] == ']') + { + switch (line.Substring(1, line.Length - 2).ToLower()) + { + case "match": + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate); + matchRegex = null; + matchString = null; + matchAction = null; + matchActionPrivate = null; + break; + } + } + else + { + string argName = line.Split('=')[0]; + if (line.Length > (argName.Length + 1)) + { + string argValue = line.Substring(argName.Length + 1); + switch (argName.ToLower()) + { + case "regex": matchRegex = new Regex(argValue); break; + case "match": matchString = argValue; break; + case "action": matchAction = argValue; break; + case "actionprivate": matchAction = argValue; break; + } + } + } + } + } + CheckAddMatch(matchRegex, matchString, matchAction, matchActionPrivate); + } + else + { + LogToConsole("File not found: '" + matchesFile + "'"); + UnloadBot(); //No need to keep the bot active + } + } + + /// + /// Create a new respond rule from the provided arguments, only if they are valid: at least one match and one action + /// + /// Matching regex + /// Matching string + /// Action if the matching message is public + /// Action if the matching message is private + private void CheckAddMatch(Regex matchRegex, string matchString, string matchAction, string matchActionPrivate) + { + if (matchAction != null || matchActionPrivate != null) + { + if (matchActionPrivate == null) + { + matchActionPrivate = matchAction; + } + + if (matchRegex != null) + { + respondRules.Add(new RespondRule(matchRegex, matchAction, matchActionPrivate)); + } + else if (matchString != null) + { + respondRules.Add(new RespondRule(matchString, matchAction, matchActionPrivate)); + } + } } public override void GetText(string text) { //Remove colour codes text = getVerbatim(text).ToLower(); - //Check text to see if bot should respond - foreach (string alert in respondon.Where(alert => text.Contains(alert))) + + //Check if this is a valid message + string sender = "", message = ""; + bool chatMessage = isChatMessage(text, ref message, ref sender); + bool privateMessage = false; + if (!chatMessage) + privateMessage = isPrivateMessage(text, ref message, ref sender); + + //Process only chat messages sent by another user + if ((chatMessage || privateMessage) && sender != Settings.Username) { - //Find what to respond with - for (int x = 0; x < respondon.Length; x++) + foreach (RespondRule rule in respondRules) { - if (respondon[x].ToString().Contains(alert)) + string toPerform = rule.Match(sender, message, privateMessage); + if (toPerform != null) { - //Respond - SendText(torespond[x].ToString()); + string response = null; + LogToConsole(header + toPerform); + performInternalCommand(toPerform, ref response); + if (!String.IsNullOrEmpty(response)) + LogToConsole(header + response); } } } diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 3f152002..46b40c59 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -106,7 +106,7 @@ namespace MinecraftClient if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); } if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.expandVars(Settings.ScriptScheduler_TasksFile))); } if (Settings.RemoteCtrl_Enabled) { BotLoad(new ChatBots.RemoteControl()); } - if (Settings.Respond_Enabled) { BotLoad(new ChatBots.AutoRespond()); } + if (Settings.AutoRespond_Enabled) { BotLoad(new ChatBots.AutoRespond(Settings.AutoRespond_Matches)); } } try diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 4c4779a7..b7daa524 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -90,16 +90,15 @@ namespace MinecraftClient public static bool RemoteCtrl_AutoTpaccept_Everyone = false; //Auto Respond - public static bool Respond_Enabled = false; - public static string Respond_MatchesFile = "detect.txt"; - public static string Respond_RespondFile = "respond.txt"; + public static bool AutoRespond_Enabled = false; + public static string AutoRespond_Matches = "matches.ini"; //Custom app variables and Minecraft accounts private static Dictionary AppVars = new Dictionary(); private static Dictionary> Accounts = new Dictionary>(); private static Dictionary> Servers = new Dictionary>(); - private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, Auto_Respond }; + private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, AutoRespond }; /// /// Load settings from the give INI file @@ -133,7 +132,7 @@ namespace MinecraftClient case "remotecontrol": pMode = ParseMode.RemoteControl; break; case "proxy": pMode = ParseMode.Proxy; break; case "appvars": pMode = ParseMode.AppVars; break; - case "auto respond": pMode = ParseMode.Auto_Respond; break; + case "autorespond": pMode = ParseMode.AutoRespond; break; default: pMode = ParseMode.Default; break; } } @@ -317,12 +316,11 @@ namespace MinecraftClient setVar(argName, argValue); break; - case ParseMode.Auto_Respond: + case ParseMode.AutoRespond: switch (argName.ToLower()) { - case "enabled": Respond_Enabled = str2bool(argValue); break; - case "matchfile": Respond_MatchesFile = argValue; break; - case "respondfile": Respond_RespondFile = argValue; break; + case "enabled": AutoRespond_Enabled = str2bool(argValue); break; + case "matchesfile": AutoRespond_Matches = argValue; break; } break; } @@ -422,12 +420,9 @@ namespace MinecraftClient + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + "\r\n" - + "[Auto Respond]\r\n" + + "[AutoRespond]\r\n" + "enabled=false\r\n" - + "matchfile=detect.txt\r\n" - + "respondfile=respond.txt\r\n" - + "#To use the bot, place the text to detect in the matchfile file and the text to respond with in the respondfile\r\n" - + "#Each line in each file is relevant to the same line in the other document, for example if the bot detects the text in line 1 of the first file, it will respond with line 1 of the second file.\r\n", Encoding.UTF8); + + "matchesfile=matches.ini\r\n", Encoding.UTF8); } public static int str2int(string str) { try { return Convert.ToInt32(str); } catch { return 0; } }