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