11e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)/* 21e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * IRCConnection is a simple implementation of the IRC protocol. A small 31e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * subset of the IRC commands are implemented. To be functional, IRCConnection 41e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * needs some mechanism of transport to be hooked up by: 51e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * -Passing in |sendFunc| and |closeFunc| which an IRCConnection to use to send 61e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * an IRC message command and to close the connection respectively. 71e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * -Connecting the in-bound functions |onOpened|, |onMessage|, and |onClosed|, 81e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * to the transport so that the IRCConnection can respond to the connection 91e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * being opened, a message being received and the connection being closed. 101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) */ 111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)function NoOp() {}; 131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)function log(message) { console.log(message); }; 141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)function IRCConnection(server, port, nick, sendFunc, closeFunc) { 161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.server = server; 171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.port = port; 181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.nick = nick; 191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.connected = false; 201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var that = this; 221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) /** 241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * Client API 251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) */ 261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onConnect = NoOp; 271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onDisconnect = NoOp; 281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onText = NoOp; 291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onNotice = NoOp; 301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onNickReferenced = NoOp; 311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.joinChannel = function(channel) { 331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.JOIN, [channel], ""); 341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) }; 351e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.sendMessage = function(recipient, message) { 371e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.PRIVMSG, [recipient], message); 381e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) }; 391e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 401e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.quitChannel = function(channel) { 411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.PART, [channel], ""); 421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.disconnect = function(message) { 451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.QUIT, [], message); 461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) closeFunc(); 471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) /** 501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * Transport Interface 511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * Whatever transport is used must provide and connect to the following 521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * in-bound events. 531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) */ 541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onOpened = function() { 551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendFunc(that.server + ":" + that.port); 561e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.NICK, [this.nick], ""); 571e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.USER, 581e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) ["chromium-irc-lib", "chromium-ircproxy", "*"], 591e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) "indigo"); 601e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) }; 611e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 621e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onMessage = function(message) { 631e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) log("<< " + message); 641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (!message || !message.length) { 651e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return; 661e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 671e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 681e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var parsed = parseMessage(message); 691e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 701e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // Respond to PING command. 711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (parsed.command == commands.PING) { 721e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendCommand(commands.PONG, [], parsed.body); 731e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return; 741e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // Process PRIVMSG. 771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (parsed.command == commands.PRIVMSG) { 781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (parsed.body.charCodeAt(0) == 1) { 791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // Ignore CTCP. 801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return; 811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.onText(parsed.parameters[0], 831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.prefix.split("!")[0], 841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.body); 851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return; 861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // TODO: Other IRC commands. 891e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var commandCode = parseInt(parsed.command); 901e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (commandCode == NaN) { 911e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return; 921e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 931e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 941e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) switch(commandCode) { 951e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) case 001: // Server welcome message. 961e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.connected = true; 971e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.onConnect(parsed.body); 981e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) break; 991e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) case 002: 1001e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) case 003: 1011e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) case 004: 1021e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) case 005: 1031e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (!that.connected) { 1041e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.connected = true; 1051e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.onConnect(); 1061e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1071e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) break; 1081e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) case 433: // TODO(rafaelw): Nickname in use. 1091e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) throw "NOT IMPLEMENTED"; 1101e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) break; 1111e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) default: 1121e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) break; 1131e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1141e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1151e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1161e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) this.onClosed = function() { 1171e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.connected = false; 1181e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) that.onDisconnect(); 1191e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) }; 1201e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1211e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) /** 1221e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * IRC Implementation 1231e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * What follows in a minimal implementation of the IRC protocol. 1241e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) * Only |commands| are currently implemented. 1251e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) */ 1261e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var commands = { 1271e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) JOIN: "JOIN", 1281e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) NICK: "NICK", 1291e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) NOTICE: "NOTICE", 1301e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) PART: "PART", 1311e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) PING: "PING", 1321e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) PONG: "PONG", 1331e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) PRIVMSG: "PRIVMSG", 1341e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) QUIT: "QUIT", 1351e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) USER: "USER" 1361e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) }; 1371e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1381e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) function parseMessage(message) { 1391e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var parsed = {}; 1401e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.prefix = ""; 1411e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.command = ""; 1421e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.parameters = []; 1431e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.body = ""; 1441e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1451e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // Trim trailing CRLF. 1461e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var crlfIndex = message.indexOf("\r\n"); 1471e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if(crlfIndex >= 0) { 1481e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) message = message.substring(0, crlfIndex); 1491e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1501e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1511e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // If leading character is ':', the message starts with a prefix. 1521e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (message.indexOf(':') == 0) { 1531e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.prefix = message.substring(1, message.indexOf(" ")); 1541e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) message = message.substring(parsed.prefix.length + 2); 1551e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1561e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // Forward past extra whitespace. 1571e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) while(message.indexOf(" ") == 0) { 1581e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) message = message.substring(1); 1591e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1601e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1611e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1621e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) // If there is still a ':', then the message has trailing body. 1631e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var bodyMarker = message.indexOf(':'); 1641e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (bodyMarker >= 0) { 1651e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.body = message.substring(bodyMarker + 1); 1661e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) message = message.substring(0, bodyMarker); 1671e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1681e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1691e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.parameters = message.split(" "); 1701e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) parsed.command = parsed.parameters.shift(); // First param is the command. 1711e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1721e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) return parsed; 1731e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1741e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1751e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) function sendCommand(command, params, message) { 1761e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) var line = command; 1771e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (params && params.length > 0) { 1781e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) line += " " + params.join(" "); 1791e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1801e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) if (message && message.length > 0) { 1811e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) line += " :" + message; 1821e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) } 1831e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) 1841e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) log(">> " + line); 1851e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) line += "\r\n"; 1861e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) sendFunc(line); 1871e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles) }; 1881e9bf3e0803691d0a228da41fc608347b6db4340Torne (Richard Coles)}; 189