////////////////////////////////////////////////
////////////////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
}
}