Home | History | Annotate | Download | only in servlet
      1 /*
      2  * IRCConnection is a simple implementation of the IRC protocol. A small
      3  * subset of the IRC commands are implemented. To be functional, IRCConnection
      4  * needs some mechanism of transport to be hooked up by:
      5  * -Passing in |sendFunc| and |closeFunc| which an IRCConnection to use to send
      6  *  an IRC message command and to close the connection respectively.
      7  * -Connecting the in-bound functions |onOpened|, |onMessage|, and |onClosed|,
      8  *  to the transport so that the IRCConnection can respond to the connection
      9  *  being opened, a message being received and the connection being closed.
     10  */
     11 
     12 function NoOp() {};
     13 function log(message) { console.log(message); };
     14 
     15 function IRCConnection(server, port, nick, sendFunc, closeFunc) {
     16   this.server = server;
     17   this.port = port;
     18   this.nick = nick;
     19   this.connected = false;
     20 
     21   var that = this;
     22 
     23   /**
     24    * Client API
     25    */
     26   this.onConnect = NoOp;
     27   this.onDisconnect = NoOp;
     28   this.onText = NoOp;
     29   this.onNotice = NoOp;
     30   this.onNickReferenced = NoOp;
     31 
     32   this.joinChannel = function(channel) {
     33     sendCommand(commands.JOIN, [channel], "");
     34   };
     35 
     36   this.sendMessage = function(recipient, message) {
     37     sendCommand(commands.PRIVMSG, [recipient], message);
     38   };
     39 
     40   this.quitChannel = function(channel) {
     41     sendCommand(commands.PART, [channel], "");
     42   }
     43 
     44   this.disconnect = function(message) {
     45     sendCommand(commands.QUIT, [], message);
     46     closeFunc();
     47   }
     48 
     49   /**
     50    * Transport Interface
     51    * Whatever transport is used must provide and connect to the following
     52    * in-bound events.
     53    */
     54   this.onOpened = function() {
     55     sendFunc(that.server + ":" + that.port);
     56     sendCommand(commands.NICK, [this.nick], "");
     57     sendCommand(commands.USER,
     58                 ["chromium-irc-lib", "chromium-ircproxy", "*"],
     59                 "indigo");
     60   };
     61 
     62   this.onMessage = function(message) {
     63     log("<< " + message);
     64     if (!message || !message.length) {
     65       return;
     66     }
     67 
     68     var parsed = parseMessage(message);
     69 
     70     // Respond to PING command.
     71     if (parsed.command == commands.PING) {
     72       sendCommand(commands.PONG, [], parsed.body);
     73       return;
     74     }
     75 
     76     // Process PRIVMSG.
     77     if (parsed.command == commands.PRIVMSG) {
     78       if (parsed.body.charCodeAt(0) == 1) {
     79         // Ignore CTCP.
     80         return;
     81       }
     82       that.onText(parsed.parameters[0],
     83                   parsed.prefix.split("!")[0],
     84                   parsed.body);
     85       return;
     86     }
     87 
     88     // TODO: Other IRC commands.
     89     var commandCode = parseInt(parsed.command);
     90     if (commandCode == NaN) {
     91       return;
     92     }
     93 
     94     switch(commandCode) {
     95       case 001:  // Server welcome message.
     96         that.connected = true;
     97         that.onConnect(parsed.body);
     98         break;
     99       case 002:
    100       case 003:
    101       case 004:
    102       case 005:
    103         if (!that.connected) {
    104           that.connected = true;
    105           that.onConnect();
    106         }
    107         break;
    108       case 433:  // TODO(rafaelw): Nickname in use.
    109         throw "NOT IMPLEMENTED";
    110         break;
    111       default:
    112         break;
    113     }
    114   }
    115 
    116   this.onClosed = function() {
    117     that.connected = false;
    118     that.onDisconnect();
    119   };
    120 
    121   /**
    122    * IRC Implementation
    123    * What follows in a minimal implementation of the IRC protocol.
    124    * Only |commands| are currently implemented.
    125    */
    126   var commands = {
    127     JOIN: "JOIN",
    128     NICK: "NICK",
    129     NOTICE: "NOTICE",
    130     PART: "PART",
    131     PING: "PING",
    132     PONG: "PONG",
    133     PRIVMSG: "PRIVMSG",
    134     QUIT: "QUIT",
    135     USER: "USER"
    136   };
    137 
    138   function parseMessage(message) {
    139     var parsed = {};
    140     parsed.prefix = "";
    141     parsed.command = "";
    142     parsed.parameters = [];
    143     parsed.body = "";
    144 
    145     // Trim trailing CRLF.
    146     var crlfIndex = message.indexOf("\r\n");
    147     if(crlfIndex >= 0) {
    148       message = message.substring(0, crlfIndex);
    149     }
    150 
    151     // If leading character is ':', the message starts with a prefix.
    152     if (message.indexOf(':') == 0) {
    153       parsed.prefix = message.substring(1, message.indexOf(" "));
    154       message = message.substring(parsed.prefix.length + 2);
    155 
    156       // Forward past extra whitespace.
    157       while(message.indexOf(" ") == 0) {
    158         message = message.substring(1);
    159       }
    160     }
    161 
    162     // If there is still a ':', then the message has trailing body.
    163     var bodyMarker = message.indexOf(':');
    164     if (bodyMarker >= 0) {
    165       parsed.body = message.substring(bodyMarker + 1);
    166       message = message.substring(0, bodyMarker);
    167     }
    168 
    169     parsed.parameters = message.split(" ");
    170     parsed.command = parsed.parameters.shift();  // First param is the command.
    171 
    172     return parsed;
    173   }
    174 
    175   function sendCommand(command, params, message) {
    176     var line = command;
    177     if (params && params.length > 0) {
    178       line += " " + params.join(" ");
    179     }
    180     if (message && message.length > 0) {
    181       line += " :"  + message;
    182     }
    183 
    184     log(">> " + line);
    185     line += "\r\n";
    186     sendFunc(line);
    187   };
    188 };
    189