«
8 minute read

Dealing With IRC in Node.js

IRC is one of the first realtime group chat protocols to surface the public Internet. It’s declined in usage since it’s inception, though areas such as open source software development have caused an increase in users over the years - freenode is one of the most significant examples.

I’m a huge fan of Node.js, an API atop v8 and libuv, enabling JavaScript developers to write seriously efficient and high performance networking applications.

It’s almost five years from Node’s introduction, however, and there are still no decent IRC modules available. node-irc appears to be the most popular with the community thus far. I have a few gripes with it:

  1. The message parser that comes bundled with node-irc is inefficient, mainly due to excessive use of regular expressions. For demanding applications, node-irc is unsuitable.
  2. The library is all one module, and one file (apart from the mIRC colour code serialiser and numeric mappings). It’s a huge mess.
  3. The current maintainer (martynsmith) does not seem to be maintaining the module anymore. There are a ton of pull requests and issues open, unresolved.

A Different Approach

For the last few months, I’ve been dealing quite a bit with the IRC protocol (and TS6 to a certain extent; which led up to this) in Node. I decided that for the benefit of the community, I’d open source what I’ve been working on, and publish it to npm.

The Basics: A Message Parser

Streaming data from a remote socket is child’s play in Node. The only really difficult part is dealing with the data a server sends. Here are some typical messages you might expect to receive:

Though this seems simple enough, the protocol has various intricacies. Amazingly, node-irc has been the only IRC parser written in JavaScript to parse messages properly, in near accordance with RFC1459 (albeit non-efficiently).

I set out to create a highly efficient, modern parser to deal with the IRC protocol of today. The parser is based on a Perl implementation written by Jon Portnoy. It parses a single line of IRC into the following:

Why’s this parser better than what’s currently available for JavaScript/Node?

A line such as @time=2014-10-19T16:40:51.620 :james!cli@127.0.0.1 PRIVMSG #Node.js :Hello, world! would result in the following:

{
  "tags": {
    "time": "2011-10-19T16:40:51.620"
  },
  "prefix": "james!cli@127.0.0.1",
  "command": "PRIVMSG",
  "params": [
    "#Node.js",
    "Hello,  world!"
  ]
}

So, how does this stack up against node-irc? Using jsPerf and Chrome (which uses the same JavaScript engine as Node), we see that this parser is up to 65% more performant than node-irc’s.

Screen%20Shot%202013-12-06%20at%2021,27,12

I did a small benchmark myself using Node, with the following code. Here are the results.

999999 iterations of message ':john!doe@myhost.local PRIVMSG #Node.js :Hello, how are things?   Things over here are pretty great, I must exclaim! Just having a spot of bother with some express middleware...  :('...
node-irc: 1731ms
irc-message: 477ms

It’s blazing fast. If interested, you can visit the GitHub repository, or download it via npm - it’s called irc-message.

Dealing with Real Connections

irc-message is great, but by itself it’s pretty useless. To actually deal with data returned from a remote server, I’ve written irc-message-stream - an object that implements a Transform stream, parses raw data written/piped to it and emits some events. Because it implements a standard stream object, you can:

It’s available on GitHub and npm. Here’s a usage example:

var net = require("net");
var IRCStream = require("irc-message-stream");

var messages = new IRCStream;
messages.on("line", function(line) {
    console.log("got raw line:", line);
});

messages.on("parsed", function(message) {
    console.log("got parsed message:", JSON.stringify(message));
});

var freenode = net.connect(6667, "irc.freenode.net");
freenode.pipe(messages);

Or, for a more practical example:

var net = require("net");
var IRCStream = require("irc-message-stream");

var messages = new IRCStream;
var freenode = net.connect(6667, "irc.freenode.net");
var nickname = "nodebot21";

freenode.on("connect", function() {
    console.log("connected");
    freenode.write("NICK " + nickname + "\r\n");
    freenode.write("USER test 1 * :Hello!\r\n");
});

messages.on("parsed", function(message) {
    switch (message.command) {
        case "PING":
            message.command = "PONG";
            freenode.write(message.toString() + "\r\n");
            break;
        case "001": // RPL_WELCOME
            console.log("server welcomed us!");
            break;
        case "376": // RPL_ENDOFMOTD
            freenode.write("JOIN #Node.js,#ubuntu,#debian,##javascript\r\n");
            break;
        case "PRIVMSG":
            var user = message.parseHostmaskFromPrefix();
            var target = message.params[0];
            console.log("[" + target + "] <" + user.nickname + "> " + message.params[1]);
            if (user.nickname.indexOf("node") !== -1) {
                freenode.write("PRIVMSG " + target + " :Hey, " + user.nickname + ", you're awesome!\r\n");
            }
            break;
        case "JOIN":
            var user = message.parseHostmaskFromPrefix();
            if (user.nickname === nickname) {
                console.log("joined", message.params[0]);
            }
            break;
    }
});

freenode.pipe(messages);

Is this it?

Hopefully not! These modules are already being used, such as IRCAnywhere’s irc-factory - used to handle concurrent and long lived connections for clients.

Eventually, I’d like to write a module that further abstracts this for people who don’t want to deal with the nitty gritty parts of IRC. For now, if you want to take advantage of irc-message in a client environment, I forked node-irc and rewrote the parser logic.

A complete bot framework is also a possibility, I’d definitely like to develop one or see one evolving.

There’s also the possibility of TS6 services being written, which would be very interesting. Due to the nature of TS5/TS6 being in the format of IRC, irc-message has no problem parsing anything. A set of IRC services in Node would be pretty awesome ;)

If you notice any weird behaviour, want to suggest something or just want to talk, feel free to join #expr on freenode. There’s bound to be plenty of bugs and undesired behaviour in both of these modules, so open an issue on GitHub, submit a pull request or just talk to me via IRC. :)

Share Comment on Twitter