Creating an IRC bot in C#

Creating an IRC bot with some basic functionality may seem like a scary thought, you have to connect to connect to the server, join channels, and respond to commands. It isn’t as bad as you may think, once you learn the basics of the IRC protocol and some the basics of socket programming in C#, in fact it actually becomes extremely simple.

In this tutorial it will be a fairly static bot which responds to commands such as “!say message”, “!join #channel”, “!part”, and “!quit”. Anybody will be able to send these commands and the bot will respond regardless of who sends them. If you wish to add aditional security it is up to you to add that functionality in.

I will be using Visual Studio 2008 in this tutorial to complete the code, if you are a student you can receive a copy free from Microsoft Dreamspark or you can download Microsoft Visual C# Express Edition for free, both of which will work fine for this task.

To begin, you will need to create a new project in Visual Studio (File->New->Project), you will then be presented with a dialog box with different project options, select “Visual C# -> Windows” from the list on the right, and “Console Application” from the right. Then at the bottom give the project a name, a location to store the files, and a solution name. The form should lookk similar to the one below.IRC Bot Project

When you press “OK” you will be presented with a template for you to fill out.

Because we will be working with sockets and streams, we need to make sure the compiler knows where to go and get that code, at the top of your file you will currently have a block of code which looks similar to this

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

We need to add in two additional lines of code so we can use sockets and streams, just add the following two lines to the bottom of the above block

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;

We now have everything we need to connect to IRC and get started, now for some real programming. You will now see a line which starts with “namespace IRCBot” or something similar (based on what you named the project). Inside this block is where our code is going to go.

The first thing we need is a basic data structure to hold the information we need to connect to IRC, including server/port bot nickname and the name of the bot. This is the first thing you need to know, the bot nickname and the name itself are two different things. To hold all of this information I used a struct with the data in, this can be done easily by adding the following inside the namespace block

    struct IRCConfig
    {
            public string server;
            public int port;
            public string nick;
            public string name;

    }

