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