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..78973a33 --- /dev/null +++ b/MinecraftClient/ChatBot.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +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 "Bots". + /// 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. */ + /* =================================================================== */ + + /// + /// 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) + { + ConsoleIO.WriteLineFormatted("§8BOT:" + text, false); + handler.SendChatMessage(text); + } + + /// + /// 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) + { + ConsoleIO.WriteLineFormatted("§8[BOT] " + text, true); + } + + /// + /// 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)); + } + } +} diff --git a/MinecraftClient/ChatBots/Alerts.cs b/MinecraftClient/ChatBots/Alerts.cs new file mode 100644 index 00000000..a5b0794b --- /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)) + { + 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 + + } + } + } + } + } + } +} 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..84ccb356 --- /dev/null +++ b/MinecraftClient/ChatBots/ChatLog.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +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) + { + 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(); + } + } +} 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..df5cde1b --- /dev/null +++ b/MinecraftClient/ChatBots/RemoteControl.cs @@ -0,0 +1,68 @@ +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 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/ChatBots/Script.cs b/MinecraftClient/ChatBots/Script.cs new file mode 100644 index 00000000..3517a827 --- /dev/null +++ b/MinecraftClient/ChatBots/Script.cs @@ -0,0 +1,134 @@ +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 + 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(); + } + } + } + } +} diff --git a/MinecraftClient/ChatBots/ScriptScheduler.cs b/MinecraftClient/ChatBots/ScriptScheduler.cs new file mode 100644 index 00000000..eee6f921 --- /dev/null +++ b/MinecraftClient/ChatBots/ScriptScheduler.cs @@ -0,0 +1,143 @@ +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 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--; + } + } +} 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/ConsoleIO.cs b/MinecraftClient/ConsoleIO.cs index 86059ebc..6f08abcb 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(); } @@ -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) + { + 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/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..5382477e --- /dev/null +++ b/MinecraftClient/Crypto/Streams/MonoAesStream.cs @@ -0,0 +1,148 @@ +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(); + } + 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..ba48a006 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -6,50 +6,63 @@ 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 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; /// - /// 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) - 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_port) { - StartClient(username, uuid, sessionID, server_port, false, handler, ""); + StartClient(username, uuid, sessionID, server_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 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_port, int protocolversion, string command) { - StartClient(username, uuid, sessionID, server_port, true, handler, command); + StartClient(username, uuid, sessionID, server_port, protocolversion, true, command); } /// @@ -61,12 +74,15 @@ namespace MinecraftClient /// 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_port, int protocolversion, bool singlecommand, string command) { - this.handler = handler; - username = user; string[] sip = server_port.Split(':'); - host = sip[0]; + + this.sessionid = sessionID; + this.uuid = uuid; + this.username = user; + this.host = sip[0]; + if (sip.Length == 1) { port = 25565; @@ -80,47 +96,44 @@ namespace MinecraftClient catch (FormatException) { port = 25565; } } + if (!singlecommand) + { + 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.ChatLog_File.Replace("%username%", Settings.Username), Settings.ChatLog_Filter, Settings.ChatLog_DateTime)); } + if (Settings.PlayerLog_Enabled) { BotLoad(new ChatBots.PlayerListLogger(Settings.PlayerLog_Delay, Settings.PlayerLog_File.Replace("%username%", Settings.Username))); } + if (Settings.AutoRelog_Enabled) { BotLoad(new ChatBots.AutoRelog(Settings.AutoRelog_Delay, Settings.AutoRelog_Retries)); } + if (Settings.ScriptScheduler_Enabled) { BotLoad(new ChatBots.ScriptScheduler(Settings.ScriptScheduler_TasksFile.Replace("%username%", Settings.Username))); } + 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."); + Console.WriteLine("Logging 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.", false); Thread.Sleep(5000); - handler.Disconnect("disconnect.quitting"); + handler.Disconnect(); Thread.Sleep(1000); } else { + foreach (ChatBot bot in scripts_on_hold) { bots.Add(bot); } + scripts_on_hold.Clear(); 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(); + StartTalk(); } } - else - { - Console.WriteLine("Login failed."); - if (!singlecommand && !handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, "Login failed.")) { Program.ReadLineReconnect(); } - } } catch (SocketException) { @@ -136,14 +149,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() { try { - //Needed if the player is dead + string text = ""; + Thread.Sleep(500); handler.SendRespawnPacket(); while (client.Client.Connected) @@ -175,7 +188,7 @@ namespace MinecraftClient } else if (text.ToLower().StartsWith("/script ")) { - handler.BotLoad(new Bots.Script(text.Substring(8))); + BotLoad(new ChatBots.Script(text.Substring(8))); } else if (text != "") { @@ -190,7 +203,7 @@ namespace MinecraftClient } else { - //Send the message splitted in several messages + //Send the message splitted into several messages while (text.Length > 100) { handler.SendChatMessage(text.Substring(0, 100)); @@ -213,44 +226,84 @@ namespace MinecraftClient 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() - /// - - private void Updater() - { - try - { - //handler.DebugDump(); - do - { - Thread.Sleep(100); - } while (handler.Update()); - } - catch (IOException) { } - catch (SocketException) { } - catch (ObjectDisposedException) { } - - if (!handler.HasBeenKicked) - { - ConsoleIO.WriteLine("Connection has been lost."); - if (!handler.OnConnectionLost(ChatBot.DisconnectReason.ConnectionLost, "Connection has been lost.") && !Program.ReadLineReconnect()) { t_sender.Abort(); } - } - else if (Program.ReadLineReconnect()) { t_sender.Abort(); } - } - /// /// Disconnect the client from the server /// 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(); 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, true); + break; + + case ChatBot.DisconnectReason.LoginRejected: + ConsoleIO.WriteLine("Login failed :"); + ConsoleIO.WriteLineFormatted(message, true); + break; + } + + foreach (ChatBot bot in bots) + will_restart |= bot.OnDisconnect(reason, message); + + if (!will_restart) { Program.ReadLineReconnect(); } + } + + /// + /// Called ~10 times per second by the protocol handler + /// + + public void OnUpdate() + { + for (int i = 0; i < bots.Count; i++) + bots[i].Update(); + } + + /// + /// Send a chat message to the server + /// + + public void SendChatMessage(string message) + { + handler.SendChatMessage(message); + } } } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 384513bd..8add0d36 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -53,7 +53,7 @@ false - resources\appicon.ico + Resources\AppIcon.ico MinecraftClient.Program @@ -70,14 +70,42 @@ - + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + @@ -103,7 +131,7 @@ - +