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.
This commit is contained in:
ORelio 2015-10-22 22:17:15 +02:00
parent 29975da627
commit 5038c3d475
3 changed files with 211 additions and 108 deletions

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Text; using System.Text;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using System.Text.RegularExpressions;
namespace MinecraftClient namespace MinecraftClient
{ {
@ -142,11 +143,11 @@ namespace MinecraftClient
protected static bool IsValidName(string username) protected static bool IsValidName(string username)
{ {
if ( String.IsNullOrEmpty(username) ) if (String.IsNullOrEmpty(username))
return false; return false;
foreach ( char c in username ) foreach (char c in username)
if ( !((c >= 'a' && c <= 'z') if (!((c >= 'a' && c <= 'z')
|| (c >= 'A' && c <= 'Z') || (c >= 'A' && c <= 'Z')
|| (c >= '0' && c <= '9') || (c >= '0' && c <= '9')
|| c == '_') ) || c == '_') )
@ -165,10 +166,15 @@ namespace MinecraftClient
protected static bool IsPrivateMessage(string text, ref string message, ref string sender) protected static bool IsPrivateMessage(string text, ref string message, ref string sender)
{ {
text = GetVerbatim(text); if (String.IsNullOrEmpty(text))
if (text == "") { return false; } return false;
string[] tmp = text.Split(' ');
text = GetVerbatim(text);
//Built-in detection routine for private messages
if (Settings.ChatFormat_Builtins)
{
string[] tmp = text.Split(' ');
try try
{ {
//Detect vanilla /tell messages //Detect vanilla /tell messages
@ -243,7 +249,22 @@ namespace MinecraftClient
} }
else return false; else return false;
} }
catch (IndexOutOfRangeException) { return false; } catch (IndexOutOfRangeException) { /* Not an expected chat format */ }
}
//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;
} }
/// <summary> /// <summary>
@ -256,17 +277,22 @@ namespace MinecraftClient
protected static bool IsChatMessage(string text, ref string message, ref string sender) protected static bool IsChatMessage(string text, ref string message, ref string sender)
{ {
if (String.IsNullOrEmpty(text))
return false;
text = GetVerbatim(text); 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 //Detect vanilla/factions Messages
//<Someone> message //<Someone> message
//<*Faction Someone> message //<*Faction Someone> message
//<*Faction Someone>: message //<*Faction Someone>: message
//<*Faction ~Nicknamed>: message //<*Faction ~Nicknamed>: message
if (text[0] == '<' && Settings.Vanilla_And_Factions_Messages_Enabled.Equals(true)) if (text[0] == '<')
{ {
try try
{ {
@ -287,7 +313,7 @@ namespace MinecraftClient
//Detect HeroChat Messages //Detect HeroChat Messages
//Public chat messages //Public chat messages
//[Channel] [Rank] User: Message //[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_end = text.IndexOf(':');
int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2; 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('>')
&& 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))
{ {
string prefix = tmp[0]; string prefix = tmp[0];
string user = tmp[1]; 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; return false;
} }
@ -332,8 +370,18 @@ namespace MinecraftClient
protected static bool IsTeleportRequest(string text, ref string sender) protected static bool IsTeleportRequest(string text, ref string sender)
{ {
if (String.IsNullOrEmpty(text))
return false;
text = GetVerbatim(text); text = GetVerbatim(text);
//Built-in detection routine for teleport requests
if (Settings.ChatFormat_Builtins)
{
string[] tmp = text.Split(' '); string[] tmp = text.Split(' ');
//Detect Essentials teleport requests, prossibly with
//nicknamed names or other modifications such as HeroChat
if (text.EndsWith("has requested to teleport to you.") if (text.EndsWith("has requested to teleport to you.")
|| text.EndsWith("has requested that you teleport to them.")) || text.EndsWith("has requested that you teleport to them."))
{ {
@ -347,10 +395,27 @@ namespace MinecraftClient
//Username has requested... //Username has requested...
else sender = tmp[0]; else sender = tmp[0];
//~Username has requested...
if (sender.Length > 1 && sender[0] == '~')
sender = sender.Substring(1);
//Final check on username validity //Final check on username validity
return IsValidName(sender); 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;
} }
/// <summary> /// <summary>

View file

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.IO; using System.IO;
using System.Text.RegularExpressions;
namespace MinecraftClient namespace MinecraftClient
{ {
@ -97,10 +98,11 @@ namespace MinecraftClient
public static bool RemoteCtrl_AutoTpaccept = true; public static bool RemoteCtrl_AutoTpaccept = true;
public static bool RemoteCtrl_AutoTpaccept_Everyone = false; public static bool RemoteCtrl_AutoTpaccept_Everyone = false;
//Chat Message Enabled / Disabled. //Chat Message Parsing
public static bool Hero_Chat_Messages_Enabled = true; public static bool ChatFormat_Builtins = true;
public static bool Unknown_Chat_Plugin_Messages_One_Enabled = true; public static Regex ChatFormat_Public = null;
public static bool Vanilla_And_Factions_Messages_Enabled = true; public static Regex ChatFormat_Private = null;
public static Regex ChatFormat_TeleportRequest = null;
//Auto Respond //Auto Respond
public static bool AutoRespond_Enabled = false; public static bool AutoRespond_Enabled = false;
@ -111,7 +113,7 @@ namespace MinecraftClient
private static readonly Dictionary<string, KeyValuePair<string, string>> Accounts = new Dictionary<string, KeyValuePair<string, string>>(); private static readonly Dictionary<string, KeyValuePair<string, string>> Accounts = new Dictionary<string, KeyValuePair<string, string>>();
private static readonly Dictionary<string, KeyValuePair<string, ushort>> Servers = new Dictionary<string, KeyValuePair<string, ushort>>(); private static readonly Dictionary<string, KeyValuePair<string, ushort>> Servers = new Dictionary<string, KeyValuePair<string, ushort>>();
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 };
/// <summary> /// <summary>
/// Load settings from the give INI file /// Load settings from the give INI file
@ -146,7 +148,7 @@ namespace MinecraftClient
case "proxy": pMode = ParseMode.Proxy; break; case "proxy": pMode = ParseMode.Proxy; break;
case "appvars": pMode = ParseMode.AppVars; break; case "appvars": pMode = ParseMode.AppVars; break;
case "autorespond": pMode = ParseMode.AutoRespond; break; case "autorespond": pMode = ParseMode.AutoRespond; break;
case "chatbotmessages": pMode = ParseMode.ChatBotMessages; break; case "chatformat": pMode = ParseMode.ChatFormat; break;
default: pMode = ParseMode.Default; break; default: pMode = ParseMode.Default; break;
} }
} }
@ -309,13 +311,13 @@ namespace MinecraftClient
} }
break; break;
case ParseMode.ChatBotMessages: case ParseMode.ChatFormat:
switch (argName.ToLower()) switch (argName.ToLower())
{ {
case "herochatmessagesenabled": Hero_Chat_Messages_Enabled = str2bool(argValue); break; case "builtins": ChatFormat_Builtins = str2bool(argValue); break;
case "unknownchatpluginmessagesone": Unknown_Chat_Plugin_Messages_One_Enabled = str2bool(argValue); break; case "public": ChatFormat_Public = new Regex(argValue); break;
case "vanillaandfactionsmessages": Vanilla_And_Factions_Messages_Enabled = str2bool(argValue); break; case "private": ChatFormat_Private = new Regex(argValue); break;
case "tprequest": ChatFormat_TeleportRequest = new Regex(argValue); break;
} }
break; break;
@ -424,6 +426,12 @@ namespace MinecraftClient
+ "username=\r\n" + "username=\r\n"
+ "password=\r\n" + "password=\r\n"
+ "\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" + "#Bot Settings\r\n"
+ "\r\n" + "\r\n"
+ "[Alerts]\r\n" + "[Alerts]\r\n"
@ -464,18 +472,39 @@ namespace MinecraftClient
+ "autotpaccept=true\r\n" + "autotpaccept=true\r\n"
+ "tpaccepteveryone=false\r\n" + "tpaccepteveryone=false\r\n"
+ "\r\n" + "\r\n"
+ "[ChatBotMessages]\r\n"
+ "vanillaandfactionsmessages=true # Chat Formats \"<User> Message\" \"<*Faction User>: Message\" \r\n"
+ "herochatmessagesenabled=true # Chat Format is \"[Channel][Rank] User: Message\"\r\n"
+ "unknownchatpluginmessagesone=true # Chat Format is \"**Faction<Rank> User : Message\"\r\n"
+ "\r\n"
+ "[AutoRespond]\r\n" + "[AutoRespond]\r\n"
+ "enabled=false\r\n" + "enabled=false\r\n"
+ "matchesfile=matches.ini\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; } } /// <summary>
public static bool str2bool(string str) { return str == "true" || str == "1"; } /// Convert the specified string to an integer, defaulting to zero if invalid argument
/// </summary>
/// <param name="str">String to parse as an integer</param>
/// <returns>Integer value</returns>
public static int str2int(string str)
{
try
{
return Convert.ToInt32(str);
}
catch { return 0; }
}
/// <summary>
/// Convert the specified string to a boolean value, defaulting to false if invalid argument
/// </summary>
/// <param name="str">String to parse as a boolean</param>
/// <returns>Boolean value</returns>
public static bool str2bool(string str)
{
if (String.IsNullOrEmpty(str))
return false;
str = str.Trim().ToLowerInvariant();
return str == "true" || str == "1";
}
/// <summary> /// <summary>
/// Load login/password using an account alias /// Load login/password using an account alias

View file

@ -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 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. 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 Using the Alerts bot
====================== ======================