Changing jade template to match changes made in index.php
[KiwiIRC.git] / node / kiwi.js
index 05585469d9844211471b0d92918998332d1fae6c..9beb306ab5987d6481157f6eb6423e4f30bc366e 100644 (file)
@@ -3,8 +3,19 @@
 var tls = require('tls'),
     net = require('net'),
     http = require('http'),
+    https = require('https'),
+    fs = require('fs'),
+    url = require('url'),
     ws = require('socket.io'),
-    _ = require('./underscore.min.js');
+    _ = require('./lib/underscore.min.js'),
+    starttls = require('./lib/starttls.js');
+
+var config = JSON.parse(fs.readFileSync(__dirname + '/config.json', 'ascii'));
+
+if (config.handle_http) {
+    var static_server = require('node-static'),
+        jade = require('jade');
+}
 
 var ircNumerics = {
     RPL_WELCOME:        '001',
@@ -21,12 +32,14 @@ var ircNumerics = {
     RPL_MOTD:           '372',
     RPL_WHOISMODES:     '379',
     ERR_NOSUCHNICK:     '401',
-    ERR_LINKCHANNEL:    '470'
+    ERR_LINKCHANNEL:    '470',
+    RPL_STARTTLS:       '670'
 };
 
 
 var parseIRCMessage = function (websocket, ircSocket, data) {
-    var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, params, prefix, prefixes, nicklist;
+    /*global ircSocketDataHandler */
+    var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, params, prefix, prefixes, nicklist, caps;
     regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@([a-z0-9\.\-:]+)) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i;
     msg = regex.exec(data);
     if (msg) {
@@ -44,6 +57,11 @@ var parseIRCMessage = function (websocket, ircSocket, data) {
             ircSocket.write('PONG ' + msg.trailing + '\r\n');
             break;
         case ircNumerics.RPL_WELCOME:
+            if (ircSocket.IRC.CAP.negotiating) {
+                ircSocket.IRC.CAP.negotiating = false;
+                ircSocket.IRC.CAP.enabled = [];
+                ircSocket.IRC.CAP.requested = [];
+            }
             websocket.emit('message', {event: 'connect', connected: true, host: null});
             break;
         case ircNumerics.RPL_ISUPPORT:
@@ -58,25 +76,27 @@ var parseIRCMessage = function (websocket, ircSocket, data) {
                         regex = /\(([^)]*)\)(.*)/;
                         matches = regex.exec(opt[1]);
                         if ((matches) && (matches.length === 3)) {
-                            options[opt[0]] = {};
+                            ircSocket.IRC.options[opt[0]] = {};
                             for (j = 0; j < matches[2].length; j++) {
-                                options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
+                                ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
                             }
                         }
                     }
                 }
             }
-            websocket.emit('message', {event: 'options', server: '', "options": options});
+            websocket.emit('message', {event: 'options', server: '', "options": ircSocket.IRC.options});
             break;
         case ircNumerics.RPL_WHOISUSER:
         case ircNumerics.RPL_WHOISSERVER:
         case ircNumerics.RPL_WHOISOPERATOR:
