//////////////////////////////////////////////// ////////////////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/14/2008 // //////////////////////////////////////////////// using System; using System.Net; using System.Net.Sockets; using System.IO; using System.Threading; using System.Collections.Generic; using System.Text; namespace Kon { #region delegates public delegate void CommandReceived(string ircCommand); public delegate void TopicSet(string channel, string ircTopic); public delegate void TopicOwner(string channel, string ircUser, string topicDate); public delegate void NamesList(string userNames); public delegate void ServerMessage(string serverMessage); public delegate void Join(string channel, string ircUser); public delegate void Part(string channel, string ircUser); public delegate void Mode(string channel, string ircUser, string userMode); public delegate void NickChange(string userOldNick, string userNewNick); public delegate void Kick(string channel, string userKicker, string userKicked, string kickMessage); public delegate void Quit(string userQuit, string quitMessage); public delegate void Query(string userName, string message); #endregion class IRC { #region variables // Public variables public bool canQuit = false; // Private variables private int ircPort = 6667; private string ircServer = "bots.esper.net"; private string nickNameMain = "Kon"; private string nickNameBckup = "KonPlushie"; private string ircUser = "USER Kon 1 * :SPLASH !!!"; private string ircRealName = "Kon"; private string channel = "#KonBot"; private string version = "Kon - Version 1.0.6 - created by James Iyouboushi [iyouboushi@gmail.com]"; 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 ircConnection; private NetworkStream ircStream; public static StreamWriter ircWriter; public static StreamWriter loggerWriter; public static StreamReader ircReader; private Thread listenerThread; 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 LOG_FILE = "KonLog.txt"; #endregion #region events public event CommandReceived eventReceiving; public event TopicSet eventTopicSet; public event TopicOwner eventTopicOwner; public event NamesList eventNamesList; public event ServerMessage eventServerMessage; public event Join eventJoin; public event Part eventPart; public event Mode eventMode; public event NickChange eventNickChange; public event Kick eventKick; public event Quit eventQuit; public event Query eventQuery; #endregion #region properties public string IrcServer { get { return this.ircServer; } set { this.ircServer = value; } } public int IrcPort { get { return this.ircPort; } set { this.ircPort = value; } } public string NickNameMain { get { return this.nickNameMain; } set { this.nickNameMain = value; } } public string NickNameBckup { get { return this.nickNameBckup; } set { this.nickNameBckup = value; } } public string IrcUser { get { return this.ircUser; } set { this.ircUser = value; } } public string IrcRealName { get { return this.ircRealName; } set { this.ircRealName = value; } } // Needs to eventually be changed to allow for more than one channel public string Channel { get { return this.channel; } set { this.channel = value; } } public TcpClient IrcConnection { get { return this.ircConnection; } set { this.ircConnection = value; } } public NetworkStream IrcStream { get { return this.ircStream; } set { this.ircStream = value; } } public StreamWriter IrcWriter { get { return ircWriter; } set { ircWriter = value; } } public StreamReader IrcReader { get { return ircReader; } set { ircReader = value; } } #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 public void connect() { // Assign events eventReceiving += new CommandReceived(ircCommandReceived); eventTopicSet += new TopicSet(ircTopicSet); eventTopicOwner += new TopicOwner(ircTopicOwner); eventNamesList += new NamesList(ircNamesList); eventServerMessage += new ServerMessage(ircServerMessage); eventJoin += new Join(ircJoin); eventPart += new Part(ircPart); eventMode += new Mode(ircMode); eventNickChange += new NickChange(ircNickChange); eventKick += new Kick(ircKick); eventQuit += new Quit(ircQuit); eventQuery += new Query(ircQuery); // Connect to the IRC server. ircConnection = new TcpClient(ircServer, ircPort); ircStream = ircConnection.GetStream(); ircReader = new StreamReader(this.ircStream); ircWriter = new StreamWriter(this.ircStream); // If logging is enabled, let's turn it on and indicate what time we started if (logging == true) { loggerWriter = new StreamWriter(LOG_FILE, true); loggerWriter.WriteLine("---------[STARTING: " + DateTime.Now + "]"); } // Authenticate our user ircWriter.WriteLine(String.Format("USER " + ircUser)); ircWriter.Flush(); // Needs to be extended to check if the main nick is in use and use the bckup if necessary ircWriter.WriteLine(String.Format("NICK " + nickNameMain)); ircWriter.Flush(); if (channel != "") { ircWriter.WriteLine(String.Format("JOIN " + channel)); ircWriter.Flush(); } // Start up our incoming data listener IncomingData(); // Turn on the AI stuff. myAI = new AI(); if (google == true) myGoogle = new googleBrain(); if (AIML == true) myAIML = new AIMLbrain(); } #endregion #region quitProgram private void quit_program() { ircWriter.Close(); ircReader.Close(); ircConnection.Close(); if (logging == true) { loggerWriter.WriteLine("---------[QUITTING: " + DateTime.Now + "]"); loggerWriter.Close(); } canQuit = true; } #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() { // Listen for commands string ircCommand; while ((ircCommand = ircReader.ReadLine()) != null) { if (eventReceiving != null) this.eventReceiving(ircCommand); string[] commandParts = new string[ircCommand.Split(' ').Length]; commandParts = ircCommand.Split(' '); if (commandParts[0].Substring(0, 1) == ":") { commandParts[0] = commandParts[0].Remove(0, 1); } if (commandParts[0] == ircServer) { // Server message switch (commandParts[1]) { case "332": ircTopic(commandParts); break; case "333": ircTopicOwner(commandParts); break; case "353": ircNamesList(commandParts); break; case "366": //this.ircEndNamesList(commandParts); break; case "372": //this.ircMOTD(commandParts); break; case "376": //*this.ircEndMOTD(commandParts); break; default: ircServerMessage(commandParts); break; } } else if (commandParts[0] == "PING") { // Server PING, send PONG back ircPing(commandParts); } else { // Normal message string commandAction = commandParts[1]; switch (commandAction) { case "JOIN": ircJoin(commandParts); break; case "PART": ircPart(commandParts); break; case "MODE": ircMode(commandParts); break; case "NICK": ircNickChange(commandParts); break; case "KICK": ircKick(commandParts); break; case "QUIT": ircQuit(commandParts); break; case "PRIVMSG": ircQuery(commandParts); break; } } } // No longer able to read data, let's quit. quit_program(); } #endregion public void getCommand() { } #region Server Messages private void ircTopic(string[] ircCommand) { string channel = ircCommand[3]; string ircTopic = ""; for (int intI = 4; intI < ircCommand.Length; intI++) { ircTopic += ircCommand[intI] + " "; } if (eventTopicSet != null) this.eventTopicSet(channel, ircTopic.Remove(0, 1).Trim()); } private void ircTopicOwner(string[] ircCommand) { string channel = ircCommand[3]; string ircUser = ircCommand[4].Split('!')[0]; string TopicDate = ircCommand[5]; if (eventTopicOwner != null) this.eventTopicOwner(channel, ircUser, TopicDate); } private void ircNamesList(string[] ircCommand) { string UserNames = ""; for (int intI = 5; intI < ircCommand.Length; intI++) { UserNames += ircCommand[intI] + " "; } if (eventNamesList != null) this.eventNamesList(UserNames.Remove(0, 1).Trim()); } private void ircServerMessage(string[] ircCommand) { string serverMessage = ""; for (int intI = 1; intI < ircCommand.Length; intI++) { serverMessage += ircCommand[intI] + " "; } if (eventServerMessage != null) this.eventServerMessage(serverMessage.Trim()); } #endregion #region User Messages private void ircJoin(string[] ircCommand) { string channel = ircCommand[2]; string ircUser = ircCommand[0].Split('!')[0]; if (eventJoin != null) this.eventJoin(channel.Remove(0, 1), ircUser); } private void ircPart(string[] ircCommand) { string channel = ircCommand[2]; string ircUser = ircCommand[0].Split('!')[0]; if (eventPart != null) this.eventPart(channel, ircUser); } /* IrcPart */ private void ircMode(string[] ircCommand) { string channel = ircCommand[2]; string ircUser = ircCommand[0].Split('!')[0]; string userMode = ""; for (int intI = 3; intI < ircCommand.Length; intI++) { userMode += ircCommand[intI] + " "; } if (userMode.Substring(0, 1) == ":") userMode = userMode.Remove(0, 1); if (eventMode != null) this.eventMode(channel, ircUser, userMode.Trim()); } private void ircNickChange(string[] ircCommand) { string userOldNick = ircCommand[0].Split('!')[0]; string userNewNick = ircCommand[2].Remove(0, 1); if (eventNickChange != null) this.eventNickChange(userOldNick, userNewNick); } private void ircKick(string[] ircCommand) { string userKicker = ircCommand[0].Split('!')[0]; string userKicked = ircCommand[3]; string channel = ircCommand[2]; string kickMessage = ""; for (int intI = 4; intI < ircCommand.Length; intI++) { kickMessage += ircCommand[intI] + " "; } if (eventKick != null) this.eventKick(channel, userKicker, userKicked, kickMessage.Remove(0, 1).Trim()); } private void ircQuit(string[] ircCommand) { string userQuit = ircCommand[0].Split('!')[0]; string quitMessage = ""; for (int intI = 2; intI < ircCommand.Length; intI++) { quitMessage += ircCommand[intI] + " "; } if (quitMessage == "") quitMessage = version; if (eventQuit != null) this.eventQuit(userQuit, quitMessage.Remove(0, 1).Trim()); } private void ircQuery(string[] ircCommand) { string ircUser = ircCommand[0].Split('!')[0]; string message = ""; for (int intI = 3; intI < ircCommand.Length; intI++) { message += ircCommand[intI] + " "; } // Let's chop off the ":" that starts each message, leaving us with the true message. message = message.TrimStart(':'); if (eventQuery != null) this.eventQuery(ircUser, message); } #endregion #region PingControl // New and improved Ping command. Will send a PONG to keep the bot connected. private void ircPing(string[] ircCommand) { string pingHash = ""; for (int intI = 1; intI < ircCommand.Length; intI++) { pingHash += ircCommand[intI] + " "; } ircWriter.WriteLine("PONG " + pingHash); ircWriter.Flush(); if (showPongReply) displayData("PONG " + pingHash); } #endregion #region logger //////////////////////////////////////////////// ///////////////////LOGGER/////////////////////// //////////////////////////////////////////////// // Dump the lines into a log file for later // // review. // //////////////////////////////////////////////// private void LogFile(string line) { if (logging == true) { loggerWriter.WriteLine(line); loggerWriter.Flush(); return; } } #endregion #region displayData 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(50); Console.WriteLine(rawLine); } #endregion private void ircCommandReceived(string ircCommand) { } private void ircTopicSet(string IrcChan, string IrcTopic) { } private void ircTopicOwner(string IrcChan, string IrcUser, string TopicDate) { } private void ircNamesList(string userNames) { displayData("Names List: " + userNames); } private void ircServerMessage(string ServerMessage) { displayData(ServerMessage); } private void ircJoin(string chan, string ircUser) { displayData(ircUser + " joins " + chan); ircWriter.Flush(); } private void ircPart(string IrcChan, string IrcUser) { } private void ircMode(string IrcChan, string IrcUser, string UserMode) { } private void ircNickChange(string UserOldNick, string UserNewNick) { } private void ircKick(string IrcChannel, string UserKicker, string UserKicked, string KickMessage) { } private void ircQuit(string userQuit, string quitMessage) { displayData(userQuit + " has quit IRC (" + quitMessage + ")"); } private void ircQuery(string nickname, string message) { 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); } #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 ircWriter.WriteLine("PRIVMSG " + channel + " :" + message); // Log it, if the logging option is on //LogFile(">> " + channel + " : " + message); displayData(">> " + channel + " : " + message); // Flush it ircWriter.Flush(); } #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!"); } if (command == "!QUIT") { // For now, the nicks allowed to use this will be hardcoded if (nick == "Iyouboushi") { ircWriter.WriteLine("QUIT " + ":" + version); ircWriter.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) || (command == "KON:")) { 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); 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); 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); 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); } 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 } }