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