//////////////////////////////////////////////// ////////////////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: 1/02/2009 // //////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Net; using System.Net.Sockets; using System.IO; namespace Kon { partial class IRC { #region variables // Public variables public bool canQuit = false; public bool connected = false; public static AI myAI; // Kon's own brain (AI.cs) // Private variables private int randomTalk = 0; private int ircPort = 6667; private int reconnectAttempts = 0; private int channelsCurrentlyIn = 0; private string ircServer = "bots.esper.net"; private string nickNameMain = "Kon"; private string nickNameBckup = "KonPlushie"; private string user = "USER Kon 8 * :SPLASH !!!"; private string channel = "#KonBot"; private string version = "Kon - Version 1.0.11 - created by James Iyouboushi [iyouboushi@gmail.com]"; private string modifiedLine = ""; private string channelUsers; private bool AIML = false; private bool google = false; private bool topics = true; private bool LB = true; public bool logging = true; public bool showServerMsgs = false; public bool showPongReply = false; private TcpClient ircClient; private NetworkStream stream; private Thread pingSender; private Thread listenerThread; private static StreamWriter writer; private static StreamWriter logger; private static StreamReader reader; private static AIMLbrain myAIML; // Kon's AIML brain (AIML.cs) private static googleBrain myGoogle; // Kon's Google brain (google.cs) private static LBbrain myLB; // Kon's LB Brain (LB.cs) private Random randnum = new Random(); // 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() { // Check to see if we need to turn on the log file. LogFileOn(); // Connect to IRC connect(); } public IRC(String server, int port, String mainNick, String backupNick, String chan, int randomTalkPercent) { // 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; if (randomTalkPercent != 0) randomTalk = randomTalkPercent; // Check to see if we need to turn on the log file. LogFileOn(); try { // Connect to IRC using the settings connect(); } catch (Exception e) { // Show the exception DisplayData("ERROR IN CONNECTING... " + e.ToString()); // Sleep, before we try again Thread.Sleep(15000); reconnect(); } } #endregion #region connect private void connect() { // Tell people we're going to connect Console.WriteLine("Connecting to " + ircServer + ":" + ircPort + " ...\n"); try { // 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(); // Authorize who we are. authorize(); // 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(); if (LB == true) myLB = new LBbrain(); // Auto join the channel, if there is one if (channel != "") { Thread.Sleep(500); join(channel); } } catch (Exception e) { // Show the exception DisplayData("ERROR: " + e.ToString()); reconnect(); } } #endregion #region reconnect private void reconnect() { // Increase the amount of times we're trying to reconnect and see if we need to alert the user/stop the reconnect process reconnectAttempts++; if (reconnectAttempts <= 5) { DisplayData("** Will reconnect in 1 minute.."); // If logging is enabled, let's write that we've been disconnected. if (logging == true) LogFile("---------[DISCONNECTED: " + DateTime.Now + "]"); connected = false; Thread.Sleep(1000); // discard everything in the reader buffer before closing it. if (reader != null) { try { reader.DiscardBufferedData(); } catch (Exception e) { } } if (writer != null) writer.Close(); if (reader != null) reader.Close(); if (ircClient != null) ircClient.Close(); if (stream != null) stream.Close(); // Throttle the connection Thread.Sleep(20000); // If logging is enabled, let's write that we're reconnecting if (logging == true) LogFile("---------[RECONNECTING: " + DateTime.Now + "]"); // Now let's reconnect to the server. connect(); } else Console.WriteLine("There has been too many reconnect attempts. Please close Kon and check your connection before retrying."); } #endregion #region authorize (ident) private void authorize() { // Send the ident information to the server (i.e. our user and who we are) writer.WriteLine(user); writer.Flush(); Thread.Sleep(500); // Need to add the ability to switch from primary to backup nick if the first is taken writer.WriteLine("NICK " + nickNameMain); writer.Flush(); Thread.Sleep(600); } #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) LogFile("---------[QUITTING: " + DateTime.Now + "]"); // Close everything down connected = false; Thread.Sleep(1000); // discard everything in the reader buffer before closing it. reader.DiscardBufferedData(); stream.Close(); writer.Close(); reader.Close(); ircClient.Close(); if (logging) logger.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) { writer.WriteLine("JOIN " + chan); writer.Flush(); channelsCurrentlyIn++; if (channelsCurrentlyIn == 1) channel = chan; } // 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("** Leaving " + chan); channelsCurrentlyIn--; if (channelsCurrentlyIn == 0) channel = ""; if (chan.ToUpper() == channel.ToUpper()) channel = ""; } #endregion #region sendToChannel // Send messages something to the channel private void sendToChannel(string message, string location) { // Let's pause a moment before sending the message Thread.Sleep(500); // Create the message writer.WriteLine("PRIVMSG " + location + " :" + message); // Log it, if the logging option is on LogFile(">> " + location + " : " + message); // Flush it writer.Flush(); } #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 { string rawLine = reader.ReadLine(); if (rawLine != null) { // 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; string messageSwitch = " "; IncomingDataString = rawLine.Split(new char[] { ' ' }); if (IncomingDataString.Length > 1) messageSwitch = IncomingDataString[1].ToString(); else messageSwitch = " "; // Before we even check anything else, is it a PING request? if (IncomingDataString[0] == "PING") { if (showPongReply) DisplayData(rawLine); handlePingRequest(IncomingDataString); } if (IncomingDataString[0] == "PONG") { if (showPongReply) DisplayData(rawLine); } // is it an error? if (IncomingDataString[0] == "ERROR") handleErrors(IncomingDataString, rawLine); else { switch (messageSwitch) { // Normal messages case "JOIN": handleJoin(IncomingDataString); break; case "NOTICE": handleNotice(IncomingDataString); break; case "QUIT": handleQuit(IncomingDataString); break; case "NICK": handleNick(IncomingDataString); break; case "PART": handlePart(IncomingDataString); break; case "MODE": handleMode(IncomingDataString, rawLine); break; case "TOPIC": DisplayData(rawLine); break; case "KICK": handleKick(IncomingDataString); break; case "PRIVMSG": handlePrivMsg(IncomingDataString, IncomingDataString[2].ToString()); break; // Server messages case "001": DisplayData(rawLine); break; case "002": DisplayData(rawLine); break; case "003": DisplayData(rawLine); break; case "004": DisplayData(rawLine); break; case "005": DisplayData(rawLine); break; case "251": // visible & invisible users on # servers if (showServerMsgs) DisplayData(rawLine); break; case "252": // IRC Operators if (showServerMsgs) DisplayData(rawLine); break; case "253": // Unknown connections if (showServerMsgs) DisplayData(rawLine); break; case "254": // # of Channels if (showServerMsgs) DisplayData(rawLine); break; case "255": // Clients and servers if (showServerMsgs) DisplayData(rawLine); break; case "265": // Current Local Users if (showServerMsgs) DisplayData(rawLine); break; case "266": // Current Global Users if (showServerMsgs) DisplayData(rawLine); break; case "332": // Channel Topic // handleChannelTopic(IncomingDataString); if (showServerMsgs) DisplayData(rawLine); break; case "333": // ?? Something to do with the channel if (showServerMsgs) DisplayData(rawLine); break; case "353": // Who is in the channel buildChannelUsers(IncomingDataString); if (showServerMsgs) DisplayData(rawLine); break; case "366": // End of /NAMES list if (showServerMsgs) DisplayData(rawLine); break; case "372": // MOTD if (showServerMsgs) DisplayData(rawLine); break; case "375": // MOTD if (showServerMsgs) DisplayData(rawLine); break; case "376": // MOTD if (showServerMsgs) DisplayData(rawLine); break; case "402": // No such server reconnect(); break; case "433": // Nickname already in use DisplayData(rawLine); nickNameMain = nickNameBckup; reconnect(); break; case "442": // Not in channel Console.WriteLine("Not currently in that channel."); channelsCurrentlyIn++; break; case "451": // "Register First" DisplayData(rawLine); Thread.Sleep(600); DisplayData("Authorizing now"); handlePingRequest(IncomingDataString); authorize(); writer.WriteLine("JOIN " + channel); writer.Flush(); break; case "462" : // "May not re-register" if (showServerMsgs) DisplayData(rawLine); break; default: // redundency check. without this, it actually shows some odd "PINGS" that the previous // didn't catch. Since i personally don't like to SEE these pings, I'll set this up to // remove them from my eyes. if (IncomingDataString[0] == "PING") { if (showPongReply) DisplayData(rawLine); } // For now, let's log everything so I can later catch it. else DisplayData(rawLine); break; } } } } catch (Exception e) { // Show the exception Console.WriteLine(e.ToString()); connected = false; Thread.Sleep(5000); reconnect(); } } } #endregion #region Methods for Handling Various Messages private void handlePrivMsg(string[] IncomingDataString, string location) { // 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.Remove(0, 1); // We have the message, let's get the nick. string nickname = getNick(IncomingDataString[0].ToString()); modifiedLine = "[" + location + "] <" + nickname + "> "; modifiedLine += message; // Okay, we're ready to display/log it. DisplayData(modifiedLine); // Try to prevent an exception if ((message == "") || (message == " ")) message = "null"; // And now we're ready to see if it's a command the bot will recognize. incomingCommand(message, nickname, location); } private void handleNotice(string[] IncomingDataString) { // 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.Remove(0, 1); // We have the message, let's get the nick. string nickname = getNick(IncomingDataString[0].ToString()); modifiedLine = "[NOTICE] " + "<" + nickname + "> " + message; // Okay, we're ready to display/log it. DisplayData(modifiedLine); // Try to prevent an exception if ((message == "") || (message == " ")) message = "null"; } private void handleQuit(string[] IncomingDataString) { string nickname = getNick(IncomingDataString[0].ToString()); string message = ""; int msgLength = IncomingDataString.Length - 1; for (int i = 3; i <= msgLength; i++) { message = message + IncomingDataString[i] + " "; } DisplayData("* " + nickname + " has quit (" + message + ")"); } private void handleJoin(string[] IncomingDataString) { string nickname = getNick(IncomingDataString[0].ToString()); DisplayData("* " + nickname + " has joined " + IncomingDataString[2].ToString().Replace(":","")); } private void handlePart(string[] IncomingDataString) { // Needs to be expanded to handle multiple channels in the future string nickname = getNick(IncomingDataString[0].ToString()); DisplayData("* " + nickname + " has left " + IncomingDataString[2].ToString()); } private void handleMode(string[] IncomingDataString, string rawLine) { // Needs to be expanded to handle multiple channels in the future string nickname = getNick(IncomingDataString[0].ToString()); if (IncomingDataString.Length == 4) DisplayData("* " + nickname + " sets mode" + IncomingDataString[3].ToString()); if (IncomingDataString.Length == 5) DisplayData("* " + nickname + " sets mode: " + IncomingDataString[3].ToString() + " " + IncomingDataString[4].ToString() + " in " + IncomingDataString[2].ToString()); if (IncomingDataString.Length == 6) DisplayData("* " + nickname + " sets mode: " + IncomingDataString[3].ToString() + " " + IncomingDataString[4].ToString() + " " + IncomingDataString[5].ToString() + " in " + IncomingDataString[2].ToString()); if (IncomingDataString.Length == 7) DisplayData("* " + nickname + " sets mode: " + IncomingDataString[3].ToString() + " " + IncomingDataString[4].ToString() + " " + IncomingDataString[5].ToString() + " in " + IncomingDataString[2].ToString()); if (IncomingDataString.Length == 8) DisplayData("* " + nickname + " sets mode: " + IncomingDataString[3].ToString() + " " + IncomingDataString[4].ToString() + " " + IncomingDataString[5].ToString() + " " + IncomingDataString[6].ToString() + " in " + IncomingDataString[2].ToString()); if ((IncomingDataString.Length > 8) || (IncomingDataString.Length < 4)) DisplayData(rawLine); } private void handleKick(string[] IncomingDataString) { string nickname = getNick(IncomingDataString[0].ToString()); string message = ""; int msgLength = IncomingDataString.Length - 1; for (int i = 4; i <= msgLength; i++) { message = message + IncomingDataString[i] + " "; } message = message.Remove(0, 1); message = message.Trim(); DisplayData("* " + IncomingDataString[3] + " was kicked out of " + IncomingDataString[2] + " by " + nickname + " (" + message + ")"); } private void handleNick(string[] IncomingDataString) { string nickname = getNick(IncomingDataString[0].ToString()); DisplayData("* " + nickname + " is now known as " + IncomingDataString[2].ToString().Replace(":", "")); } private void handleErrors(string[] IncomingDataString, string rawLine) { string typeOfError = IncomingDataString[1] + " " + IncomingDataString[2]; if (typeOfError == ":Closing Link:") { string closingType = IncomingDataString[4]; if (closingType == "(Ping") { connected = false; //Throttle the connection Thread.Sleep(5000); DisplayData(rawLine); // Reconnect reconnect(); } if (closingType == "(Quit)") DisplayData("** Quitting IRC"); } else DisplayData(rawLine); } private void handlePingRequest(string[] IncomingDataString) { string pingHash = ""; for (int i = 1; i < IncomingDataString.Length; i++) { pingHash += IncomingDataString[i] + " "; } writer.WriteLine("PONG " + pingHash); writer.Flush(); if (showPongReply) DisplayData("PONG " + pingHash); } #endregion 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; } #region Channel Users commands private void buildChannelUsers(string[] IncomingDataString) { channelUsers = ""; for (int i = 5; i < IncomingDataString.Length - 1; i++) { string currentName = IncomingDataString[i].ToString(); currentName = currentName.Replace(":", ""); currentName = currentName.Replace("@", ""); currentName = currentName.Replace("+", ""); if (currentName != nickNameMain) channelUsers += currentName + " "; } } private string randomChannelUser() { // Take the current channel user list, split it by spaces. Add them to an array. Randomly pick one from // the array. Return that user. string[] channelUsersArray; channelUsersArray = channelUsers.Split(new char[] { ' ' }); // Get a random user. Thread.Sleep(50); int rnd = randnum.Next((channelUsersArray.Length-1)); user = (string)channelUsersArray[rnd]; return user; } #endregion } }