/list response now shows in a new tab
[KiwiIRC.git] / node / app.js
index 47857c27cd578d629cabc9667f4b9289fa295312..6025dd41a62a7bfd6f632d8f27142b224e87ddff 100644 (file)
@@ -1,3 +1,5 @@
+/*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
+/*globals kiwi_root */
 var tls = null;
 var net = null;
 var http = null;
@@ -28,7 +30,7 @@ this.init = function (objs) {
        _ = objs._;
        starttls = objs.starttls;
        kiwi = require('./kiwi.js');
-}
+};
 
 
 
@@ -40,7 +42,7 @@ this.init = function (objs) {
  */
 this.setTitle = function () {
        process.title = 'kiwiirc';
-}
+};
 
 this.changeUser = function () {
     if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
@@ -72,6 +74,7 @@ this.changeUser = function () {
 
 var ircNumerics = {
     RPL_WELCOME:            '001',
+    RPL_MYINFO:             '004',
     RPL_ISUPPORT:           '005',
     RPL_WHOISUSER:          '311',
     RPL_WHOISSERVER:        '312',
@@ -79,6 +82,9 @@ var ircNumerics = {
     RPL_WHOISIDLE:          '317',
     RPL_ENDOFWHOIS:         '318',
     RPL_WHOISCHANNELS:      '319',
+    RPL_LISTSTART:          '321',
+    RPL_LIST:               '322',
+    RPL_LISTEND:            '323',
     RPL_NOTOPIC:            '331',
     RPL_TOPIC:              '332',
     RPL_NAMEREPLY:          '353',
@@ -105,8 +111,11 @@ var ircNumerics = {
 
 this.parseIRCMessage = function (websocket, ircSocket, data) {
     /*global ircSocketDataHandler */
-    var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, prefix, prefixes, nicklist, caps, rtn, obj;
-    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;
+    var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, prefix, prefixes, nicklist, caps, rtn, obj, tmp, namespace;
+    //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;
+    //regex = /^(?::(\S+) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
+    regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
+
     msg = regex.exec(data);
     if (msg) {
         msg = {
@@ -118,6 +127,7 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
             params:     msg[6] || '',
             trailing:   (msg[7]) ? msg[7].trim() : ''
         };
+        
         switch (msg.command.toUpperCase()) {
         case 'PING':
             websocket.sendServerLine('PONG ' + msg.trailing);
@@ -129,7 +139,10 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
                 ircSocket.IRC.CAP.requested = [];
                 ircSocket.IRC.registered = true;
             }
-            websocket.sendClientEvent('connect', {connected: true, host: null});
+            //regex = /([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)/i;
+            //matches = regex.exec(msg.trailing);
+            nick =  msg.params.split(' ')[0];
+            websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
             break;
         case ircNumerics.RPL_ISUPPORT:
             opts = msg.params.split(" ");
@@ -137,7 +150,7 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
             for (i = 0; i < opts.length; i++) {
                 opt = opts[i].split("=", 2);
                 opt[0] = opt[0].toUpperCase();
-                ircSocket.IRC.options[opt[0]] = opt[1] || true;
+                ircSocket.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
                 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) {
                     if (opt[0] === 'PREFIX') {
                         regex = /\(([^)]*)\)(.*)/;
@@ -147,13 +160,13 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
                             for (j = 0; j < matches[2].length; j++) {
                                 //ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
                                 ircSocket.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
-                                //console.log({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
                             }
-                            //console.log(ircSocket.IRC.options);
+
                         }
                     }
                 }
             }
+
             websocket.sendClientEvent('options', {server: '', "options": ircSocket.IRC.options});
             break;
         case ircNumerics.RPL_WHOISUSER:
@@ -164,6 +177,38 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
         case ircNumerics.RPL_WHOISMODES:
             websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
             break;
+
+        case ircNumerics.RPL_LISTSTART:
+            (function () {
+                websocket.sendClientEvent('list_start', {server: ''});
+            }());
+            break;
+        case ircNumerics.RPL_LISTEND:
+            (function () {
+                websocket.sendClientEvent('list_end', {server: ''});
+            }());
+            break;
+        
+        case ircNumerics.RPL_LIST:
+            (function () {
+                var parts, channel, num_users, modes, topic;
+
+                parts = msg.params.split(' ');
+                channel = parts[1];
+                num_users = parts[2];
+                modes = msg.trailing.split(' ', 1);
+                topic = msg.trailing.substring(msg.trailing.indexOf(' ') + 1);
+
+                websocket.sendClientEvent('list_channel', {
+                    server: '',
+                    channel: channel,
+                    topic: topic,
+                    modes: modes,
+                    num_users: num_users
+                });
+            }());
+            break;
+
         case ircNumerics.RPL_WHOISIDLE:
             params = msg.params.split(" ", 4);
             rtn = {server: '', nick: params[1], idle: params[2]};
@@ -285,6 +330,11 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
                 // It's a CTCP request
                 if (msg.trailing.substr(1, 6) === 'ACTION') {
                     websocket.sendClientEvent('action', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(7, msg.trailing.length - 2)});
+                } else if (msg.trailing.substr(1, 4) === 'KIWI') {
+                    tmp = msg.trailing.substr(6, msg.trailing.length - 2);
+                    namespace = tmp.split(' ', 1)[0];
+                    websocket.sendClientEvent('kiwi', {namespace: namespace, data: tmp.substr(namespace.length + 1)});
+                    
                 } else if (msg.trailing.substr(1, 7) === 'VERSION') {
                     ircSocket.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
                 } else {
@@ -401,7 +451,7 @@ this.parseIRCMessage = function (websocket, ircSocket, data) {
             console.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
         }
     } else {
-        console.log("Malformed IRC line");
+        console.log("Malformed IRC line: " + data);
     }
 };
 
@@ -443,20 +493,23 @@ this.ircSocketDataHandler = function (data, websocket, ircSocket) {
 
 
 this.httpHandler = function (request, response) {
-    var uri, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
+    var uri, uri_parts, subs, useragent, agent, server_set, server, nick, debug, touchscreen, hash,
         min = {}, public_http_path;
     if (kiwi.config.handle_http) {
         uri = url.parse(request.url, true);
+        uri_parts = uri.pathname.split('/');
+        
         subs = uri.pathname.substr(0, 4);
         if (uri.pathname === '/js/all.js') {
             if (kiwi.cache.alljs === '') {
                 public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;
 
+                               min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js');
                 min.util = fs.readFileSync(public_http_path + 'js/util.js');
                 min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
                 min.front = fs.readFileSync(public_http_path + 'js/front.js');
                 min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
-                min.ast = jsp.parse(min.util + min.gateway + min.front + min.iscroll);
+                min.ast = jsp.parse(min.underscore + min.util + min.gateway + min.front + min.iscroll);
                 min.ast = pro.ast_mangle(min.ast);
                 min.ast = pro.ast_squeeze(min.ast);
                 min.final_code = pro.gen_code(min.ast);
@@ -475,35 +528,44 @@ this.httpHandler = function (request, response) {
             request.addListener('end', function () {
                 kiwi.fileServer.serve(request, response);
             });
-        } else if (uri.pathname === '/') {
-            useragent = (response.headers) ? response.headers['user-agent'] : '';
-            if (useragent.indexOf('android') !== -1) {
+        } else if (uri.pathname === '/' || uri_parts[1] === 'client') {
+            useragent = (typeof request.headers === 'string') ? request.headers['user-agent'] : '';
+            if (useragent.match(/android/i) !== -1) {
                 agent = 'android';
                 touchscreen = true;
-            } else if (useragent.indexOf('iphone') !== -1) {
+            } else if (useragent.match(/iphone/) !== -1) {
                 agent = 'iphone';
                 touchscreen = true;
-            } else if (useragent.indexOf('ipad') !== -1) {
+            } else if (useragent.match(/ipad/) !== -1) {
                 agent = 'ipad';
                 touchscreen = true;
-            } else if (useragent.indexOf('ipod') !== -1) {
+            } else if (useragent.match(/ipod/) !== -1) {
                 agent = 'ipod';
                 touchscreen = true;
             } else {
                 agent = 'normal';
                 touchscreen = false;
             }
+            agent = 'normal';
+            touchscreen = false;
             
-            if (uri.query) {
-                server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
-                server = uri.query.server || 'irc.anonnet.org';
-                nick = uri.query.nick || '';
-                debug = (uri.query.debug !== '');
+            if (uri_parts[1] !== 'client') {
+                if (uri.query) {
+                    server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
+                    server = uri.query.server || 'irc.anonnet.org';
+                    nick = uri.query.nick || '';
+                    debug = (uri.query.debug !== '');
+                } else {
+                    server_set = false;
+                    server = 'irc.anonnet.org';
+                    nick = '';
+                }
             } else {
-                server_set = false;
-                server = 'irc.anonnet.org';
-                nick = '';
+                server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
+                server = server_set ? uri_parts[2] : 'irc.anonnet.org';
+                nick = uri.query.nick || '';
             }
+
             response.setHeader('X-Generated-By', 'KiwiIRC');
             hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f').update(debug ? 't' : 'f').update(server_set ? 't' : 'f').update(server).update(nick).update(agent).update(JSON.stringify(kiwi.config)).digest('base64');
             if (kiwi.cache.html[hash]) {
@@ -591,7 +653,9 @@ this.websocketConnection = function (websocket) {
 
         websocket.sendClientEvent = function (event_name, data) {
             var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
-            if(ev === null) return;
+            if (ev === null) {
+                return;
+            }
 
             data.event = event_name;
             websocket.emit('message', data);
@@ -599,7 +663,10 @@ this.websocketConnection = function (websocket) {
 
         websocket.sendServerLine = function (data, eol) {
             eol = (typeof eol === 'undefined') ? '\r\n' : eol;
-            websocket.ircSocket.write(data + eol);
+
+            try {
+                websocket.ircSocket.write(data + eol);
+            } catch (e) { }
         };
 
         websocket.on('irc connect', kiwi.websocketIRCConnect);
@@ -647,7 +714,7 @@ this.websocketIRCConnect = function (websocket, nick, host, port, ssl, callback)
         }
         websocket.sendServerLine('CAP LS');
         websocket.sendServerLine('NICK ' + nick);
-        websocket.sendServerLine('USER ' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + '_kiwi 0 0 :' + nick);
+        websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);
 
         if ((callback) && (typeof (callback) === 'function')) {
             callback();
@@ -673,9 +740,16 @@ this.websocketMessage = function (websocket, msg, callback) {
             break;
         case 'action':
             if ((args.target) && (args.msg)) {
-                websocket.sendServerLine('PRIVMSG ' + args.target + ' :\ 1' + String.fromCharCode(1) + 'ACTION ' + args.msg + String.fromCharCode(1));
+                websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'ACTION ' + args.msg + String.fromCharCode(1));
             }
             break;
+
+        case 'kiwi':
+            if ((args.target) && (args.data)) {
+                websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1));
+            }
+            break;
+
         case 'raw':
             websocket.sendServerLine(args.data);
             break;
@@ -797,37 +871,44 @@ this.rehash = function () {
 /*
  * KiwiIRC controlling via STDIN
  */
-this.startControll = function () {
-       process.stdin.resume();
-       process.stdin.on('data', function (chunk) {
-        var parts = chunk.toString().trim().split(' ');
-           switch (parts[0]) {
-           case 'rehash':
-               console.log('Rehashing...');
-               console.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
-               break;
-
-               case 'recode':
-               console.log('Recoding...');
-               console.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
-               break;
-
-        case 'mod':
-            if (parts[1] === 'reload') {
-                console.log('Reloading module (' + parts[2] + ')..');
-                kiwi.kiwi_mod.reloadModule(parts[2]);
-            }
-            break;
+this.manageControll = function (data) {
+    var parts = data.toString().trim().split(' '),
+        connections_cnt = 0,
+        i;
+    switch (parts[0]) {
+    case 'rehash':
+        console.log('Rehashing...');
+        console.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
+        break;
+
+       case 'recode':
+        console.log('Recoding...');
+        console.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
+        break;
+
+    case 'mod':
+        if (parts[1] === 'reload') {
+            console.log('Reloading module (' + parts[2] + ')..');
+            kiwi.kiwi_mod.reloadModule(parts[2]);
+        }
+        break;
 
-        case 'cache':
-            if (parts[1] === 'clear') {
-                kiwi.cache.html = {};
-                console.log('HTML cache cleared');
-            }
-            break;
+    case 'cache':
+        if (parts[1] === 'clear') {
+            kiwi.cache.html = {};
+            kiwi.cache.alljs = '';
+            console.log('HTML cache cleared');
+        }
+        break;
 
-           default:
-               console.log('Unknown command \'' + parts[0] + '\'');
-           }
-       });
-};
\ No newline at end of file
+    case 'status':
+        for (i in kiwi.connections) {
+            connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
+        }
+        console.log(connections_cnt.toString() + ' connected clients');
+        break;
+
+    default:
+        console.log('Unknown command \'' + parts[0] + '\'');
+    }
+};