mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
482 lines
20 KiB
C#
482 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.IO;
|
|
using System.Threading;
|
|
|
|
namespace MinecraftClient
|
|
{
|
|
///
|
|
/// Welcome to the Bot API file !
|
|
/// The virtual class "ChatBot" contains anything you need for creating chat bots
|
|
/// Inherit from this class while adding your bot class to the folder "ChatBots".
|
|
/// Override the methods you want for handling events: Initialize, Update, GetText.
|
|
/// Once your bot is created, read the explanations below to start using it in the MinecraftClient app.
|
|
///
|
|
/// Pieces of code to add in other parts of the program for your bot. Line numbers are approximative.
|
|
/// Settings.cs:73 | public static bool YourBot_Enabled = false;
|
|
/// Settings.cs:74 | private enum ParseMode { /* [...] */, YourBot };
|
|
/// Settings.cs:106 | case "yourbot": pMode = ParseMode.YourBot; break;
|
|
/// Settings.cs:197 | case ParseMode.YourBot: switch (argName.ToLower()) { case "enabled": YourBot_Enabled = str2bool(argValue); break; } break;
|
|
/// Settings.cs:267 | + "[YourBot]\r\n" + "enabled=false\r\n"
|
|
/// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); }
|
|
/// Here your are. Now you will have a setting in MinecraftClient.ini for enabling your brand new bot.
|
|
/// Delete MinecraftClient.ini to re-generate it or add the lines [YourBot] and enabled=true to the existing one.
|
|
///
|
|
|
|
/// <summary>
|
|
/// The virtual class containing anything you need for creating chat bots.
|
|
/// </summary>
|
|
|
|
public abstract class ChatBot
|
|
{
|
|
public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost };
|
|
|
|
//Handler will be automatically set on bot loading, don't worry about this
|
|
public void SetHandler(McTcpClient handler) { this._handler = handler; }
|
|
protected void SetMaster(ChatBot master) { this.master = master; }
|
|
protected void LoadBot(ChatBot bot) { Handler.BotUnLoad(bot); Handler.BotLoad(bot); }
|
|
private McTcpClient Handler { get { return master != null ? master.Handler : _handler; } }
|
|
private McTcpClient _handler = null;
|
|
private ChatBot master = null;
|
|
|
|
/* ================================================== */
|
|
/* Main methods to override for creating your bot */
|
|
/* ================================================== */
|
|
|
|
/// <summary>
|
|
/// Anything you want to initialize your bot, will be called on load by MinecraftCom
|
|
/// </summary>
|
|
|
|
public virtual void Initialize() { }
|
|
|
|
/// <summary>
|
|
/// Will be called every ~100ms (10fps) if loaded in MinecraftCom
|
|
/// </summary>
|
|
|
|
public virtual void Update() { }
|
|
|
|
/// <summary>
|
|
/// Any text sent by the server will be sent here by MinecraftCom
|
|
/// </summary>
|
|
/// <param name="text">Text from the server</param>
|
|
|
|
public virtual void GetText(string text) { }
|
|
|
|
/// <summary>
|
|
/// Is called when the client has been disconnected fom the server
|
|
/// </summary>
|
|
/// <param name="reason">Disconnect Reason</param>
|
|
/// <param name="message">Kick message, if any</param>
|
|
/// <returns>Return TRUE if the client is about to restart</returns>
|
|
|
|
public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; }
|
|
|
|
/* =================================================================== */
|
|
/* ToolBox - Methods below might be useful while creating your bot. */
|
|
/* You should not need to interact with other classes of the program. */
|
|
/* All the methods in this ChatBot class should do the job for you. */
|
|
/* =================================================================== */
|
|
|
|
/// <summary>
|
|
/// Send text to the server. Can be anything such as chat messages or commands
|
|
/// </summary>
|
|
/// <param name="text">Text to send to the server</param>
|
|
/// <returns>True if the text was sent with no error</returns>
|
|
|
|
protected bool SendText(string text)
|
|
{
|
|
LogToConsole("Sending '" + text + "'");
|
|
return Handler.SendText(text);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
|
|
/// </summary>
|
|
/// <param name="command">The command to process</param>
|
|
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
|
|
|
|
protected bool PerformInternalCommand(string command)
|
|
{
|
|
string temp = "";
|
|
return Handler.PerformInternalCommand(command, ref temp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Perform an internal MCC command (not a server command, use SendText() instead for that!)
|
|
/// </summary>
|
|
/// <param name="command">The command to process</param>
|
|
/// <param name="response_msg">May contain a confirmation or error message after processing the command, or "" otherwise.</param>
|
|
/// <returns>TRUE if the command was indeed an internal MCC command</returns>
|
|
|
|
protected bool PerformInternalCommand(string command, ref string response_msg)
|
|
{
|
|
return Handler.PerformInternalCommand(command, ref response_msg);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove color codes ("§c") from a text message received from the server
|
|
/// </summary>
|
|
|
|
protected static string GetVerbatim(string text)
|
|
{
|
|
if ( String.IsNullOrEmpty(text) )
|
|
return String.Empty;
|
|
|
|
int idx = 0;
|
|
var data = new char[text.Length];
|
|
|
|
for ( int i = 0; i < text.Length; i++ )
|
|
if ( text[i] != '§' )
|
|
data[idx++] = text[i];
|
|
else
|
|
i++;
|
|
|
|
return new string(data, 0, idx);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that a string contains only a-z A-Z 0-9 and _ characters.
|
|
/// </summary>
|
|
|
|
protected static bool IsValidName(string username)
|
|
{
|
|
if ( String.IsNullOrEmpty(username) )
|
|
return false;
|
|
|
|
foreach ( char c in username )
|
|
if ( !((c >= 'a' && c <= 'z')
|
|
|| (c >= 'A' && c <= 'Z')
|
|
|| (c >= '0' && c <= '9')
|
|
|| c == '_') )
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the text passed is a private message sent to the bot
|
|
/// </summary>
|
|
/// <param name="text">text to test</param>
|
|
/// <param name="message">if it's a private message, this will contain the message</param>
|
|
/// <param name="sender">if it's a private message, this will contain the player name that sends the message</param>
|
|
/// <returns>Returns true if the text is a private message</returns>
|
|
|
|
protected static bool IsPrivateMessage(string text, ref string message, ref string sender)
|
|
{
|
|
text = GetVerbatim(text);
|
|
if (text == "") { return false; }
|
|
string[] tmp = text.Split(' ');
|
|
|
|
try
|
|
{
|
|
//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")
|
|
{
|
|
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);
|
|
}
|
|
|
|
//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 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 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 Messages
|
|
//[Channel] [Rank] User: Message
|
|
else if (text.StartsWith("[") && text.Contains(':') && tmp.Length > 2 && Settings.Hero_Chat_Messages_Enabled.Equals(true))
|
|
{
|
|
int name_end = text.IndexOf(':');
|
|
int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2;
|
|
sender = text.Substring(name_start, name_end - name_start);
|
|
message = text.Substring(name_end + 2);
|
|
return IsValidName(sender);
|
|
}
|
|
|
|
else return false;
|
|
}
|
|
catch (IndexOutOfRangeException) { return false; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the text passed is a public message written by a player on the chat
|
|
/// </summary>
|
|
/// <param name="text">text to test</param>
|
|
/// <param name="message">if it's message, this will contain the message</param>
|
|
/// <param name="sender">if it's message, this will contain the player name that sends the message</param>
|
|
/// <returns>Returns true if the text is a chat message</returns>
|
|
|
|
protected static bool IsChatMessage(string text, ref string message, ref string sender)
|
|
{
|
|
|
|
text = GetVerbatim(text);
|
|
string[] tmp = text.Split(' ');
|
|
if (text.Length > 0)
|
|
{
|
|
//Detect vanilla/factions Messages
|
|
//<Someone> message
|
|
//<*Faction Someone> message
|
|
//<*Faction Someone>: message
|
|
//<*Faction ~Nicknamed>: message
|
|
if (text[0] == '<')
|
|
{
|
|
try
|
|
{
|
|
text = text.Substring(1);
|
|
string[] tmp2 = text.Split('>');
|
|
sender = tmp2[0];
|
|
message = text.Substring(sender.Length + 2);
|
|
if (message.Length > 1 && message[0] == ' ')
|
|
{ message = message.Substring(1); }
|
|
tmp2 = sender.Split(' ');
|
|
sender = tmp2[tmp2.Length - 1];
|
|
if (sender[0] == '~') { sender = sender.Substring(1); }
|
|
return IsValidName(sender);
|
|
}
|
|
catch (IndexOutOfRangeException) { return false; }
|
|
}
|
|
|
|
//Detect HeroChat Messages
|
|
//[Channel] [Rank] User: Message
|
|
else if (text[0] == '[' && text.Contains(':') && tmp.Length > 2)
|
|
{
|
|
int name_end = text.IndexOf(':');
|
|
int name_start = text.Substring(0, name_end).LastIndexOf(']') + 2;
|
|
sender = text.Substring(name_start, name_end - name_start);
|
|
message = text.Substring(name_end + 2);
|
|
return IsValidName(sender);
|
|
}
|
|
|
|
//Detect (Unknown Plugin) Messages
|
|
//**Faction<Rank> User : Message
|
|
else if (text[0] == '*'
|
|
&& text.Length > 1
|
|
&& text[1] != ' '
|
|
&& text.Contains('<') && text.Contains('>')
|
|
&& text.Contains(' ') && text.Contains(':')
|
|
&& text.IndexOf('*') < text.IndexOf('<')
|
|
&& text.IndexOf('<') < text.IndexOf('>')
|
|
&& text.IndexOf('>') < text.IndexOf(' ')
|
|
&& text.IndexOf(' ') < text.IndexOf(':'))
|
|
{
|
|
string prefix = tmp[0];
|
|
string user = tmp[1];
|
|
string semicolon = tmp[2];
|
|
if (prefix.All(c => char.IsLetterOrDigit(c) || new char[] { '*', '<', '>', '_' }.Contains(c))
|
|
&& semicolon == ":")
|
|
{
|
|
message = text.Substring(prefix.Length + user.Length + 4);
|
|
return IsValidName(user);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the text passed is a teleport request (Essentials)
|
|
/// </summary>
|
|
/// <param name="text">Text to parse</param>
|
|
/// <param name="sender">Will contain the sender's username, if it's a teleport request</param>
|
|
/// <returns>Returns true if the text is a teleport request</returns>
|
|
|
|
protected static bool IsTeleportRequest(string text, ref string sender)
|
|
{
|
|
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."))
|
|
{
|
|
//<Rank> 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];
|
|
|
|
//Username has requested...
|
|
else sender = tmp[0];
|
|
|
|
//Final check on username validity
|
|
return IsValidName(sender);
|
|
}
|
|
else return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Write some text in the console. Nothing will be sent to the server.
|
|
/// </summary>
|
|
/// <param name="text">Log text to write</param>
|
|
|
|
protected void LogToConsole(object text)
|
|
{
|
|
ConsoleIO.WriteLogLine(String.Format("[{0}] {1}", this.GetType().Name, text));
|
|
string logfile = Settings.ExpandVars(Settings.chatbotLogFile);
|
|
|
|
if (!String.IsNullOrEmpty(logfile))
|
|
{
|
|
if (!File.Exists(logfile))
|
|
{
|
|
try { Directory.CreateDirectory(Path.GetDirectoryName(logfile)); }
|
|
catch { return; /* Invalid path or access denied */ }
|
|
try { File.WriteAllText(logfile, ""); }
|
|
catch { return; /* Invalid file name or access denied */ }
|
|
}
|
|
|
|
File.AppendAllLines(logfile, new string[] { GetTimestamp() + ' ' + text });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from the server and restart the program
|
|
/// It will unload and reload all the bots and then reconnect to the server
|
|
/// </summary>
|
|
/// <param name="attempts">If connection fails, the client will make X extra attempts</param>
|
|
|
|
protected void ReconnectToTheServer(int ExtraAttempts = 3)
|
|
{
|
|
McTcpClient.AttemptsLeft = ExtraAttempts;
|
|
Program.Restart();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disconnect from the server and exit the program
|
|
/// </summary>
|
|
|
|
protected void DisconnectAndExit()
|
|
{
|
|
Program.Exit();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unload the chatbot, and release associated memory.
|
|
/// </summary>
|
|
|
|
protected void UnloadBot()
|
|
{
|
|
Handler.BotUnLoad(this);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Send a private message to a player
|
|
/// </summary>
|
|
/// <param name="player">Player name</param>
|
|
/// <param name="message">Message</param>
|
|
|
|
protected void SendPrivateMessage(string player, string message)
|
|
{
|
|
SendText("/tell " + player + ' ' + message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Run a script from a file using a Scripting bot
|
|
/// </summary>
|
|
/// <param name="filename">File name</param>
|
|
/// <param name="playername">Player name to send error messages, if applicable</param>
|
|
|
|
protected void RunScript(string filename, string playername = "")
|
|
{
|
|
Handler.BotLoad(new ChatBots.Script(filename, playername));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get a Y-M-D h:m:s timestamp representing the current system date and time
|
|
/// </summary>
|
|
|
|
protected static string GetTimestamp()
|
|
{
|
|
DateTime time = DateTime.Now;
|
|
return String.Format("{0}-{1}-{2} {3}:{4}:{5}",
|
|
time.Year.ToString("0000"),
|
|
time.Month.ToString("00"),
|
|
time.Day.ToString("00"),
|
|
time.Hour.ToString("00"),
|
|
time.Minute.ToString("00"),
|
|
time.Second.ToString("00"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Load entries from a file as a string array, removing duplicates and empty lines
|
|
/// </summary>
|
|
/// <param name="file">File to load</param>
|
|
/// <returns>The string array or an empty array if failed to load the file</returns>
|
|
|
|
protected string[] LoadDistinctEntriesFromFile(string file)
|
|
{
|
|
if (File.Exists(file))
|
|
{
|
|
//Read all lines from file, remove lines with no text, convert to lowercase,
|
|
//remove duplicate entries, convert to a string array, and return the result.
|
|
return File.ReadAllLines(file)
|
|
.Where(line => !String.IsNullOrWhiteSpace(line))
|
|
.Select(line => line.ToLower())
|
|
.Distinct().ToArray();
|
|
}
|
|
else
|
|
{
|
|
LogToConsole("File not found: " + Settings.Alerts_MatchesFile);
|
|
return new string[0];
|
|
}
|
|
}
|
|
}
|
|
}
|