//////////////////////////////////////////////// ////////////////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. // //////////////////////////////////////////////// // The "LB Brain" was based on a chatterbot by// // BishounenNightBird of Esper.net called "LB"// // ("Language Bot"). While this brain does // // not follow LB exactly, he still inspired me// // and I want to thank him. // //////////////////////////////////////////////// // "Kon" is an ongoing project started by // // James Iyouboushi. // // Emails: jmp1139@my.gulfcoast.edu // // Iyouboushi@gmail.com // //////////////////////////////////////////////// // This file was last updated on: 12/02/2009 // //////////////////////////////////////////////// using System; using System.Collections; using System.Text; using System.Text.RegularExpressions; using System.IO; using System.Threading; namespace Kon { class LBbrain { #region variables public static StreamWriter brain; public bool brainInUse = false; private string BRAIN_FILE = "lb-question.brain"; // default to questions, but it'll change private const string BRAIN_FILE_QUESTIONS = "lb-question.brain"; private const string BRAIN_FILE_STATEMENTS = "lb-statements.brain"; private Random randnum = new Random(); private string lastLine = ""; private string previousSearch = ""; private bool shortDepth = false; private bool oldStyleTopicSearch = false; private string[] commonWords = { "THERE", "THAT", "THIS", "HERE", "YOUR", "THEY", "IT'S", "OTHER", "HAVE", "STILL", "VERY", "THEIR" }; private const String regExPattern = @".*?(?.*?)(?.*?).*?"; #endregion public LBbrain() { } #region Add To Brain public void addToBrain(string conversation, string nick) { // These are never going to change and can be filtered here. if (((conversation.StartsWith("http:") || (conversation.StartsWith("https:")) || (conversation.StartsWith("www."))))) return; // We don't want to add URLs. if (conversation.StartsWith("!")) return; // if (conversation.StartsWith("")) // return; if (conversation.StartsWith("[")) return; if (conversation.StartsWith("<")) return; if (conversation.StartsWith(">")) return; if (conversation.StartsWith("#")) return; // We don't want to capture stuff that's supposed to be for or from LB either. if ((conversation.StartsWith("lb:") || (conversation.StartsWith("LB:")))) return; if (nick.ToUpper() == "LB4") return; if (nick.ToUpper() == "LB5") return; if (nick.ToUpper() == "LB6") return; if ((conversation.ToUpper().StartsWith("PING")) || (conversation.ToUpper().StartsWith("VERSION"))) return; // we don't want stuff that's too short. if (conversation.Length < 5) return; // Let's start filtering out some stuff. Filter filter = new Filter(); // This method will call upon a bit of lengthy code to replace all instances of "kon" with "UNNAMED_USER" conversation = filter.replaceKonWithUNNAMED_USER(conversation); // This method will call upon a bit of lengthy code to replace words in the conversation that are urls conversation = filter.replaceHTMLWithNull(conversation); // filter emoticons conversation = filter.replaceWithCustom_emoticons(conversation); // filter image extensions. conversation = filter.replaceWithNull_ImageExt(conversation); // filter out some things that I personally don't want in it. conversation = filter.replace_custom(conversation); // filter out the HTML and web-based stuff conversation = filter.replaceWithNull_HTML(conversation); conversation = filter.replaceWithNull_webbased(conversation); // Finally, we don't want the bot to have a bad potty mouth. conversation = filter.replaceWithNull_swears(conversation); // Tidy up some stuff. conversation = conversation.Trim(); // Well, that's good enough for now. Let's make sure it's not null and add it to the brain! if (conversation != "") { // Determine if what is being said is a question (ends with a ?) or is a statement (ends with anything else) // and write it to the appropriate brain file. if (conversation.EndsWith("?")) BRAIN_FILE = BRAIN_FILE_QUESTIONS; else BRAIN_FILE = BRAIN_FILE_STATEMENTS; // Let's get a list of all the starting sentences.. will be used later to filter out repeat sentence starters. string line; ArrayList startingLines = new ArrayList(); using (StreamReader brainFile = File.OpenText(BRAIN_FILE)) { while ((line = brainFile.ReadLine()) != null) { if (line.StartsWith("START_SENTENCE")) startingLines.Add(line.ToUpper()); } brainFile.Close(); } // Let's open the brain file and get ready to add stuff. brain = new StreamWriter(BRAIN_FILE, true); string[] messageArray; messageArray = conversation.Split(new char[] { ' ' }); int lastWordPos = (messageArray.Length - 1); // Now we're going to write the brain file. In 1.0.7 and 1.0.8 the LB Brain used a "short depth" structure // but I feel that this just isn't sufficient in larger brain files. Thus I've now allowed a // long depth. If you'd like to use the old shorter depth set the bool shortDepth to true. // Otherwise, it'll try to use the longer depth when it can. if (shortDepth) { int j = 1; for (int i = 0; i < messageArray.Length; i++) { if (i == 0) { string startingLine = ""; if (lastWordPos == 0) startingLine = "START_SENTENCE " + messageArray[i]; if (lastWordPos > 0) startingLine = "START_SENTENCE " + messageArray[i] + " " + messageArray[i + 1]; if (!startingLines.Contains(startingLine.ToUpper())) brain.WriteLine(startingLine); brain.Flush(); } else { if (i == lastWordPos) brain.WriteLine(messageArray[i - 1] + " " + messageArray[i] + " END_SENTENCE"); else { brain.WriteLine(messageArray[i - j] + " " + messageArray[i] + " " + messageArray[i + 1]); } brain.Flush(); } } j++; } else { int j = 1; for (int i = 0; i < messageArray.Length; i++) { if (i == 0) { string startingLine = ""; if (lastWordPos == 0) startingLine = "START_SENTENCE " + messageArray[i]; if ((lastWordPos > 0) && (lastWordPos < 2)) startingLine = "START_SENTENCE " + messageArray[i] + " " + messageArray[i + 1]; if (lastWordPos >= 2) startingLine = "START_SENTENCE " + messageArray[i] + " " + messageArray[i + 1] + " " + messageArray[i + 2]; if (!startingLines.Contains(startingLine.ToUpper())) brain.WriteLine(startingLine); brain.Flush(); } else { if (i == lastWordPos) brain.WriteLine(messageArray[i - 1] + " " + messageArray[i] + " END_SENTENCE"); else { if ((i + 2) <= lastWordPos) brain.WriteLine(messageArray[i - j] + " " + messageArray[i] + " " + messageArray[i + 1] + " " + messageArray[i + 2]); else brain.WriteLine(messageArray[i - j] + " " + messageArray[i] + " " + messageArray[i + 1]); } brain.Flush(); } } j++; } } brain.Close(); } #endregion #region Pull From Brain public String pullFromBrain(string conversation, bool topic) { // Does the bot have a brain? If not, we can't proceede without crashing. if (!File.Exists(BRAIN_FILE_QUESTIONS)) { Console.WriteLine(BRAIN_FILE_QUESTIONS + " does not exist."); return "I need to see some conversations before I can reply using this brain!"; } if (!File.Exists(BRAIN_FILE_STATEMENTS)) { Console.WriteLine(BRAIN_FILE_STATEMENTS + " does not exist."); return "I need to see some conversations before I can reply using this brain!"; } // Okay, it does. Continue. brainInUse = true; conversation = conversation.Trim(); String randomLine = ""; String convoStart; String lastCharacter; if (topic) convoStart = getTopicFromConvo(conversation); else convoStart = ""; // The idea here is if the conversation to the bot ends with a question the bot will want to give a statement. // If the conversation ends with anything else, then it'll be a random choice. try { lastCharacter = conversation.Substring(conversation.Length - 1, 1); } catch (Exception e) { lastCharacter = "."; } if (lastCharacter == "?") BRAIN_FILE = BRAIN_FILE_STATEMENTS; else { // Random Choice Thread.Sleep(50); int rnd = randnum.Next(2); if (rnd == 1) BRAIN_FILE = BRAIN_FILE_STATEMENTS; else BRAIN_FILE = BRAIN_FILE_QUESTIONS; } // If a topic was chosen we need to build a sentence AROUND the topic. We shouldn't start the sentence // with the topic. Although that'll work, it isn't always coherent. So let's try to find a word or // two before it. if (convoStart != "") randomLine = getTopicStarter(convoStart); // Let's grab the start of our sentence now. randomLine = getStartingSentence(randomLine); string searchWord = ""; // While the searchWord is not "END_SENTENCE" let's continue to build the sentence. do { searchWord = getLastWord(randomLine); if (searchWord != "") randomLine += getLine(searchWord); ///Console.WriteLine("current line: " + randomLine); } while ((searchWord != "END_SENTENCE END_SENTENCE") && (randomLine.Length <= 800)); // Last step: clean up. // remove "START_SENTENCE" and "END_SENTENCE", trim up the space and replace the emoticons. randomLine = randomLine.Replace("START_SENTENCE", ""); randomLine = randomLine.Replace("END_SENTENCE", ""); randomLine = randomLine.Replace(" ", ""); randomLine = randomLine.Trim(); // Let's start cleaning up this mess of a line. Filter filter = new Filter(); // Let's remove some random things. if (randomLine.EndsWith("\"") && (!randomLine.StartsWith("\""))) randomLine = randomLine.Replace("\"", ""); if (randomLine.StartsWith("\"") && (!randomLine.EndsWith("\""))) randomLine = randomLine.Replace("\"", ""); if (randomLine.EndsWith("") && (!randomLine.StartsWith("ACTION"))) randomLine = randomLine.Replace("", ""); if (randomLine.EndsWith(")") && (!randomLine.StartsWith("("))) randomLine = randomLine.Replace(")", ""); if (randomLine.StartsWith("(") && (!randomLine.EndsWith(")"))) randomLine = randomLine.Replace("(", ""); // Let's try to add some punctuation string lastReplyCharacter = randomLine.Substring(randomLine.Length-1, 1); if ((((((lastReplyCharacter != ".") && (lastReplyCharacter != ",") && ((lastReplyCharacter != "?") && ((lastReplyCharacter != "!") && ((lastReplyCharacter != ":") && (lastReplyCharacter != ";"))))))))) { if ((!randomLine.EndsWith("RANDOM_EMOTICON_HAPPY") && (!randomLine.EndsWith("RANDOM_EMOTICON_SAD")))) randomLine = randomLine + filter.randomPunctuation(); } // Replace an emoticon with a random one. randomLine = randomLine.Replace("RANDOM_EMOTICON_HAPPY", filter.randomEmoticon_happy()); randomLine = randomLine.Replace("RANDOM_EMOTICON_SAD", filter.randomEmoticon_sad()); // Finally, we have our response. Let's return it. brainInUse = false; return randomLine; } #endregion #region getTopicFromConvo private String getTopicFromConvo(string conversation) { string topic = ""; string[] conversationBroken; ArrayList topics = new ArrayList(); conversationBroken = conversation.Split(new char[] { ' ' }); int numberOfWords = conversationBroken.Length - 1; if (oldStyleTopicSearch) { // NOTE: This function is not gramatically correct. It doesn't find the REAL topic of the sentence. // Instead, it tries to throw away the word "there" and very small words, leaving behind less common words. for (int i = 0; i <= numberOfWords; i++) { if (conversationBroken[i].ToString().Length > 4) { if (conversationBroken[i].ToString() != "there") topics.Add(conversationBroken[i].ToString()); } } } else { // With the new code, it will attempt to filter out common words and pick a random topic. string currentWord; int strNumber; int strIndex = 0; for (int i = 0; i <= numberOfWords; i++) { currentWord = conversationBroken[i].ToString(); for (strNumber = 0; strNumber < commonWords.Length; strNumber++) { strIndex = commonWords[strNumber].IndexOf(currentWord.ToUpper()); // If it finds the word in the array, we need to break and try the next word. if (strIndex != -1) break; // Else, we need to try to add it to the list of topics.. else { if (currentWord.Length > 4) { topics.Add(currentWord); break; } } } } } // If the ArrayList isn't null let's pick a topic. if (topics.Count > 0) { Thread.Sleep(50); int rnd = randnum.Next(topics.Count); topic = (string)topics[rnd]; } // Then strip the "topic" of all punctuation, just in case. topic = topic.Replace(".", ""); topic = topic.Replace(",", ""); topic = topic.Replace("!", ""); topic = topic.Replace("?", ""); // Return the "topic" return topic; } #endregion #region getStartingSentence() private String getStartingSentence(string convoStart) { ArrayList startingLines = new ArrayList(); string line = ""; int numberOfLines; if (convoStart != "") { // If a topic has been found then we need to build a sentence around that topic. using (StreamReader brainFile = File.OpenText(BRAIN_FILE)) { while ((line = brainFile.ReadLine()) != null) { string upperLine = line.ToUpper(); if (upperLine.StartsWith(convoStart.ToUpper())) startingLines.Add(line); } brainFile.Close(); } } // If no starting sentence was found that relates to the topic we need to pick a random one. if (startingLines.Count == 0) { using (StreamReader brainFile = File.OpenText(BRAIN_FILE)) { while ((line = brainFile.ReadLine()) != null) { if (line.StartsWith("START_SENTENCE")) startingLines.Add(line); } brainFile.Close(); } } numberOfLines = startingLines.Count; // Now we need to randomly pick a line and pull a line. Thread.Sleep(50); int rnd = randnum.Next(numberOfLines); string randomStart = (string)startingLines[rnd]; return randomStart; } #endregion #region getLastWord(randomLine) private String getLastWord(string randomLine) { string[] messageArray; messageArray = randomLine.Split(new char[] { ' ' }); int lastWord = (messageArray.Length - 1); if (lastWord >= 1) return messageArray[lastWord - 1] + " " + messageArray[lastWord]; else return messageArray[lastWord]; } #endregion #region getTopicStarter private String getTopicStarter(string currentLine) { if (currentLine != "") { ArrayList startingLines = new ArrayList(); string line = ""; int numberOfLines; // TO DO: make it search the entire line (except for the start) for the word. using (StreamReader brainFile = File.OpenText(BRAIN_FILE)) { Filter filter = new Filter(); string lineWithoutPunct = ""; while ((line = brainFile.ReadLine()) != null) { lineWithoutPunct = filter.replaceWithNull_Punctuation(line); if (lineWithoutPunct.ToUpper().EndsWith(currentLine.ToUpper())) startingLines.Add(line); } brainFile.Close(); } numberOfLines = startingLines.Count; // If we find no lines, then let's attempt to find a line that STARTS with it.. if (numberOfLines == 0) currentLine = getStartingSentence(currentLine); if (numberOfLines > 0) { // Now we need to randomly pick a line and pull a line. Thread.Sleep(50); int rnd = randnum.Next(numberOfLines); currentLine = (string)startingLines[rnd]; } } return currentLine; } #endregion #region getLine(string searchWord) private String getLine(string searchWord) { using (StreamReader brainFile = File.OpenText(BRAIN_FILE)) { // This will add all of the lines in the brain file to an array so we can use them ArrayList lines = new ArrayList(); string line; int numberOfLines; searchWord = searchWord.Replace("START_SENTENCE", ""); searchWord = searchWord.Trim(); while ((line = brainFile.ReadLine()) != null) { if (line.StartsWith(searchWord)) lines.Add(line); } numberOfLines = lines.Count; if (numberOfLines == 0) return " END_SENTENCE"; if (previousSearch == searchWord) return " END_SENTENCE"; // Now we need to randomly pick a line and pull a line. Thread.Sleep(50); int rnd = randnum.Next(numberOfLines); string randomLine = (string)lines[rnd]; lastLine = randomLine; // If I don't do this it'll repeat the word twice in the sentence. randomLine = randomLine.Replace(searchWord, ""); previousSearch = searchWord; brainFile.Close(); return randomLine; } } #endregion #region getBrainLength() public int getBrainLength() { int questionCount = 0; int statementCount = 0; int totalCount = 0; using (StreamReader brainFile = File.OpenText(BRAIN_FILE_QUESTIONS)) { // This will add all of the lines in the brain file to an array so we can count them ArrayList lines = new ArrayList(); string line; while ((line = brainFile.ReadLine()) != null) lines.Add(line); brainFile.Close(); questionCount = lines.Count; } using (StreamReader brainFile = File.OpenText(BRAIN_FILE_STATEMENTS)) { // This will add all of the lines in the brain file to an array so we can count them ArrayList lines = new ArrayList(); string line; while ((line = brainFile.ReadLine()) != null) lines.Add(line); brainFile.Close(); statementCount = lines.Count; } totalCount = statementCount + questionCount; return totalCount; } #endregion } }