Writing an IRC Bot with JavaScript/Node.js and coffea


I am one of the main developers of coffea and, as you probably figured, I don’t write much (non-code). This is my first post on medium! Personally, I love getting started with a new library by following a tutorial about building something cool with that library, so here’s my article/tutorial on writing an IRC bot with JavaScript, Node.js and coffea.

You won’t need to know about Node.js or coffea, knowing JavaScript before doing this tutorial is strongly encouraged though.

Let’s keep it as simple as possible and write a bot that can:

  • Respond to !ping
  • Join channels: !join channel

If you’ve worked with Node.js before, you can probably skip the next section and start at First steps with coffea.

Setting up the project

First of all, you will need to have Node.js set up. If you haven’t done so, it shouldn’t take you long to get it up and running, just follow the instructions on nodejs.org. Now you should be able to use the npm command. Try running: npm -v

Next, we are going to create a directory for our project (mkdir bot) and change into it (cd bot). Run npm init and fill out the information (or simply keep pressing return/enter) to setup your project. This creates a package.json file in the directory, which will store information about the project (like the name, version and dependencies).

First steps with coffea

To install coffea, run: npm install --save coffea (the --save flag saves the dependency to package.json, this is usually what you want; if someone runs npm install after downloading your project, it will install all dependencies specified in package.json)

Now let’s create a new file (touch index.js) and open it with your favorite editor (e.g. vim index.js). Type the following and save the file:

var client = require('coffea')('chat.freenode.net');

This will create a coffea IRC client and connect to chat.freenode.net. You can configure much more here than just setting the host as a string, simply replace the host with a JavaScript object, like this:

var client = require('coffea')({
host: 'chat.freenode.net',
port: 6667, // default value: 6667
ssl: false, // set to true if you want to use ssl
ssl_allow_invalid: false, // set to true if the server has a custom ssl certificate
prefix: '!', // used to parse commands and emit on('command') events, default: !
channels: ['#foo', '#bar'], // autojoin channels, default: []
nick: 'test', // default value: 'coffea' with random number
username: 'test', // default value: username = nick
realname: 'test', // default value: realname = nick
pass: 'sup3rS3cur3P4ssw0rd', // by default no password will be sent
nickserv: {
username: 'test',
password: 'l33tp455w0rD'
},
throttling: 250 // default value: 250ms, 1 message every 250ms, disable by setting to false
});

To connect to multiple networks, just use a JavaScript array:

var client = require('coffea')(['chat.freenode.net', 'irc.oftc.net']);

Of course this also works with objects instead of strings.

As an example, we are going to connect to freenode with a custom nick and make the bot join a channel (multiple channels can be specified in the JavaScript array):

var client = require('coffea')({
host: 'chat.freenode.net',
nick: 'coffea-testbot',
channels: ['#caffeinery']
});

Now let’s run the program with Node.js: node index.js

[16:20:45] coffea-testbot (~coffea-te@***) joined the channel

Success! That wasn’t that hard was it?

Responding to commands

coffea is event-based, that means you have to define event listeners (like on message or on join). Let’s try out one of them:

client.on('message', function (event) {
console.log(event.channel.name, event.user.nick, event.message);
});

Add this to the end of index.js and restart the bot, then type a few messages in a channel the bot is in, to see what happens:

#caffeinery omnidan test

Let’s filter out commands by listening to on command events instead:

client.on('command', function (event) {
console.log(event.channel.name, event.user.nick, event.message);
});

Try this out by typing test, then !test and compare the results!

#caffeinery omnidan !test

Time to write our first command, it will be !ping to which the bot will respond with pong. The coffea library defines client.send, which allows us to send messages to a certain user or channel, but since we want to reply to whoever sent us the command, whether it be a private message or a channel, we are going to make use of the event.reply helper:

client.on('command', function (event) {
switch (event.cmd) {
case 'ping':
event.reply('pong');
break;
}
console.log(event.channel.name, event.user.nick, event.message);
});

Simple, right? Let’s try it out:

[10:42:57] <+omnidan> !ping
[10:42:58] <coffea-testbot> pong

Joining channels

Next we are going to implement the !join command. We can start by adding our command to the switch block:

case 'join':
event.reply('joining channel');
// TODO: join channel here
break;

But how do we actually join the channel? Let’s take a look at the coffea documentation index and look for join() (coffea function): http://coffea.caffeinery.org/en/latest/channel.html#coffeafunction-join(channels,keys,network,fn)

join(channels, keys, network, fn)

All coffea functions are prefixed with client. (at least as long as you import coffea via var client) Let’s try out the function — we need to pass a channel or a list of channels first, then the key(s) for the channel, then the network the channel is on and last but not least, we can specify a callback, which is a function that will be triggered after the join completes. As our channel won’t be secured by a password, we won’t need to specify any keys.

case 'join':
event.reply('joining channel');
client.join(args[0], event.network, function () {
event.reply('joined channel');
});
break;
[11:04:39] <+omnidan> !join #omnidan
[11:04:40] <coffea-testbot> joining channel
[11:04:40] coffea-testbot (~coffea-te@***) joined the channel
[11:04:41] <coffea-testbot> joined channel

But what if we don’t specify an argument to our !join command? The bot crashes — that’s not good! Let’s check the arguments first:

case 'join':
if (args.length < 1) {
event.reply('not enough arguments: please specify a channel');
break;
}
  event.reply('joining channel');
client.join(args[0], event.network, function () {
event.reply('joined channel');
});
break;

Now it works fine.

[11:09:04] <+omnidan> !join
[11:09:04] <coffea-testbot> not enough arguments: please specify a channel
[11:09:07] <+omnidan> !join #omnidan
[11:09:08] <coffea-testbot> joining channel
[11:09:08] <coffea-testbot> joined channel
[11:09:23] <+omnidan> !ping
[11:09:24] <coffea-testbot> pong

That’s it! I know this guide could’ve been much shorter, but I wanted to describe the whole process from zero to a working IRC bot. Furthermore, I tried making it more interactive, so that readers can get a feel for working with coffea — even without actually trying it out. However, I do highly encourage doing so and tweaking the bot, adding more features, etc. to get more familiar with the library and event-based development.

Oh and, here is the source of the bot as a gist.

Going deeper

Check out the coffea documentation: http://coffea.caffeinery.org/en/latest/ which has an index of all coffea functions and events: http://coffea.caffeinery.org/en/latest/genindex.html

If you’re feeling experimental, you can try out coffea 0.5, which supports multiple protocols (like telegram), which means your IRC bot will automatically work on telegram too (as long as you load the plugin): https://github.com/caffeinery/coffea-telegram#setup This is still really new and experimental, so don’t get frustrated if something doesn’t work correctly. (Keep calm and report the issue)


Liked this post? Leave me a comment below or recommend it!

Also make sure to check out my other projects on my website or github — whichever you prefer :)