From 5038c3d475869310ae4f01d8229b4ae66d6a622e Mon Sep 17 00:00:00 2001 From: ORelio Date: Thu, 22 Oct 2015 22:17:15 +0200 Subject: [PATCH] Add regex settings for parsing chat messages Allows user-defined regexes to be used instead of built-in chat detection routines for matching messages on server using a non-standard chat format. Built-in detection routines can be disabled using a single setting, based on a contribution by ZizzyDizzyMC. --- MinecraftClient/ChatBot.cs | 245 +++++++++++++++++++----------- MinecraftClient/Settings.cs | 65 +++++--- MinecraftClient/config/README.txt | 9 ++ 3 files changed, 211 insertions(+), 108 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 5d568289..6540b892 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Text; using System.IO; using System.Threading; +using System.Text.RegularExpressions; namespace MinecraftClient { @@ -142,11 +143,11 @@ namespace MinecraftClient protected static bool IsValidName(string username) { - if ( String.IsNullOrEmpty(username) ) + if (String.IsNullOrEmpty(username)) return false; - foreach ( char c in username ) - if ( !((c >= 'a' && c <= 'z') + foreach (char c in username) + if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') ) @@ -165,85 +166,105 @@ namespace MinecraftClient protected static bool IsPrivateMessage(string text, ref string message, ref string sender) { + if (String.IsNullOrEmpty(text)) + return false; + text = GetVerbatim(text); - if (text == "") { return false; } - string[] tmp = text.Split(' '); - try + //Built-in detection routine for private messages + if (Settings.ChatFormat_Builtins) { - //Detect vanilla /tell messages - //Someone whispers message (MC 1.5) - //Someone whispers to you: message (MC 1.7) - if (tmp.Length > 2 && tmp[1] == "whispers") + string[] tmp = text.Split(' '); + try { - if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:") + //Detect vanilla /tell messages + //Someone whispers message (MC 1.5) + //Someone whispers to you: message (MC 1.7) + if (tmp.Length > 2 && tmp[1] == "whispers") { - message = text.Substring(tmp[0].Length + 18); //MC 1.7 + if (tmp.Length > 4 && tmp[2] == "to" && tmp[3] == "you:") + { + message = text.Substring(tmp[0].Length + 18); //MC 1.7 + } + else message = text.Substring(tmp[0].Length + 10); //MC 1.5 + sender = tmp[0]; + return IsValidName(sender); } - else message = text.Substring(tmp[0].Length + 10); //MC 1.5 - sender = tmp[0]; - return IsValidName(sender); - } - //Detect Essentials (Bukkit) /m messages - //[Someone -> me] message - //[~Someone -> me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->" - && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers - { - message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Essentials (Bukkit) /m messages + //[Someone -> me] message + //[~Someone -> me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "->" + && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers + { + message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 1); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect Modified server messages. /m - //[Someone @ me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" - && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers - { - message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Modified server messages. /m + //[Someone @ me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[1] == "@" + && (tmp[2] == "me]" || tmp[2] == "moi]")) //'me' is replaced by 'moi' in french servers + { + message = text.Substring(tmp[0].Length + 4 + tmp[2].Length + 0); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect Essentials (Bukkit) /me messages with some custom prefix - //[Prefix] [Someone -> me] message - //[Prefix] [~Someone -> me] message - else if (text[0] == '[' && tmp[0][tmp[0].Length - 1] == ']' - && tmp[1][0] == '[' && tmp.Length > 4 && tmp[2] == "->" - && (tmp[3] == "me]" || tmp[3] == "moi]")) - { - message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1); - sender = tmp[1].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Essentials (Bukkit) /me messages with some custom prefix + //[Prefix] [Someone -> me] message + //[Prefix] [~Someone -> me] message + else if (text[0] == '[' && tmp[0][tmp[0].Length - 1] == ']' + && tmp[1][0] == '[' && tmp.Length > 4 && tmp[2] == "->" + && (tmp[3] == "me]" || tmp[3] == "moi]")) + { + message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[3].Length + 1); + sender = tmp[1].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect Essentials (Bukkit) /me messages with some custom rank - //[Someone [rank] -> me] message - //[~Someone [rank] -> me] message - else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->" - && (tmp[3] == "me]" || tmp[3] == "moi]")) - { - message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); - sender = tmp[0].Substring(1); - if (sender[0] == '~') { sender = sender.Substring(1); } - return IsValidName(sender); - } + //Detect Essentials (Bukkit) /me messages with some custom rank + //[Someone [rank] -> me] message + //[~Someone [rank] -> me] message + else if (text[0] == '[' && tmp.Length > 3 && tmp[2] == "->" + && (tmp[3] == "me]" || tmp[3] == "moi]")) + { + message = text.Substring(tmp[0].Length + 1 + tmp[1].Length + 4 + tmp[2].Length + 1); + sender = tmp[0].Substring(1); + if (sender[0] == '~') { sender = sender.Substring(1); } + return IsValidName(sender); + } - //Detect HeroChat PMsend - //From Someone: message - else if (text.StartsWith("From ")) - { - sender = text.Substring(5).Split(':')[0]; - message = text.Substring(text.IndexOf(':') + 2); - return IsValidName(sender); + //Detect HeroChat PMsend + //From Someone: message + else if (text.StartsWith("From ")) + { + sender = text.Substring(5).Split(':')[0]; + message = text.Substring(text.IndexOf(':') + 2); + return IsValidName(sender); + } + else return false; } - else return false; + catch (IndexOutOfRangeException) { /* Not an expected chat format */ } } - catch (IndexOutOfRangeException) { return false; } + + //User-defined regex for private chat messages + if (Settings.ChatFormat_Private != null) + { + Match regexMatch = Settings.ChatFormat_Private.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 3) + { + sender = regexMatch.Groups[1].Value; + message = regexMatch.Groups[2].Value; + return IsValidName(sender); + } + } + + return false; } /// @@ -256,17 +277,22 @@ namespace MinecraftClient protected static bool IsChatMessage(string text, ref string message, ref string sender) { - + if (String.IsNullOrEmpty(text)) + return false; + text = GetVerbatim(text); - string[] tmp = text.Split(' '); - if (text.Length > 0) + + //Built-in detection routine for public messages + if (Settings.ChatFormat_Builtins) { + string[] tmp = text.Split(' '); + //Detect vanilla/factions Messages // message //<*Faction Someone> message //<*Faction Someone>: message //<*Faction ~Nicknamed>: message - if (text[0] == '<' && Settings.Vanilla_And_Factions_Messages_Enabled.Equals(true)) + if (text[0] == '<') { try { @@ -287,7 +313,7 @@ namespace MinecraftClient //Detect HeroChat Messages //Public chat messages //[Channel] [Rank] User: Message - else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true)) + else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2) { int name_end = text.IndexOf(':'); int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; @@ -306,8 +332,7 @@ namespace MinecraftClient && text.IndexOf('*') < text.IndexOf('<') && text.IndexOf('<') < text.IndexOf('>') && text.IndexOf('>') < text.IndexOf(' ') - && text.IndexOf(' ') < text.IndexOf(':') - && Settings.Unknown_Chat_Plugin_Messages_One_Enabled.Equals(true)) + && text.IndexOf(' ') < text.IndexOf(':')) { string prefix = tmp[0]; string user = tmp[1]; @@ -320,6 +345,19 @@ namespace MinecraftClient } } } + + //User-defined regex for public chat messages + if (Settings.ChatFormat_Public != null) + { + Match regexMatch = Settings.ChatFormat_Public.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 3) + { + sender = regexMatch.Groups[1].Value; + message = regexMatch.Groups[2].Value; + return IsValidName(sender); + } + } + return false; } @@ -332,25 +370,52 @@ namespace MinecraftClient protected static bool IsTeleportRequest(string text, ref string sender) { + if (String.IsNullOrEmpty(text)) + return false; + text = GetVerbatim(text); - string[] tmp = text.Split(' '); - if (text.EndsWith("has requested to teleport to you.") - || text.EndsWith("has requested that you teleport to them.")) + + //Built-in detection routine for teleport requests + if (Settings.ChatFormat_Builtins) { - // Username has requested... - //[Rank] Username has requested... - if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">")) - || (tmp[0].StartsWith("[") && tmp[0].EndsWith("]"))) - && tmp.Length > 1) - sender = tmp[1]; + string[] tmp = text.Split(' '); - //Username has requested... - else sender = tmp[0]; + //Detect Essentials teleport requests, prossibly with + //nicknamed names or other modifications such as HeroChat + if (text.EndsWith("has requested to teleport to you.") + || text.EndsWith("has requested that you teleport to them.")) + { + // Username has requested... + //[Rank] Username has requested... + if (((tmp[0].StartsWith("<") && tmp[0].EndsWith(">")) + || (tmp[0].StartsWith("[") && tmp[0].EndsWith("]"))) + && tmp.Length > 1) + sender = tmp[1]; - //Final check on username validity - return IsValidName(sender); + //Username has requested... + else sender = tmp[0]; + + //~Username has requested... + if (sender.Length > 1 && sender[0] == '~') + sender = sender.Substring(1); + + //Final check on username validity + return IsValidName(sender); + } } - else return false; + + //User-defined regex for teleport requests + if (Settings.ChatFormat_TeleportRequest != null) + { + Match regexMatch = Settings.ChatFormat_TeleportRequest.Match(text); + if (regexMatch.Success && regexMatch.Groups.Count >= 2) + { + sender = regexMatch.Groups[1].Value; + return IsValidName(sender); + } + } + + return false; } /// diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index 407d730f..c0119ff1 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; +using System.Text.RegularExpressions; namespace MinecraftClient { @@ -97,10 +98,11 @@ namespace MinecraftClient public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept_Everyone = false; - //Chat Message Enabled / Disabled. - public static bool Hero_Chat_Messages_Enabled = true; - public static bool Unknown_Chat_Plugin_Messages_One_Enabled = true; - public static bool Vanilla_And_Factions_Messages_Enabled = true; + //Chat Message Parsing + public static bool ChatFormat_Builtins = true; + public static Regex ChatFormat_Public = null; + public static Regex ChatFormat_Private = null; + public static Regex ChatFormat_TeleportRequest = null; //Auto Respond public static bool AutoRespond_Enabled = false; @@ -111,7 +113,7 @@ namespace MinecraftClient private static readonly Dictionary> Accounts = new Dictionary>(); private static readonly Dictionary> Servers = new Dictionary>(); - private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, ChatBotMessages, AutoRespond }; + private enum ParseMode { Default, Main, AppVars, Proxy, AntiAFK, Hangman, Alerts, ChatLog, AutoRelog, ScriptScheduler, RemoteControl, ChatFormat, AutoRespond }; /// /// Load settings from the give INI file @@ -146,7 +148,7 @@ namespace MinecraftClient case "proxy": pMode = ParseMode.Proxy; break; case "appvars": pMode = ParseMode.AppVars; break; case "autorespond": pMode = ParseMode.AutoRespond; break; - case "chatbotmessages": pMode = ParseMode.ChatBotMessages; break; + case "chatformat": pMode = ParseMode.ChatFormat; break; default: pMode = ParseMode.Default; break; } } @@ -309,13 +311,13 @@ namespace MinecraftClient } break; - case ParseMode.ChatBotMessages: + case ParseMode.ChatFormat: switch (argName.ToLower()) { - case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; - case "unknownchatpluginmessagesone": Unknown_Chat_Plugin_Messages_One_Enabled = str2bool(argValue); break; - case "vanillaandfactionsmessages": Vanilla_And_Factions_Messages_Enabled = str2bool(argValue); break; - + case "builtins": ChatFormat_Builtins = str2bool(argValue); break; + case "public": ChatFormat_Public = new Regex(argValue); break; + case "private": ChatFormat_Private = new Regex(argValue); break; + case "tprequest": ChatFormat_TeleportRequest = new Regex(argValue); break; } break; @@ -424,6 +426,12 @@ namespace MinecraftClient + "username=\r\n" + "password=\r\n" + "\r\n" + + "[ChatFormat]\r\n" + + "builtins=true #support for handling vanilla and common message formats\r\n" + + "#public=^<([a-zA-Z0-9_]+)> (.+)$ #uncomment and adapt if necessary\r\n" + + "#private=^([a-zA-Z0-9_]+) whispers to you: (.+)$ #vanilla example\r\n" + + "#tprequest=^([a-zA-Z0-9_]+) has requested (?:to|that you) teleport to (?:you|them)\\.$\r\n" + + "\r\n" + "#Bot Settings\r\n" + "\r\n" + "[Alerts]\r\n" @@ -464,18 +472,39 @@ namespace MinecraftClient + "autotpaccept=true\r\n" + "tpaccepteveryone=false\r\n" + "\r\n" - + "[ChatBotMessages]\r\n" - + "vanillaandfactionsmessages=true # Chat Formats \" Message\" \"<*Faction User>: Message\" \r\n" - + "herochatmessagesenabled=true # Chat Format is \"[Channel][Rank] User: Message\"\r\n" - + "unknownchatpluginmessagesone=true # Chat Format is \"**Faction User : Message\"\r\n" - + "\r\n" + "[AutoRespond]\r\n" + "enabled=false\r\n" + "matchesfile=matches.ini\r\n", Encoding.UTF8); } - public static int str2int(string str) { try { return Convert.ToInt32(str); } catch { return 0; } } - public static bool str2bool(string str) { return str == "true" || str == "1"; } + /// + /// Convert the specified string to an integer, defaulting to zero if invalid argument + /// + /// String to parse as an integer + /// Integer value + + public static int str2int(string str) + { + try + { + return Convert.ToInt32(str); + } + catch { return 0; } + } + + /// + /// Convert the specified string to a boolean value, defaulting to false if invalid argument + /// + /// String to parse as a boolean + /// Boolean value + + public static bool str2bool(string str) + { + if (String.IsNullOrEmpty(str)) + return false; + str = str.Trim().ToLowerInvariant(); + return str == "true" || str == "1"; + } /// /// Load login/password using an account alias diff --git a/MinecraftClient/config/README.txt b/MinecraftClient/config/README.txt index fa71e994..04e460ae 100644 --- a/MinecraftClient/config/README.txt +++ b/MinecraftClient/config/README.txt @@ -119,6 +119,15 @@ These files describe how some messages should be printed depending on your prefe The client will automatically load en_GB.lang from your Minecraft folder if Minecraft is installed on your computer, or download it from Mojang's servers. You may choose another language in the config file. +========================= + Detecting chat messages +========================= + +Minecraft Console Client can parse messages from the server in order to detect private and public messages. +This is useful for reacting to messages eg when using the AutoRespond, Hangman game, or RemoteControl bots. +However, for unusual chat formats, so you may need to tinker with the ChatFormat section of the config file. +Building regular expressions can be a bit tricky, so you might want to try them out eg on regex101.com + ====================== Using the Alerts bot ======================