diff --git a/MinecraftClient/Bots.cs b/MinecraftClient/Bots.cs
deleted file mode 100644
index 8260fcb6..00000000
--- a/MinecraftClient/Bots.cs
+++ /dev/null
@@ -1,1167 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Globalization;
-
-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 namespace "Bots", below.
- /// 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.
- /// Program.cs:166 | if (Settings.YourBot_Enabled){ handler.BotLoad(new Bots.YourBot()); }
- /// 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"
- /// 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.
- ///
-
- ///
- /// The virtual class containing anything you need for creating chat bots.
- ///
-
- public abstract class ChatBot
- {
- public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost };
-
- #region MinecraftCom Handler for this bot
-
- //Will be automatically set on bot loading, don't worry about this
- public void SetHandler(MinecraftCom handler) { this.handler = handler; }
- private MinecraftCom handler;
-
- #endregion
-
- ///
- /// Anything you want to initialize your bot, will be called on load by MinecraftCom
- ///
-
- public virtual void Initialize() { }
-
- ///
- /// Will be called every ~100ms (10fps) if loaded in MinecraftCom
- ///
-
- public virtual void Update() { }
-
- ///
- /// Any text sent by the server will be sent here by MinecraftCom
- ///
- /// Text from the server
-
- public virtual void GetText(string text) { }
-
- ///
- /// Is called when the client has been disconnected fom the server
- ///
- /// Disconnect Reason
- /// Kick message, if any
- /// Return TRUE if the client is about to restart
-
- public virtual bool OnDisconnect(DisconnectReason reason, string message) { return false; }
-
- #region ToolBox
-
- ///
- /// Send text to the server. Can be anything such as chat messages or commands
- ///
- /// Text to send to the server
-
- protected void SendText(string text)
- {
- Console.ForegroundColor = ConsoleColor.DarkGray;
- ConsoleIO.WriteLine("BOT:" + text);
- handler.SendChatMessage(text);
- Console.ForegroundColor = ConsoleColor.Gray;
- }
-
- ///
- /// Remove color codes ("§c") from a text message received from the server
- ///
-
- 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);
- }
-
- ///
- /// Verify that a string contains only a-z A-Z 0-9 and _ characters.
- ///
-
- 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;
- }
-
- ///
- /// Returns true is the text passed is a private message sent to the bot
- ///
- /// text to test
- /// if it's a private message, this will contain the message
- /// if it's a private message, this will contain the player name that sends the message
- /// Returns true if the text is a private message
-
- protected static bool isPrivateMessage(string text, ref string message, ref string sender)
- {
- if (text == "") { return false; }
- string[] tmp = text.Split(' ');
-
- try
- {
- //Detect vanilla /tell messages
- //Someone whispers to you: message
- if (tmp.Length > 2 && tmp[1] == "whispers")
- {
- message = text.Substring(tmp[0].Length + 18);
- sender = tmp[0];
- return isValidName(sender);
- }
-
- //Detect Essentials (Bukkit) /m messages
- //[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);
- }
- else return false;
- }
- catch (IndexOutOfRangeException) { return false; }
- }
-
- ///
- /// Returns true is the text passed is a public message written by a player on the chat
- ///
- /// text to test
- /// if it's message, this will contain the message
- /// if it's message, this will contain the player name that sends the message
- /// Returns true if the text is a chat message
-
- protected static bool isChatMessage(string text, ref string message, ref string sender)
- {
- //Detect chat messages
- // message
- //<*Faction Someone> message
- //<*Faction Someone>: message
- //<*Faction ~Nicknamed>: message
- if (text == "") { return false; }
- if (text[0] == '<')
- {
- try
- {
- text = text.Substring(1);
- string[] tmp = text.Split('>');
- sender = tmp[0];
- message = text.Substring(sender.Length + 2);
- if (message.Length > 1 && message[0] == ' ')
- { message = message.Substring(1); }
- tmp = sender.Split(' ');
- sender = tmp[tmp.Length - 1];
- if (sender[0] == '~') { sender = sender.Substring(1); }
- return isValidName(sender);
- }
- catch (IndexOutOfRangeException) { return false; }
- }
- else return false;
- }
-
- ///
- /// Writes some text in the console. Nothing will be sent to the server.
- ///
- /// Log text to write
-
- public static void LogToConsole(string text)
- {
- Console.ForegroundColor = ConsoleColor.DarkGray;
- ConsoleIO.WriteLine("[BOT] " + text);
- Console.ForegroundColor = ConsoleColor.Gray;
- }
-
- ///
- /// Disconnect from the server and restart the program
- /// It will unload & reload all the bots and then reconnect to the server
- ///
-
- protected void ReconnectToTheServer() { ReconnectToTheServer(3); }
-
- ///
- /// Disconnect from the server and restart the program
- /// It will unload & reload all the bots and then reconnect to the server
- ///
- /// If connection fails, the client will make X extra attempts
-
- protected void ReconnectToTheServer(int ExtraAttempts)
- {
- McTcpClient.AttemptsLeft = ExtraAttempts;
- Program.Restart();
- }
-
- ///
- /// Disconnect from the server and exit the program
- ///
-
- protected void DisconnectAndExit()
- {
- Program.Exit();
- }
-
- ///
- /// Unload the chatbot, and release associated memory.
- ///
-
- protected void UnloadBot()
- {
- handler.BotUnLoad(this);
- }
-
- ///
- /// Send a private message to a player
- ///
- /// Player name
- /// Message
-
- protected void SendPrivateMessage(string player, string message)
- {
- SendText("/tell " + player + ' ' + message);
- }
-
- ///
- /// Run a script from a file using a Scripting bot
- ///
- /// File name
- /// Player name to send error messages, if applicable
-
- protected void RunScript(string filename, string playername = "")
- {
- handler.BotLoad(new Bots.Script(filename, playername));
- }
-
- #endregion
- }
-
- namespace Bots
- {
- ///
- /// Example of message receiving.
- ///
-
- public class TestBot : ChatBot
- {
- public override void GetText(string text)
- {
- string message = "";
- string username = "";
- text = getVerbatim(text);
-
- if (isPrivateMessage(text, ref message, ref username))
- {
- ConsoleIO.WriteLine("Bot: " + username + " told me : " + message);
- }
- else if (isChatMessage(text, ref message, ref username))
- {
- ConsoleIO.WriteLine("Bot: " + username + " said : " + message);
- }
- }
- }
-
- ///
- /// This bot sends a command every 60 seconds in order to stay non-afk.
- ///
-
- public class AntiAFK : ChatBot
- {
- private int count;
- private int timeping;
-
- ///
- /// This bot sends a /ping command every X seconds in order to stay non-afk.
- ///
- /// Time amount between each ping (10 = 1s, 600 = 1 minute, etc.)
-
- public AntiAFK(int pingparam)
- {
- count = 0;
- timeping = pingparam;
- if (timeping < 10) { timeping = 10; } //To avoid flooding
- }
-
- public override void Update()
- {
- count++;
- if (count == timeping)
- {
- SendText(Settings.AntiAFK_Command);
- count = 0;
- }
- }
- }
-
- ///
- /// This bot sends a /list command every X seconds and save the result.
- ///
-
- public class PlayerListLogger : ChatBot
- {
- private int count;
- private int timeping;
- private string file;
-
- ///
- /// This bot sends a /list command every X seconds and save the result.
- ///
- /// Time amount between each list ping (10 = 1s, 600 = 1 minute, etc.)
-
- public PlayerListLogger(int pingparam, string filetosavein)
- {
- count = 0;
- file = filetosavein;
- timeping = pingparam;
- if (timeping < 10) { timeping = 10; } //To avoid flooding
-
- }
-
- public override void Update()
- {
- count++;
- if (count == timeping)
- {
- SendText("/list");
- count = 0;
- }
- }
-
- public override void GetText(string text)
- {
- if (text.Contains("Joueurs en ligne") || text.Contains("Connected:") || text.Contains("online:"))
- {
- LogToConsole("Saving Player List");
- DateTime now = DateTime.Now;
- string TimeStamp = "[" + now.Year + '/' + now.Month + '/' + now.Day + ' ' + now.Hour + ':' + now.Minute + ']';
- System.IO.File.AppendAllText(file, TimeStamp + "\n" + getVerbatim(text) + "\n\n");
- }
- }
- }
-
- ///
- /// "Le jeu du Pendu" (Hangman game)
- ///
-
- public class Pendu : ChatBot
- {
- private int vie = 0;
- private int vie_param = 10;
- private int compteur = 0;
- private int compteur_param = 3000; //5 minutes
- private bool running = false;
- private bool[] discovered;
- private string word = "";
- private string letters = "";
- private bool English;
-
- ///
- /// "Le jeu du Pendu" (Hangman Game)
- ///
- /// if true, the game will be in english. If false, the game will be in french.
-
- public Pendu(bool english)
- {
- English = english;
- }
-
- public override void Update()
- {
- if (running)
- {
- if (compteur > 0)
- {
- compteur--;
- }
- else
- {
- SendText(English ? "You took too long to try a letter." : "Temps imparti écoulé !");
- SendText(English ? "Game canceled." : "Partie annulée.");
- running = false;
- }
- }
- }
-
- public override void GetText(string text)
- {
- string message = "";
- string username = "";
- text = getVerbatim(text);
-
- if (isPrivateMessage(text, ref message, ref username))
- {
- if (Settings.Bots_Owners.Contains(username.ToLower()))
- {
- switch (message)
- {
- case "start":
- start();
- break;
- case "stop":
- running = false;
- break;
- default:
- break;
- }
- }
- }
- else
- {
- if (running && isChatMessage(text, ref message, ref username))
- {
- if (message.Length == 1)
- {
- char letter = message.ToUpper()[0];
- if (letter >= 'A' && letter <= 'Z')
- {
- if (letters.Contains(letter))
- {
- SendText(English ? ("Letter " + letter + " has already been tried.") : ("Le " + letter + " a déjà été proposé."));
- }
- else
- {
- letters += letter;
- compteur = compteur_param;
-
- if (word.Contains(letter))
- {
- for (int i = 0; i < word.Length; i++) { if (word[i] == letter) { discovered[i] = true; } }
- SendText(English ? ("Yes, the word contains a " + letter + '!') : ("Le " + letter + " figurait bien dans le mot :)"));
- }
- else
- {
- vie--;
- if (vie == 0)
- {
- SendText(English ? "Game Over! :]" : "Perdu ! Partie terminée :]");
- SendText(English ? ("The word was: " + word) : ("Le mot était : " + word));
- running = false;
- }
- else SendText(English ? ("The " + letter + "? No.") : ("Le " + letter + " ? Non."));
- }
-
- if (running)
- {
- SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
- : ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
- }
-
- if (winner)
- {
- SendText(English ? ("Congrats, " + username + '!') : ("Félicitations, " + username + " !"));
- running = false;
- }
- }
- }
- }
- }
- }
- }
-
- private void start()
- {
- vie = vie_param;
- running = true;
- letters = "";
- word = chooseword();
- compteur = compteur_param;
- discovered = new bool[word.Length];
-
- SendText(English ? "Hangman v1.0 - By ORelio" : "Pendu v1.0 - Par ORelio");
- SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
- : ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
- SendText(English ? ("Try some letters ... :)") : ("Proposez une lettre ... :)"));
- }
-
- private string chooseword()
- {
- if (System.IO.File.Exists(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR))
- {
- string[] dico = System.IO.File.ReadAllLines(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR);
- return dico[new Random().Next(dico.Length)];
- }
- else
- {
- LogToConsole(English ? "File not found: " + Settings.Hangman_FileWords_EN : "Fichier introuvable : " + Settings.Hangman_FileWords_FR);
- return English ? "WORDSAREMISSING" : "DICOMANQUANT";
- }
- }
-
- private string word_cached
- {
- get
- {
- string printed = "";
- for (int i = 0; i < word.Length; i++)
- {
- if (discovered[i])
- {
- printed += word[i];
- }
- else printed += '_';
- }
- return printed;
- }
- }
-
- private bool winner
- {
- get
- {
- for (int i = 0; i < discovered.Length; i++)
- {
- if (!discovered[i])
- {
- return false;
- }
- }
- return true;
- }
- }
- }
-
- ///
- /// This bot make the console beep on some specified words. Useful to detect when someone is talking to you, for example.
- ///
-
- public class Alerts : ChatBot
- {
- private string[] dictionary = new string[0];
- private string[] excludelist = new string[0];
-
- public override void Initialize()
- {
- if (System.IO.File.Exists(Settings.Alerts_MatchesFile))
- {
- dictionary = System.IO.File.ReadAllLines(Settings.Alerts_MatchesFile);
-
- for (int i = 0; i < dictionary.Length; i++)
- {
- dictionary[i] = dictionary[i].ToLower();
- }
- }
- else LogToConsole("File not found: " + Settings.Alerts_MatchesFile);
-
- if (System.IO.File.Exists(Settings.Alerts_ExcludesFile))
- {
- excludelist = System.IO.File.ReadAllLines(Settings.Alerts_ExcludesFile);
-
- for (int i = 0; i < excludelist.Length; i++)
- {
- excludelist[i] = excludelist[i].ToLower();
- }
- }
- else LogToConsole("File not found : " + Settings.Alerts_ExcludesFile);
- }
-
- public override void GetText(string text)
- {
- text = getVerbatim(text);
- string comp = text.ToLower();
- foreach (string alert in dictionary)
- {
- if (comp.Contains(alert))
- {
- bool ok = true;
-
- foreach (string exclusion in excludelist)
- {
- if (comp.Contains(exclusion))
- {
- ok = false;
- break;
- }
- }
-
- if (ok)
- {
- if (Settings.Alerts_Beep_Enabled) { Console.Beep(); } //Text found !
-
- if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(comp.Replace(alert, "§c" + alert + "§r")); } else {
-
- #region Displaying the text with the interesting part highlighted
-
- Console.BackgroundColor = ConsoleColor.DarkGray;
- Console.ForegroundColor = ConsoleColor.White;
-
- //Will be used for text displaying
- string[] temp = comp.Split(alert.Split(','), StringSplitOptions.RemoveEmptyEntries);
- int p = 0;
-
- //Special case : alert in the beginning of the text
- string test = "";
- for (int i = 0; i < alert.Length; i++)
- {
- test += comp[i];
- }
- if (test == alert)
- {
- Console.BackgroundColor = ConsoleColor.Yellow;
- Console.ForegroundColor = ConsoleColor.Red;
- for (int i = 0; i < alert.Length; i++)
- {
- ConsoleIO.Write(text[p]);
- p++;
- }
- }
-
- //Displaying the rest of the text
- for (int i = 0; i < temp.Length; i++)
- {
- Console.BackgroundColor = ConsoleColor.DarkGray;
- Console.ForegroundColor = ConsoleColor.White;
- for (int j = 0; j < temp[i].Length; j++)
- {
- ConsoleIO.Write(text[p]);
- p++;
- }
- Console.BackgroundColor = ConsoleColor.Yellow;
- Console.ForegroundColor = ConsoleColor.Red;
- try
- {
- for (int j = 0; j < alert.Length; j++)
- {
- ConsoleIO.Write(text[p]);
- p++;
- }
- }
- catch (IndexOutOfRangeException) { }
- }
- Console.BackgroundColor = ConsoleColor.Black;
- Console.ForegroundColor = ConsoleColor.Gray;
- ConsoleIO.Write('\n');
-
- #endregion
-
- }
- }
- }
- }
- }
- }
-
- ///
- /// This bot saves the received messages in a text file.
- ///
-
- public class ChatLog : ChatBot
- {
- public enum MessageFilter { AllText, AllMessages, OnlyChat, OnlyWhispers };
- private bool dateandtime;
- private bool saveOther = true;
- private bool saveChat = true;
- private bool savePrivate = true;
- private string logfile;
-
- ///
- /// This bot saves the messages received in the specified file, with some filters and date/time tagging.
- ///
- /// The file to save the log in
- /// The kind of messages to save
- /// Add a date and time before each message
-
- public ChatLog(string file, MessageFilter filter, bool AddDateAndTime)
- {
- dateandtime = AddDateAndTime;
- logfile = file;
- switch (filter)
- {
- case MessageFilter.AllText:
- saveOther = true;
- savePrivate = true;
- saveChat = true;
- break;
- case MessageFilter.AllMessages:
- saveOther = false;
- savePrivate = true;
- saveChat = true;
- break;
- case MessageFilter.OnlyChat:
- saveOther = false;
- savePrivate = false;
- saveChat = true;
- break;
- case MessageFilter.OnlyWhispers:
- saveOther = false;
- savePrivate = true;
- saveChat = false;
- break;
- }
- }
-
- public static MessageFilter str2filter(string filtername)
- {
- switch (filtername.ToLower())
- {
- case "all": return MessageFilter.AllText;
- case "messages": return MessageFilter.AllMessages;
- case "chat": return MessageFilter.OnlyChat;
- case "private": return MessageFilter.OnlyWhispers;
- default: return MessageFilter.AllText;
- }
- }
-
- public override void GetText(string text)
- {
- text = getVerbatim(text);
- string sender = "";
- string message = "";
-
- if (saveChat && isChatMessage(text, ref message, ref sender))
- {
- save("Chat " + sender + ": " + message);
- }
- else if (savePrivate && isPrivateMessage(text, ref message, ref sender))
- {
- save("Private " + sender + ": " + message);
- }
- else if (saveOther)
- {
- save("Other: " + text);
- }
- }
-
- private void save(string tosave)
- {
- if (dateandtime)
- {
- int day = DateTime.Now.Day, month = DateTime.Now.Month;
- int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
-
- string D = day < 10 ? "0" + day : "" + day;
- string M = month < 10 ? "0" + month : "" + day;
- string Y = "" + DateTime.Now.Year;
-
- string h = hour < 10 ? "0" + hour : "" + hour;
- string m = minute < 10 ? "0" + minute : "" + minute;
- string s = second < 10 ? "0" + second : "" + second;
-
- tosave = "" + D + '-' + M + '-' + Y + ' ' + h + ':' + m + ':' + s + ' ' + tosave;
- }
-
- System.IO.FileStream stream = new System.IO.FileStream(logfile, System.IO.FileMode.OpenOrCreate);
- System.IO.StreamWriter writer = new System.IO.StreamWriter(stream);
- stream.Seek(0, System.IO.SeekOrigin.End);
- writer.WriteLine(tosave);
- writer.Dispose();
- stream.Close();
- }
- }
-
- ///
- /// This bot automatically re-join the server if kick message contains predefined string (Server is restarting ...)
- ///
-
- public class AutoRelog : ChatBot
- {
- private string[] dictionary = new string[0];
- private int attempts;
- private int delay;
-
- ///
- /// This bot automatically re-join the server if kick message contains predefined string
- ///
- /// Delay before re-joining the server (in seconds)
- /// Number of retries if connection fails (-1 = infinite)
-
- public AutoRelog(int DelayBeforeRelog, int retries)
- {
- attempts = retries;
- if (attempts == -1) { attempts = int.MaxValue; }
- McTcpClient.AttemptsLeft = attempts;
- delay = DelayBeforeRelog;
- if (delay < 1) { delay = 1; }
- }
-
- public override void Initialize()
- {
- McTcpClient.AttemptsLeft = attempts;
- if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile))
- {
- dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile);
-
- for (int i = 0; i < dictionary.Length; i++)
- {
- dictionary[i] = dictionary[i].ToLower();
- }
- }
- else LogToConsole("File not found: " + Settings.AutoRelog_KickMessagesFile);
- }
-
- public override bool OnDisconnect(DisconnectReason reason, string message)
- {
- message = getVerbatim(message);
- string comp = message.ToLower();
- foreach (string msg in dictionary)
- {
- if (comp.Contains(msg))
- {
- LogToConsole("Waiting " + delay + " seconds before reconnecting...");
- System.Threading.Thread.Sleep(delay * 1000);
- McTcpClient.AttemptsLeft = attempts;
- ReconnectToTheServer();
- return true;
- }
- }
- return false;
- }
- }
-
- ///
- /// Runs a list of commands
- ///
-
- public class Script : ChatBot
- {
- private string file;
- private string[] lines = new string[0];
- private int sleepticks = 10;
- private int sleepticks_interval = 10;
- private int nextline = 0;
- private string owner;
-
- public Script(string filename)
- {
- file = filename;
- }
-
- public Script(string filename, string ownername)
- :this(filename)
- {
- if (ownername != "")
- owner = ownername;
- }
-
- public static bool lookForScript(ref string filename)
- {
- //Automatically look in subfolders and try to add ".txt" file extension
- string[] files = new string[]
- {
- filename,
- filename + ".txt",
- "scripts\\" + filename,
- "scripts\\" + filename + ".txt",
- "config\\" + filename,
- "config\\" + filename + ".txt",
- };
-
- foreach (string possible_file in files)
- {
- if (System.IO.File.Exists(possible_file))
- {
- filename = possible_file;
- return true;
- }
- }
-
- return false;
- }
-
- public override void Initialize()
- {
- //Load the given file from the startup parameters
- if (lookForScript(ref file))
- {
- lines = System.IO.File.ReadAllLines(file);
- if (owner != null) { SendPrivateMessage(owner, "Script '" + file + "' loaded."); }
- }
- else
- {
- LogToConsole("File not found: '" + file + "'");
- if (owner != null)
- SendPrivateMessage(owner, "File not found: '" + file + "'");
- UnloadBot(); //No need to keep the bot active
- }
- }
-
- public override void Update()
- {
- if (sleepticks > 0) { sleepticks--; }
- else
- {
- if (nextline < lines.Length) //Is there an instruction left to interpret?
- {
- string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line
- nextline++; //Move the cursor so that the next time the following line will be interpreted
- sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming
-
- if (instruction_line.Length > 1)
- {
- if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/')
- {
- string instruction_name = instruction_line.Split(' ')[0];
- switch (instruction_name.ToLower())
- {
- case "send":
- SendText(instruction_line.Substring(5, instruction_line.Length - 5));
- break;
- case "wait":
- int ticks = 10;
- try
- {
- ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5));
- }
- catch { }
- sleepticks = ticks;
- break;
- case "disconnect":
- DisconnectAndExit();
- break;
- case "exit": //Exit bot & stay connected to the server
- UnloadBot();
- break;
- case "connect":
- if (instruction_line.Length >= 9)
- {
- Settings.ServerIP = instruction_line.Substring(8);
- ReconnectToTheServer();
- }
- break;
- default:
- sleepticks = 0; Update(); //Unknown command : process next line immediately
- break;
- }
- }
- else { sleepticks = 0; Update(); } //Comment: process next line immediately
- }
- }
- else
- {
- //No more instructions to interpret
- UnloadBot();
- }
- }
- }
- }
-
- ///
- /// Trigger scripts on specific events
- ///
-
- public class ScriptScheduler : ChatBot
- {
- private class TaskDesc
- {
- public string script_file = null;
- public bool triggerOnFirstLogin = false;
- public bool triggerOnLogin = false;
- public bool triggerOnTime = false;
- public List triggerOnTime_Times = new List();
- public bool alreadyTriggered = false;
- }
-
- private static bool firstlogin_done = false;
-
- private string tasksfile;
- private bool serverlogin_done;
- private List tasks = new List();
- private int verifytasks_timeleft = 10;
- private int verifytasks_delay = 10;
-
- public ScriptScheduler(string tasksfile)
- {
- this.tasksfile = tasksfile;
- serverlogin_done = false;
- }
-
- public override void Initialize()
- {
- //Load the given file from the startup parameters
- if (System.IO.File.Exists(tasksfile))
- {
- TaskDesc current_task = null;
- String[] lines = System.IO.File.ReadAllLines(tasksfile);
- foreach (string lineRAW in lines)
- {
- 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 "task":
- checkAddTask(current_task);
- current_task = new TaskDesc(); //Create a blank task
- break;
- }
- }
- else if (current_task != null)
- {
- string argName = line.Split('=')[0];
- if (line.Length > (argName.Length + 1))
- {
- string argValue = line.Substring(argName.Length + 1);
- switch (argName.ToLower())
- {
- case "triggeronfirstlogin": current_task.triggerOnFirstLogin = Settings.str2bool(argValue); break;
- case "triggeronlogin": current_task.triggerOnLogin = Settings.str2bool(argValue); break;
- case "triggerontime": current_task.triggerOnTime = Settings.str2bool(argValue); break;
- case "timevalue": try { current_task.triggerOnTime_Times.Add(DateTime.ParseExact(argValue, "HH:mm", CultureInfo.InvariantCulture)); } catch { } break;
- case "script": current_task.script_file = argValue; break;
- }
- }
- }
- }
- }
- checkAddTask(current_task);
- }
- else
- {
- LogToConsole("File not found: '" + tasksfile + "'");
- UnloadBot(); //No need to keep the bot active
- }
- }
-
- private void checkAddTask(TaskDesc current_task)
- {
- if (current_task != null)
- {
- //Check if we built a valid task before adding it
- if (current_task.script_file != null && Script.lookForScript(ref current_task.script_file) //Check if file exists
- && (current_task.triggerOnLogin || (current_task.triggerOnTime && current_task.triggerOnTime_Times.Count > 0))) //Look for a valid trigger
- {
- tasks.Add(current_task);
- }
- }
- }
-
- public override void Update()
- {
- if (verifytasks_timeleft <= 0)
- {
- verifytasks_timeleft = verifytasks_delay;
- if (serverlogin_done)
- {
- foreach (TaskDesc task in tasks)
- {
- if (task.triggerOnTime)
- {
- foreach (DateTime time in task.triggerOnTime_Times)
- {
- if (time.Hour == DateTime.Now.Hour && time.Minute == DateTime.Now.Minute)
- {
- if (!task.alreadyTriggered)
- {
- task.alreadyTriggered = true;
- RunScript(task.script_file);
- }
- }
- }
- }
- else task.alreadyTriggered = false;
- }
- }
- else
- {
- foreach (TaskDesc task in tasks)
- {
- if (task.triggerOnLogin || (firstlogin_done == false && task.triggerOnFirstLogin))
- RunScript(task.script_file);
- }
-
- firstlogin_done = true;
- serverlogin_done = true;
- }
- }
- else verifytasks_timeleft--;
- }
- }
-
- ///
- /// Allow to perform operations using whispers to the bot
- ///
-
- public class RemoteControl : ChatBot
- {
- public override void GetText(string text)
- {
- text = getVerbatim(text);
- string command = "", sender = "";
- if (isPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower()))
- {
- string cmd_name = command.Split(' ')[0];
- switch (cmd_name.ToLower())
- {
- case "exit":
- DisconnectAndExit();
- break;
- case "reco":
- ReconnectToTheServer();
- break;
- case "script":
- if (command.Length >= 8)
- RunScript(command.Substring(7), sender);
- break;
- case "send":
- if (command.Length >= 6)
- SendText(command.Substring(5));
- break;
- case "connect":
- if (command.Length >= 9)
- {
- Settings.ServerIP = command.Substring(8);
- ReconnectToTheServer();
- }
- break;
- case "help":
- if (command.Length >= 6)
- {
- string help_cmd_name = command.Substring(5).ToLower();
- switch (help_cmd_name)
- {
- case "exit": SendPrivateMessage(sender, "exit: disconnect from the server."); break;
- case "reco": SendPrivateMessage(sender, "reco: restart and reconnct to the server."); break;
- case "script": SendPrivateMessage(sender, "script : run a script file."); break;
- case "send": SendPrivateMessage(sender, "send : send a chat message or command."); break;
- case "connect": SendPrivateMessage(sender, "connect : connect to the specified server."); break;
- case "help": SendPrivateMessage(sender, "help : show brief help about a command."); break;
- default: SendPrivateMessage(sender, "help: unknown command '" + help_cmd_name + "'."); break;
- }
- }
- else SendPrivateMessage(sender, "help . Available commands: exit, reco, script, send, connect.");
- break;
- default:
- SendPrivateMessage(sender, "Unknown command '" + cmd_name + "'. Use 'help' for help.");
- break;
- }
- }
- }
- }
- }
-}
diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs
new file mode 100644
index 00000000..08933c3a
--- /dev/null
+++ b/MinecraftClient/ChatBot.cs
@@ -0,0 +1,369 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+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.
+ /// McTcpClient:110 | if (Settings.YourBot_Enabled) { handler.BotLoad(new ChatBots.YourBot()); }
+ /// 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"
+ /// 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.
+ ///
+
+ ///
+ /// The virtual class containing anything you need for creating chat bots.
+ ///
+
+ public abstract class ChatBot
+ {
+ public enum DisconnectReason { InGameKick, LoginRejected, ConnectionLost };
+
+ //Will be automatically set on bot loading, don't worry about this
+ public void SetHandler(McTcpClient handler) { this.handler = handler; }
+ private McTcpClient handler;
+
+ /* ================================================== */
+ /* Main methods to override for creating your bot */
+ /* ================================================== */
+
+ ///
+ /// Anything you want to initialize your bot, will be called on load by MinecraftCom
+ ///
+
+ public virtual void Initialize() { }
+
+ ///
+ /// Will be called every ~100ms (10fps) if loaded in MinecraftCom
+ ///
+
+ public virtual void Update() { }
+
+ ///
+ /// Any text sent by the server will be sent here by MinecraftCom
+ ///
+ /// Text from the server
+
+ public virtual void GetText(string text) { }
+
+ ///
+ /// Is called when the client has been disconnected fom the server
+ ///
+ /// Disconnect Reason
+ /// Kick message, if any
+ /// Return TRUE if the client is about to restart
+
+ 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. */
+ /* =================================================================== */
+
+ ///
+ /// Send text to the server. Can be anything such as chat messages or commands
+ ///
+ /// Text to send to the server
+ /// True if the text was sent with no error
+
+ protected bool SendText(string text)
+ {
+ LogToConsole("Sending '" + text + "'");
+ return handler.SendText(text);
+ }
+
+ ///
+ /// Perform an internal MCC command (not a server command, use SendText() instead for that!)
+ ///
+ /// The command to process
+ /// TRUE if the command was indeed an internal MCC command
+
+ protected bool performInternalCommand(string command)
+ {
+ string temp = "";
+ return handler.performInternalCommand(command, ref temp);
+ }
+
+ ///
+ /// Perform an internal MCC command (not a server command, use SendText() instead for that!)
+ ///
+ /// The command to process
+ /// May contain a confirmation or error message after processing the command, or "" otherwise.
+ /// TRUE if the command was indeed an internal MCC command
+
+ protected bool performInternalCommand(string command, ref string response_msg)
+ {
+ return handler.performInternalCommand(command, ref response_msg);
+ }
+
+ ///
+ /// Remove color codes ("§c") from a text message received from the server
+ ///
+
+ 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);
+ }
+
+ ///
+ /// Verify that a string contains only a-z A-Z 0-9 and _ characters.
+ ///
+
+ 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;
+ }
+
+ ///
+ /// Returns true if the text passed is a private message sent to the bot
+ ///
+ /// text to test
+ /// if it's a private message, this will contain the message
+ /// if it's a private message, this will contain the player name that sends the message
+ /// Returns true if the text is a private message
+
+ 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 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]")) //'me' is replaced by 'moi' in french servers
+ {
+ 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);
+ }
+
+ else return false;
+ }
+ catch (IndexOutOfRangeException) { return false; }
+ }
+
+ ///
+ /// Returns true if the text passed is a public message written by a player on the chat
+ ///
+ /// text to test
+ /// if it's message, this will contain the message
+ /// if it's message, this will contain the player name that sends the message
+ /// Returns true if the text is a chat message
+
+ protected static bool isChatMessage(string text, ref string message, ref string sender)
+ {
+ //Detect chat messages
+ // message
+ //<*Faction Someone> message
+ //<*Faction Someone>: message
+ //<*Faction ~Nicknamed>: message
+ text = getVerbatim(text);
+ if (text == "") { return false; }
+ if (text[0] == '<')
+ {
+ try
+ {
+ text = text.Substring(1);
+ string[] tmp = text.Split('>');
+ sender = tmp[0];
+ message = text.Substring(sender.Length + 2);
+ if (message.Length > 1 && message[0] == ' ')
+ { message = message.Substring(1); }
+ tmp = sender.Split(' ');
+ sender = tmp[tmp.Length - 1];
+ if (sender[0] == '~') { sender = sender.Substring(1); }
+ return isValidName(sender);
+ }
+ catch (IndexOutOfRangeException) { return false; }
+ }
+ else return false;
+ }
+
+ ///
+ /// Returns true if the text passed is a teleport request (Essentials)
+ ///
+ /// Text to parse
+ /// Will contain the sender's username, if it's a teleport request
+ /// Returns true if the text is a teleport request
+
+ protected static bool isTeleportRequest(string text, ref string sender)
+ {
+ text = getVerbatim(text);
+ sender = text.Split(' ')[0];
+ if (text.EndsWith("has requested to teleport to you.")
+ || text.EndsWith("has requested that you teleport to them."))
+ {
+ return isValidName(sender);
+ }
+ else return false;
+ }
+
+ ///
+ /// Writes some text in the console. Nothing will be sent to the server.
+ ///
+ /// Log text to write
+
+ public static void LogToConsole(string text)
+ {
+ ConsoleIO.WriteLineFormatted("§8[BOT] " + text);
+
+ if (!String.IsNullOrEmpty(Settings.chatbotLogFile))
+ {
+ if (!File.Exists(Settings.chatbotLogFile))
+ {
+ try { Directory.CreateDirectory(Path.GetDirectoryName(Settings.chatbotLogFile)); }
+ catch { return; /* Invalid path or access denied */ }
+ try { File.WriteAllText(Settings.chatbotLogFile, ""); }
+ catch { return; /* Invalid file name or access denied */ }
+ }
+
+ File.AppendAllLines(Settings.chatbotLogFile, new string[] { getTimestamp() + ' ' + text });
+ }
+ }
+
+ ///
+ /// Disconnect from the server and restart the program
+ /// It will unload & reload all the bots and then reconnect to the server
+ ///
+
+ protected void ReconnectToTheServer() { ReconnectToTheServer(3); }
+
+ ///
+ /// Disconnect from the server and restart the program
+ /// It will unload & reload all the bots and then reconnect to the server
+ ///
+ /// If connection fails, the client will make X extra attempts
+
+ protected void ReconnectToTheServer(int ExtraAttempts)
+ {
+ McTcpClient.AttemptsLeft = ExtraAttempts;
+ Program.Restart();
+ }
+
+ ///
+ /// Disconnect from the server and exit the program
+ ///
+
+ protected void DisconnectAndExit()
+ {
+ Program.Exit();
+ }
+
+ ///
+ /// Unload the chatbot, and release associated memory.
+ ///
+
+ protected void UnloadBot()
+ {
+ handler.BotUnLoad(this);
+ }
+
+ ///
+ /// Send a private message to a player
+ ///
+ /// Player name
+ /// Message
+
+ protected void SendPrivateMessage(string player, string message)
+ {
+ SendText("/tell " + player + ' ' + message);
+ }
+
+ ///
+ /// Run a script from a file using a Scripting bot
+ ///
+ /// File name
+ /// Player name to send error messages, if applicable
+
+ protected void RunScript(string filename, string playername = "")
+ {
+ handler.BotLoad(new ChatBots.Script(filename, playername));
+ }
+
+ ///
+ /// Get a D-M-Y h:m:s timestamp representing the current system date and time
+ ///
+
+ protected static string getTimestamp()
+ {
+ DateTime time = DateTime.Now;
+
+ string D = time.Day.ToString("00");
+ string M = time.Month.ToString("00");
+ string Y = time.Year.ToString("0000");
+
+ string h = time.Hour.ToString("00");
+ string m = time.Minute.ToString("00");
+ string s = time.Second.ToString("00");
+
+ return "" + D + '-' + M + '-' + Y + ' ' + h + ':' + m + ':' + s;
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/Alerts.cs b/MinecraftClient/ChatBots/Alerts.cs
new file mode 100644
index 00000000..4be3b16c
--- /dev/null
+++ b/MinecraftClient/ChatBots/Alerts.cs
@@ -0,0 +1,129 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// This bot make the console beep on some specified words. Useful to detect when someone is talking to you, for example.
+ ///
+
+ public class Alerts : ChatBot
+ {
+ private string[] dictionary = new string[0];
+ private string[] excludelist = new string[0];
+
+ public override void Initialize()
+ {
+ if (System.IO.File.Exists(Settings.Alerts_MatchesFile))
+ {
+ List tmp_dictionary = new List();
+ string[] file_lines = System.IO.File.ReadAllLines(Settings.Alerts_MatchesFile);
+ foreach (string line in file_lines)
+ if (line.Trim().Length > 0 && !tmp_dictionary.Contains(line.ToLower()))
+ tmp_dictionary.Add(line.ToLower());
+ dictionary = tmp_dictionary.ToArray();
+ }
+ else LogToConsole("File not found: " + Settings.Alerts_MatchesFile);
+
+ if (System.IO.File.Exists(Settings.Alerts_ExcludesFile))
+ {
+ List tmp_excludelist = new List();
+ string[] file_lines = System.IO.File.ReadAllLines(Settings.Alerts_ExcludesFile);
+ foreach (string line in file_lines)
+ if (line.Trim().Length > 0 && !tmp_excludelist.Contains(line.Trim().ToLower()))
+ tmp_excludelist.Add(line.ToLower());
+ excludelist = tmp_excludelist.ToArray();
+ }
+ else LogToConsole("File not found : " + Settings.Alerts_ExcludesFile);
+ }
+
+ public override void GetText(string text)
+ {
+ text = getVerbatim(text);
+ string comp = text.ToLower();
+ foreach (string alert in dictionary)
+ {
+ if (comp.Contains(alert))
+ {
+ bool ok = true;
+
+ foreach (string exclusion in excludelist)
+ {
+ if (comp.Contains(exclusion))
+ {
+ ok = false;
+ break;
+ }
+ }
+
+ if (ok)
+ {
+ if (Settings.Alerts_Beep_Enabled) { Console.Beep(); } //Text found !
+
+ if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(comp.Replace(alert, "§c" + alert + "§r")); }
+ else
+ {
+
+ #region Displaying the text with the interesting part highlighted
+
+ Console.BackgroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ConsoleColor.White;
+
+ //Will be used for text displaying
+ string[] temp = comp.Split(alert.Split(','), StringSplitOptions.None);
+ int p = 0;
+
+ //Special case : alert in the beginning of the text
+ string test = "";
+ for (int i = 0; i < alert.Length; i++)
+ {
+ test += comp[i];
+ }
+ if (test == alert)
+ {
+ Console.BackgroundColor = ConsoleColor.Yellow;
+ Console.ForegroundColor = ConsoleColor.Red;
+ for (int i = 0; i < alert.Length; i++)
+ {
+ ConsoleIO.Write(text[p]);
+ p++;
+ }
+ }
+
+ //Displaying the rest of the text
+ for (int i = 0; i < temp.Length; i++)
+ {
+ Console.BackgroundColor = ConsoleColor.DarkGray;
+ Console.ForegroundColor = ConsoleColor.White;
+ for (int j = 0; j < temp[i].Length; j++)
+ {
+ ConsoleIO.Write(text[p]);
+ p++;
+ }
+ Console.BackgroundColor = ConsoleColor.Yellow;
+ Console.ForegroundColor = ConsoleColor.Red;
+ try
+ {
+ for (int j = 0; j < alert.Length; j++)
+ {
+ ConsoleIO.Write(text[p]);
+ p++;
+ }
+ }
+ catch (IndexOutOfRangeException) { }
+ }
+ Console.BackgroundColor = ConsoleColor.Black;
+ Console.ForegroundColor = ConsoleColor.Gray;
+ ConsoleIO.Write('\n');
+
+ #endregion
+
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/AntiAFK.cs b/MinecraftClient/ChatBots/AntiAFK.cs
new file mode 100644
index 00000000..a54e8bcc
--- /dev/null
+++ b/MinecraftClient/ChatBots/AntiAFK.cs
@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// This bot sends a command every 60 seconds in order to stay non-afk.
+ ///
+
+ public class AntiAFK : ChatBot
+ {
+ private int count;
+ private int timeping;
+
+ ///
+ /// This bot sends a /ping command every X seconds in order to stay non-afk.
+ ///
+ /// Time amount between each ping (10 = 1s, 600 = 1 minute, etc.)
+
+ public AntiAFK(int pingparam)
+ {
+ count = 0;
+ timeping = pingparam;
+ if (timeping < 10) { timeping = 10; } //To avoid flooding
+ }
+
+ public override void Update()
+ {
+ count++;
+ if (count == timeping)
+ {
+ SendText(Settings.AntiAFK_Command);
+ count = 0;
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/AutoRelog.cs b/MinecraftClient/ChatBots/AutoRelog.cs
new file mode 100644
index 00000000..5f2b9e2c
--- /dev/null
+++ b/MinecraftClient/ChatBots/AutoRelog.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// This bot automatically re-join the server if kick message contains predefined string (Server is restarting ...)
+ ///
+
+ public class AutoRelog : ChatBot
+ {
+ private string[] dictionary = new string[0];
+ private int attempts;
+ private int delay;
+
+ ///
+ /// This bot automatically re-join the server if kick message contains predefined string
+ ///
+ /// Delay before re-joining the server (in seconds)
+ /// Number of retries if connection fails (-1 = infinite)
+
+ public AutoRelog(int DelayBeforeRelog, int retries)
+ {
+ attempts = retries;
+ if (attempts == -1) { attempts = int.MaxValue; }
+ McTcpClient.AttemptsLeft = attempts;
+ delay = DelayBeforeRelog;
+ if (delay < 1) { delay = 1; }
+ }
+
+ public override void Initialize()
+ {
+ McTcpClient.AttemptsLeft = attempts;
+ if (System.IO.File.Exists(Settings.AutoRelog_KickMessagesFile))
+ {
+ dictionary = System.IO.File.ReadAllLines(Settings.AutoRelog_KickMessagesFile);
+
+ for (int i = 0; i < dictionary.Length; i++)
+ {
+ dictionary[i] = dictionary[i].ToLower();
+ }
+ }
+ else LogToConsole("File not found: " + Settings.AutoRelog_KickMessagesFile);
+ }
+
+ public override bool OnDisconnect(DisconnectReason reason, string message)
+ {
+ message = getVerbatim(message);
+ string comp = message.ToLower();
+ foreach (string msg in dictionary)
+ {
+ if (comp.Contains(msg))
+ {
+ LogToConsole("Waiting " + delay + " seconds before reconnecting...");
+ System.Threading.Thread.Sleep(delay * 1000);
+ McTcpClient.AttemptsLeft = attempts;
+ ReconnectToTheServer();
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/ChatLog.cs b/MinecraftClient/ChatBots/ChatLog.cs
new file mode 100644
index 00000000..c20d6d4d
--- /dev/null
+++ b/MinecraftClient/ChatBots/ChatLog.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// This bot saves the received messages in a text file.
+ ///
+
+ public class ChatLog : ChatBot
+ {
+ public enum MessageFilter { AllText, AllMessages, OnlyChat, OnlyWhispers };
+ private bool dateandtime;
+ private bool saveOther = true;
+ private bool saveChat = true;
+ private bool savePrivate = true;
+ private string logfile;
+
+ ///
+ /// This bot saves the messages received in the specified file, with some filters and date/time tagging.
+ ///
+ /// The file to save the log in
+ /// The kind of messages to save
+ /// Add a date and time before each message
+
+ public ChatLog(string file, MessageFilter filter, bool AddDateAndTime)
+ {
+ dateandtime = AddDateAndTime;
+ logfile = file;
+ switch (filter)
+ {
+ case MessageFilter.AllText:
+ saveOther = true;
+ savePrivate = true;
+ saveChat = true;
+ break;
+ case MessageFilter.AllMessages:
+ saveOther = false;
+ savePrivate = true;
+ saveChat = true;
+ break;
+ case MessageFilter.OnlyChat:
+ saveOther = false;
+ savePrivate = false;
+ saveChat = true;
+ break;
+ case MessageFilter.OnlyWhispers:
+ saveOther = false;
+ savePrivate = true;
+ saveChat = false;
+ break;
+ }
+ }
+
+ public static MessageFilter str2filter(string filtername)
+ {
+ switch (filtername.ToLower())
+ {
+ case "all": return MessageFilter.AllText;
+ case "messages": return MessageFilter.AllMessages;
+ case "chat": return MessageFilter.OnlyChat;
+ case "private": return MessageFilter.OnlyWhispers;
+ default: return MessageFilter.AllText;
+ }
+ }
+
+ public override void GetText(string text)
+ {
+ text = getVerbatim(text);
+ string sender = "";
+ string message = "";
+
+ if (saveChat && isChatMessage(text, ref message, ref sender))
+ {
+ save("Chat " + sender + ": " + message);
+ }
+ else if (savePrivate && isPrivateMessage(text, ref message, ref sender))
+ {
+ save("Private " + sender + ": " + message);
+ }
+ else if (saveOther)
+ {
+ save("Other: " + text);
+ }
+ }
+
+ private void save(string tosave)
+ {
+ if (dateandtime)
+ tosave = getTimestamp() + ' ' + tosave;
+
+ Directory.CreateDirectory(Path.GetDirectoryName(logfile));
+ FileStream stream = new FileStream(logfile, FileMode.OpenOrCreate);
+ StreamWriter writer = new StreamWriter(stream);
+ stream.Seek(0, SeekOrigin.End);
+ writer.WriteLine(tosave);
+ writer.Dispose();
+ stream.Close();
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/HangmanGame.cs b/MinecraftClient/ChatBots/HangmanGame.cs
new file mode 100644
index 00000000..9b11cc2c
--- /dev/null
+++ b/MinecraftClient/ChatBots/HangmanGame.cs
@@ -0,0 +1,188 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// In-Chat Hangman game
+ ///
+
+ public class HangmanGame : ChatBot
+ {
+ private int vie = 0;
+ private int vie_param = 10;
+ private int compteur = 0;
+ private int compteur_param = 3000; //5 minutes
+ private bool running = false;
+ private bool[] discovered;
+ private string word = "";
+ private string letters = "";
+ private bool English;
+
+ ///
+ /// Le jeu du Pendu / Hangman Game
+ ///
+ /// if true, the game will be in english. If false, the game will be in french.
+
+ public HangmanGame(bool english)
+ {
+ English = english;
+ }
+
+ public override void Update()
+ {
+ if (running)
+ {
+ if (compteur > 0)
+ {
+ compteur--;
+ }
+ else
+ {
+ SendText(English ? "You took too long to try a letter." : "Temps imparti écoulé !");
+ SendText(English ? "Game canceled." : "Partie annulée.");
+ running = false;
+ }
+ }
+ }
+
+ public override void GetText(string text)
+ {
+ string message = "";
+ string username = "";
+ text = getVerbatim(text);
+
+ if (isPrivateMessage(text, ref message, ref username))
+ {
+ if (Settings.Bots_Owners.Contains(username.ToLower()))
+ {
+ switch (message)
+ {
+ case "start":
+ start();
+ break;
+ case "stop":
+ running = false;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ else
+ {
+ if (running && isChatMessage(text, ref message, ref username))
+ {
+ if (message.Length == 1)
+ {
+ char letter = message.ToUpper()[0];
+ if (letter >= 'A' && letter <= 'Z')
+ {
+ if (letters.Contains(letter))
+ {
+ SendText(English ? ("Letter " + letter + " has already been tried.") : ("Le " + letter + " a déjà été proposé."));
+ }
+ else
+ {
+ letters += letter;
+ compteur = compteur_param;
+
+ if (word.Contains(letter))
+ {
+ for (int i = 0; i < word.Length; i++) { if (word[i] == letter) { discovered[i] = true; } }
+ SendText(English ? ("Yes, the word contains a " + letter + '!') : ("Le " + letter + " figurait bien dans le mot :)"));
+ }
+ else
+ {
+ vie--;
+ if (vie == 0)
+ {
+ SendText(English ? "Game Over! :]" : "Perdu ! Partie terminée :]");
+ SendText(English ? ("The word was: " + word) : ("Le mot était : " + word));
+ running = false;
+ }
+ else SendText(English ? ("The " + letter + "? No.") : ("Le " + letter + " ? Non."));
+ }
+
+ if (running)
+ {
+ SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
+ : ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
+ }
+
+ if (winner)
+ {
+ SendText(English ? ("Congrats, " + username + '!') : ("Félicitations, " + username + " !"));
+ running = false;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void start()
+ {
+ vie = vie_param;
+ running = true;
+ letters = "";
+ word = chooseword();
+ compteur = compteur_param;
+ discovered = new bool[word.Length];
+
+ SendText(English ? "Hangman v1.0 - By ORelio" : "Pendu v1.0 - Par ORelio");
+ SendText(English ? ("Mysterious word: " + word_cached + " (lives : " + vie + ")")
+ : ("Mot mystère : " + word_cached + " (vie : " + vie + ")"));
+ SendText(English ? ("Try some letters ... :)") : ("Proposez une lettre ... :)"));
+ }
+
+ private string chooseword()
+ {
+ if (System.IO.File.Exists(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR))
+ {
+ string[] dico = System.IO.File.ReadAllLines(English ? Settings.Hangman_FileWords_EN : Settings.Hangman_FileWords_FR);
+ return dico[new Random().Next(dico.Length)];
+ }
+ else
+ {
+ LogToConsole(English ? "File not found: " + Settings.Hangman_FileWords_EN : "Fichier introuvable : " + Settings.Hangman_FileWords_FR);
+ return English ? "WORDSAREMISSING" : "DICOMANQUANT";
+ }
+ }
+
+ private string word_cached
+ {
+ get
+ {
+ string printed = "";
+ for (int i = 0; i < word.Length; i++)
+ {
+ if (discovered[i])
+ {
+ printed += word[i];
+ }
+ else printed += '_';
+ }
+ return printed;
+ }
+ }
+
+ private bool winner
+ {
+ get
+ {
+ for (int i = 0; i < discovered.Length; i++)
+ {
+ if (!discovered[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/PlayerListLogger.cs b/MinecraftClient/ChatBots/PlayerListLogger.cs
new file mode 100644
index 00000000..9124b664
--- /dev/null
+++ b/MinecraftClient/ChatBots/PlayerListLogger.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// This bot sends a /list command every X seconds and save the result.
+ ///
+
+ public class PlayerListLogger : ChatBot
+ {
+ private int count;
+ private int timeping;
+ private string file;
+
+ ///
+ /// This bot sends a /list command every X seconds and save the result.
+ ///
+ /// Time amount between each list ping (10 = 1s, 600 = 1 minute, etc.)
+
+ public PlayerListLogger(int pingparam, string filetosavein)
+ {
+ count = 0;
+ file = filetosavein;
+ timeping = pingparam;
+ if (timeping < 10) { timeping = 10; } //To avoid flooding
+
+ }
+
+ public override void Update()
+ {
+ count++;
+ if (count == timeping)
+ {
+ SendText("/list");
+ count = 0;
+ }
+ }
+
+ public override void GetText(string text)
+ {
+ if (text.Contains("Joueurs en ligne") || text.Contains("Connected:") || text.Contains("online:"))
+ {
+ LogToConsole("Saving Player List");
+ DateTime now = DateTime.Now;
+ string TimeStamp = "[" + now.Year + '/' + now.Month + '/' + now.Day + ' ' + now.Hour + ':' + now.Minute + ']';
+ System.IO.File.AppendAllText(file, TimeStamp + "\n" + getVerbatim(text) + "\n\n");
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/RemoteControl.cs b/MinecraftClient/ChatBots/RemoteControl.cs
new file mode 100644
index 00000000..818bab6f
--- /dev/null
+++ b/MinecraftClient/ChatBots/RemoteControl.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// Allow to perform operations using whispers to the bot
+ ///
+
+ public class RemoteControl : ChatBot
+ {
+ public override void GetText(string text)
+ {
+ text = getVerbatim(text);
+ string command = "", sender = "";
+ if (isPrivateMessage(text, ref command, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower()))
+ {
+ string response = "";
+ performInternalCommand(command, ref response);
+ if (response.Length > 0)
+ {
+ SendPrivateMessage(sender, response);
+ }
+ }
+ else if (Settings.RemoteCtrl_AutoTpaccept && isTeleportRequest(text, ref sender) && Settings.Bots_Owners.Contains(sender.ToLower()))
+ {
+ SendText("/tpaccept");
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs
new file mode 100644
index 00000000..cb96ffa8
--- /dev/null
+++ b/MinecraftClient/ChatBots/Script.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// Runs a list of commands
+ ///
+
+ public class Script : ChatBot
+ {
+ private string file;
+ private string[] lines = new string[0];
+ private int sleepticks = 10;
+ private int sleepticks_interval = 10;
+ private int nextline = 0;
+ private string owner;
+
+ public Script(string filename)
+ {
+ file = filename;
+ }
+
+ public Script(string filename, string ownername)
+ : this(filename)
+ {
+ if (ownername != "")
+ owner = ownername;
+ }
+
+ public static bool lookForScript(ref string filename)
+ {
+ //Automatically look in subfolders and try to add ".txt" file extension
+ char dir_slash = Program.isUsingMono ? '/' : '\\';
+ string[] files = new string[]
+ {
+ filename,
+ filename + ".txt",
+ "scripts" + dir_slash + filename,
+ "scripts" + dir_slash + filename + ".txt",
+ "config" + dir_slash + filename,
+ "config" + dir_slash + filename + ".txt",
+ };
+
+ foreach (string possible_file in files)
+ {
+ if (System.IO.File.Exists(possible_file))
+ {
+ filename = possible_file;
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public override void Initialize()
+ {
+ //Load the given file from the startup parameters
+ if (lookForScript(ref file))
+ {
+ lines = System.IO.File.ReadAllLines(file);
+ if (owner != null) { SendPrivateMessage(owner, "Script '" + file + "' loaded."); }
+ }
+ else
+ {
+ LogToConsole("File not found: '" + file + "'");
+ if (owner != null)
+ SendPrivateMessage(owner, "File not found: '" + file + "'");
+ UnloadBot(); //No need to keep the bot active
+ }
+ }
+
+ public override void Update()
+ {
+ if (sleepticks > 0) { sleepticks--; }
+ else
+ {
+ if (nextline < lines.Length) //Is there an instruction left to interpret?
+ {
+ string instruction_line = lines[nextline].Trim(); // Removes all whitespaces at start and end of current line
+ nextline++; //Move the cursor so that the next time the following line will be interpreted
+ sleepticks = sleepticks_interval; //Used to delay next command sending and prevent from beign kicked for spamming
+
+ if (instruction_line.Length > 1)
+ {
+ if (instruction_line[0] != '#' && instruction_line[0] != '/' && instruction_line[1] != '/')
+ {
+ instruction_line = Settings.expandVars(instruction_line);
+ string instruction_name = instruction_line.Split(' ')[0];
+ switch (instruction_name.ToLower())
+ {
+ case "wait":
+ int ticks = 10;
+ try
+ {
+ ticks = Convert.ToInt32(instruction_line.Substring(5, instruction_line.Length - 5));
+ }
+ catch { }
+ sleepticks = ticks;
+ break;
+ default:
+ if (!performInternalCommand(instruction_line))
+ {
+ sleepticks = 0; Update(); //Unknown command : process next line immediately
+ }
+ else if (instruction_name.ToLower() != "log") { LogToConsole(instruction_line); }
+ break;
+ }
+ }
+ else { sleepticks = 0; Update(); } //Comment: process next line immediately
+ }
+ }
+ else
+ {
+ //No more instructions to interpret
+ UnloadBot();
+ }
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/ScriptScheduler.cs b/MinecraftClient/ChatBots/ScriptScheduler.cs
new file mode 100644
index 00000000..d1783cb7
--- /dev/null
+++ b/MinecraftClient/ChatBots/ScriptScheduler.cs
@@ -0,0 +1,165 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Globalization;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// Trigger scripts on specific events
+ ///
+
+ public class ScriptScheduler : ChatBot
+ {
+ private class TaskDesc
+ {
+ public string script_file = null;
+ public bool triggerOnFirstLogin = false;
+ public bool triggerOnLogin = false;
+ public bool triggerOnTime = false;
+ public bool triggerOnInterval = false;
+ public int triggerOnInterval_Interval = 0;
+ public int triggerOnInterval_Interval_Countdown = 0;
+ public List triggerOnTime_Times = new List();
+ public bool triggerOnTime_alreadyTriggered = false;
+ }
+
+ private static bool firstlogin_done = false;
+
+ private string tasksfile;
+ private bool serverlogin_done;
+ private List tasks = new List();
+ private int verifytasks_timeleft = 10;
+ private int verifytasks_delay = 10;
+
+ public ScriptScheduler(string tasksfile)
+ {
+ this.tasksfile = tasksfile;
+ serverlogin_done = false;
+ }
+
+ public override void Initialize()
+ {
+ //Load the given file from the startup parameters
+ if (System.IO.File.Exists(tasksfile))
+ {
+ TaskDesc current_task = null;
+ String[] lines = System.IO.File.ReadAllLines(tasksfile);
+ foreach (string lineRAW in lines)
+ {
+ 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 "task":
+ checkAddTask(current_task);
+ current_task = new TaskDesc(); //Create a blank task
+ break;
+ }
+ }
+ else if (current_task != null)
+ {
+ string argName = line.Split('=')[0];
+ if (line.Length > (argName.Length + 1))
+ {
+ string argValue = line.Substring(argName.Length + 1);
+ switch (argName.ToLower())
+ {
+ case "triggeronfirstlogin": current_task.triggerOnFirstLogin = Settings.str2bool(argValue); break;
+ case "triggeronlogin": current_task.triggerOnLogin = Settings.str2bool(argValue); break;
+ case "triggerontime": current_task.triggerOnTime = Settings.str2bool(argValue); break;
+ case "triggeroninterval": current_task.triggerOnInterval = Settings.str2bool(argValue); break;
+ case "timevalue": try { current_task.triggerOnTime_Times.Add(DateTime.ParseExact(argValue, "HH:mm", CultureInfo.InvariantCulture)); } catch { } break;
+ case "timeinterval": int interval = 1; int.TryParse(argValue, out interval); current_task.triggerOnInterval_Interval = interval; break;
+ case "script": current_task.script_file = argValue; break;
+ }
+ }
+ }
+ }
+ }
+ checkAddTask(current_task);
+ }
+ else
+ {
+ LogToConsole("File not found: '" + tasksfile + "'");
+ UnloadBot(); //No need to keep the bot active
+ }
+ }
+
+ private void checkAddTask(TaskDesc current_task)
+ {
+ if (current_task != null)
+ {
+ //Check if we built a valid task before adding it
+ if (current_task.script_file != null && Script.lookForScript(ref current_task.script_file) //Check if file exists
+ && (current_task.triggerOnLogin
+ || (current_task.triggerOnTime && current_task.triggerOnTime_Times.Count > 0))
+ || (current_task.triggerOnInterval && current_task.triggerOnInterval_Interval > 0)) //Look for a valid trigger
+ {
+ current_task.triggerOnInterval_Interval_Countdown = current_task.triggerOnInterval_Interval; //Init countdown for interval
+ tasks.Add(current_task);
+ }
+ }
+ }
+
+ public override void Update()
+ {
+ if (verifytasks_timeleft <= 0)
+ {
+ verifytasks_timeleft = verifytasks_delay;
+ if (serverlogin_done)
+ {
+ foreach (TaskDesc task in tasks)
+ {
+ if (task.triggerOnTime)
+ {
+ bool matching_time_found = false;
+
+ foreach (DateTime time in task.triggerOnTime_Times)
+ {
+ if (time.Hour == DateTime.Now.Hour && time.Minute == DateTime.Now.Minute)
+ {
+ matching_time_found = true;
+ if (!task.triggerOnTime_alreadyTriggered)
+ {
+ task.triggerOnTime_alreadyTriggered = true;
+ RunScript(task.script_file);
+ }
+ }
+ }
+
+ if (!matching_time_found)
+ task.triggerOnTime_alreadyTriggered = false;
+ }
+
+ if (task.triggerOnInterval)
+ {
+ if (task.triggerOnInterval_Interval_Countdown == 0)
+ {
+ task.triggerOnInterval_Interval_Countdown = task.triggerOnInterval_Interval;
+ RunScript(task.script_file);
+ }
+ else task.triggerOnInterval_Interval_Countdown--;
+ }
+ }
+ }
+ else
+ {
+ foreach (TaskDesc task in tasks)
+ {
+ if (task.triggerOnLogin || (firstlogin_done == false && task.triggerOnFirstLogin))
+ RunScript(task.script_file);
+ }
+
+ firstlogin_done = true;
+ serverlogin_done = true;
+ }
+ }
+ else verifytasks_timeleft--;
+ }
+ }
+}
diff --git a/MinecraftClient/ChatBots/TestBot.cs b/MinecraftClient/ChatBots/TestBot.cs
new file mode 100644
index 00000000..ffd921f8
--- /dev/null
+++ b/MinecraftClient/ChatBots/TestBot.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.ChatBots
+{
+ ///
+ /// Example of message receiving.
+ ///
+
+ public class TestBot : ChatBot
+ {
+ public override void GetText(string text)
+ {
+ string message = "";
+ string username = "";
+ text = getVerbatim(text);
+
+ if (isPrivateMessage(text, ref message, ref username))
+ {
+ ConsoleIO.WriteLine("Bot: " + username + " told me : " + message);
+ }
+ else if (isChatMessage(text, ref message, ref username))
+ {
+ ConsoleIO.WriteLine("Bot: " + username + " said : " + message);
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Command.cs b/MinecraftClient/Command.cs
new file mode 100644
index 00000000..ecc5437f
--- /dev/null
+++ b/MinecraftClient/Command.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient
+{
+ ///
+ /// Represents an internal MCC command: Command name, source code and usage message
+ /// To add a new command, inherit from this class while adding the command class to the folder "Commands".
+ /// If inheriting from the 'Command' class and placed in the 'Commands' namespace, the command will be
+ /// automatically loaded and available in main chat prompt, scripts, remote control and command help.
+ ///
+
+ public abstract class Command
+ {
+ ///
+ /// The command name
+ ///
+
+ public abstract string CMDName { get; }
+
+ ///
+ /// Usage message, eg: 'name [args]: do something'
+ ///
+
+ public abstract string CMDDesc { get; }
+
+ ///
+ /// Perform the command
+ ///
+ /// The full command, eg: 'mycommand arg1 arg2'
+ /// A confirmation/error message, or "" if no message
+
+ public abstract string Run(McTcpClient handler, string command);
+
+ ///
+ /// Return a list of aliases for this command.
+ /// Override this method if you wish to put aliases to the command
+ ///
+
+ public virtual IEnumerable getCMDAliases() { return new string[0]; }
+
+ ///
+ /// Check if at least one argument has been passed to the command
+ ///
+
+ public static bool hasArg(string command)
+ {
+ int first_space = command.IndexOf(' ');
+ return (first_space > 0 && first_space < command.Length - 1);
+ }
+
+ ///
+ /// Extract the argument string from the command
+ ///
+ /// Argument or "" if no argument
+
+ public static string getArg(string command)
+ {
+ if (hasArg(command))
+ {
+ return command.Substring(command.IndexOf(' ') + 1);
+ }
+ else return "";
+ }
+
+ ///
+ /// Extract the arguments as a string array from the command
+ ///
+ /// Argument array or empty array if no arguments
+
+ public static string[] getArgs(string command)
+ {
+ string[] args = getArg(command).Split(' ');
+ if (args.Length == 1 && args[0] == "")
+ {
+ return new string[] { };
+ }
+ else
+ {
+ return args;
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Connect.cs b/MinecraftClient/Commands/Connect.cs
new file mode 100644
index 00000000..3d57dbd3
--- /dev/null
+++ b/MinecraftClient/Commands/Connect.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Connect : Command
+ {
+ public override string CMDName { get { return "connect"; } }
+ public override string CMDDesc { get { return "connect [account]: connect to the specified server."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ if (hasArg(command))
+ {
+ string[] args = getArgs(command);
+ if (args.Length > 1)
+ {
+ if (!Settings.setAccount(args[1]))
+ {
+ return "Unknown account '" + args[1] + "'.";
+ }
+ }
+
+ if (Settings.setServerIP(args[0]))
+ {
+ Program.Restart();
+ return "";
+ }
+ else return "Invalid server IP '" + args[0] + "'.";
+ }
+ else return CMDDesc;
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Exit.cs b/MinecraftClient/Commands/Exit.cs
new file mode 100644
index 00000000..db26410d
--- /dev/null
+++ b/MinecraftClient/Commands/Exit.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Exit : Command
+ {
+ public override string CMDName { get { return "exit"; } }
+ public override string CMDDesc { get { return "exit: disconnect from the server."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ Program.Exit();
+ return "";
+ }
+
+ public override IEnumerable getCMDAliases()
+ {
+ return new string[] { "quit" };
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Log.cs b/MinecraftClient/Commands/Log.cs
new file mode 100644
index 00000000..afc2dcef
--- /dev/null
+++ b/MinecraftClient/Commands/Log.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Log : Command
+ {
+ public override string CMDName { get { return "log"; } }
+ public override string CMDDesc { get { return "log : log some text to the console."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ if (hasArg(command))
+ {
+ ChatBot.LogToConsole(getArg(command));
+ return "";
+ }
+ else return CMDDesc;
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Reco.cs b/MinecraftClient/Commands/Reco.cs
new file mode 100644
index 00000000..520f4fea
--- /dev/null
+++ b/MinecraftClient/Commands/Reco.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Reco : Command
+ {
+ public override string CMDName { get { return "reco"; } }
+ public override string CMDDesc { get { return "reco [account]: restart and reconnect to the server."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ string[] args = getArgs(command);
+ if (args.Length > 0)
+ {
+ if (!Settings.setAccount(args[0]))
+ {
+ return "Unknown account '" + args[0] + "'.";
+ }
+ }
+ Program.Restart();
+ return "";
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Respawn.cs b/MinecraftClient/Commands/Respawn.cs
new file mode 100644
index 00000000..cfee205f
--- /dev/null
+++ b/MinecraftClient/Commands/Respawn.cs
@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Respawn : Command
+ {
+ public override string CMDName { get { return "respawn"; } }
+ public override string CMDDesc { get { return "respawn: Use this to respawn if you are dead."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ handler.SendRespawnPacket();
+ return "You have respawned.";
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Script.cs b/MinecraftClient/Commands/Script.cs
new file mode 100644
index 00000000..3273b60c
--- /dev/null
+++ b/MinecraftClient/Commands/Script.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Script : Command
+ {
+ public override string CMDName { get { return "script"; } }
+ public override string CMDDesc { get { return "script : run a script file."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ if (hasArg(command))
+ {
+ handler.BotLoad(new ChatBots.Script(getArg(command)));
+ return "";
+ }
+ else return CMDDesc;
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Send.cs b/MinecraftClient/Commands/Send.cs
new file mode 100644
index 00000000..4ca7b7b7
--- /dev/null
+++ b/MinecraftClient/Commands/Send.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Send : Command
+ {
+ public override string CMDName { get { return "send"; } }
+ public override string CMDDesc { get { return "send : send a chat message or command."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ if (hasArg(command))
+ {
+ handler.SendText(getArg(command));
+ return "";
+ }
+ else return CMDDesc;
+ }
+ }
+}
diff --git a/MinecraftClient/Commands/Set.cs b/MinecraftClient/Commands/Set.cs
new file mode 100644
index 00000000..9316077a
--- /dev/null
+++ b/MinecraftClient/Commands/Set.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Commands
+{
+ public class Set : Command
+ {
+ public override string CMDName { get { return "set"; } }
+ public override string CMDDesc { get { return "set varname=value: set a custom %variable%."; } }
+
+ public override string Run(McTcpClient handler, string command)
+ {
+ if (hasArg(command))
+ {
+ string[] temp = getArg(command).Split('=');
+ if (temp.Length > 1)
+ {
+ if (Settings.setVar(temp[0], getArg(command).Substring(temp[0].Length + 1)))
+ {
+ return ""; //Success
+ }
+ else return "variable name must be A-Za-z0-9.";
+ }
+ else return CMDDesc;
+ }
+ else return CMDDesc;
+ }
+ }
+}
diff --git a/MinecraftClient/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs
index 86059ebc..f73514a5 100644
--- a/MinecraftClient/ConsoleIO.cs
+++ b/MinecraftClient/ConsoleIO.cs
@@ -25,7 +25,10 @@ namespace MinecraftClient
private static bool reading_lock = false;
private static bool writing_lock = false;
- #region Read User Input
+ ///
+ /// Read a password from the standard input
+ ///
+
public static string ReadPassword()
{
string password = "";
@@ -71,6 +74,10 @@ namespace MinecraftClient
return password;
}
+ ///
+ /// Read a line from the standard input
+ ///
+
public static string ReadLine()
{
if (basicIO) { return Console.ReadLine(); }
@@ -152,7 +159,7 @@ namespace MinecraftClient
if (tmp.Length > 0)
{
string word_tocomplete = tmp[tmp.Length - 1];
- string word_autocomplete = autocomplete_engine.AutoComplete(word_tocomplete);
+ string word_autocomplete = autocomplete_engine.AutoComplete(buffer);
if (!String.IsNullOrEmpty(word_autocomplete) && word_autocomplete != word_tocomplete)
{
while (buffer.Length > 0 && buffer[buffer.Length - 1] != ' ') { RemoveOneChar(); }
@@ -174,9 +181,11 @@ namespace MinecraftClient
previous.AddLast(buffer + buffer2);
return buffer + buffer2;
}
- #endregion
+
+ ///
+ /// Write a string to the standard output, without newline character
+ ///
- #region Console Output
public static void Write(string text)
{
if (basicIO) { Console.Write(text); return; }
@@ -216,16 +225,79 @@ namespace MinecraftClient
writing_lock = false;
}
+ ///
+ /// Write a string to the standard output with a trailing newline
+ ///
+
public static void WriteLine(string line)
{
Write(line + '\n');
}
+ ///
+ /// Write a single character to the standard output
+ ///
+
public static void Write(char c)
{
Write("" + c);
}
- #endregion
+
+ ///
+ /// Write a Minecraft-Formatted string to the standard output, using §c color codes
+ ///
+ /// String to write
+ /// If false, space are printed instead of newlines
+
+ public static void WriteLineFormatted(string str, bool acceptnewlines = true)
+ {
+ if (basicIO) { Console.WriteLine(str); return; }
+ if (!String.IsNullOrEmpty(str))
+ {
+ if (Settings.chatTimeStamps)
+ {
+ int hour = DateTime.Now.Hour, minute = DateTime.Now.Minute, second = DateTime.Now.Second;
+ ConsoleIO.Write(hour.ToString("00") + ':' + minute.ToString("00") + ':' + second.ToString("00") + ' ');
+ }
+ if (!acceptnewlines) { str = str.Replace('\n', ' '); }
+ if (ConsoleIO.basicIO) { ConsoleIO.WriteLine(str); return; }
+ string[] subs = str.Split(new char[] { '§' });
+ if (subs[0].Length > 0) { ConsoleIO.Write(subs[0]); }
+ for (int i = 1; i < subs.Length; i++)
+ {
+ if (subs[i].Length > 0)
+ {
+ switch (subs[i][0])
+ {
+ case '0': Console.ForegroundColor = ConsoleColor.Gray; break; //Should be Black but Black is non-readable on a black background
+ case '1': Console.ForegroundColor = ConsoleColor.DarkBlue; break;
+ case '2': Console.ForegroundColor = ConsoleColor.DarkGreen; break;
+ case '3': Console.ForegroundColor = ConsoleColor.DarkCyan; break;
+ case '4': Console.ForegroundColor = ConsoleColor.DarkRed; break;
+ case '5': Console.ForegroundColor = ConsoleColor.DarkMagenta; break;
+ case '6': Console.ForegroundColor = ConsoleColor.DarkYellow; break;
+ case '7': Console.ForegroundColor = ConsoleColor.Gray; break;
+ case '8': Console.ForegroundColor = ConsoleColor.DarkGray; break;
+ case '9': Console.ForegroundColor = ConsoleColor.Blue; break;
+ case 'a': Console.ForegroundColor = ConsoleColor.Green; break;
+ case 'b': Console.ForegroundColor = ConsoleColor.Cyan; break;
+ case 'c': Console.ForegroundColor = ConsoleColor.Red; break;
+ case 'd': Console.ForegroundColor = ConsoleColor.Magenta; break;
+ case 'e': Console.ForegroundColor = ConsoleColor.Yellow; break;
+ case 'f': Console.ForegroundColor = ConsoleColor.White; break;
+ case 'r': Console.ForegroundColor = ConsoleColor.White; break;
+ }
+
+ if (subs[i].Length > 1)
+ {
+ ConsoleIO.Write(subs[i].Substring(1, subs[i].Length - 1));
+ }
+ }
+ }
+ ConsoleIO.Write('\n');
+ }
+ Console.ForegroundColor = ConsoleColor.Gray;
+ }
#region Subfunctions
private static void ClearLineAndBuffer()
diff --git a/MinecraftClient/ConsoleIcon.cs b/MinecraftClient/ConsoleIcon.cs
new file mode 100644
index 00000000..2ef20783
--- /dev/null
+++ b/MinecraftClient/ConsoleIcon.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Net;
+using System.IO;
+using System.Drawing;
+using System.Windows.Forms;
+
+namespace MinecraftClient
+{
+ ///
+ /// Allow to set the player skin as console icon, on Windows only.
+ /// See StackOverflow no. 2986853
+ ///
+
+ public static class ConsoleIcon
+ {
+ [DllImport("kernel32.dll", SetLastError = true)]
+ private static extern bool SetConsoleIcon(IntPtr hIcon);
+
+ ///
+ /// Asynchronously download the player's skin and set the head as console icon
+ ///
+
+ public static void setPlayerIconAsync(string playerName)
+ {
+ if (!Program.isUsingMono) //Windows Only
+ {
+ Thread t = new Thread(new ThreadStart(delegate
+ {
+ HttpWebRequest httpWebRequest = (HttpWebRequest)HttpWebRequest.Create("http://skins.minecraft.net/MinecraftSkins/" + playerName + ".png");
+ try
+ {
+ using (HttpWebResponse httpWebReponse = (HttpWebResponse)httpWebRequest.GetResponse())
+ {
+ Bitmap skin = new Bitmap(Image.FromStream(httpWebReponse.GetResponseStream())); //Read skin from network
+ skin = skin.Clone(new Rectangle(8, 8, 8, 8), skin.PixelFormat); //Crop skin
+ SetConsoleIcon(skin.GetHicon()); //Set skin as icon
+ }
+ }
+ catch (WebException) //Skin not found? Reset to default icon
+ {
+ try
+ {
+ SetConsoleIcon(Icon.ExtractAssociatedIcon(Application.ExecutablePath).Handle);
+ }
+ catch { }
+ }
+ }
+ ));
+ t.Name = "Player skin icon setter";
+ t.Start();
+ }
+ }
+
+ ///
+ /// Set the icon back to the default CMD icon
+ ///
+
+ public static void revertToCMDIcon()
+ {
+ if (!Program.isUsingMono) //Windows Only
+ {
+ try
+ {
+ SetConsoleIcon(Icon.ExtractAssociatedIcon(Environment.SystemDirectory + "\\cmd.exe").Handle);
+ }
+ catch { }
+ }
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto.cs b/MinecraftClient/Crypto/CryptoHandler.cs
similarity index 50%
rename from MinecraftClient/Crypto.cs
rename to MinecraftClient/Crypto/CryptoHandler.cs
index 80501a98..7d31e162 100644
--- a/MinecraftClient/Crypto.cs
+++ b/MinecraftClient/Crypto/CryptoHandler.cs
@@ -3,14 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
+using System.IO;
-namespace MinecraftClient
+namespace MinecraftClient.Crypto
{
///
/// Methods for handling all the crypto stuff: RSA (Encryption Key Request), AES (Encrypted Stream), SHA-1 (Server Hash).
///
- public class Crypto
+ public class CryptoHandler
{
///
/// Get a cryptographic service for encrypting data using the server's RSA public key
@@ -192,249 +193,20 @@ namespace MinecraftClient
}
///
- /// Interface for AES stream
- /// Allows to use any object which has a Read() and Write() method.
+ /// Get a new AES-encrypted stream for wrapping a non-encrypted stream.
///
+ /// Stream to encrypt
+ /// Key to use
+ /// Padding provider for Mono implementation
+ /// Return an appropriate stream depending on the framework being used
- public interface IAesStream
+ public static IAesStream getAesStream(Stream underlyingStream, byte[] AesKey, IPaddingProvider paddingProvider)
{
- int Read(byte[] buffer, int offset, int count);
- void Write(byte[] buffer, int offset, int count);
- }
-
- ///
- /// An encrypted stream using AES, used for encrypting network data on the fly using AES.
- /// This is the regular AesStream class used with the regular .NET framework from Microsoft.
- ///
-
- public class AesStream : System.IO.Stream, IAesStream
- {
- CryptoStream enc;
- CryptoStream dec;
- public AesStream(System.IO.Stream stream, byte[] key)
+ if (Program.isUsingMono)
{
- BaseStream = stream;
- enc = new CryptoStream(stream, GenerateAES(key).CreateEncryptor(), CryptoStreamMode.Write);
- dec = new CryptoStream(stream, GenerateAES(key).CreateDecryptor(), CryptoStreamMode.Read);
- }
- public System.IO.Stream BaseStream { get; set; }
-
- public override bool CanRead
- {
- get { return true; }
- }
-
- public override bool CanSeek
- {
- get { return false; }
- }
-
- public override bool CanWrite
- {
- get { return true; }
- }
-
- public override void Flush()
- {
- BaseStream.Flush();
- }
-
- public override long Length
- {
- get { throw new NotSupportedException(); }
- }
-
- public override long Position
- {
- get
- {
- throw new NotSupportedException();
- }
- set
- {
- throw new NotSupportedException();
- }
- }
-
- public override int ReadByte()
- {
- return dec.ReadByte();
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- return dec.Read(buffer, offset, count);
- }
-
- public override long Seek(long offset, System.IO.SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
-
- public override void WriteByte(byte b)
- {
- enc.WriteByte(b);
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- enc.Write(buffer, offset, count);
- }
-
- private RijndaelManaged GenerateAES(byte[] key)
- {
- RijndaelManaged cipher = new RijndaelManaged();
- cipher.Mode = CipherMode.CFB;
- cipher.Padding = PaddingMode.None;
- cipher.KeySize = 128;
- cipher.FeedbackSize = 8;
- cipher.Key = key;
- cipher.IV = key;
- return cipher;
- }
- }
-
- ///
- /// An encrypted stream using AES, used for encrypting network data on the fly using AES.
- /// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks.
- /// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible.
- ///
-
- public class MonoAesStream : System.IO.Stream, IAesStream
- {
- ICryptoTransform enc;
- ICryptoTransform dec;
- List dec_cache = new List();
- List tosend_cache = new List();
- public MonoAesStream(System.IO.Stream stream, byte[] key)
- {
- BaseStream = stream;
- RijndaelManaged aes = GenerateAES(key);
- enc = aes.CreateEncryptor();
- dec = aes.CreateDecryptor();
- }
- public System.IO.Stream BaseStream { get; set; }
-
- public override bool CanRead
- {
- get { return true; }
- }
-
- public override bool CanSeek
- {
- get { return false; }
- }
-
- public override bool CanWrite
- {
- get { return true; }
- }
-
- public override void Flush()
- {
- BaseStream.Flush();
- }
-
- public override long Length
- {
- get { throw new NotSupportedException(); }
- }
-
- public override long Position
- {
- get
- {
- throw new NotSupportedException();
- }
- set
- {
- throw new NotSupportedException();
- }
- }
-
- public override int ReadByte()
- {
- byte[] temp = new byte[1];
- Read(temp, 0, 1);
- return temp[0];
- }
-
- public override int Read(byte[] buffer, int offset, int count)
- {
- while (dec_cache.Count < count)
- {
- byte[] temp_in = new byte[16];
- byte[] temp_out = new byte[16];
- int read = 0;
- while (read < 16)
- read += BaseStream.Read(temp_in, read, 16 - read);
- dec.TransformBlock(temp_in, 0, 16, temp_out, 0);
- foreach (byte b in temp_out)
- dec_cache.Add(b);
- }
-
- for (int i = offset; i - offset < count; i++)
- {
- buffer[i] = dec_cache[0];
- dec_cache.RemoveAt(0);
- }
-
- return count;
- }
-
- public override long Seek(long offset, System.IO.SeekOrigin origin)
- {
- throw new NotSupportedException();
- }
-
- public override void SetLength(long value)
- {
- throw new NotSupportedException();
- }
-
- public override void WriteByte(byte b)
- {
- Write(new byte[] { b }, 0, 1);
- }
-
- public override void Write(byte[] buffer, int offset, int count)
- {
- for (int i = offset; i - offset < count; i++)
- tosend_cache.Add(buffer[i]);
-
- if (tosend_cache.Count < 16)
- tosend_cache.AddRange(MinecraftCom.getPaddingPacket());
-
- while (tosend_cache.Count > 16)
- {
- byte[] temp_in = new byte[16];
- byte[] temp_out = new byte[16];
- for (int i = 0; i < 16; i++)
- {
- temp_in[i] = tosend_cache[0];
- tosend_cache.RemoveAt(0);
- }
- enc.TransformBlock(temp_in, 0, 16, temp_out, 0);
- BaseStream.Write(temp_out, 0, 16);
- }
- }
-
- private RijndaelManaged GenerateAES(byte[] key)
- {
- RijndaelManaged cipher = new RijndaelManaged();
- cipher.Mode = CipherMode.CFB;
- cipher.Padding = PaddingMode.None;
- cipher.KeySize = 128;
- cipher.FeedbackSize = 8;
- cipher.Key = key;
- cipher.IV = key;
- return cipher;
+ return new Streams.MonoAesStream(underlyingStream, AesKey, paddingProvider);
}
+ else return new Streams.RegularAesStream(underlyingStream, AesKey);
}
}
}
diff --git a/MinecraftClient/Crypto/IAesStream.cs b/MinecraftClient/Crypto/IAesStream.cs
new file mode 100644
index 00000000..23f4a83b
--- /dev/null
+++ b/MinecraftClient/Crypto/IAesStream.cs
@@ -0,0 +1,18 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Crypto
+{
+ ///
+ /// Interface for AES stream
+ /// Allows to use a different implementation depending on the framework being used.
+ ///
+
+ public interface IAesStream
+ {
+ int Read(byte[] buffer, int offset, int count);
+ void Write(byte[] buffer, int offset, int count);
+ }
+}
diff --git a/MinecraftClient/Crypto/IPaddingProvider.cs b/MinecraftClient/Crypto/IPaddingProvider.cs
new file mode 100644
index 00000000..48290494
--- /dev/null
+++ b/MinecraftClient/Crypto/IPaddingProvider.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace MinecraftClient.Crypto
+{
+ ///
+ /// Interface for padding provider
+ /// Allow to get a padding plugin message from the current network protocol implementation.
+ ///
+
+ public interface IPaddingProvider
+ {
+ byte[] getPaddingPacket();
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/MonoAesStream.cs b/MinecraftClient/Crypto/Streams/MonoAesStream.cs
new file mode 100644
index 00000000..b7d2875f
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/MonoAesStream.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+using System.IO;
+
+namespace MinecraftClient.Crypto.Streams
+{
+ ///
+ /// An encrypted stream using AES, used for encrypting network data on the fly using AES.
+ /// This is a mono-compatible adaptation which only sends and receive 16 bytes at a time, and manually transforms blocks.
+ /// Data is cached before reaching the 128bits block size necessary for mono which is not CFB-8 compatible.
+ ///
+
+ public class MonoAesStream : Stream, IAesStream
+ {
+ IPaddingProvider pad;
+ ICryptoTransform enc;
+ ICryptoTransform dec;
+ List dec_cache = new List();
+ List tosend_cache = new List();
+ public MonoAesStream(System.IO.Stream stream, byte[] key, IPaddingProvider provider)
+ {
+ BaseStream = stream;
+ RijndaelManaged aes = GenerateAES(key);
+ enc = aes.CreateEncryptor();
+ dec = aes.CreateDecryptor();
+ pad = provider;
+ }
+ public System.IO.Stream BaseStream { get; set; }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return true; }
+ }
+
+ public override void Flush()
+ {
+ BaseStream.Flush();
+ }
+
+ public override long Length
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override int ReadByte()
+ {
+ byte[] temp = new byte[1];
+ Read(temp, 0, 1);
+ return temp[0];
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ while (dec_cache.Count < count)
+ {
+ byte[] temp_in = new byte[16];
+ byte[] temp_out = new byte[16];
+ int read = 0;
+ while (read < 16)
+ read += BaseStream.Read(temp_in, read, 16 - read);
+ dec.TransformBlock(temp_in, 0, 16, temp_out, 0);
+ foreach (byte b in temp_out)
+ dec_cache.Add(b);
+ }
+
+ for (int i = offset; i - offset < count; i++)
+ {
+ buffer[i] = dec_cache[0];
+ dec_cache.RemoveAt(0);
+ }
+
+ return count;
+ }
+
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void WriteByte(byte b)
+ {
+ Write(new byte[] { b }, 0, 1);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ for (int i = offset; i - offset < count; i++)
+ tosend_cache.Add(buffer[i]);
+
+ if (tosend_cache.Count < 16)
+ tosend_cache.AddRange(pad.getPaddingPacket());
+
+ while (tosend_cache.Count > 16)
+ {
+ byte[] temp_in = new byte[16];
+ byte[] temp_out = new byte[16];
+ for (int i = 0; i < 16; i++)
+ {
+ temp_in[i] = tosend_cache[0];
+ tosend_cache.RemoveAt(0);
+ }
+ enc.TransformBlock(temp_in, 0, 16, temp_out, 0);
+ BaseStream.Write(temp_out, 0, 16);
+ }
+ }
+
+ private RijndaelManaged GenerateAES(byte[] key)
+ {
+ RijndaelManaged cipher = new RijndaelManaged();
+ cipher.Mode = CipherMode.CFB;
+ cipher.Padding = PaddingMode.None;
+ cipher.KeySize = 128;
+ cipher.FeedbackSize = 8;
+ cipher.Key = key;
+ cipher.IV = key;
+ return cipher;
+ }
+ }
+}
diff --git a/MinecraftClient/Crypto/Streams/RegularAesStream.cs b/MinecraftClient/Crypto/Streams/RegularAesStream.cs
new file mode 100644
index 00000000..e3d021c0
--- /dev/null
+++ b/MinecraftClient/Crypto/Streams/RegularAesStream.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Security.Cryptography;
+using System.IO;
+
+namespace MinecraftClient.Crypto.Streams
+{
+ ///
+ /// An encrypted stream using AES, used for encrypting network data on the fly using AES.
+ /// This is the regular AesStream class used with the regular .NET framework from Microsoft.
+ ///
+
+ public class RegularAesStream : Stream, IAesStream
+ {
+ CryptoStream enc;
+ CryptoStream dec;
+ public RegularAesStream(Stream stream, byte[] key)
+ {
+ BaseStream = stream;
+ enc = new CryptoStream(stream, GenerateAES(key).CreateEncryptor(), CryptoStreamMode.Write);
+ dec = new CryptoStream(stream, GenerateAES(key).CreateDecryptor(), CryptoStreamMode.Read);
+ }
+ public System.IO.Stream BaseStream { get; set; }
+
+ public override bool CanRead
+ {
+ get { return true; }
+ }
+
+ public override bool CanSeek
+ {
+ get { return false; }
+ }
+
+ public override bool CanWrite
+ {
+ get { return true; }
+ }
+
+ public override void Flush()
+ {
+ BaseStream.Flush();
+ }
+
+ public override long Length
+ {
+ get { throw new NotSupportedException(); }
+ }
+
+ public override long Position
+ {
+ get
+ {
+ throw new NotSupportedException();
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public override int ReadByte()
+ {
+ return dec.ReadByte();
+ }
+
+ public override int Read(byte[] buffer, int offset, int count)
+ {
+ return dec.Read(buffer, offset, count);
+ }
+
+ public override long Seek(long offset, System.IO.SeekOrigin origin)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void SetLength(long value)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void WriteByte(byte b)
+ {
+ enc.WriteByte(b);
+ }
+
+ public override void Write(byte[] buffer, int offset, int count)
+ {
+ enc.Write(buffer, offset, count);
+ }
+
+ private RijndaelManaged GenerateAES(byte[] key)
+ {
+ RijndaelManaged cipher = new RijndaelManaged();
+ cipher.Mode = CipherMode.CFB;
+ cipher.Padding = PaddingMode.None;
+ cipher.KeySize = 128;
+ cipher.FeedbackSize = 8;
+ cipher.Key = key;
+ cipher.IV = key;
+ return cipher;
+ }
+ }
+}
diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs
index 6f1a3adf..e2c1b6c6 100644
--- a/MinecraftClient/McTcpClient.cs
+++ b/MinecraftClient/McTcpClient.cs
@@ -6,50 +6,72 @@ using System.Net.Sockets;
using System.Threading;
using System.IO;
using System.Net;
+using MinecraftClient.Protocol;
+using MinecraftClient.Proxy;
namespace MinecraftClient
{
///
/// The main client class, used to connect to a Minecraft server.
- /// It allows message sending and text receiving.
///
- class McTcpClient
+ public class McTcpClient : IMinecraftComHandler
{
+ private static List cmd_names = new List();
+ private static Dictionary cmds = new Dictionary();
+ private List bots = new List();
+ private static List scripts_on_hold = new List();
+ public void BotLoad(ChatBot b) { b.SetHandler(this); bots.Add(b); b.Initialize(); Settings.SingleCommand = ""; }
+ public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); }
+ public void BotClear() { bots.Clear(); }
+
public static int AttemptsLeft = 0;
- string host;
- int port;
- string username;
- string text;
- Thread t_updater;
- Thread t_sender;
+ private string host;
+ private int port;
+ private string username;
+ private string uuid;
+ private string sessionid;
+
+ public int getServerPort() { return port; }
+ public string getServerHost() { return host; }
+ public string getUsername() { return username; }
+ public string getUserUUID() { return uuid; }
+ public string getSessionID() { return sessionid; }
+
TcpClient client;
- MinecraftCom handler;
+ IMinecraftCom handler;
+ Thread cmdprompt;
///
- /// Starts the main chat client, wich will login to the server using the MinecraftCom class.
+ /// Starts the main chat client
///
/// The chosen username of a premium Minecraft Account
- /// A valid sessionID obtained with MinecraftCom.GetLogin()
- /// The server IP (serveradress or serveradress:port)
+ /// The player's UUID for online-mode authentication
+ /// A valid sessionID obtained after logging in
+ /// The server IP
+ /// The server port to use
+ /// Minecraft protocol version to use
- public McTcpClient(string username, string uuid, string sessionID, string server_port, MinecraftCom handler)
+ public McTcpClient(string username, string uuid, string sessionID, int protocolversion, string server_ip, short port)
{
- StartClient(username, uuid, sessionID, server_port, false, handler, "");
+ StartClient(username, uuid, sessionID, server_ip, port, protocolversion, false, "");
}
///
- /// Starts the main chat client in single command sending mode, wich will login to the server using the MinecraftCom class, send the command and close.
+ /// Starts the main chat client in single command sending mode
///
/// The chosen username of a premium Minecraft Account
- /// A valid sessionID obtained with MinecraftCom.GetLogin()
- /// The server IP (serveradress or serveradress:port)
+ /// The player's UUID for online-mode authentication
+ /// A valid sessionID obtained after logging in
+ /// The server IP
+ /// The server port to use
+ /// Minecraft protocol version to use
/// The text or command to send.
- public McTcpClient(string username, string uuid, string sessionID, string server_port, MinecraftCom handler, string command)
+ public McTcpClient(string username, string uuid, string sessionID, string server_ip, short port, int protocolversion, string command)
{
- StartClient(username, uuid, sessionID, server_port, true, handler, command);
+ StartClient(username, uuid, sessionID, server_ip, port, protocolversion, true, command);
}
///
@@ -57,70 +79,64 @@ namespace MinecraftClient
///
/// The chosen username of a premium Minecraft Account
/// A valid sessionID obtained with MinecraftCom.GetLogin()
- /// The server IP (serveradress or serveradress:port)/param>
+ /// The server IP
+ /// The server port to use
+ /// Minecraft protocol version to use
+ /// The player's UUID for online-mode authentication
/// If set to true, the client will send a single command and then disconnect from the server
/// The text or command to send. Will only be sent if singlecommand is set to true.
- private void StartClient(string user, string uuid, string sessionID, string server_port, bool singlecommand, MinecraftCom handler, string command)
+ private void StartClient(string user, string uuid, string sessionID, string server_ip, short port, int protocolversion, bool singlecommand, string command)
{
- this.handler = handler;
- username = user;
- string[] sip = server_port.Split(':');
- host = sip[0];
- if (sip.Length == 1)
+ this.sessionid = sessionID;
+ this.uuid = uuid;
+ this.username = user;
+ this.host = server_ip;
+ this.port = port;
+
+ if (!singlecommand)
{
- port = 25565;
- }
- else
- {
- try
- {
- port = Convert.ToInt32(sip[1]);
- }
- catch (FormatException) { port = 25565; }
+ if (Settings.AntiAFK_Enabled) { BotLoad(new ChatBots.AntiAFK(Settings.AntiAFK_Delay)); }
+ if (Settings.Hangman_Enabled) { BotLoad(new ChatBots.HangmanGame(Settings.Hangman_English)); }
+ if (Settings.Alerts_Enabled) { BotLoad(new ChatBots.Alerts()); }
+ if (Settings.ChatLog_Enabled) { BotLoad(new ChatBots.ChatLog(Settings.expandVars(Settings.ChatLog_File), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); }
+ if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.expandVars(Settings.PlayerLog_File))); }
+ 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()); }
}
try
{
- Console.WriteLine("Logging in...");
- client = new TcpClient(host, port);
+ client = ProxyHandler.newTcpClient(host, port);
client.ReceiveBufferSize = 1024 * 1024;
- handler.setClient(client);
- if (handler.Login(user, uuid, sessionID, host, port))
+ handler = Protocol.ProtocolHandler.getProtocolHandler(client, protocolversion, this);
+ Console.WriteLine("Version is supported.\nLogging in...");
+
+ if (handler.Login())
{
- //Single command sending
if (singlecommand)
{
handler.SendChatMessage(command);
- Console.Write("Command ");
- Console.ForegroundColor = ConsoleColor.DarkGray;
- Console.Write(command);
- Console.ForegroundColor = ConsoleColor.Gray;
- Console.WriteLine(" sent.");
+ ConsoleIO.WriteLineFormatted("§7Command §8" + command + "§7 sent.");
Thread.Sleep(5000);
- handler.Disconnect("disconnect.quitting");
+ handler.Disconnect();
Thread.Sleep(1000);
}
else
{
- Console.WriteLine("Server was successfully joined.\nType '/quit' to leave the server.");
-
- //Command sending thread, allowing user input
- t_sender = new Thread(new ThreadStart(StartTalk));
- t_sender.Name = "CommandSender";
- t_sender.Start();
-
- //Data receiving thread, allowing text receiving
- t_updater = new Thread(new ThreadStart(Updater));
- t_updater.Name = "PacketHandler";
- t_updater.Start();
+ foreach (ChatBot bot in scripts_on_hold) { bots.Add(bot); }
+ scripts_on_hold.Clear();
+
+ Console.WriteLine("Server was successfully joined.\nType '"
+ + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar)
+ + "quit' to leave the server.");
+
+ cmdprompt = new Thread(new ThreadStart(CommandPrompt));
+ cmdprompt.Name = "MCC Command prompt";
+ cmdprompt.Start();
}
}
- else
- {
- Console.WriteLine("Login failed.");
- if (!singlecommand && !handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Login failed.")) { Program.ReadLineReconnect(); }
- }
}
catch (SocketException)
{
@@ -136,14 +152,14 @@ namespace MinecraftClient
///
/// Allows the user to send chat messages, commands, and to leave the server.
- /// Will be automatically called on a separate Thread by StartClient()
///
- private void StartTalk()
+ private void CommandPrompt()
{
try
{
- //Needed if the player is dead
+ string text = "";
+ Thread.Sleep(500);
handler.SendRespawnPacket();
while (client.Client.Connected)
@@ -164,80 +180,94 @@ namespace MinecraftClient
else
{
text = text.Trim();
- if (text.ToLower() == "/quit" || text.ToLower() == "/reco")
+ if (text.Length > 0)
{
- break;
- }
- else if (text.ToLower() == "/respawn")
- {
- handler.SendRespawnPacket();
- ConsoleIO.WriteLine("You have respawned.");
- }
- else if (text.ToLower().StartsWith("/script "))
- {
- handler.BotLoad(new Bots.Script(text.Substring(8)));
- }
- else if (text != "")
- {
- //Message is too long
- if (text.Length > 100)
+ if (Settings.internalCmdChar == ' ' || text[0] == Settings.internalCmdChar)
{
- if (text[0] == '/')
+ string response_msg = "";
+ string command = Settings.internalCmdChar == ' ' ? text : text.Substring(1);
+ if (!performInternalCommand(Settings.expandVars(command), ref response_msg) && Settings.internalCmdChar == '/')
{
- //Send the first 100 chars of the command
- text = text.Substring(0, 100);
- handler.SendChatMessage(text);
+ SendText(text);
}
- else
+ else if (response_msg.Length > 0)
{
- //Send the message splitted in several messages
- while (text.Length > 100)
- {
- handler.SendChatMessage(text.Substring(0, 100));
- text = text.Substring(100, text.Length - 100);
- }
- handler.SendChatMessage(text);
+ ConsoleIO.WriteLineFormatted("§8MCC: " + response_msg);
}
}
- else handler.SendChatMessage(text);
+ else SendText(text);
}
}
}
-
- switch (text.ToLower())
- {
- case "/quit": Program.Exit(); break;
- case "/reco": Program.Restart(); break;
- }
}
catch (IOException) { }
}
///
- /// Receive the data (including chat messages) from the server, and keep the connection alive.
- /// Will be automatically called on a separate Thread by StartClient()
+ /// Perform an internal MCC command (not a server command, use SendText() instead for that!)
///
+ /// The command
+ /// Set to true if command was sent by the user using the command prompt
+ /// May contain a confirmation or error message after processing the command, or "" otherwise.
+ /// TRUE if the command was indeed an internal MCC command
- private void Updater()
+ public bool performInternalCommand(string command, ref string response_msg)
{
- try
- {
- //handler.DebugDump();
- do
- {
- Thread.Sleep(100);
- } while (handler.Update());
- }
- catch (IOException) { }
- catch (SocketException) { }
- catch (ObjectDisposedException) { }
+ /* Load commands from the 'Commands' namespace */
- if (!handler.HasBeenKicked)
+ if (cmds.Count == 0)
{
- ConsoleIO.WriteLine("Connection has been lost.");
- if (!handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection has been lost.") && !Program.ReadLineReconnect()) { t_sender.Abort(); }
+ Type[] cmds_classes = Program.GetTypesInNamespace("MinecraftClient.Commands");
+ foreach (Type type in cmds_classes)
+ {
+ if (type.IsSubclassOf(typeof(Command)))
+ {
+ try
+ {
+ Command cmd = (Command)Activator.CreateInstance(type);
+ cmds[cmd.CMDName.ToLower()] = cmd;
+ cmd_names.Add(cmd.CMDName.ToLower());
+ foreach (string alias in cmd.getCMDAliases())
+ cmds[alias.ToLower()] = cmd;
+ }
+ catch (Exception e)
+ {
+ ConsoleIO.WriteLine(e.Message);
+ }
+ }
+ }
}
- else if (Program.ReadLineReconnect()) { t_sender.Abort(); }
+
+ /* Process the provided command */
+
+ string command_name = command.Split(' ')[0].ToLower();
+ if (command_name == "help")
+ {
+ if (Command.hasArg(command))
+ {
+ string help_cmdname = Command.getArgs(command)[0].ToLower();
+ if (help_cmdname == "help")
+ {
+ response_msg = "help : show brief help about a command.";
+ }
+ else if (cmds.ContainsKey(help_cmdname))
+ {
+ response_msg = cmds[help_cmdname].CMDDesc;
+ }
+ else response_msg = "Unknown command '" + command_name + "'. Use 'help' for command list.";
+ }
+ else response_msg = "help . Available commands: " + String.Join(", ", cmd_names.ToArray());
+ }
+ else if (cmds.ContainsKey(command_name))
+ {
+ response_msg = cmds[command_name].Run(this, command);
+ }
+ else
+ {
+ response_msg = "Unknown command '" + command_name + "'. Use '" + (Settings.internalCmdChar == ' ' ? "" : "" + Settings.internalCmdChar) + "help' for help.";
+ return false;
+ }
+ return true;
}
///
@@ -246,11 +276,122 @@ namespace MinecraftClient
public void Disconnect()
{
- handler.Disconnect("disconnect.quitting");
+ foreach (ChatBot bot in bots)
+ if (bot is ChatBots.Script)
+ scripts_on_hold.Add((ChatBots.Script)bot);
+
+ handler.Disconnect();
+ handler.Dispose();
+
+ if (cmdprompt != null)
+ cmdprompt.Abort();
+
Thread.Sleep(1000);
- if (t_updater != null) { t_updater.Abort(); }
- if (t_sender != null) { t_sender.Abort(); }
+
if (client != null) { client.Close(); }
}
+
+ ///
+ /// Received some text from the server
+ ///
+ /// Text received
+
+ public void OnTextReceived(string text)
+ {
+ ConsoleIO.WriteLineFormatted(text, false);
+ foreach (ChatBot bot in bots)
+ bot.GetText(text);
+ }
+
+ ///
+ /// When connection has been lost
+ ///
+
+ public void OnConnectionLost(ChatBot.DisconnectReason reason, string message)
+ {
+ bool will_restart = false;
+
+ switch (reason)
+ {
+ case ChatBot.DisconnectReason.ConnectionLost:
+ message = "Connection has been lost.";
+ ConsoleIO.WriteLine(message);
+ break;
+
+ case ChatBot.DisconnectReason.InGameKick:
+ ConsoleIO.WriteLine("Disconnected by Server :");
+ ConsoleIO.WriteLineFormatted(message);
+ break;
+
+ case ChatBot.DisconnectReason.LoginRejected:
+ ConsoleIO.WriteLine("Login failed :");
+ ConsoleIO.WriteLineFormatted(message);
+ break;
+ }
+
+ foreach (ChatBot bot in bots)
+ will_restart |= bot.OnDisconnect(reason, message);
+
+ if (!will_restart) { Program.OfflineCommandPrompt(); }
+ }
+
+ ///
+ /// Called ~10 times per second by the protocol handler
+ ///
+
+ public void OnUpdate()
+ {
+ for (int i = 0; i < bots.Count; i++)
+ {
+ try
+ {
+ bots[i].Update();
+ }
+ catch (Exception e)
+ {
+ ConsoleIO.WriteLineFormatted("§8Got error from " + bots[i].ToString() + ": " + e.ToString());
+ }
+ }
+ }
+
+ ///
+ /// Send a chat message or command to the server
+ ///
+ /// Text to send to the server
+ /// True if the text was sent with no error
+
+ public bool SendText(string text)
+ {
+ if (text.Length > 100) //Message is too long?
+ {
+ if (text[0] == '/')
+ {
+ //Send the first 100 chars of the command
+ text = text.Substring(0, 100);
+ return handler.SendChatMessage(text);
+ }
+ else
+ {
+ //Send the message splitted into several messages
+ while (text.Length > 100)
+ {
+ handler.SendChatMessage(text.Substring(0, 100));
+ text = text.Substring(100, text.Length - 100);
+ }
+ return handler.SendChatMessage(text);
+ }
+ }
+ else return handler.SendChatMessage(text);
+ }
+
+ ///
+ /// Allow to respawn after death
+ ///
+ /// True if packet successfully sent
+
+ public bool SendRespawnPacket()
+ {
+ return handler.SendRespawnPacket();
+ }
}
}
diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj
index 384513bd..4bbb262f 100644
--- a/MinecraftClient/MinecraftClient.csproj
+++ b/MinecraftClient/MinecraftClient.csproj
@@ -53,7 +53,7 @@
false
- resources\appicon.ico
+ Resources\AppIcon.ico
MinecraftClient.Program
@@ -62,6 +62,7 @@
+
@@ -70,14 +71,65 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -103,7 +155,7 @@
-
+