-        case ircNumerics.RPL_WHOISIDLE:
         case ircNumerics.RPL_ENDOFWHOIS:
         case ircNumerics.RPL_WHOISCHANNELS:
         case ircNumerics.RPL_WHOISMODES:
             websocket.emit('message', {event: 'whois', server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
             break;
+        case ircNumerics.RPL_WHOISIDLE:
+            params = msg.params.split(" ", 3);
+            websocket.emit('message', {event: 'whois', server: '', nick: params[1], "msg": params[2] + ' ' + msg.trailing});
         case ircNumerics.RPL_MOTD:
             websocket.emit('message', {event: 'motd', server: '', "msg": msg.trailing});
             break;
@@ -169,17 +189,168 @@ var parseIRCMessage = function (websocket, ircSocket, data) {
         case 'PRIVMSG':
             websocket.emit('message', {event: 'msg', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing});
             break;
+        case 'CAP':
+            caps = config.cap_options;
+            options = msg.trailing.split(" ");
+            switch (_.first(msg.params.split(" "))) {
+            case 'LS':
+                opts = '';
+                _.each(_.intersect(caps, options), function (cap) {
+                    if (opts !== '') {
+                        opts += " ";
+                    }
+                    opts += cap;
+                    ircSocket.IRC.CAP.requested.push(cap);
+                });
+                if (opts.length > 0) {
+                    ircSocket.write('CAP REQ :' + opts + '\r\n');
+                } else {
+                    ircSocket.write('CAP END\r\n');
+                }
+                // TLS is special
+                /*if (_.include(options, 'tls')) {
+                    ircSocket.write('STARTTLS\r\n');
+                    ircSocket.IRC.CAP.requested.push('tls');
+                }*/
+                break;
+            case 'ACK':
+                _.each(options, function (cap) {
+                    ircSocket.IRC.CAP.enabled.push(cap);
+                });
+                if (_.last(msg.params.split(" ")) !== '*') {
+                    ircSocket.IRC.CAP.requested = [];
+                    ircSocket.IRC.CAP.negotiating = false;
+                    ircSocket.write('CAP END\r\n');
+                }
+                break;
+            case 'NAK':
+                ircSocket.IRC.CAP.requested = [];
+                ircSocket.IRC.CAP.negotiating = false;
+                ircSocket.write('CAP END\r\n');
+                break;
+            }
+            break;
+        /*case ircNumerics.RPL_STARTTLS:
+            try {
+                IRC = ircSocket.IRC;
+                listeners = ircSocket.listeners('data');
+                ircSocket.removeAllListeners('data');
+                ssl_socket = starttls(ircSocket, {}, function () {
+                    ssl_socket.on("data", function (data) {
+                        ircSocketDataHandler(data, websocket, ssl_socket);
+                    });
+                    ircSocket = ssl_socket;
+                    ircSocket.IRC = IRC;
+                    _.each(listeners, function (listener) {
+                        ircSocket.addListener('data', listener);
+                    });
+                });
+                //console.log(ircSocket);
+            } catch (e) {
+                console.log(e);
+            }
+            break;*/
         }
     } else {
         console.log("Unknown command.\r\n");
     }
 };
 
+var ircSocketDataHandler = function (data, websocket, ircSocket) {
+    var i;
+    if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
+        data = ircSocket.held + data;
+        ircSocket.holdLast = false;
+        ircSocket.held = '';
+    }
+    if (data.substr(-2) === '\r\n') {
+        ircSocket.holdLast = true;
+    }
+    data = data.split("\r\n");         
+    for (i = 0; i < data.length; i++) {
+        if (data[i]) {
+            if ((ircSocket.holdLast) && (i === data.length - 1)) {
+                ircSocket.held = data[i];
+                break;
+            }
+            console.log("->" + data[i]);
+            parseIRCMessage(websocket, ircSocket, data[i]);
+        }
+    }
+};
+
+if (config.handle_http) {
+    var fileServer = new (static_server.Server)(__dirname + config.public_http);
+}
+
+var httpHandler = function (request, response) {
+    var uri, subs, useragent, agent, server_set, server, nick, debug, touchscreen;
+    if (config.handle_http) {
+        uri = url.parse(request.url);
+        subs = uri.pathname.substr(0, 4);
+        if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
+            request.addListener('end', function () {
+                fileServer.serve(request, response);
+            });
+        } else if (uri.pathname === '/') {
+            useragent = (response.headers) ? response.headers['user-agent']: '';
+            if (useragent.indexOf('android') !== -1) {
+                agent = 'android';
+                touchscreen = true;
+            } else if (useragent.indexOf('iphone') !== -1) {
+                agent = 'iphone';
+                touchscreen = true;
+            } else if (useragent.indexOf('ipad') !== -1) {
+                agent = 'ipad';
+                touchscreen = true;
+            } else if (useragent.indexOf('ipod') !== -1) {
+                agent = 'ipod';
+                touchscreen = true;
+            } else {
+                agent = 'normal';
+                touchscreen = false;
+            }
+            if (uri.query) {
+                server_set = (uri.query.server !== '');
+                server = uri.query.server || 'irc.anonnet.org';
+                nick = uri.query.nick || '';
+                debug = (uri.query.debug !== '');
+            } else {
+                server = 'irc.anonnet.org';
+                nick = '';
+            }
+            response.setHeader('Connection', 'close');
+            response.setHeader('X-Generated-By', 'KiwiIRC');
+            jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": config }}, function (err, html) {
+                if (!err) {
+                    response.write(html);
+                } else {
+                    response.statusCode = 500;
+                }
+                response.end();
+            });
+        } else if (uri.pathname.substr(0, 10) === '/socket.io') {
+            // Do nothing!
+        } else {
+            response.statusCode = 404;
+            response.end();
+        }
+    }
+};
+
 //setup websocket listener
