Code Project: Build an IRC bot

Code

Here at TuxRadar we love quick little programming projects, and hope you do too. The word 'projects' is important here: we're not going to dwell on theory or mundane technical gubbins, but instead look at making cool things - after all, programming is the most fun when you're actually making things rather than spending hours learning about tedious loop constructs!

In this tutorial we're going to produce an IRC bot written in Perl. If you're an old-school internetter, you'll probably have used IRC before; if not, see the Hang on, what is IRC? box explaining the basics overpage. In a nutshell, IRC is a real-time chat protocol, commonly used in open source projects for interaction between developers. It's simple, fast and easy to understand - and best of all, it lets you create virtual participants in the conversation.

That's what we're going to do here: write a 'robot' that joins in an IRC conversation, and with which you can converse to get information about a machine. Say, for instance, that you're at work or on holiday, and want to keep tabs on a system back home (or a co-located server). Our IRC bot will sit quietly in a channel, waiting for you to come along and ask it questions about its uptime and load level.

There are a myriad of system monitoring tools out there, but they tend to be all-or-nothing programs that flood your inbox with unnecessary minutiae about a machine. Our bot makes things much simpler: if you want to find out how much disk space is left on your machine, or if it's running low on RAM, you can hop onto IRC and communicate with the bot instantly (in private messages). Most importantly, we'll restrict it so that only you can get information back from it!

A quick Perl primer

Before we get cracking, here's a quick run-through of Perl basics. Right now, you may be thinking: "Why on earth would you write an IRC bot in Perl?" Fair question - Perl is a scripting language primarily geared towards working with text. But it's also supplemented by a vast array of add-on modules, one of which makes it supremely easy to interface with IRC servers.

