//////////////////////////////////////////////// ////////////////PROGRAM INFO//////////////////// // "Kon" is a "learning bot" written in C#. // // The bot will connect to an IRC server and // // "watch" the conversations. It will then // // "learn" from them. You'll then be able to // // talk to it and hopefully it'll hold a conv-// // ersation. // //////////////////////////////////////////////// // "Kon" is an ongoing project started by // // James Iyouboushi. // // Emails: jmp1139@my.gulfcoast.edu // // Iyouboushi@gmail.com // //////////////////////////////////////////////// // This file was last updated on: 2/13/2008 // //////////////////////////////////////////////// ////////////////TO DO/////////////////////////// // Some IRC commands need to be cleaned up and// // touched up. Need to make it so you can set// // a list of users that can use the bot's // // commands. // //////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Net; using System.Net.Sockets; using System.IO; namespace Kon { class IRC { #region variables // Public variables public bool canQuit = false; public bool connected = false; // Private variables private int ircPort = 6667; private int numberOfErrorTries = 0; private string ircServer = "bots.esper.net"; private string nickNameMain = "Kon"; private string nickNameBckup = "KonPlushie"; private string user = "USER Kon 1 * :SPLASH !!!"; private string channel = "#KonBot"; private string version = "Kon - Version 1.0.6 - created by James Iyouboushi [iyouboushi@gmail.com]"; private string rawLine = ""; private string modifiedLine = ""; private bool AIML = false; private bool google = false; public bool logging = true; public bool showServerMsgs = false; public bool showPongReply = false; private TcpClient ircClient; private NetworkStream stream; private Thread pingSender; private Thread listenerThread; public static StreamWriter writer; public static StreamWriter logger; public static StreamReader reader; public static AI myAI; // Kon's own brain (AI.cs) public static AIMLbrain myAIML; // Kon's AIML brain (AIML.cs) public static googleBrain myGoogle; // Kon's Google brain (google.cs) // Private constants private const string CRLF = "\r\n"; private const string PING = "PING :"; private const string PONG = "PONG "; private const string LOG_FILE = "KonLog.txt"; #endregion #region constructors //////////////////////////////////////////////// //////////////CONSTRUCTORS////////////////////// // There are two constructors. One will only // // use defaults found at the top of this file.// // That constructor shouldn't be called but is// // merely a failsafe. // //////////////////////////////////////////////// public IRC() { // Connect to IRC connect(); } public IRC(String server, int port, String mainNick, String backupNick, String chan) { // Validate the incoming information. Keep in mind that if any of these are wrong it // will use defaults defined at the top of this file. if (server != "") ircServer = server; if (port != 0) ircPort = port; if (mainNick != "") nickNameMain = mainNick; if (backupNick != "") nickNameBckup = backupNick; if (chan != "") channel = chan; // Connect to IRC using the settings connect(); } #endregion #region connect private void connect() { // Tell people we're going to connect System.Console.WriteLine("Connecting to " + ircServer + ":" + ircPort + " ...\n"); try { // If logging is enabled, let's turn it on and indicate what time we started if (logging == true) { logger = new StreamWriter(LOG_FILE, true); logger.WriteLine("---------[STARTING: " + DateTime.Now + "]"); } // Try to make a connection to the server ircClient = new TcpClient(ircServer, ircPort); stream = ircClient.GetStream(); reader = new StreamReader(stream); writer = new StreamWriter(stream); // Start up our ping handler PingControl(); // Send the ident information to the server (i.e. our user and who we are) writer.WriteLine(user); writer.Flush(); // Need to add the ability to switch from primary to backup nick if the first is taken writer.WriteLine("NICK " + nickNameMain); writer.Flush(); // We're connected, so let's set our bool to true. connected = true; // Start up our incoming data listener IncomingData(); // Let's turn on the AI stuff. myAI = new AI(); if (google == true) myGoogle = new googleBrain(); if (AIML == true) myAIML = new AIMLbrain(); // Auto join the channel, if there is one if (channel != "") { Thread.Sleep(500); writer.WriteLine("JOIN " + channel); writer.Flush(); } } catch (Exception e) { // Show the exception Console.WriteLine(e.ToString() + CRLF); // Sleep, before we try again Thread.Sleep(5000); connect(); } } #endregion #region quit private void quit() { // Message that we're quitting writer.WriteLine("QUIT "); writer.Flush(); // Exit out of the program. quit_program(); } private void quit(string message) { // Message that we're quitting writer.WriteLine("QUIT " + ":" + message); writer.Flush(); // Exit out of the program. quit_program(); } private void quit_program() { // If logging is enabled, let's write that we're quitting. if (logging == true) { logger.WriteLine("---------[QUITTING: " + DateTime.Now + "]"); logger.Close(); } // Close everything down connected = false; stream.Close(); writer.Close(); reader.Close(); ircClient.Close(); // Tell the console that we can quit and release control canQuit = true; // Slow it down for a second Thread.Sleep(1000); } #endregion #region joinChannel // Join a channel, no key private void join(string chan) { if (channel == "") { channel = chan; writer.WriteLine("JOIN " + channel); writer.Flush(); } else { Console.WriteLine("This client only supports one channel right now.\n"); } } // Join a channel with a key private void join(string channel, string key) { } #endregion #region partChannel private void part(string chan) { // tell the server that we're leaving the channel. writer.WriteLine("PART " + chan); writer.Flush(); // tell the user that we've parted. Console.WriteLine("LEFT " + chan); // let's set the channel to null. channel = ""; } #endregion #region sendToChannel // Send messages something to the channel private void sendToChannel(string message) { // Let's pause a moment before sending the message Thread.Sleep(500); // Create the message writer.WriteLine("PRIVMSG " + channel + " :" + message); // Log it, if the logging option is on LogFile(">> " + channel + " : " + message); // Flush it writer.Flush(); } #endregion #region ConsoleCommands //////////////////////////////////////////////// /////////////CONSOLE COMMAND PARSER///////////// // The command parser takes the incoming text // // from the console and splits it up into // // tokens as well as determines what command // // has been used and what function should be // // called. // //////////////////////////////////////////////// public void getCommand() { // Get the command string from the console string rawCommandString = Console.ReadLine(); // Break the command into their tokenized parts so we can determine what to do string[] commandString; commandString = rawCommandString.Split(new char[] { ' ' }); // Get the first element of the command string and convert it to upper case string command = commandString[0].ToString(); command = command.ToUpper(); /////////////////////////////////////////////////// // The QUIT command. Used to disconnect from the// // server. It'll also cause our console to stop.// /////////////////////////////////////////////////// if (command == "/QUIT") { // We need to check to see if the person has typed a quit message int commandLength = commandString.Length; if (commandLength >= 2) { // Determine what the message is. commandLength = commandString.Length - 1; string message = ""; for (int i = 1; i <= commandLength; i++) { message = message + commandString[i] + " "; } // Let's quit with our message quit(message); } else quit(version); // Quit with a default message of the version. } /////////////////////////////////////////////////// // The JOIN command. Used to join an IRC channel// // and may have a key (if the channel is locked) // /////////////////////////////////////////////////// if (((command == "/JOIN") || (command == "/J"))) { // Set our channel string chan = commandString[1].ToString(); // Does the channel have the necessary #? if (!chan.StartsWith("#")) { // We'll add it here. chan = "#" + commandString[1].ToString(); } // Does this join command have a key for the channel? // Check for the max number of words in the array first. int commandLength = commandString.Length; if (commandLength >= 3) { // Is the next word a space or null? if (commandString[2].ToString() != "") { // We have a key! Let's get the key and join the channel. string key = commandString[2].ToString(); join(chan, key); } } // No, it doesn't, so just join a regular channel else join(chan); } /////////////////////////////////////////////////// // The PART command. Used to leave a channel // /////////////////////////////////////////////////// if (((command == "/PART") || (command == "/LEAVE"))) { if (channel != "") part(channel); } /////////////////////////////////////////////////// // The "Say" command to send a message to the // // active channel. // /////////////////////////////////////////////////// if (command == "/SAY") { if (channel != "") { // Determine what the message is. int commandLength = commandString.Length -1; string message = ""; for (int i = 1; i <= commandLength; i++) { message = message + commandString[i] + " "; } // Send the message to the command sendToChannel(message); } else { Console.WriteLine("You must first join a channel.\n"); } } } #endregion #region PingControl //////////////////////////////////////////////// ////////////////PING CONROL///////////////////// //////////////////////////////////////////////// // PING control is a set of two functions that// // will ping the server every 15 seconds on a // // seperate thread until we are no longer // // connected to the server. // //////////////////////////////////////////////// private void PingControl() { // Start a new thread for the Ping Control pingSender = new Thread (new ThreadStart(PingRun)); pingSender.Start(); // Begin running the control PingRun(); } // Send PING to irc server every 15 seconds private void PingRun() { try { // Is the client still running? If so, we need to ping the server while ((canQuit == false) && (connected == true)) { writer.WriteLine(PING + ircServer); writer.Flush(); Thread.Sleep(15000); } } catch (Exception e) { // Show the exception Console.WriteLine(e.ToString() + CRLF); // Sleep, before we try again Thread.Sleep(5000); } } #endregion #region IncomingData //////////////////////////////////////////////// /////////////INCOMING DATA////////////////////// //////////////////////////////////////////////// // These methods will handle the incoming // // data and process it. // //////////////////////////////////////////////// private void IncomingData() { // Start a new thread for the incoming data listenerThread = new Thread(new ThreadStart(ReceiveData)); listenerThread.Start(); } private void ReceiveData() { while (connected == true) { try { rawLine = reader.ReadLine(); // We have to take a looksie at the line and determine if it's going to be shown/logged. // Break the raw line into tokenized parts so we can determine what to do string[] IncomingDataString; IncomingDataString = rawLine.Split(new char[] { ' ' }); // First priority is the PING event. If the server is pinging us, we need to PONG back or else it'll disconnect us. if (IncomingDataString[0].ToString() == "PING") { writer.WriteLine(PONG + IncomingDataString[1].ToString()); // it has to respond to the server sending writer.Flush(); } // Is this incoming data NOTICES, QUITs, JOINs, NICKs, or MODEs? We need to display those. if ((((((IncomingDataString[1].ToString() == "JOIN") || (IncomingDataString[1].ToString() == "NOTICE") || (IncomingDataString[1].ToString() == "QUIT") || (IncomingDataString[1].ToString() == "NICK") || (IncomingDataString[1].ToString() == "PART") || (IncomingDataString[1].ToString() == "MODE")))))) DisplayData(rawLine); // Is this incoming data the server info? Might be important to show it. if (((((IncomingDataString[1].ToString() == "001") || (IncomingDataString[1].ToString() == "002") || (IncomingDataString[1].ToString() == "003") || (IncomingDataString[1].ToString() == "004") || (IncomingDataString[1].ToString() == "005"))))) //&& (showServerMsgs == true)) DisplayData(rawLine); // Is this incoming data the MOTD of the server? If so, are we going to allow it to be shown? if ((((IncomingDataString[1].ToString() == "375") || (IncomingDataString[1].ToString() == "372") || (IncomingDataString[1].ToString() == "376"))) && (showServerMsgs)) DisplayData(rawLine); // Is this incoming data the PONG reply? If so, are we going to allow it to be shown? if ((IncomingDataString[1].ToString() == "PONG") && (showPongReply)) DisplayData(rawLine); // For now, we'll make it a little easier to read the chat lines and not worry about deciphering commands if (IncomingDataString[1].ToString() == "PRIVMSG") { // Determine what the message is. int msgLength = IncomingDataString.Length - 1; string message = ""; for (int i = 3; i <= msgLength; i++) { message = message + IncomingDataString[i] + " "; } // Let's chop off the ":" that starts each message, leaving us with the true message. message = message.TrimStart(':'); // We have the message, let's get the nick. string nickname = getNick(IncomingDataString[0].ToString()); modifiedLine = "<" + nickname + "> " + message; // Okay, we're ready to display/log it. DisplayData(modifiedLine); // And now we're ready to see if it's a command the bot will recognize. incomingCommand(message, nickname); } // else // { // For testing and debugging purposes, if it's a message that isn't recognized and isn't PONG, we're going to dump it into a file. // using (StreamWriter dumpFile = File.AppendText("DumpFile.txt")) // { // if (IncomingDataString[1].ToString() != "PONG") // dumpFile.Write(rawLine + CRLF); // } // } } catch (Exception e) { // Show the exception Console.WriteLine(e.ToString() + CRLF); // Increase the number of error tries numberOfErrorTries++; // Sleep, before we try again Thread.Sleep(5000); // This is a way to stop the infinite loop. if (numberOfErrorTries > 3) connected = false; } } } private void DisplayData(string rawLine) { // If logging is enabled, let's write it to a log file. LogFile(rawLine); // Write the line to the console, after a small throttle. Thread.Sleep(100); Console.WriteLine(rawLine); } private string getNick(string rawName) { string nickname = ""; int nameLength = rawName.Length - 1; for (int i = 1; i <= nameLength; i++) { if (rawName[i].ToString() == "!") break; else nickname = nickname + rawName[i]; } return nickname; } #endregion #region IncomingCommandParser //////////////////////////////////////////////// /////////////INCOMING COMMAND PARSER//////////// // The command parser takes the incoming text // // from the channel and determines if it's a // // command it recognizes and will perform it, // // if it is. // //////////////////////////////////////////////// // Available Commands: // // !splash, !quit, !reload, !reloadAIML, // // !toggleAIML, !toggleGoogle, !braininfo // //////////////////////////////////////////////// private void incomingCommand(string message, string nick) { // Break the command into their tokenized parts so we can determine what to do string[] commandString; commandString = message.Split(new char[] { ' ' }); // Get the first element of the command string and convert it to upper case string command = commandString[0].ToString(); command = command.ToUpper(); #region Commands if (command == "!SPLASH") { Thread.Sleep(1000); // throttle it so it won't commit a flood if tons of people use it. sendToChannel("Kon Splash!"); Console.WriteLine(">> " + channel + " : Kon Splash!"); } if (command == "!QUIT") { // For now, the nicks allowed to use this will be hardcoded if (nick == "Iyouboushi") { writer.WriteLine("QUIT " + ":" + version); writer.Flush(); Console.WriteLine("Kon can now be closed"); Thread.Sleep(500); quit_program(); } } if (command == "!RELOADAIML") { // For now, the nicks allowed to use this will be hardcoded if (nick == "Iyouboushi") { sendToChannel("4Reloading AIML..please wait before using any commands.."); Console.WriteLine(">> RELOADING AI"); myAI = null; myAI = new AI(); // Let's put the thread to sleep while it's reloading. Thread.Sleep(1300); sendToChannel("2AI Reloaded!"); } } if (command == "!TOGGLEAIML") { // For now, the nicks allowed to use this will be hardcoded if (nick == "Iyouboushi") { if (AIML) { AIML = false; myAIML = null; sendToChannel("4No longer using AIML for replies..."); } else { AIML = true; google = false; // Can't run both google and AIML. sendToChannel("4Loading AIML files..please wait before using any commands.."); myAIML = new AIMLbrain(); sendToChannel("4Now using AIML in addition to my brain for replies..."); } } } if (command == "!TOGGLEGOOGLE") { // For now, the nicks allowed to use this will be hardcoded if (nick == "Iyouboushi") { if (google) { google = false; sendToChannel("4No longer using Google for replies..."); myGoogle = null; } else { google = true; AIML = false; // Can't run both google and AIML. sendToChannel("4Now using Google in addition to my brain for replies..."); myGoogle = new googleBrain(); myAIML = null; } } } if (command == "!BRAININFO") { sendToChannel("My brain contains " + myAI.getBrainLength() + " lines."); } #endregion // Time to see if someone is talking to the bot. string addressingBot = nickNameMain + ":"; addressingBot = addressingBot.ToUpper(); if (command == addressingBot) { string conversation = ""; string[] messageWithoutCommand; messageWithoutCommand = message.Split(new char[] { ' ' }); int msgLength = messageWithoutCommand.Length - 1; for (int i = 1; i <= msgLength; i++) { conversation = conversation + messageWithoutCommand[i] + " "; } // Check to see if it's just an emoticon.. and if so.. let's not pass it. if (conversation.StartsWith(":")) { // Let's pull something from Kon's brain. string reply = myAI.pullFromBrain(); reply = reply.Replace("un-named user", nick); reply = reply.Replace("UNNAMED USER", nick); sendToChannel(reply); Console.WriteLine(">> " + channel + " : " + reply); Thread.Sleep(500); } if (conversation.StartsWith(";")) { // Pull something from Kon's random brain string reply = myAI.pullFromBrain(); reply = reply.Replace("un-named user", nick); reply = reply.Replace("UNNAMED USER", nick); sendToChannel(reply); Console.WriteLine(">> " + channel + " : " + reply); Thread.Sleep(500); } if (conversation.StartsWith("kill me")) { // Pull something from Kon's random brain string reply = myAI.pullFromBrain(); reply = reply.Replace("un-named user", nick); reply = reply.Replace("UNNAMED USER", nick); sendToChannel(reply); Console.WriteLine(">> " + channel + " : " + reply); Thread.Sleep(500); } else { try { // Replace all punctuation with spaces to prevent it from going nuts conversation = conversation.Replace(".", ""); conversation = conversation.Replace("!", ""); conversation = conversation.Replace(",", ""); conversation = conversation.Replace("?", ""); // Get the reply from the bot String reply = ""; // Throttle it a little bit in case there's too many people talking to it at once. Thread.Sleep(500); #region AIMLbrain // If AIML is enabled we'll use the AIML files to find a reply for the user. if (AIML) { // Let's get the reply from the AIML structure reply = AIMLbrain.getReply(reply, conversation, nick); } #endregion #region googleBrain // If the google method is enabled, we'll turn to the web to pull out a response. if (google) { conversation = conversation.Replace("?", ""); conversation.Trim(); reply = myGoogle.getReply(conversation); reply = reply.Replace("UNNAMED USER", nick); } #endregion // This is a last ditch effort to try and respond to the user. If the other methods have failed, // this will reach into kon's most basic brain and pull out a reply. if (reply == "") { reply = myAI.pullFromBrain(); reply = reply.Replace("un-named user", nick); // this will change later. reply = reply.Replace("UNNAMED USER", nick); // so will this. } // Make sure the reply isn't too long so it doesn't crash the bot. int replyLength = reply.Length; if (replyLength >= 150) reply = reply.Substring(0, 150); else reply = reply.Substring(0, replyLength); // Display the response and log it. sendToChannel(reply); Console.WriteLine(">> " + channel + " : " + reply); } catch (Exception e) { // Show the exception Console.WriteLine(e.ToString() + CRLF); // Sleep, before we try again Thread.Sleep(1000); } } } else { // We're going to force the bot to learn something myAI.addToBrain(message, nick); } } #endregion #region logger //////////////////////////////////////////////// ///////////////////LOGGER/////////////////////// //////////////////////////////////////////////// // Dump the lines into a log file for later // // review. // //////////////////////////////////////////////// private void LogFile(string line) { if (logging == true) { logger.WriteLine(line); logger.Flush(); return; } } #endregion } }