It holds all the information we need inside a simple little block making it easy for us to grab the information when we need it. Now we can begin with the main IRCBot class. To begin with, we will need a class with a few variables, including a TCPClient connection, the IRCConfig, a NetworkStream, a StreamReader, and a StreamWriter

    class IRCBot
    {
            TcpClient IRCConnection = null;
            IRCConfig config;
            NetworkStream ns = null;
            StreamReader sr = null;
            StreamWriter sw = null;

The TCPClient “IRCConnection” will serve as the socket between the computer our bot runs on, and the server, the IRCConfig “config” will hold the information we need about the bot, the NetworkStream “ns” will provide information to the StreamReader “sr” for us to handle, and the StreamWriter “sw” will push data out through it to the IRC Server.

Next, we need to create the constructor of the IRCBot class, inside the constructor we will begin the connection to the server, get the streams running, and then close them straight after, the inner logic will be implemented later.

public IRCBot(IRCConfig config)
{
          this.config = config;
          try
          {
            IRCConnection = new TcpClient(config.server, config.port);
          }
          catch
          {
             Console.WriteLine("Connection Error");
          }

           try
           {
                ns = IRCConnection.GetStream();
                sr = new StreamReader(ns);
                sw = new StreamWriter(ns);
            }
            catch
            {
                Console.WriteLine("Communication error");
            }
            finally
            {
                if (sr != null)
                    sr.Close();
                if (sw != null)
                    sw.Close();
                if (ns != null)
                    ns.Close();
                if (IRCConnection != null)
                    IRCConnection.Close();
            }

        }

Currently, the above code is fairly useless, it opens connections, starts a read and write stream, then closes them straight after, it is also worth noting that it will currently won’t successfully connect with the IRC server – the IRC server expects us to give information to it. First we need to create a few more methods.

The first method we’re going to create is “SendData”, this method will take two parameters, the command and the paramaters for that command, both of which will be of string type.

   public void sendData(string cmd, string param)
        {
            if (param == null)
            {
                sw.WriteLine(cmd);
                sw.Flush();
                Console.WriteLine(cmd);
            }
            else
            {
                sw.WriteLine(cmd + " " + param);
                sw.Flush();
                Console.WriteLine(cmd + " " + param);
            }
        }

You will notice the method checks if the param value is null, if it is it will only send the command part of the method to the server, we also write out the result to the console window (this isn’t a very good way of doing things – this presumes a console window will be active and it won’t be used on a windows form, however for the purpose of this tutorial it will do).

Now with our sendData method in place, we can go back and edit our constructor a little bit, we need to send the USER and NICK commands to the IRC server, this is done by adding the following two lines below where we create our streams in the constructor

                sendData("USER", config.nick + " swivvet.com " + " swivvet.com" + " :" + config.name);
                sendData("NICK", config.nick);

For more information about the commands and the parameters of the above commands, I recommend reading chapter 4 of the IRC protocol documentation. Basically, the server requires a little bit of information about the bot, so we need to give it to them before the connection can go forward.

Now we have enough information for us to connect to the IRC server, however the connection will be dropped pretty quickly, infact it will send the user details and then disconnect, not exactly useful to us in any way. We need to add a final method which will read the messages we receive, and keep our connection alive. I called this “IRCWork” as it does most of the work for us – it takes no parameters and returns no result.

        public void IRCWork()
        {
            string[] ex;
            string data;
            bool shouldRun = true;
            while (shouldRun)
            {
                data = sr.ReadLine();
                Console.WriteLine(data);
                char[] charSeparator = new char[] { ' ' };
                ex = data.Split(charSeparator, 5);

               if (ex[0] == "PING")
                {
                    sendData("PONG", ex[1]);
                }
            }
       }

Ok, so now we have something which will keep our bot connected and responding to any “PING” requests made by the server, if you are unsure what this means Chapter 4.6 of the IRC documentation explains it. What is happening isn’t quite clear. we have a few variables at the top of the method, a string array called “ex” this is used to split up any commands received from the IRC server so we can interpret them and handle them appropriately, a string called “data” which reads each line from the server as it is received, and a boolean variable called “shouldRun” which at the moment is always set to true, which is fine for now, this is used when we add commands such as “!quit”.

We’re almost finished, we just need to make the bot respond to commands and we will be finished! This is probably the most simple part considering we’ve already sent data to the server, we just need to read the commands, and we’ve already done that in the IRCWork method, we read the server to see if we receive a PING command.

First we need to handle the commands with user paramaters added, for example !join #chan, or !say blah blah, !quit quitMessage, secondly we need to add the commands where the bot needs to work out the parameters, for example !part.

Before we even try to interpret a command, we need to make sure enough data was sent, if there was we can see if the data sent was a command, and if it was handle it, if not, do nothing.

                if (ex.Length > 4) //is the command received long enough to be a bot command?
                {
                    string command = ex[3]; //grab the command sent

                     switch (command)
                    {
                        case ":!join":
                            sendData("JOIN", ex[4]); //if the command is !join send the "JOIN" command to the server with the parameters set by the user
                            break;
                        case ":!say":
                            sendData("PRIVMSG", ex[2] + " " + ex[4]); //if the command is !say, send a message to the chan (ex[2]) followed by the actual message (ex[4]).
                            break;
                        case ":!quit":
                            sendData("QUIT", ex[4]); //if the command is quit, send the QUIT command to the server with a quit message
                            shouldRun = false; //turn shouldRun to false - the server will stop sending us data so trying to read it will not work and result in an error. This stops the loop from running and we will close off the connections properly
                            break;
                    }
                }

And finally we need to do commands with no parameters – pretty much the same as the above.

                if (ex.Length > 3)
                {
                    string command = ex[3];

                    switch (command)
                    {
                        case ":!part":
                            sendData("PART", ex[2]);
                            break;
                                                }
                }

With this done, the bot will now handle commands appropriately, now all we need to do is add the IRCWork() method to the constructor, below where we send the user data to the server and we have finished the IRCBot class!

Now all we need to do is run it, when we created the new project in Visual Studio, a class should have already been made called “Program”, inside the method named “Main” for this class, add the following and our bot will run:

            IRCConfig conf = new IRCConfig();
            conf.name = "SwivvetBot";
            conf.nick = "SwivvetBot";
            conf.port = 6667;
            conf.server = "127.0.0.1";
            new IRCBot(conf);
            Console.WriteLine("Bot quit/crashed");
            Console.ReadLine();

Now finally, run the programand our bot should connect, don’t forget to change the parameters in the Main method of the Program class to the server details you want. If you wish to add more commands, I strongly recommend reading the IRC protocol documentation. Remember – the bot is not very secure and it is up to you to expand on it to make it so.

Disclaimer: This tutorial was wrote a long time ago, since then my knowledge of programming has increased a lot, therefore it should be assumed there are some pretty awful implementations in this tutorial.