-var io = ws.listen(7777, {secure: true});
-io.sockets.on('connection', function (websocket) {
+if (config.listen_ssl) {
+    var httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + config.ssl_key), cert: fs.readFileSync(__dirname + '/' + config.ssl_cert)}, httpHandler);
+    var io = ws.listen(httpServer, {secure: true});
+    httpServer.listen(config.port, config.bind_address);
+} else {
+    var httpServer = http.createServer(httpHandler);
+    var io = ws.listen(httpServer, {secure: false});
+    httpServer.listen(config.port, config.bind_address);
+}
+io.of('/kiwi').on('connection', function (websocket) {
     websocket.on('irc connect', function (nick, host, port, ssl, callback) {
-        var ircSocket, i;
+        var ircSocket;
         //setup IRC connection
         if (!ssl) {
             ircSocket = net.createConnection(port, host);
@@ -187,21 +358,18 @@ io.sockets.on('connection', function (websocket) {
             ircSocket = tls.connect(port, host);
         }
         ircSocket.setEncoding('ascii');
-        ircSocket.IRC = {options: {}};
+        ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}};
         websocket.ircSocket = ircSocket;
+        ircSocket.holdLast = false;
+        ircSocket.held = '';
         
         ircSocket.on('data', function (data) {
-            data = data.split("\r\n");            
-            for (i = 0; i < data.length; i++) {
-                if (data[i]) {
-                    console.log("->" + data[i]);
-                    parseIRCMessage(websocket, ircSocket, data[i]);
-                }
-            }
+            ircSocketDataHandler(data, websocket, ircSocket);
         });
         
         ircSocket.IRC.nick = nick;
         // Send the login data
+        ircSocket.write('CAP LS\r\n');
         ircSocket.write('NICK ' + nick + '\r\n');
         ircSocket.write('USER ' + nick + '_kiwi 0 0 :' + nick + '\r\n');
         
@@ -241,6 +409,11 @@ io.sockets.on('connection', function (websocket) {
                 websocket.ircSocket.destroySoon();
                 websocket.disconnect();
                 break;
+            case 'notice':
+                if ((args.target) && (args.msg)) {
+                    websocket.ircSocket.write('NOTICE ' + args.target + ' :' + args.msg + '\r\n');
+                }
+                break;
             default:
             }
             if ((callback) && (typeof (callback) === 'function')) {
@@ -252,7 +425,7 @@ io.sockets.on('connection', function (websocket) {
     });
     websocket.on('disconnect', function () {
         if ((!websocket.sentQUIT) && (websocket.ircSocket)) {
-            websocket.ircSocket.end('QUIT :KiwiIRC\r\n');
+            websocket.ircSocket.end('QUIT :' + config.quit_message + '\r\n');
             websocket.sentQUIT = true;
             websocket.ircSocket.destroySoon();
         }