Perl is pre-installed with nigh-on every distro today, but if you don't have it on your system, you'll almost certainly find it in your package manager. The language is interpreted, and offers object orientation and dynamic typing (that is, variables don't need to be pre-declared nor assigned to a specific data type). You don't need to enter the following code snippets - just see how they work. Printing strings is a cinch:

print "Blimey!";

In C fashion, statements must end with semicolons (;). Variables start with dollar signs:

$num = 25;
$string = "cheeseburgers";
print "I've eaten $num $string\n";

You can see that Perl doesn't worry about variable types - it works them out on-the-fly. The \n character prints a newline, as in C. Arrays are defined with the @ character:

@distros = ("Ubuntu", "Fedora", "SUSE");
print $distros[0];

This prints "Ubuntu" to the screen. As with many languages, the first element in an array is given the number 0. A useful command for arrays is shift, which removes the first element from an array and places it into a variable:

@distros = ("Ubuntu", "Fedora", "SUSE");
$foo = shift(@distros);
print "$foo\n";
$foo = shift(@distros);
print "$foo\n";

Here, we set up an array containing three strings, then shift the first element into the $foo variable. We print it, and then do another shift. So the output is "Ubuntu" followed by "Fedora" - and at the end, the only element left in the array is "SUSE". An enhanced version of an array is a 'dictionary' (aka 'hash'), denoted with a percentage (%) sign:

%weight = ("Mike" => 8, "Paul" => 12, "Nick" => 25);

This associates a list of words with values. For instance, if you wanted to print out the weight of the dictionary entry 'Nick':

print $weight{"Nick"};

That will print "25". if constructs are the same as in most other languages:

$x = 64;

if ($x == 64) {
        print "Yep, x is 64!\n";
}

And finally, subroutines (functions) are declared simply with sub:

sub saystuff {
	print "This is saystuff!\n";
}

saystuff();

So, those are the nuts-and-bolts of Perl programming - everything we need to start writing our IRC bot!

Hang on, what's IRC?

IRC (Internet Relay Chat) is a real-time text-based discussion system that predates the World Wide Web, having been invented in 1988. IRC is an open, plain-text protocol, and anyone can write a front-end program to the system. IRC discussions take place on separate networks; some are oriented towards open source (irc.freenode.net), whereas others are focused on gaming.

Each IRC network is made up of multiple servers that link together - you can choose a server geographically close to you, or just pick any one at random. Either way, you'll be part of the same network. For instance, to start chatting on the Freenode network, get an IRC client (eg X-Chat), fire it up and enter:

/server irc.freenode.net

(IRC commands begin with forward slashes.) You can then set your nickname and register it with a password using:

/nick 
/msg nickserv register 

Then, you can join a 'channel' (like a specific chat room) with:

/join #linuxformat

(Channel names begin with hash marks.) Then you can start chatting with everyone in the channel, or talk privately with individuals (eg in X-Chat, right-click their names and choose 'Open dialog').

Setting up

We're going to use a special add-on module for Perl called Net::IRC. This provides an interface to the IRC protocol, which means we don't have to write reams of code to mess around with sockets and connections. Instead, we can tell Net::IRC to sort out all the networking business, leaving us free to focus on making the bot do what we want.

You may be able to find Net::IRC in your distro's package manager; it will be called perl-irc or net.pm or something similar. If you can't find it, no problem - it's a doddle to build from source. Go to http://search.cpan.org/dist/Net-IRC/ and grab Net-IRC-0.75.tar.gz.

Extract the tarball, switch into the resulting directory and enter:

perl Makefile.PL
make
make install

You will have to enter the last command as root. This will install the Net::IRC module into your systemwide Perl installation, so the program we're going to write can use it!

Next, we need two user accounts on IRC. The first is our own, for chatting, and the second is for our bot. (If you're a regular IRCer, you'll already have your own username.) This is an important step because many IRC servers, in an effort to combat spam messages, now require usernames to be registered before they can send private messages to other chatters.

Log on to IRC and switch to your normal username. If you haven't registered it yet, you'll want to enter something like:

/msg nickserv register <password>

Replace <password> with whatever you choose. These examples are for the Freenode servers; if you're on a different network, the commands may vary. Now your username is registered on the IRC server - that is, nobody can come along and steal it forever. You can tell the IRC server who you are with:

/msg nickserv identify <password>

We will also need to set up a registered username for our soon-to-be-born IRC bot. Switch your username and assign a password like this:

/nick UltraCoolLXFBot
/msg nickserv register thisismypassword

Of course, you can set the nickname and password to whatever you want, but remember them - we'll use them in the bot's Perl code! So now we have two registered usernames: ours, and the one for the bot.

Show me the code

Now we're ready to run the bot. Here's the code: you won't want to type it in by hand, so download it from here. Before you run it, though, read through the code and then the following description...

use Net::IRC;

$server = 'irc.freenode.net';
$channel = '#blergh';
$botnick = 'MegaMikeBot';
$password = 'foobar';
$botadmin = 'M-Saunders';

$irc = new Net::IRC;

$conn = $irc->newconn(Nick => $botnick,
     Server => $server, Port => 6667);

$conn->add_global_handler('376', \&on_connect);
$conn->add_global_handler('disconnect', \&on_disconnect);
$conn->add_global_handler('kick', \&on_kick);
$conn->add_global_handler('msg', \&on_msg);

$irc->start;

sub on_connect {
     $self = shift;
     $self->privmsg('nickserv', "identify $password");
     $self->join($channel);
     $self->privmsg($channel, "Lo! I'm just a silent bot.");
}

sub on_disconnect {
     $self = shift;
     $self->connect();
}

sub on_kick {
     $self = shift;
     $self->join($channel);
     $self->privmsg($channel, "Please don't kick me!");
}

sub on_msg {
     $self = shift;
     $self->privmsg($botadmin, `uptime`);
}

Let's step through this. The first line simply tells Perl that we want to use the Net::IRC module that we installed earlier. After that, we have five lines declaring configuration variables - you'll need to change these lines when running the script.

We define the IRC server to which our bot will connect, and a channel that the bot will join. Then we set the bot's username (aka nickname), and the password it will use to identify itself to the IRC server. Finally, we define the nickname of the user that the bot will talk to - so this will be your normal IRC nickname.

After this, the $irc = new Net::IRC; line declares a new IRC bot object called, imaginatively, $irc. We then set up a $conn object which creates a connection to an IRC server, using the information we provided before. So, at this stage, our bot has the information it needs, and is ready to connect to the IRC server.

But there's one more thing we need to do first: attach some bot actions to messages from the IRC server. Our bot isn't going to be completely silent, so it needs to know which messages it should respond to. The four add_global_handler lines deal with this - they tell our bot, "If you receive message A, jump to subroutine B".

For instance, the first line in this chunk of code says, "If we receive message 376, call the on_connect function". Message 376 is a special code that the IRC server sends back during the connection phase - it indicates that the server has delivered the normal connection information, and is now ready to accept commands.

If you're a regular IRCer, you'll know that when connecting to a server, you see reams of information about the server status, community messages and so forth. The 376 code indicates that the welcome messages have finished.

Then we add handlers for three more messages: disconnection, kicking, and private message. Respectively, these tell our bot to call certain functions if it disconnects, or is kicked from a channel, or receives a private message. IRC bots can respond to a much wider range of messages than these, but in our case, we only need to handle a few specific messages.

Next up we have $irc->start; which kicks our bot into action. It knows which server and channel it's going to use, and which subroutines to call when it receives certain messages. So the bot connects, and after a few seconds, it receives the 376 message mentioned previously. This triggers the on_connect function.

In this function, you'll see shift as described in our Perl primer section. You don't need to worry about this, but here's a nanosummary: arguments are passed to functions in an array, and the first argument is the calling object. So we pop the first argument, the calling object, into $self so that we can call its own routines. We don't have to do it this way, but it's a great help in order to keep code modular.

So, when the bot connects, we send a privmsg (private message) to nickserv, which is the nickname handler on most IRC networks. We provide the bot's password to authenticate it on the server. Then we join our specified channel, and send out a little message stating that we're going to be quiet - not flood the channel with random rubbish!

With that done, the bot will sit silently unless it is disconnected or kicked, in which case, it will call the following two functions and reconnect or print a puppy-eyed "Please don't kick me!" message.

The final function, on_msg, is the most important here. This is called when the bot receives a private message - that is, a message in a separate dialog, not something in the main channel. If the bot receives any kind of message, it sends the output of `uptime` to the $botadmin user, which is your normal nickname.

Note the backtick (`) characters around `uptime`: these tell Perl to execute the specific command and return the output as a string. So the privmsg line sends a private message containing the output of uptime to our normal user. You can find the backtick key below the Escape key on most PC keyboards.

The first version of our bot: when we speak to it, it provides uptime information. But it could do more...

The first version of our bot: when we speak to it, it provides uptime information. But it could do more...

Keep on running

Let's run the bot now. Open up bot1.pl and change the five configuration variables at the top to match your setup - ie give it the correct bot nickname and password you registered earlier, and set the $botadmin name to be your normal user account. Now fire up your IRC client, identify yourself to the IRC server and join the channel you specified for the bot. Then run the bot with:

perl bot1.pl

After a few seconds, the bot will connect, identify itself with the IRC server, and join the channel you specified. You should see it appear with its little welcome message! Now start a private dialog (privmsg) with it, and type in whatever you want - the bot will respond with the output of uptime. Excellent! You can now run the bot on any machine you choose, and keep track of that machine's uptime (and load level) simply by logging on to IRC and talking to it.

Except, of course, it's pretty limited at this stage. Whenever anyone talks to the bot in a private message, it'll send the output of uptime to you. Also, it's limited to that single uptime command - fair enough if that's all you want to monitor, but it could do so much more.

If everything's running OK, you can expand the last function (on_msg) with more features, like this (the full code is available here):

sub on_msg {
     $self = shift;
     $event = shift;

     if ($event->nick eq $botadmin) {
          foreach $arg ($event->args) {
               if ($arg =~ m/uptime/) {
                    @output = `uptime`;
                    foreach $line (@output) {
                         $self->privmsg($botadmin, $line);
                    }
               }
               if ($arg =~ m/df/) {
                    @output = `df`;
                    foreach $line (@output) {
                         $self->privmsg($botadmin, $line);
                    }
               }
          }
     }
}

This time, on the third line of the function (if), we check to see who is sending a private message to the bot. We only continue if $botadmin is talking to the bot - in other words, if it's us! Then we look into the actual text that we sent to the box. This is provided in the $event->args array, which contains a list of text we sent to the bot.

We cycle through the text, and check to see if it matches a command we want to execute. That's what the if ($arg =~ m/uptime/) line does: it says "If one of the lines of text sent to the bot contains the string uptime, do the following..." The m/uptime/ bit is a regular expression, checking to see if the text matches uptime.

If it does, we call uptime on the system and get its resulting lines of text into our @output array. Then we cycle through the array and print every line of text contained therein.

Now, uptime only returns one line, so it seems futile to cycle through every possible line of output. But you'll notice that the next code chunk checks to see if we sent df to the bot - and df produces several lines of output. So if the bot gets df in a private message from us, it accumulates the output and sends it back, line-by-line.

Now you can add as many commands as you want to the bot, by copying and pasting the if code chunks, changing text that it checks for and the command that it executes. For example, you may add an if ($arg =~ m/ps/) section which gets a process list via @output = `ps`;. Or any other command you want - the bot is your oyster!

You now have an extensible IRC bot that responds to your commands, and only responds to you; it won't spew out information to random strangers (unless they log in with your nickname and know your password). Naturally, you don't want to give the bot power to power-down your machine, but you can use it to get lots of feedback on a machine.

This gives you heaps of flexibility: you can run the bot on your web server, and if you suspect anything is up with the machine, just hop onto IRC and have a quick natter with the bot. Or you could run the bot on your home desktop machine, and communicate with it from work, telling it to run/stop certain processes when necessary. The possibilities are infinite - and all you need is access to IRC!

Bot 2.0 now lets us give it specific commands to execute, and it won't let anybody else talk to it!

Bot 2.0 now lets us give it specific commands to execute, and it won't let anybody else talk to it!

First published in Linux Format

First published in Linux Format magazine

You should follow us on Identi.ca or Twitter


Your comments

where is everybody on IRC?

I hung around all day and posted a couple of comments engendering 1 reply
/me crawls off in a huff ;-)

IRC

You'll find most of the action goes on late in the evening ...

im having a bit of a problem

im having a bit of a problem with the Net::IRC, im reading too go into the directory but then it says "enter:

perl Makefile.PL
make
make install" and i got no idea where or how i enter this... help?

Use CPAN to install

Why not just use the cpan to install the module directly from the command line:
cpan Net::IRC

Extra features

Hi just wondering how it would be possible to make it so the bot posts a message every 20mins to the channel like an advertisement or something. I tried multi threading with an infinte loop but that didn't work dnt think it's compatible with net::irc
Also how would it be possible to check that the bot admin is in the channel and if they aren't post reply's to a different user.

More Modern Modules

No decent Perl programmer is going to use something like Net::IRC these days. POE::Component::IRC makes life much easier. And built on top of that are Bot::BasicBot and Bot::BasicBot::Pluggable - both of which make it possible to write a useful bot in just a few lines of code.

It's not surprising that people think that Perl is a dead language when magazines like LXF promote ten year old methods for doing stuff.

Bot Commands

Ey i Want to know hot To Configurate the Bot to Respond to Certain Commands i have Tested Tons of Configurations but im a newbie so All were Syntax Errors :P

i Want something Like

<Crazy> !Yo
<Bot> Yo Crazy

Can you help me out ???

hehHow To Join

How can the Bot Join channels That have a key ????????'

Give the full fucking code =S

I cant open it, what the fuck?

*facepalm*

*facepalm*

what

The code seems to break at line 21 after perl bot1.pl is executed because it can not locate Net/IRC.pm in @INC. What there is no @INC in vista unless it means something else. Could some one help explain this to me?

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

CAPTCHA
We can't accept links (unless you obfuscate them). You also need to negotiate the following CAPTCHA...

Username:   Password:
Create Account | About TuxRadar