18ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen/*
28ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * IRCConnection is a simple implementation of the IRC protocol. A small
38ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * subset of the IRC commands are implemented. To be functional, IRCConnection
48ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * needs some mechanism of transport to be hooked up by:
58ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * -Passing in |sendFunc| and |closeFunc| which an IRCConnection to use to send
68ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *  an IRC message command and to close the connection respectively.
78ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen * -Connecting the in-bound functions |onOpened|, |onMessage|, and |onClosed|,
88ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *  to the transport so that the IRCConnection can respond to the connection
98ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen *  being opened, a message being received and the connection being closed.
108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen */
118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction NoOp() {};
138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction log(message) { console.log(message); };
148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsenfunction IRCConnection(server, port, nick, sendFunc, closeFunc) {
168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.server = server;
178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.port = port;
188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.nick = nick;
198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.connected = false;
208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var that = this;
228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * Client API
258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onConnect = NoOp;
278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onDisconnect = NoOp;
288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onText = NoOp;
298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onNotice = NoOp;
308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onNickReferenced = NoOp;
318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.joinChannel = function(channel) {
338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendCommand(commands.JOIN, [channel], "");
348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.sendMessage = function(recipient, message) {
378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendCommand(commands.PRIVMSG, [recipient], message);
388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.quitChannel = function(channel) {
418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendCommand(commands.PART, [channel], "");
428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.disconnect = function(message) {
458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendCommand(commands.QUIT, [], message);
468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    closeFunc();
478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * Transport Interface
518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * Whatever transport is used must provide and connect to the following
528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * in-bound events.
538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onOpened = function() {
558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendFunc(that.server + ":" + that.port);
568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendCommand(commands.NICK, [this.nick], "");
578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendCommand(commands.USER,
588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                ["chromium-irc-lib", "chromium-ircproxy", "*"],
598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                "indigo");
608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onMessage = function(message) {
638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    log("<< " + message);
648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (!message || !message.length) {
658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      return;
668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var parsed = parseMessage(message);
698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // Respond to PING command.
718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (parsed.command == commands.PING) {
728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      sendCommand(commands.PONG, [], parsed.body);
738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      return;
748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // Process PRIVMSG.
778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (parsed.command == commands.PRIVMSG) {
788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      if (parsed.body.charCodeAt(0) == 1) {
798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        // Ignore CTCP.
808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        return;
818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      that.onText(parsed.parameters[0],
838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                  parsed.prefix.split("!")[0],
848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen                  parsed.body);
858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      return;
868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // TODO: Other IRC commands.
898ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var commandCode = parseInt(parsed.command);
908ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (commandCode == NaN) {
918ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      return;
928ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
938ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
948ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    switch(commandCode) {
958ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      case 001:  // Server welcome message.
968ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        that.connected = true;
978ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        that.onConnect(parsed.body);
988ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        break;
998ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      case 002:
1008ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      case 003:
1018ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      case 004:
1028ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      case 005:
1038ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        if (!that.connected) {
1048ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen          that.connected = true;
1058ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen          that.onConnect();
1068ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        }
1078ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        break;
1088ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      case 433:  // TODO(rafaelw): Nickname in use.
1098ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        throw "NOT IMPLEMENTED";
1108ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        break;
1118ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      default:
1128ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        break;
1138ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1148ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1158ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1168ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  this.onClosed = function() {
1178ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    that.connected = false;
1188ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    that.onDisconnect();
1198ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
1208ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1218ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  /**
1228ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * IRC Implementation
1238ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * What follows in a minimal implementation of the IRC protocol.
1248ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   * Only |commands| are currently implemented.
1258ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen   */
1268ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  var commands = {
1278ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    JOIN: "JOIN",
1288ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    NICK: "NICK",
1298ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    NOTICE: "NOTICE",
1308ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    PART: "PART",
1318ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    PING: "PING",
1328ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    PONG: "PONG",
1338ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    PRIVMSG: "PRIVMSG",
1348ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    QUIT: "QUIT",
1358ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    USER: "USER"
1368ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
1378ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1388ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  function parseMessage(message) {
1398ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var parsed = {};
1408ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parsed.prefix = "";
1418ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parsed.command = "";
1428ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parsed.parameters = [];
1438ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parsed.body = "";
1448ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1458ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // Trim trailing CRLF.
1468ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var crlfIndex = message.indexOf("\r\n");
1478ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if(crlfIndex >= 0) {
1488ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      message = message.substring(0, crlfIndex);
1498ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1508ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1518ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // If leading character is ':', the message starts with a prefix.
1528ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (message.indexOf(':') == 0) {
1538ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      parsed.prefix = message.substring(1, message.indexOf(" "));
1548ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      message = message.substring(parsed.prefix.length + 2);
1558ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1568ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      // Forward past extra whitespace.
1578ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      while(message.indexOf(" ") == 0) {
1588ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen        message = message.substring(1);
1598ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      }
1608ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1618ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1628ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    // If there is still a ':', then the message has trailing body.
1638ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var bodyMarker = message.indexOf(':');
1648ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (bodyMarker >= 0) {
1658ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      parsed.body = message.substring(bodyMarker + 1);
1668ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      message = message.substring(0, bodyMarker);
1678ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1688ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1698ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parsed.parameters = message.split(" ");
1708ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    parsed.command = parsed.parameters.shift();  // First param is the command.
1718ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1728ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    return parsed;
1738ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  }
1748ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1758ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  function sendCommand(command, params, message) {
1768ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    var line = command;
1778ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (params && params.length > 0) {
1788ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      line += " " + params.join(" ");
1798ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1808ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    if (message && message.length > 0) {
1818ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen      line += " :"  + message;
1828ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    }
1838ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen
1848ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    log(">> " + line);
1858ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    line += "\r\n";
1868ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen    sendFunc(line);
1878ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen  };
1888ae428e0fb7feea16d79853f29447469a93bedffKristian Monsen};
189