Grafting the new server to the new backbone client
authorJack Allnutt <m2ys4u@Gmail.com>
Fri, 20 Jul 2012 14:11:27 +0000 (15:11 +0100)
committerJack Allnutt <m2ys4u@Gmail.com>
Fri, 20 Jul 2012 14:11:27 +0000 (15:11 +0100)
34 files changed:
.gitignore
client/js/backbone-0.5.3-min.js [changed mode: 0644->0755]
client/js/iscroll.js [changed mode: 0644->0755]
client/js/jquery-ui.1.8.16.min.js [changed mode: 0644->0755]
client/js/jquery.1.6.4.min.js [changed mode: 0644->0755]
client/js/jquery.json-2.2.min.js [changed mode: 0644->0755]
client/js/mobile.js [changed mode: 0644->0755]
client/js/touchscreen_tweaks.js [changed mode: 0644->0755]
client/js/underscore.min.js [changed mode: 0644->0755]
client_backbone/css/style.css [moved from client_backbone/style.css with 100% similarity]
client_backbone/index.html [deleted file]
client_backbone/index.jade [new file with mode: 0644]
client_backbone/js/backbone-git.js [moved from client_backbone/backbone-git.js with 100% similarity, mode: 0755]
client_backbone/js/jquery-1.7.1.min.js [moved from client_backbone/jquery-1.7.1.min.js with 100% similarity, mode: 0755]
client_backbone/js/model.js [moved from client_backbone/model.js with 100% similarity, mode: 0755]
client_backbone/js/model_application.js [moved from client_backbone/model_application.js with 95% similarity, mode: 0755]
client_backbone/js/model_gateway.js [moved from client_backbone/model_gateway.js with 83% similarity, mode: 0755]
client_backbone/js/underscore-min.js [moved from client_backbone/underscore-min.js with 100% similarity, mode: 0755]
client_backbone/js/utils.js [moved from client_backbone/utils.js with 100% similarity, mode: 0755]
client_backbone/js/view.js [moved from client_backbone/view.js with 93% similarity, mode: 0755]
client_backbone/manifest.json [new file with mode: 0644]
server/app.js [deleted file]
server/client.js [new file with mode: 0755]
server/http-handler.js [new file with mode: 0755]
server/irc-commands.js [new file with mode: 0755]
server/irc-connection.js [new file with mode: 0755]
server/kiwi.js
server/kiwi_modules/forcessl.js [deleted file]
server/kiwi_modules/spamfilter.js [deleted file]
server/kiwi_modules/statistics.js [deleted file]
server/lib/kiwi_mod.js [deleted file]
server/lib/starttls.js [deleted file]
server/lib/underscore.min.js [deleted file]
server/web.js [new file with mode: 0755]

index 6d95f8767ba786fcab56719bc9f0957d7b61dfc2..0880872fd603687832b6ceb156f02c0505b8b6ee 100644 (file)
@@ -1,3 +1,4 @@
 *.DS_*
 node/node_modules/
 node_modules/
+doc/
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/client_backbone/index.html b/client_backbone/index.html
deleted file mode 100644 (file)
index ea3a52a..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-<html>
-<head>
-<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> 
-
-<title> KiwiIRC </title>
-
-<link rel="stylesheet" type="text/css" href="style.css" />
-
-</head>
-<body>
-    <div id="kiwi">
-        <div id="toolbar">
-            <ul class="panellist channels"></ul>
-
-            <div id="topic">
-                <input type="text" />
-            </div>
-        </div>
-
-        <div id="panels">
-            <div class="panel_container container1"></div>
-        </div>
-
-        <div id="memberlists"></div>
-
-        <div id="controlbox">
-            <div class="input">
-                <span class="nick"> </span>
-                <div class="input_wrap"><input type="text" class="inp" /></div>
-            </div>
-        </div>
-    </div>
-
-    <script type="text/x-jquery-tmpl" id="tmpl_userbox">
-        <div class="userbox">
-            <a class="query">Message</a>
-            <a class="info">Info</a>
-        </div>
-    </script>
-
-    <script src="jquery-1.7.1.min.js"></script>
-    <script src="underscore-min.js"></script>
-    <script src="backbone-git.js"></script>
-    <script src="http://localhost:7778/socket.io/socket.io.js"></script>
-
-    <script src="utils.js"></script>
-    <script src="model.js"></script>
-    <script src="model_application.js"></script>
-    <script src="model_gateway.js"></script>
-    <script src="view.js"></script>
-
-<script>
-    $(function () {
-        kiwi.app = new kiwi.model.Application({container: $('body')[0]});
-    });
-</script>
-</body>
-</html>
\ No newline at end of file
diff --git a/client_backbone/index.jade b/client_backbone/index.jade
new file mode 100644 (file)
index 0000000..4f66130
--- /dev/null
@@ -0,0 +1,31 @@
+html
+    head
+        meta(http-equiv='Content-Type', content='text/html; charset=UTF-8')
+        title KiwiIRC 
+        link(rel='stylesheet', type='text/css', href='/css/style.css')
+        script
+            document.write(unescape('%3Cscript type="text/javascript" src="' + document.location.protocol + '//' + document.location.host + '/socket.io/socket.io.js"%3E%3C/script%3E'));
+    body
+        #kiwi
+            #toolbar
+                ul.panellist.channels
+                #topic
+                    input(type='text')
+            #panels
+                .panel_container.container1
+            #memberlists
+            #controlbox
+                .input
+                    span.nick    
+                    .input_wrap
+                        input.inp(type='text')
+        script#tmpl_userbox(type='text/x-jquery-tmpl')
+            <div class="userbox">
+            <a class="query">Message</a>
+            <a class="info">Info</a>
+            </div>
+        script(src='all.js')
+        script
+            $(function () {
+                kiwi.app = new kiwi.model.Application({container: $('body')[0]});
+            });
\ No newline at end of file
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from client_backbone/backbone-git.js
rename to client_backbone/js/backbone-git.js
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from client_backbone/jquery-1.7.1.min.js
rename to client_backbone/js/jquery-1.7.1.min.js
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from client_backbone/model.js
rename to client_backbone/js/model.js
old mode 100644 (file)
new mode 100755 (executable)
similarity index 95%
rename from client_backbone/model_application.js
rename to client_backbone/js/model_application.js
index d5cc4bc..fde35bb
@@ -13,7 +13,7 @@ kiwi.model.Application = Backbone.Model.extend(new (function () {
         this.initializeClient();\r
 \r
         kiwi.gateway.set('nick', 'kiwi_' + Math.ceil(Math.random() * 10000).toString());\r
-        kiwi.gateway.connect('ate.anonnet.org', 6667, false, false, function () {\r
+        kiwi.gateway.connect('localhost', 6667, false, false, function () {\r
             console.log('gateway connected');\r
         });\r
 \r
old mode 100644 (file)
new mode 100755 (executable)
similarity index 83%
rename from client_backbone/model_gateway.js
rename to client_backbone/js/model_gateway.js
index a901fc7..8460449
@@ -37,7 +37,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         *   @type   String
         */
         //kiwi_server: '//kiwi'
-        kiwi_server: 'http://localhost:7778/kiwi'
+        kiwi_server: document.location.protocol + '//' + document.location.host + '/kiwi'
     };
 
 
@@ -48,9 +48,9 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         // For ease of access. The socket.io object
         this.socket = this.get('socket');
 
-        // Redundant perhaps? Legacy
-        this.session_id = '';
+        this.server_num = null;
 
+        // Global variable? ~Jack
         network = this;
     };
 
@@ -90,15 +90,25 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         });
 
         this.socket.on('connect', function () {
-            this.emit('irc connect', that.get('nick'), host, port, ssl, password, callback);
-            console.log("kiwi.gateway.socket.on('connect')");
+            //{command: 'connect', nick: kiwi.gateway.nick, hostname: host, port: port, ssl: ssl, password: password}
+            this.emit('kiwi', {command: 'connect', nick: that.get('nick'), hostname: host, port: port, ssl: ssl, password:password}, function (err, server_num) {
+                console.log('err, server_num', err, server_num);
+                if (!err) {
+                    that.server_num = server_num;
+                    console.log("kiwi.gateway.socket.on('connect')");
+                } else {
+                    console.log("kiwi.gateway.socket.on('error')", {reason: err});
+                }
+            });
         });
 
         this.socket.on('too_many_connections', function () {
             this.emit("connect_fail", {reason: 'too_many_connections'});
         });
 
-        this.socket.on('message', this.parse);
+        this.socket.on('irc', function (data, callback) {
+            that.parse(data.command, data.data);
+        });
 
         this.socket.on('disconnect', function () {
             this.emit("disconnect", {});
@@ -142,18 +152,18 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
     /**
     *   Parses the response from the server
     */
-    this.parse = function (item) {
-        console.log('gateway event', item);
-        if (item.event !== undefined) {
-            that.trigger('on' + item.event, item);
+    this.parse = function (command, data) {
+        console.log('gateway event', command, data);
+        if (command !== undefined) {
+            that.trigger('on' + command, data);
 
-            switch (item.event) {
+            switch (command) {
             case 'options':
-                $.each(item.options, function (name, value) {
+                $.each(data.options, function (name, value) {
                     switch (name) {
                     case 'CHANTYPES':
                         // TODO: Check this. Why is it only getting the first char?
-                        that.set('channel_prefix', value.charAt(0));
+                        that.set('channel_prefix', value.join('').charAt(0));
                         break;
                     case 'NETWORK':
                         that.set('name', value);
@@ -166,11 +176,11 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
                 break;
 
             case 'connect':
-                that.set('nick', item.nick);
+                that.set('nick', data.nick);
                 break;
 
             case 'nick':
-                that.set('nick', item.newnick);
+                that.set('nick', data.newnick);
                 break;
             /*
             case 'sync':
@@ -182,7 +192,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
             */
 
             case 'kiwi':
-                this.emit('kiwi.' + item.namespace, item.data);
+                this.emit('kiwi.' + data.namespace, data);
                 break;
             }
         }
@@ -195,7 +205,9 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
     *   @param  {Function}  callback    A callback function
     */
     this.sendData = function (data, callback) {
-        this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback);
+        //this.socket.emit('message', {sid: this.session_id, data: JSON.stringify(data)}, callback);
+        //kiwi.gateway.socket.emit('irc', {server: this.server_num, data: $.toJSON(data)}, callback);
+        this.socket.emit('irc', {server: this.server_num, data: JSON.stringify(data)}, callback);
     };
 
     /**
@@ -208,8 +220,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'privmsg',
             args: {
-                target: target,
-                msg: msg
+                params: [target],
+                trailing: msg
             }
         };
 
@@ -226,8 +238,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'notice',
             args: {
-                target: target,
-                msg: msg
+                params: [target],
+                trailing: msg
             }
         };
 
@@ -275,8 +287,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'join',
             args: {
-                channel: channel,
-                key: key
+                params: [channel, key]
             }
         };
 
@@ -292,7 +303,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'part',
             args: {
-                channel: channel
+                params: channel
             }
         };
 
@@ -309,8 +320,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'topic',
             args: {
-                channel: channel,
-                topic: new_topic
+                params: [channel],
+                trailing: new_topic
             }
         };
 
@@ -328,9 +339,8 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'kick',
             args: {
-                channel: channel,
-                nick: nick,
-                reason: reason
+                params: [channel, nick],
+                trailing: reason
             }
         };
 
@@ -347,7 +357,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'quit',
             args: {
-                message: msg
+                trailing: msg
             }
         };
 
@@ -379,7 +389,7 @@ kiwi.model.Gateway = Backbone.Model.extend(new (function () {
         var data = {
             method: 'nick',
             args: {
-                nick: new_nick
+                params: [new_nick]
             }
         };
 
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from client_backbone/underscore-min.js
rename to client_backbone/js/underscore-min.js
old mode 100644 (file)
new mode 100755 (executable)
similarity index 100%
rename from client_backbone/utils.js
rename to client_backbone/js/utils.js
old mode 100644 (file)
new mode 100755 (executable)
similarity index 93%
rename from client_backbone/view.js
rename to client_backbone/js/view.js
index 65158fe..b19d5bc
@@ -157,7 +157,9 @@ kiwi.view.Panel = Backbone.View.extend({
     // Scroll to the bottom of the panel\r
     scrollToBottom: function () {\r
         // TODO: Don't scroll down if we're scrolled up the panel a little\r
-        this.$container[0].scrollTop = this.$container[0].scrollHeight;\r
+        if (this.$container[0]) {\r
+            this.$container[0].scrollTop = this.$container[0].scrollHeight;\r
+        }\r
     }\r
 });\r
 \r
@@ -325,9 +327,9 @@ kiwi.view.ControlBox = Backbone.View.extend({
 \r
         // If we didn't have any listeners for this event, fire a special case\r
         // TODO: This feels dirty. Should this really be done..?\r
-        if (!this._callbacks['command' + command]) {\r
+        /*if (!this._callbacks['command' + command]) {\r
             this.trigger('unknown_command', {command: command, params: params});\r
-        }\r
+        }*/\r
     }\r
 });\r
 \r
@@ -356,8 +358,10 @@ kiwi.view.Application = Backbone.View.extend({
         }\r
 \r
         // If we're typing into an input box somewhere, ignore\r
-        if (ev.srcElement.tagName.toLowerCase() === 'input') {\r
-            return;\r
+        if (ev.secElement) {\r
+            if (ev.srcElement.tagName.toLowerCase() === 'input') {\r
+                return;\r
+            }\r
         }\r
 \r
         $('#controlbox .inp').focus();\r
diff --git a/client_backbone/manifest.json b/client_backbone/manifest.json
new file mode 100644 (file)
index 0000000..48eec3d
--- /dev/null
@@ -0,0 +1,13 @@
+{\r
+    "js": [\r
+        "jquery-1.7.1.min.js",\r
+        "underscore-min.js",\r
+        "backbone-git.js",\r
+        "utils.js",\r
+        "model.js",\r
+        "model_application.js",\r
+        "model_gateway.js",\r
+        "view.js"\r
+    ],\r
+    "css": ["style.css"]\r
+}
\ No newline at end of file
diff --git a/server/app.js b/server/app.js
deleted file mode 100755 (executable)
index fa08a42..0000000
+++ /dev/null
@@ -1,1252 +0,0 @@
-/*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
-/*globals kiwi_root */
-/* Fuck you, git. */
-var tls = null,
-    net = null,
-    http = null,
-    https = null,
-    fs = null,
-    url = null,
-    dns = null,
-    crypto = null,
-    events = null,
-    util = null,
-    ws = null,
-    jsp = null,
-    pro = null,
-    _ = null,
-    starttls = null,
-    kiwi = null;
-
-this.init = function (objs) {
-    tls = objs.tls;
-    net = objs.net;
-    http = objs.http;
-    https = objs.https;
-    fs = objs.fs;
-    url = objs.url;
-    dns = objs.dns;
-    crypto = objs.crypto;
-    events = objs.events;
-    util = objs.util;
-    ws = objs.ws;
-    jsp = objs.jsp;
-    pro = objs.pro;
-    _ = objs._;
-    starttls = objs.starttls;
-    kiwi = require('./kiwi.js');
-
-    util.inherits(this.IRCConnection, events.EventEmitter);
-};
-
-
-
-
-
-
-/*
- * Some process changes
- */
-this.setTitle = function () {
-    process.title = 'kiwiirc';
-};
-
-this.changeUser = function () {
-    if (typeof kiwi.config.group !== 'undefined' && kiwi.config.group !== '') {
-        try {
-            process.setgid(kiwi.config.group);
-        } catch (err) {
-            kiwi.log('Failed to set gid: ' + err);
-            process.exit();
-        }
-    }
-
-    if (typeof kiwi.config.user !== 'undefined' && kiwi.config.user !== '') {
-        try {
-            process.setuid(kiwi.config.user);
-        } catch (e) {
-            kiwi.log('Failed to set uid: ' + e);
-            process.exit();
-        }
-    }
-};
-
-
-
-
-
-
-
-
-
-var ircNumerics = {
-    RPL_WELCOME:            '001',
-    RPL_MYINFO:             '004',
-    RPL_ISUPPORT:           '005',
-    RPL_WHOISUSER:          '311',
-    RPL_WHOISSERVER:        '312',
-    RPL_WHOISOPERATOR:      '313',
-    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_TOPICWHOTIME:       '333',
-    RPL_NAMEREPLY:          '353',
-    RPL_ENDOFNAMES:         '366',
-    RPL_BANLIST:            '367',
-    RPL_ENDOFBANLIST:       '368',
-    RPL_MOTD:               '372',
-    RPL_MOTDSTART:          '375',
-    RPL_ENDOFMOTD:          '376',
-    RPL_WHOISMODES:         '379',
-    ERR_NOSUCHNICK:         '401',
-    ERR_CANNOTSENDTOCHAN:   '404',
-    ERR_TOOMANYCHANNELS:    '405',
-    ERR_NICKNAMEINUSE:      '433',
-    ERR_USERNOTINCHANNEL:   '441',
-    ERR_NOTONCHANNEL:       '442',
-    ERR_NOTREGISTERED:      '451',
-    ERR_LINKCHANNEL:        '470',
-    ERR_CHANNELISFULL:      '471',
-    ERR_INVITEONLYCHAN:     '473',
-    ERR_BANNEDFROMCHAN:     '474',
-    ERR_BADCHANNELKEY:      '475',
-    ERR_CHANOPRIVSNEEDED:   '482',
-    RPL_STARTTLS:           '670'
-};
-
-this.bindIRCCommands = function (irc_connection, websocket) {
-    var bound_events = [],
-        bindCommand = function (command, listener) {
-            command = 'irc_' + command;
-            irc_connection.on(command, listener);
-            bound_events.push({"command": command, "listener": listener});
-        };
-
-    bindCommand('PING', function (msg) {
-        websocket.sendServerLine('PONG ' + msg.trailing);
-    });
-
-    bindCommand(ircNumerics.RPL_WELCOME, function (msg) {
-        if (irc_connection.IRC.CAP.negotiating) {
-            irc_connection.IRC.CAP.negotiating = false;
-            irc_connection.IRC.CAP.enabled = [];
-            irc_connection.IRC.CAP.requested = [];
-            irc_connection.IRC.registered = true;
-        }
-        var nick =  msg.params.split(' ')[0];
-        websocket.sendClientEvent('connect', {connected: true, host: null, nick: nick});
-    });
-
-    bindCommand(ircNumerics.RPL_ISUPPORT, function (msg) {
-        var opts = msg.params.split(" "),
-            opt,
-            i,
-            j,
-            regex,
-            matches;
-        for (i = 0; i < opts.length; i++) {
-            opt = opts[i].split("=", 2);
-            opt[0] = opt[0].toUpperCase();
-            irc_connection.IRC.options[opt[0]] = (typeof opt[1] !== 'undefined') ? opt[1] : true;
-            if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt[0])) {
-                if (opt[0] === 'PREFIX') {
-                    regex = /\(([^)]*)\)(.*)/;
-                    matches = regex.exec(opt[1]);
-                    if ((matches) && (matches.length === 3)) {
-                        irc_connection.IRC.options[opt[0]] = [];
-                        for (j = 0; j < matches[2].length; j++) {
-                            irc_connection.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
-                        }
-
-                    }
-                }
-                if (opt[0] === 'NAMESX') {
-                    websocket.sendServerLine('PROTOCTL NAMESX');
-                }
-            }
-        }
-
-        websocket.sendClientEvent('options', {server: '', "options": irc_connection.IRC.options});
-    });
-
-    bindCommand(ircNumerics.RPL_ENDOFWHOIS, function (msg) {
-        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: true});
-    });
-
-    bindCommand(ircNumerics.RPL_WHOISUSER, function (msg) {
-        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
-    });
-
-    bindCommand(ircNumerics.RPL_WHOISSERVER, function (msg) {
-        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
-    });
-
-    bindCommand(ircNumerics.RPL_WHOISOPERATOR, function (msg) {
-        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
-    });
-
-    bindCommand(ircNumerics.RPL_WHOISCHANNELS, function (msg) {
-        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
-    });
-
-    bindCommand(ircNumerics.RPL_WHOISMODES, function (msg) {
-        websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
-    });
-
-    bindCommand(ircNumerics.RPL_LISTSTART, function (msg) {
-        websocket.sendClientEvent('list_start', {server: ''});
-        websocket.kiwi.buffer.list = [];
-    });
-
-    bindCommand(ircNumerics.RPL_LISTEND, function (msg) {
-        if (websocket.kiwi.buffer.list.length > 0) {
-            websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
-                return channel.num_users;
-            });
-            websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
-            websocket.kiwi.buffer.list = [];
-        }
-        websocket.sendClientEvent('list_end', {server: ''});
-    });
-
-    bindCommand(ircNumerics.RPL_LIST, function (msg) {
-        var parts, channel, num_users, topic;
-
-        parts = msg.params.split(' ');
-        channel = parts[1];
-        num_users = parts[2];
-        topic = msg.trailing;
-
-        //websocket.sendClientEvent('list_channel', {
-        websocket.kiwi.buffer.list.push({
-            server: '',
-            channel: channel,
-            topic: topic,
-            //modes: modes,
-            num_users: parseInt(num_users, 10)
-        });
-
-        if (websocket.kiwi.buffer.list.length > 200) {
-            websocket.kiwi.buffer.list = _.sortBy(websocket.kiwi.buffer.list, function (channel) {
-                return channel.num_users;
-            });
-            websocket.sendClientEvent('list_channel', {chans: websocket.kiwi.buffer.list});
-            websocket.kiwi.buffer.list = [];
-        }
-    });
-
-    bindCommand(ircNumerics.RPL_WHOISIDLE, function (msg) {
-        var params = msg.params.split(" ", 4),
-            rtn = {server: '', nick: params[1], idle: params[2]};
-        if (params[3]) {
-            rtn.logon = params[3];
-        }
-        websocket.sendClientEvent('whois', rtn);
-    });
-
-    bindCommand(ircNumerics.RPL_MOTD, function (msg) {
-        websocket.kiwi.buffer.motd += msg.trailing + '\n';
-    });
-
-    bindCommand(ircNumerics.RPL_MOTDSTART, function (msg) {
-        websocket.kiwi.buffer.motd = '';
-    });
-
-    bindCommand(ircNumerics.RPL_ENDOFMOTD, function (msg) {
-        websocket.sendClientEvent('motd', {server: '', 'msg': websocket.kiwi.buffer.motd});
-    });
-
-    bindCommand(ircNumerics.RPL_NAMEREPLY, function (msg) {
-        var params = msg.params.split(" "),
-            chan = params[2],
-            users = msg.trailing.split(" "),
-            nicklist = [],
-            i = 0;
-
-        _.each(users, function (user) {
-            var j, k, modes = [];
-            for (j = 0; j < user.length; j++) {
-                for (k = 0; k < irc_connection.IRC.options.PREFIX.length; k++) {
-                    if (user.charAt(j) === irc_connection.IRC.options.PREFIX[k].symbol) {
-                        modes.push(irc_connection.IRC.options.PREFIX[k].mode);
-                    }
-                }
-            }
-            nicklist.push({nick: user, modes: modes});
-            if (i++ >= 50) {
-                websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
-                nicklist = [];
-                i = 0;
-            }
-        });
-        if (i > 0) {
-            websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
-        } else {
-            kiwi.log("oops");
-        }
-    });
-
-    bindCommand(ircNumerics.RPL_ENDOFNAMES, function (msg) {
-        websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
-    });
-
-    bindCommand(ircNumerics.ERR_LINKCHANNEL, function (msg) {
-        var params = msg.params.split(" ");
-        websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
-    });
-
-    bindCommand(ircNumerics.ERR_NOSUCHNICK, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.RPL_BANLIST, function (msg) {
-        var params = msg.params.split(" ");
-        kiwi.log(params);
-        websocket.sendClientEvent('banlist', {server: '', channel: params[1], banned: params[2], banned_by: params[3], banned_at: params[4]});
-    });
-
-    bindCommand(ircNumerics.RPL_ENDOFBANLIST, function (msg) {
-        websocket.sendClientEvent('banlist_end', {server: '', channel: msg.params.split(" ")[1]});
-    });
-
-    bindCommand('JOIN', function (msg) {
-        var channel;
-
-        // Some BNC's send malformed JOIN causing the channel to be as a
-        // parameter instead of trailing.
-        if (typeof msg.trailing === 'string' && msg.trailing !== '') {
-            channel = msg.trailing;
-        } else if (typeof msg.params === 'string' && msg.params !== '') {
-            channel = msg.params;
-        }
-
-        websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
-        if (msg.nick === irc_connection.IRC.nick) {
-            websocket.sendServerLine('NAMES ' + msg.trailing);
-        }
-    });
-
-    bindCommand('PART', function (msg) {
-        websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
-    });
-
-    bindCommand('KICK', function (msg) {
-        var params = msg.params.split(" ");
-        websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
-    });
-
-    bindCommand('QUIT', function (msg) {
-        websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
-    });
-
-    bindCommand('NOTICE', function (msg) {
-        if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
-            // It's a CTCP response
-            websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
-        } else {
-            websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
-        }
-    });
-
-    bindCommand('NICK', function (msg) {
-        websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
-    });
-
-    bindCommand('TOPIC', function (msg) {
-        var obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
-        websocket.sendClientEvent('topic', obj);
-    });
-
-    bindCommand(ircNumerics.RPL_TOPIC, function (msg) {
-        var obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
-        websocket.sendClientEvent('topic', obj);
-    });
-
-    bindCommand(ircNumerics.RPL_NOTOPIC, function (msg) {
-        var obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
-        websocket.sendClientEvent('topic', obj);
-    });
-
-    bindCommand(ircNumerics.RPL_TOPICWHOTIME, function (msg) {
-        var parts = msg.params.split(' '),
-            nick = parts[2],
-            channel = parts[1],
-            when = parts[3],
-            obj = {nick: nick, channel: channel, when: when};
-        websocket.sendClientEvent('topicsetby', obj);
-    });
-
-    bindCommand('MODE', function (msg) {
-        var opts = msg.params.split(" "),
-            params = {nick: msg.nick};
-
-        switch (opts.length) {
-        case 1:
-            params.effected_nick = opts[0];
-            params.mode = msg.trailing;
-            break;
-        case 2:
-            params.channel = opts[0];
-            params.mode = opts[1];
-            break;
-        default:
-            params.channel = opts[0];
-            params.mode = opts[1];
-            params.effected_nick = opts[2];
-            break;
-        }
-        websocket.sendClientEvent('mode', params);
-    });
-
-    bindCommand('PRIVMSG', function (msg) {
-        var tmp, namespace, obj;
-        if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
-            // 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') {
-                irc_connection.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
-            } else {
-                websocket.sendClientEvent('ctcp_request', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
-            }
-        } else {
-            obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
-            websocket.sendClientEvent('msg', obj);
-        }
-    });
-
-    bindCommand('CAP', function (msg) {
-        var caps = kiwi.config.cap_options,
-            options = msg.trailing.split(" "),
-            opts;
-
-        switch (_.last(msg.params.split(" "))) {
-        case 'LS':
-            opts = '';
-            _.each(_.intersect(caps, options), function (cap) {
-                if (opts !== '') {
-                    opts += " ";
-                }
-                opts += cap;
-                irc_connection.IRC.CAP.requested.push(cap);
-            });
-            if (opts.length > 0) {
-                websocket.sendServerLine('CAP REQ :' + opts);
-            } else {
-                websocket.sendServerLine('CAP END');
-            }
-            // TLS is special
-            /*if (_.include(options, 'tls')) {
-                websocket.sendServerLine('STARTTLS');
-                ircSocket.IRC.CAP.requested.push('tls');
-            }*/
-            break;
-        case 'ACK':
-            _.each(options, function (cap) {
-                irc_connection.IRC.CAP.enabled.push(cap);
-            });
-            if (_.last(msg.params.split(" ")) !== '*') {
-                irc_connection.IRC.CAP.requested = [];
-                irc_connection.IRC.CAP.negotiating = false;
-                websocket.sendServerLine('CAP END');
-            }
-            break;
-        case 'NAK':
-            irc_connection.IRC.CAP.requested = [];
-            irc_connection.IRC.CAP.negotiating = false;
-            websocket.sendServerLine('CAP END');
-            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);
-                    });
-                });
-                //log(ircSocket);
-            } catch (e) {
-                kiwi.log(e);
-            }
-            break;*/
-    bindCommand(ircNumerics.ERR_CANNOTSENDTOCHAN, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_TOOMANYCHANNELS, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_USERNOTINCHANNEL, function (msg) {
-        var params = msg.params.split(" ");
-        websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
-    });
-
-    bindCommand(ircNumerics.ERR_NOTONCHANNEL, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_CHANNELISFULL, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_INVITEONLYCHAN, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_BANNEDFROMCHAN, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_BADCHANNELKEY, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_CHANOPRIVSNEEDED, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
-    });
-
-    bindCommand(ircNumerics.ERR_NICKNAMEINUSE, function (msg) {
-        websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
-    });
-
-    bindCommand('ERROR', function (msg) {
-        irc_connection.end();
-        websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
-        websocket.disconnect();
-    });
-
-    bindCommand(ircNumerics.ERR_NOTREGISTERED, function (msg) {
-        if (irc_connection.IRC.registered) {
-            kiwi.log('Kiwi thinks user is registered, but the IRC server thinks differently');
-        }
-    });
-
-    return bound_events;
-};
-
-this.rebindIRCCommands = function () {
-    _.each(kiwi.connections, function (con) {
-        _.each(con.sockets, function (sock) {
-            sock.ircConnection.rebindIRCCommands();
-        });
-    });
-};
-
-
-this.httpHandler = function (request, response, serverconf) {
-    var uri, uri_parts, subs, useragent, agent, server_set, serverconf, nick, debug, touchscreen, hash,
-        min = {}, public_http_path, port, ssl, obj, args, ircuri, target, modifiers, query,
-        secure = serverconf.secure || false;
-
-    try {
-        if (kiwi.config.handle_http) {
-            // Run through any plugins..
-            args = {request: request, response: response, ssl: secure};
-            obj = kiwi.kiwi_mod.run('http', args);
-            if (obj === null) {
-                return;
-            }
-            response = args.response;
-
-            uri = url.parse(request.url, true);
-            uri_parts = uri.pathname.split('/');
-
-            subs = uri.pathname.substr(0, 4);
-            public_http_path = kiwi.kiwi_root + '/' + kiwi.config.public_http;
-
-            if (typeof uri.query.ircuri !== 'undefined') {
-                ircuri = url.parse(uri.query.ircuri, true);
-                if (ircuri.protocol === 'irc:') {
-                    uri_parts = /^\/([^,\?]*)((,[^,\?]*)*)?$/.exec(ircuri.pathname);
-                    target = uri_parts[1];
-                    modifiers = (typeof uri_parts[2] !== 'undefined') ? uri_parts[2].split(',') : [];
-                    query = ircuri.query;
-
-                    nick = _.detect(modifiers, function (mod) {
-                        return mod === ',isnick';
-                    });
-                    kiwi.log(request.headers);
-                    response.statusCode = 303;
-                    response.setHeader('Location', 'http' + ((secure) ? 's' : '') + '://' + request.headers.host + '/client/' + ircuri.host + '/' + ((!nick) ? target : ''));
-                    response.end();
-                }
-            } else if (uri.pathname === '/js/all.js') {
-                if (kiwi.cache.alljs === '') {
-
-                    min.underscore = fs.readFileSync(public_http_path + 'js/underscore.min.js');
-                    min.util = fs.readFileSync(public_http_path + 'js/util.js');
-                    min.backbone = fs.readFileSync(public_http_path + 'js/backbone-git.js');
-                    min.gateway = fs.readFileSync(public_http_path + 'js/gateway.js');
-                    min.model = fs.readFileSync(public_http_path + 'js/model.js');
-                    min.view = fs.readFileSync(public_http_path + 'js/view.js');
-                    min.front = fs.readFileSync(public_http_path + 'js/front.js');
-                    min.front_events = fs.readFileSync(public_http_path + 'js/front.events.js');
-                    min.front_ui = fs.readFileSync(public_http_path + 'js/front.ui.js');
-                    min.iscroll = fs.readFileSync(public_http_path + 'js/iscroll.js');
-                    min.ast = jsp.parse(min.underscore + min.util + min.backbone + min.gateway + min.model + min.view + min.front + min.front_events + min.front_ui + min.iscroll);
-                    min.ast = pro.ast_mangle(min.ast);
-                    min.ast = pro.ast_squeeze(min.ast);
-                    min.final_code = pro.gen_code(min.ast);
-                    kiwi.cache.alljs = min.final_code;
-                    hash = crypto.createHash('md5').update(kiwi.cache.alljs);
-                    kiwi.cache.alljs_hash = hash.digest('base64');
-                }
-                if (request.headers['if-none-match'] === kiwi.cache.alljs_hash) {
-                    response.statusCode = 304;
-                } else {
-                    response.setHeader('Content-type', 'application/javascript');
-                    response.setHeader('ETag', kiwi.cache.alljs_hash);
-                    if ((secure) && (serverconf.hsts)) {
-                        response.setHeader("Strict-Transport-Security", "max-age=604 800");
-                    }
-                    response.write(kiwi.cache.alljs);
-                }
-                response.end();
-            } else if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
-                request.addListener('end', function () {
-                    kiwi.fileServer.serve(request, response);
-                });
-            } 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.match(/iphone/) !== -1) {
-                    agent = 'iphone';
-                    touchscreen = true;
-                } else if (useragent.match(/ipad/) !== -1) {
-                    agent = 'ipad';
-                    touchscreen = true;
-                } else if (useragent.match(/ipod/) !== -1) {
-                    agent = 'ipod';
-                    touchscreen = true;
-                } else {
-                    agent = 'normal';
-                    touchscreen = false;
-                }
-                agent = 'normal';
-                touchscreen = false;
-
-                debug = (typeof uri.query.debug !== 'undefined');
-
-                ssl = secure;   // ssl is passed to the client
-                port = ssl ? kiwi.config.client_defaults.port_ssl : kiwi.config.client_defaults.port;
-                if (uri_parts[1] !== 'client') {
-                    if (uri.query) {
-                        server_set = ((typeof uri.query.server !== 'undefined') && (uri.query.server !== ''));
-                        server = uri.query.server || kiwi.config.client_defaults.server;
-                        nick = uri.query.nick || '';
-                    } else {
-                        server_set = false;
-                        server = kiwi.config.client_defaults.server;
-                        nick = '';
-                    }
-                } else {
-                    server_set = ((typeof uri_parts[2] !== 'undefined') && (uri_parts[2] !== ''));
-                    server = server_set ? uri_parts[2] : kiwi.config.client_defaults.server;
-                    if (server.search(/:/) > 0) {
-                        port = server.substring(server.search(/:/) + 1);
-                        server = server.substring(0, server.search(/:/));
-                        if (port[0] === '+') {
-                            port = port.substring(1);
-                            ssl = true;
-                        } else {
-                            ssl = false;
-                        }
-                    }
-                    nick = uri.query.nick || '';
-                }
-
-                // Set the default nick if one isn't provided
-                if (nick === '') {
-                    nick = 'kiwi_?';
-                }
-
-                // Set any random numbers if needed
-                nick = nick.replace('?', Math.floor(Math.random() * 100000).toString());
-
-                response.setHeader('X-Generated-By', 'KiwiIRC');
-                hash = crypto.createHash('md5').update(touchscreen ? 't' : 'f')
-                    .update(debug ? 't' : 'f')
-                    .update(server_set ? 't' : 'f')
-                    .update(secure ? 't' : 'f')
-                    .update(server)
-                    .update(port.toString())
-                    .update(ssl  ? 't' : 'f')
-                    .update(nick)
-                    .update(agent)
-                    .update(JSON.stringify(kiwi.config))
-                    .digest('base64');
-                if (kiwi.cache.html[hash]) {
-                    if (request.headers['if-none-match'] === kiwi.cache.html[hash].hash) {
-                        response.statusCode = 304;
-                    } else {
-                        response.setHeader('Etag', kiwi.cache.html[hash].hash);
-                        response.setHeader('Content-type', 'text/html');
-                        if ((secure) && (serverconf.hsts)) {
-                            response.setHeader("Strict-Transport-Security", "max-age=604 800");
-                        }
-                        response.write(kiwi.cache.html[hash].html);
-                    }
-                    response.end();
-                } else {
-                    fs.readFile(public_http_path + 'index.html.jade', 'utf8', function (err, str) {
-                        var html, hash2;
-                        if (!err) {
-                            try {
-                                html = kiwi.jade.compile(str)({ "touchscreen": touchscreen, "debug": debug, "secure": secure, "server_set": server_set, "server": server, "port": port, "ssl": ssl, "nick": nick, "agent": agent, "config": kiwi.config });
-                                hash2 = crypto.createHash('md5').update(html).digest('base64');
-                                kiwi.cache.html[hash] = {"html": html, "hash": hash2};
-                                if (request.headers['if-none-match'] === hash2) {
-                                    response.statusCode = 304;
-                                } else {
-                                    response.setHeader('Etag', hash2);
-                                    response.setHeader('Content-type', 'text/html');
-                                    if ((secure) && (serverconf.hsts)) {
-                                        response.setHeader("Strict-Transport-Security", "max-age=604 800");
-                                    }
-                                    response.write(html);
-                                }
-                            } catch (e) {
-                                response.statusCode = 500;
-                                kiwi.log(e);
-                            }
-                        } else {
-                            kiwi.log(err);
-                            response.statusCode = 500;
-                        }
-                        response.end();
-                    });
-                }
-            } else if (uri.pathname.substr(0, 10) === '/socket.io') {
-                return;
-            } else {
-                response.statusCode = 404;
-                response.end();
-            }
-        }
-
-    } catch (e) {
-        kiwi.log('ERROR app.httpHandler()');
-        kiwi.log(e);
-    }
-};
-
-
-
-
-this.websocketListen = function (servers, handler) {
-    if (kiwi.httpServers.length > 0) {
-        _.each(kiwi.httpServers, function (hs) {
-            hs.close();
-        });
-        kiwi.httpsServers = [];
-    }
-
-    _.each(servers, function (server) {
-        var hs, opts;
-        if (server.secure === true) {
-            // Start some SSL server up
-            opts = {
-                key: fs.readFileSync(__dirname + '/' + server.ssl_key),
-                cert: fs.readFileSync(__dirname + '/' + server.ssl_cert)
-            };
-
-            // Do we have an intermediate certificate?
-            if (typeof server.ssl_ca !== 'undefined') {
-                opts.ca = fs.readFileSync(__dirname + '/' + server.ssl_ca);
-            }
-
-            hs = https.createServer(opts, function (request, response) {
-                handler(request, response, server);
-            });
-            kiwi.io.push(ws.listen(hs, {secure: true}));
-            hs.listen(server.port, server.address);
-            kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' with SSL');
-        } else {
-            // Start some plain-text server up
-            hs = http.createServer(function (request, response) {
-                handler(request, response, server);
-            });
-            kiwi.io.push(ws.listen(hs, {secure: false}));
-            hs.listen(server.port, server.address);
-            kiwi.log('Listening on ' + server.address + ':' + server.port.toString() + ' without SSL');
-        }
-
-        kiwi.httpServers.push(hs);
-    });
-
-    _.each(kiwi.io, function (io) {
-        io.set('log level', 1);
-        io.enable('browser client minification');
-        io.enable('browser client etag');
-        io.set('transports', kiwi.config.transports);
-
-        io.of('/kiwi').authorization(function (handshakeData, callback) {
-            var address = handshakeData.address.address;
-            if (typeof kiwi.connections[address] === 'undefined') {
-                kiwi.connections[address] = {count: 0, sockets: []};
-            }
-            callback(null, true);
-        }).on('connection', kiwi.websocketConnection);
-        io.of('/kiwi').on('error', console.log);
-    });
-};
-
-
-
-
-
-
-this.websocketConnection = function (websocket) {
-    var con;
-    kiwi.log("New connection!");
-    websocket.kiwi = {address: websocket.handshake.address.address, buffer: {list: []}};
-    con = kiwi.connections[websocket.kiwi.address];
-
-    if (con.count >= kiwi.config.max_client_conns) {
-        websocket.emit('too_many_connections');
-        websocket.disconnect();
-    } else {
-        con.count += 1;
-        con.sockets.push(websocket);
-
-        websocket.sendClientEvent = function (event_name, data) {
-            var ev = kiwi.kiwi_mod.run(event_name, data, {websocket: this});
-            if (ev === null) {
-                return;
-            }
-
-            data.event = event_name;
-            websocket.emit('message', data);
-        };
-
-        websocket.sendServerLine = function (data, eol, callback) {
-            if ((arguments.length < 3) && (typeof eol === 'function')) {
-                callback = eol;
-            }
-            eol = (typeof eol !== 'string') ? '\r\n' : eol;
-
-            try {
-                websocket.ircConnection.write(data + eol, 'utf-8', callback);
-            } catch (e) { }
-        };
-
-        websocket.on('irc connect', function (nick, host, port, ssl, password, callback) {
-            websocket.ircConnection = new kiwi.IRCConnection(this, nick, host, port, ssl, password, callback);
-        });
-        websocket.on('message', kiwi.websocketMessage);
-        websocket.on('disconnect', kiwi.websocketDisconnect);
-        websocket.on('error', console.log);
-    }
-};
-
-
-this.IRCConnection = function (websocket, nick, host, port, ssl, password, callback) {
-    var ircSocket,
-        that = this,
-        regex,
-        onConnectHandler,
-        bound_events;
-
-    events.EventEmitter.call(this);
-
-    onConnectHandler = function () {
-        that.IRC.nick = nick;
-        // Send the login data
-        dns.reverse(websocket.kiwi.address, function (err, domains) {
-            websocket.kiwi.hostname = (err) ? websocket.kiwi.address : _.first(domains);
-            if ((kiwi.config.webirc) && (kiwi.config.webirc_pass[host])) {
-                websocket.sendServerLine('WEBIRC ' + kiwi.config.webirc_pass[host] + ' KiwiIRC ' + websocket.kiwi.hostname + ' ' + websocket.kiwi.address);
-            }
-            if (password) {
-                websocket.sendServerLine('PASS ' + password);
-            }
-            websocket.sendServerLine('CAP LS');
-            websocket.sendServerLine('NICK ' + nick);
-            websocket.sendServerLine('USER kiwi_' + nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick);
-
-            that.connected = true;
-            that.emit('connect');
-        });
-    };
-
-    if (!ssl) {
-        ircSocket = net.createConnection(port, host);
-        ircSocket.on('connect', onConnectHandler);
-    } else {
-        ircSocket = tls.connect(port, host, {}, onConnectHandler);
-    }
-
-    ircSocket.setEncoding('utf-8');
-    this.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
-
-    this.on('error', function (e) {
-        if (that.IRC.registered) {
-            websocket.emit('disconnect');
-        } else {
-            websocket.emit('error', e.message);
-        }
-    });
-
-    ircSocket.on('error', function (e) {
-        that.connected = false;
-        that.emit('error', e);
-        that.destroySoon();
-    });
-
-    if (typeof callback === 'function') {
-        this.on('connect', callback);
-    }
-
-    regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
-    ircSocket.holdLast = false;
-    ircSocket.held = '';
-    ircSocket.on('data', function (data) {
-        var i, msg;
-        if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
-            data = ircSocket.held + data;
-            ircSocket.holdLast = false;
-            ircSocket.held = '';
-        }
-        if (data.substr(-1) !== '\n') {
-            ircSocket.holdLast = true;
-        }
-        data = data.split("\n");
-        for (i = 0; i < data.length; i++) {
-            if (data[i]) {
-                if ((ircSocket.holdLast) && (i === data.length - 1)) {
-                    ircSocket.held = data[i];
-                    break;
-                }
-
-                // We have a complete line of data, parse it!
-                msg = regex.exec(data[i].replace(/^\r+|\r+$/, ''));
-                if (msg) {
-                    msg = {
-                        prefix:     msg[1],
-                        nick:       msg[2],
-                        ident:      msg[3],
-                        hostname:   msg[4] || '',
-                        command:    msg[5],
-                        params:     msg[6] || '',
-                        trailing:   (msg[7]) ? msg[7].trim() : ''
-                    };
-                    that.emit('irc_' + msg.command.toUpperCase(), msg);
-                    if (that.listeners('irc_' + msg.command.toUpperCase()).length < 1) {
-                        kiwi.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
-                    }
-                } else {
-                    kiwi.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
-                }
-            }
-        }
-    });
-
-    if (callback) {
-        ircSocket.on('connect', callback);
-    }
-
-    ircSocket.on('end', function () {
-        that.connected = false;
-        that.emit('disconnect', false);
-    });
-
-    ircSocket.on('close', function (had_error) {
-        that.connected = false;
-        that.emit('disconnect', had_error);
-    });
-
-    ircSocket.on('timeout', function () {
-        ircSocket.destroy();
-        that.connected = false;
-        that.emit('error', {message: 'Connection timed out'});
-    });
-
-    ircSocket.on('drain', function () {
-        that.emit('drain');
-    });
-
-    this.write = function (data, encoding, callback) {
-        ircSocket.write(data, encoding, callback);
-    };
-
-    this.end = function (data, encoding, callback) {
-        that.connected = false;
-        ircSocket.end(data, encoding, callback);
-    };
-
-    this.destroySoon = function () {
-        ircSocket.destroySoon();
-    };
-
-    bound_events = kiwi.bindIRCCommands(this, websocket);
-
-    this.rebindIRCCommands = function () {
-        _.each(bound_events, function (event) {
-            that.removeListener(event.command, event.listener);
-        });
-        bound_events = kiwi.bindIRCCommands(that, websocket);
-    };
-    
-    that.on('error', console.log);
-    
-};
-
-
-
-this.websocketMessage = function (websocket, msg, callback) {
-    var args, obj, channels, keys;
-    //try {
-        if ((callback) && (typeof (callback) !== 'function')) {
-            callback = null;
-        }
-        try {
-            msg.data = JSON.parse(msg.data);
-        } catch (e) {
-            kiwi.log('[app.websocketMessage] JSON parsing error ' + msg.data);
-            return;
-        }
-        args = msg.data.args;
-        switch (msg.data.method) {
-        case 'privmsg':
-            if ((args.target) && (args.msg)) {
-                obj = kiwi.kiwi_mod.run('msgsend', args, {websocket: websocket});
-                if (obj !== null) {
-                    websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg, callback);
-                }
-            }
-            break;
-        case 'ctcp':
-            if ((args.target) && (args.type)) {
-                if (args.request) {
-                    websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1), callback);
-                } else {
-                    websocket.sendServerLine('NOTICE ' + args.target + ' :' + String.fromCharCode(1) + args.type.toUpperCase() + ' ' + args.params + String.fromCharCode(1), callback);
-                }
-            }
-            break;
-
-        case 'raw':
-            websocket.sendServerLine(args.data, callback);
-            break;
-
-        case 'join':
-            if (args.channel) {
-                channels = args.channel.split(",");
-                keys = (args.key) ? args.key.split(",") : [];
-                _.each(channels, function (chan, index) {
-                    websocket.sendServerLine('JOIN ' + chan + ' ' + (keys[index] || ''), callback);
-                });
-            }
-            break;
-
-        case 'part':
-            if (args.channel) {
-                _.each(args.channel.split(","), function (chan) {
-                    websocket.sendServerLine('PART ' + chan, callback);
-                });
-            }
-            break;
-
-        case 'topic':
-            if (args.channel) {
-                if (args.topic) {
-                    websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic, callback);
-                } else {
-                    websocket.sendServerLine('TOPIC ' + args.channel, callback);
-                }
-            }
-            break;
-
-        case 'kick':
-            if ((args.channel) && (args.nick)) {
-                websocket.sendServerLine('KICK ' + args.channel + ' ' + args.nick + ':' + args.reason, callback);
-            }
-            break;
-
-        case 'quit':
-            websocket.ircConnection.end('QUIT :' + args.message + '\r\n');
-            websocket.sentQUIT = true;
-            websocket.ircConnection.destroySoon();
-            websocket.disconnect();
-            break;
-
-        case 'notice':
-            if ((args.target) && (args.msg)) {
-                websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg, callback);
-            }
-            break;
-
-        case 'mode':
-            if ((args.target) && (args.mode)) {
-                websocket.sendServerLine('MODE ' + args.target + ' ' + args.mode + ' ' + args.params, callback);
-            }
-            break;
-
-        case 'nick':
-            if (args.nick) {
-                websocket.sendServerLine('NICK ' + args.nick, callback);
-            }
-            break;
-
-        case 'kiwi':
-            if ((args.target) && (args.data)) {
-                websocket.sendServerLine('PRIVMSG ' + args.target + ': ' + String.fromCharCode(1) + 'KIWI ' + args.data + String.fromCharCode(1), callback);
-            }
-            break;
-        default:
-        }
-    //} catch (e) {
-    //    kiwi.log("Caught error (app.websocketMessage): " + e);
-    //}
-};
-
-
-
-this.websocketDisconnect = function (websocket) {
-    var con;
-
-    if ((!websocket.sentQUIT) && (websocket.ircConnection.connected)) {
-        try {
-            websocket.ircConnection.end('QUIT :' + kiwi.config.quit_message + '\r\n');
-            websocket.sentQUIT = true;
-            websocket.ircConnection.destroySoon();
-        } catch (e) {
-        }
-    }
-    con = kiwi.connections[websocket.kiwi.address];
-    con.count -= 1;
-    con.sockets = _.reject(con.sockets, function (sock) {
-        return sock === websocket;
-    });
-};
-
-
-
-
-
-
-this.rehash = function () {
-    var changes, i,
-        reload_config = kiwi.loadConfig();
-
-    // If loading the new config errored out, dont attempt any changes
-    if (reload_config === false) {
-        return false;
-    }
-
-    // We just want the settings that have been changed
-    changes = reload_config[1];
-
-    if (Object.keys(changes).length !== 0) {
-        kiwi.log('%s config changes: \n', Object.keys(changes).length, changes);
-        for (i in changes) {
-            switch (i) {
-            case 'servers':
-                kiwi.websocketListen(kiwi.config.servers, kiwi.httpHandler);
-                delete changes.ports;
-                delete changes.bind_address;
-                delete changes.ssl_key;
-                delete changes.ssl_cert;
-                break;
-            case 'user':
-            case 'group':
-                kiwi.changeUser();
-                delete changes.user;
-                delete changes.group;
-                break;
-            case 'module_dir':
-            case 'modules':
-                kiwi.kiwi_mod.loadModules(kiwi_root, kiwi.config);
-                kiwi.kiwi_mod.printMods();
-                delete changes.module_dir;
-                delete changes.modules;
-                break;
-            }
-        }
-    }
-
-    // Also clear the kiwi.cached javascript and HTML
-    if (kiwi.config.handle_http) {
-        kiwi.cache = {alljs: '', html: []};
-    }
-
-    return true;
-};
-
-
-
-
-
-/*
- * KiwiIRC controlling via STDIN
- */
-this.manageControll = function (data) {
-    var parts = data.toString().trim().split(' '),
-        connections_cnt = 0,
-        i;
-    switch (parts[0]) {
-    case 'rehash':
-        kiwi.log('Rehashing...');
-        kiwi.log(kiwi.rehash() ? 'Rehash complete' : 'Rehash failed');
-        break;
-
-    case 'recode':
-        kiwi.log('Recoding...');
-        kiwi.log(kiwi.recode() ? 'Recode complete' : 'Recode failed');
-        break;
-
-    case 'mod':
-        if (parts[1] === 'reload') {
-            if (!parts[2]) {
-                kiwi.log('Usage: mod reload module_name');
-                return;
-            }
-
-            kiwi.log('Reloading module (' + parts[2] + ')..');
-            kiwi.kiwi_mod.reloadModule(parts[2]);
-        } else if (parts[1] === 'list') {
-            kiwi.kiwi_mod.printMods();
-        }
-        break;
-
-    case 'cache':
-        if (parts[1] === 'clear') {
-            kiwi.cache.html = {};
-            kiwi.cache.alljs = '';
-            kiwi.log('HTML cache cleared');
-        }
-        break;
-
-    case 'status':
-        for (i in kiwi.connections) {
-            connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
-        }
-        kiwi.log(connections_cnt.toString() + ' connected clients');
-        break;
-
-    default:
-        kiwi.log('Unknown command \'' + parts[0] + '\'');
-    }
-};
diff --git a/server/client.js b/server/client.js
new file mode 100755 (executable)
index 0000000..0f30cc5
--- /dev/null
@@ -0,0 +1,135 @@
+var util            = require('util'),
+    events          = require('events'),
+    IRCConnection   = require('./irc-connection.js').IRCConnection;
+    IRCCommands     = require('./irc-commands.js');
+
+var Client = function (websocket) {
+    var c = this;
+    
+    events.EventEmitter.call(this);
+    this.websocket = websocket;
+    
+    this.IRC_connections = [];
+    this.next_connection = 0;
+    
+    this.buffer = {
+        list: [],
+        motd: ''
+    };
+    
+    websocket.on('irc', function () {
+        IRC_command.apply(c, arguments);
+    });
+    websocket.on('kiwi', function () {
+        kiwi_command.apply(c, arguments);
+    });
+    websocket.on('disconnect', function () {
+        disconnect.apply(c, arguments);
+    });
+    websocket.on('error', function () {
+        error.apply(c, arguments);
+    });
+};
+util.inherits(Client, events.EventEmitter);
+
+module.exports.Client = Client;
+
+// Callback API:
+// Callbacks SHALL accept 2 arguments, error and response, in that order.
+// error MUST be null where the command is successul.
+// error MUST otherwise be a truthy value and SHOULD be a string where the cause of the error is known.
+// response MAY be given even if error is truthy
+
+Client.prototype.sendIRCCommand = function (command, data, callback) {
+    var c = {command: command, data: data};
+    console.log('C<--', c);
+    this.websocket.emit('irc', c, callback);
+};
+
+Client.prototype.sendKiwiCommand = function (command, callback) {
+    this.websocket.emit('kiwi', command, callback);
+};
+
+var IRC_command = function (command, callback) {
+    console.log('C-->', command);
+    var method, str = '';
+    if (typeof callback !== 'function') {
+        callback = function () {};
+    }
+    if ((command.server === null) || (typeof command.server !== 'number')) {
+        return callback('server not specified');
+    } else if (!this.IRC_connections[command.server]) {
+        return callback('not connected to server');
+    }
+    
+    command.data = JSON.parse(command.data);
+    
+    if (command.data.method === 'ctcp') {
+        if (command.data.args.request) {
+            str += 'PRIVMSG ';
+        } else {
+            str += 'NOTICE ';
+        }
+        str += command.data.args.target + ' :'
+        str += String.fromCharCode(1) + command.data.args.type + ' ';
+        str += command.data.args.params + String.fromCharCode(1);
+        this.IRC_connections[command.server].send(str);
+    } else if (command.data.method === 'raw') {
+        this.IRC_connections[command.server].send(command.data.args.data);
+    } else if (command.data.method === 'kiwi') {
+        // do special Kiwi stuff here
+    } else {
+        method = command.data.method;
+        command.data = command.data.args;
+        this.IRC_connections[command.server].send(method + ((command.data.params) ? ' ' + command.data.params.join(' ') : '') + ((command.data.trailing) ? ' :' + command.data.trailing : ''), callback);
+    }
+};
+
+var kiwi_command = function (command, callback) {
+    console.log(typeof callback);
+    if (typeof callback !== 'function') {
+        callback = function () {};
+    }
+    switch (command.command) {
+               case 'connect':
+                       if ((command.hostname) && (command.port) && (command.nick)) {
+                               var con = new IRCConnection(command.hostname, command.port, command.ssl,
+                                       command.nick, {hostname: this.websocket.handshake.revdns, address: this.websocket.handshake.address.address},
+                                       command.password, null);
+
+                               var con_num = this.next_connection++;
+                               this.IRC_connections[con_num] = con;
+
+                               var binder = new IRCCommands.Binder(con, con_num, this);
+                               binder.bind_irc_commands();
+                               
+                               con.on('connected', function () {
+                    console.log("con.on('connected')");
+                                       return callback(null, con_num);
+                               });
+                               
+                               con.on('error', function (err) {
+                                       this.websocket.sendKiwiCommand('error', {server: con_num, error: err});
+                               });
+                       } else {
+                               return callback('Hostname, port and nickname must be specified');
+                       }
+               break;
+               default:
+                       callback();
+    }
+};
+
+var extension_command = function (command, callback) {
+    if (typeof callback === 'function') {
+        callback('not implemented');
+    }
+};
+
+var disconnect = function () {
+    this.emit('destroy');
+};
+
+var error = function () {
+    this.emit('destroy');
+};
diff --git a/server/http-handler.js b/server/http-handler.js
new file mode 100755 (executable)
index 0000000..aa3d2c0
--- /dev/null
@@ -0,0 +1,169 @@
+var fs          = require('fs'),
+    crypto      = require('crypto');
+    url         = require('url'),
+    _           = require('underscore'),
+    uglify      = require('uglify-js'),
+    jade        = require('jade'),
+    node_static = require ('node-static');
+
+var HTTPHandler = function (config) {
+    var site = config.site;
+    this.config = config;
+    var files;
+    files = fs.readdirSync('client');
+    if ((typeof site !== 'undefined') && (typeof site === 'string') && (_.include(files, site))) {
+        this.site = site;
+        this.static_file_server = new StaticFileServer(site);
+    }
+    else {
+        this.site = 'default';
+        this.static_file_server = null;
+    }
+    
+};
+
+module.exports.HTTPHandler = HTTPHandler;
+
+var default_static_file_server = new node_static.Server('client_backbone/');
+
+var StaticFileServer = function (site) {
+    this.fileServer = new node_static.Server('client_backbone/');
+};
+
+StaticFileServer.prototype.serve = function (request, response) {
+    this.fileServer.serve(request, response, function (err) {
+        if (err) {
+            default_static_file_server.serve(request, response);
+        }
+    });
+};
+
+var serve_static_file = function (request, response) {
+    if (this.static_file_server !== null) {
+        this.static_file_server.serve(request, response);
+    } else {
+        default_static_file_server.serve(request, response);
+    }
+};
+
+HTTPHandler.prototype.handler = function (request, response) {
+    var file_list, default_file_list, hash, uri, site, subs, self = this;
+    
+    site = 'default';
+    uri = url.parse(request.url, true);
+    subs = uri.pathname.substr(0, 4);
+    
+    if (uri.pathname === '/all.js') {
+        hash = is_cached(site,'all.js');
+        if (!hash) {
+            file_list = [];
+                       default_file_list = [];
+            console.log('a');
+            fs.readFile('client_backbone/manifest.json', 'utf-8', function (err, manifest) {
+                console.log('b');
+                var js = '';
+                manifest = JSON.parse(manifest);
+                _.each(manifest.js, function (file) {
+                    console.log(file)
+                    js += fs.readFileSync('client_backbone/js/' + file, 'utf-8') + '\r\n';
+                });
+                
+                // TODO: Replace false with check for debug flag
+                if (/* debug === */ false) {
+                    js = uglify.uglify.gen_code(uglify.uglify.ast_squeeze(uglify.uglify.ast_mangle(uglify.parser.parse(js))));
+                }
+                
+                hash = set_cache(site, 'all.js', js);
+                if (request.headers['if-none-match'] === hash) {
+                    response.statusCode = 304;
+                } else {
+                    response.setHeader('Content-type', 'application/javascript');
+                    response.setHeader('ETag', hash);
+                    response.write(js);
+                }
+                response.end();
+            });
+        } else {
+            if (request.headers['if-none-match'] === hash) {
+                response.statusCode = 304;
+            } else {
+                response.setHeader('Content-type', 'application/javascript');
+                response.setHeader('ETag', hash);
+                response.write(get_cache(site, 'all.js'));
+            }
+            response.end();
+        }
+    } else if (uri.pathname === '/') {
+        var jadefile = '';
+        
+        hash = is_cached(site, '/');
+        
+        if (!hash) {
+            try {
+                fs.readFile('client_backbone/index.jade', 'utf-8', function (err, str) {
+                    if (err) {
+                        console.log(err + '');
+                        response.end();
+                    } else {
+                        jadefile = str;
+                    }
+                    hash = set_cache('default', '/', jade.compile(jadefile, {pretty: true})());
+                    if (response.statusCode !== 500) {
+                        if (request.headers['if-none-match'] === hash) {
+                            response.statusCode = 304;
+                        } else {
+                            response.setHeader('Content-type', 'text/html; charset=utf-8');
+                            response.setHeader('ETag', hash);
+                            response.write(get_cache(site, '/'));
+                        }
+                    }
+                    response.end();
+                });
+                
+            } catch (e) {
+                console.log(e);
+                response.statusCode = 500;
+                response.end();
+            }
+        } else {
+            if (request.headers['if-none-match'] === hash) {
+                response.statusCode = 304;
+            } else {
+                response.setHeader('Content-type', 'text/html; charset=utf-8');
+                response.setHeader('ETag', hash);
+                response.write(get_cache(site, '/'));
+            }
+            response.end();
+        }
+    } else if ((subs === '/img') || (subs === '/css')) {
+        serve_static_file.call(this, request, response);
+    } else if (uri.pathname.substr(0, 10) === '/socket.io') {
+        return;
+    } else {
+        response.statusCode = 404;
+        response.end();
+    }
+};
+
+var cache = Object.create(null);
+
+var set_cache = function (site, file, data) {
+    if (!cache[site]) {
+        cache[site] = Object.create(null);
+    }
+    var hash = crypto.createHash('md5').update(data).digest('base64');
+    cache[site][file] = {'data': data, 'hash': hash};
+    return hash;
+};
+
+var is_cached = function (site, file) {
+    if ((cache[site]) && (cache[site][file])) {
+        return cache[site][file].hash;
+    } else {
+        return false;
+    }
+};
+
+var get_cache = function (site, file) {
+    return cache[site][file].data;
+};
diff --git a/server/irc-commands.js b/server/irc-commands.js
new file mode 100755 (executable)
index 0000000..23667c0
--- /dev/null
@@ -0,0 +1,480 @@
+var _ = require('underscore');
+
+var irc_numerics = {
+    RPL_WELCOME:            '001',
+    RPL_MYINFO:             '004',
+    RPL_ISUPPORT:           '005',
+    RPL_WHOISUSER:          '311',
+    RPL_WHOISSERVER:        '312',
+    RPL_WHOISOPERATOR:      '313',
+    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_TOPICWHOTIME:       '333',
+    RPL_NAMEREPLY:          '353',
+    RPL_ENDOFNAMES:         '366',
+    RPL_BANLIST:            '367',
+    RPL_ENDOFBANLIST:       '368',
+    RPL_MOTD:               '372',
+    RPL_MOTDSTART:          '375',
+    RPL_ENDOFMOTD:          '376',
+    RPL_WHOISMODES:         '379',
+    ERR_NOSUCHNICK:         '401',
+    ERR_CANNOTSENDTOCHAN:   '404',
+    ERR_TOOMANYCHANNELS:    '405',
+    ERR_NICKNAMEINUSE:      '433',
+    ERR_USERNOTINCHANNEL:   '441',
+    ERR_NOTONCHANNEL:       '442',
+    ERR_NOTREGISTERED:      '451',
+    ERR_LINKCHANNEL:        '470',
+    ERR_CHANNELISFULL:      '471',
+    ERR_INVITEONLYCHAN:     '473',
+    ERR_BANNEDFROMCHAN:     '474',
+    ERR_BADCHANNELKEY:      '475',
+    ERR_CHANOPRIVSNEEDED:   '482',
+    RPL_STARTTLS:           '670'
+};
+
+
+var Binder = function (irc_connection, con_num, client) {
+    this.irc_connection = irc_connection;
+    this.con_num = con_num;
+    this.client = client;
+};
+module.exports.Binder = Binder;
+
+Binder.prototype.bind_irc_commands = function () {
+    var that = this;
+    _.each(listeners, function (listener, command) {
+        var s = command.substr(0, 4);
+        if ((s === 'RPL_') || (s === 'ERR_')) {
+            command = irc_numerics[command];
+        }
+        that.irc_connection.on('irc_' + command, function () {
+            listener.apply(that, arguments);
+        });
+    });
+};
+
+var listeners = {
+    'RPL_WELCOME':            function (command) {
+                var nick =  command.params[0];
+                this.irc_connection.registered = true;
+                this.client.sendKiwiCommand({server: this.con_num, command: 'connect', nick: nick});
+            },
+    'RPL_ISUPPORT':           function (command) {
+                var options, i, option, matches, j;
+                options = command.params;
+                for (i = 1; i < options.length; i++) {
+                    option = options[i].split("=", 2);
+                    option[0] = option[0].toUpperCase();
+                    this.irc_connection.options[option[0]] = (typeof option[1] !== 'undefined') ? option[1] : true;
+                    if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option[0])) {
+                        if (option[0] === 'PREFIX') {
+                            matches = /\(([^)]*)\)(.*)/.exec(option[1]);
+                            if ((matches) && (matches.length === 3)) {
+                                this.irc_connection.options.PREFIX = [];
+                                for (j = 0; j < matches[2].length; j++) {
+                                    this.irc_connection.options.PREFIX.push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
+                                }
+                            }
+                                               } else if (option[0] === 'CHANTYPES') {
+                                                       this.irc_connection.options.CHANTYPES = this.irc_connection.options.CHANTYPES.split('');
+                                               } else if (option[0] === 'CHANMODES') {
+                                                       this.irc_connection.options.CHANMODES = option[1].split(',');
+                        } else if (option[0] === 'NAMESX') {
+                            this.irc_connection.send('PROTOCTL NAMESX');
+                        }
+                    }
+                }
+                //this.client.sendIRCCommand({server: this.con_num, command: 'RPL_ISUPPORT', options: this.irc_connection.options});
+                //websocket.sendClientEvent('options', {server: '', "options": irc_connection.IRC.options});
+                this.client.sendIRCCommand('options', {server: this.con_num, options: this.irc_connection.options});
+            },
+    'RPL_ENDOFWHOIS':         function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_ENDOFWHOIS';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: true});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: true});
+            },
+    'RPL_WHOISUSER':          function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_WHOISUSER';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false});
+            },
+    'RPL_WHOISSERVER':        function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_WHOISSERVER';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false});
+            },
+    'RPL_WHOISOPERATOR':      function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_WHOISOPERATOR';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false});
+            },
+    'RPL_WHOISCHANNELS':      function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_WHOISCHANNELS';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false});
+            },
+    'RPL_WHOISMODES':         function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_WHOISMODES';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false});
+            },
+    'RPL_WHOISIDLE':          function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_WHOISIDLE';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing, end: false});
+                this.client.sendIRCCommand('whois', {server: this.con_num, nick: command.params[0], msg: command.trailing, end: false});
+            },
+    'RPL_LISTSTART':          function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_LISTSTART';
+                               this.client.sendIRCCommand(command);*/
+                this.client.sendIRCCommand('list_start', {server: this.con_num});
+                this.client.buffer.list = [];
+            },
+    'RPL_LISTEND':            function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_LISTEND';
+                               this.client.sendIRCCommand(command);*/
+                if (this.client.buffer.list.length > 0) {
+                    this.client.buffer.list = _.sortBy(this.client.buffer.list, function (channel) {
+                        return channel.num_users;
+                    });
+                    this.client.sendIRCCommand('list_channel', {server: this.con_num, chans: this.client.buffer.list});
+                    this.client.buffer.list = [];
+                }
+                this.client.sendIRCCommand('list_end', {server: this.con_num});
+            },
+    'RPL_LIST':               function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_LIST';
+                               this.client.sendIRCCommand(command);*/
+                this.client.buffer.list.push({server: this.con_num, channel: command.params[1], num_users: parseInt(command.params[2]), topic: command.trailing});
+                if (this.client.buffer.list.length > 200){
+                    this.client.buffer.list = _.sortBy(this.client.buffer.list, function (channel) {
+                        return channel.num_users;
+                    });
+                    this.client.sendIRCCommand('list_channel', {server: this.con_num, chans: this.client.buffer.list});
+                    this.client.buffer.list = [];
+                }
+            },
+    'RPL_MOTD':               function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_MOTD';
+                               this.client.sendIRCCommand(command);*/
+                this.client.buffer.motd += command.trailing + '\n';
+            },
+    'RPL_MOTDSTART':          function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_MOTDSTART';
+                               this.client.sendIRCCommand(command);*/
+                this.client.buffer.motd = '';
+            },
+    'RPL_ENDOFMOTD':          function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_ENDOFMOTD';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('motd', {server: '', 'msg': websocket.kiwi.buffer.motd});
+                this.client.sendIRCCommand('motd', {server: this.con_num, msg: this.client.buffer.motd});
+            },
+    'RPL_NAMEREPLY':          function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_NAMEREPLY';
+                               this.client.sendIRCCommand(command);*/
+                var members = command.trailing.split(' ');
+                var member_list = [];
+                var that = this;
+                var i = 0;
+                _.each(members, function (member) {
+                    var j, k, modes = [];
+                    for (j = 0; j < member.length; j++) {
+                        for (k = 0; k < that.irc_connection.options.PREFIX.length; k++) {
+                            if (member.charAt(j) === that.irc_connection.options.PREFIX[k].symbol) {
+                                modes.push(that.irc_connection.options.PREFIX[k].mode);
+                                i++;
+                            }
+                        }
+                    }
+                    member_list.push({nick: member, modes: modes});
+                    if (i++ >= 50) {
+                        that.client.sendIRCCommand('userlist', {server: that.con_num, users: member_list, channel: command.params[2]});
+                        member_list = [];
+                        i = 0;
+                    }
+                });
+                if (i > 0) {
+                    this.client.sendIRCCommand('userlist', {server: this.con_num, users: member_list, channel: command.params[2]});
+                }
+            },
+    'RPL_ENDOFNAMES':         function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_ENDOFNAMES';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
+                this.client.sendIRCCommand('userlist_end', {server: this.con_num, channel: command.params[1]});
+            },
+    'RPL_BANLIST':            function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_BANLIST';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('banlist', {server: '', channel: params[1], banned: params[2], banned_by: params[3], banned_at: params[4]});
+                this.client.sendIRCCommand('banlist', {server: this.con_num, channel: command.params[1], banned: command.params[2], banned_by: command.params[3], banned_at: command.params[4]});
+            },
+    'RPL_ENDOFBANLIST':       function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_ENDOFBANLIST';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('banlist_end', {server: '', channel: msg.params.split(" ")[1]});
+                this.client.sendIRCCommand('banlist_end', {server: this.con_num, channel: command.params[1]});
+            },
+    'RPL_TOPIC':              function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_TOPIC';
+                               this.client.sendIRCCommand(command);*/
+                //{nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
+                this.client.sendIRCCommand('topic', {server: this.con_num, nick: '', channel: command.params[1], topic: command.trailing});
+            },
+    'RPL_NOTOPIC':            function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_NOTOPIC';
+                               this.client.sendIRCCommand(command);*/
+                this.client.sendIRCCommand('topic', {server: this.con_num, nick: '', channel: command.params[1], topic: ''});
+            },
+    'RPL_TOPICWHOTIME':       function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'RPL_TOPICWHOTIME';
+                               this.client.sendIRCCommand(command);*/
+                //{nick: nick, channel: channel, when: when};
+                this.client.sendIRCCommand('topicsetby', {server: this.con_num, nick: command.params[2], channel: command.params[1], when: command.params[3]});
+            },
+    'PING':                 function (command) {
+                this.irc_connection.send('PONG ' + command.trailing);
+            },
+    'JOIN':                 function (command) {
+                               var channel;
+                               if (typeof command.trailing === 'string' && command.trailing !== '') {
+                                       channel = command.trailing;
+                               } else if (typeof command.params[0] === 'string' && command.params[0] !== '') {
+                                       channel = command.params[0];
+                               }
+                               /*command.server = this.con_num;
+                               command.command = 'JOIN';
+                               command.params = [channel];
+                this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
+                this.client.sendIRCCommand('join', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: channel});
+                
+                if (command.nick === this.nick) {
+                    this.irc_connection.send('NAMES ' + channel);
+                }
+            },
+    'PART':                 function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'PART';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
+                this.client.sendIRCCommand('part', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], message: command.trailing});
+            },
+    'KICK':                 function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'KICK';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
+                this.client.sendIRCCommand('kick', {server: this.con_num, kicked: command.params[1], nick: command.nick, ident: command.ident, hostname: command.hostname, channel: params[0], message: command.trailing});
+            },
+    'QUIT':                 function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'QUIT';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
+                this.client.sendIRCCommand('quit', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, message: command.trailing});
+            },
+    'NOTICE':               function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'NOTICE';
+                               this.client.sendIRCCommand(command);*/
+                if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
+                    // It's a CTCP response
+                    //websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
+                    this.client.sendIRCCommand('ctcp_response', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing.substr(1, command.trailing.length - 2)});
+                } else {
+                    //websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, target: msg.params.trim(), msg: msg.trailing});
+                    this.client.sendIRCCommand('notice', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, target: command.params[0], msg: command.trailing});
+                }
+            },
+    'NICK':                 function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'NICK';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
+                this.client.sendIRCCommand('nick', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, newnick: command.trailing});
+            },
+    'TOPIC':                function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'TOPIC';
+                               this.client.sendIRCCommand(command);*/
+                //{nick: msg.nick, channel: msg.params, topic: msg.trailing};
+                this.client.sendIRCCommand('topic', {server: this.con_num, nick: command.nick, channel: msg.params[0], topic: command.trailing});
+            },
+    'MODE':                 function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'MODE';
+                               this.client.sendIRCCommand(command);*/
+                var ret = { server: this.con_num, nick: command.nick }
+                switch (command.params.length) {
+                    case 1:
+                        ret.affected_nick = command.params[0];
+                        ret.mode = command.trailing;
+                        break;
+                    case 2:
+                        ret.channel = command.params[0];
+                        ret.mode = command.params[1];
+                        break;
+                    default:
+                        ret.channel = command.params[0];
+                        ret.mode = command.params[1];
+                        ret.affected_nick = command.params[2];
+                        break;
+                }
+                this.client.sendIRCCommand('mode', ret);
+            },
+    'PRIVMSG':              function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'PRIVMSG';
+                               this.client.sendIRCCommand(command);*/
+                var tmp, namespace;
+                if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
+                    //CTCP request
+                    if (command.trailing.substr(1, 6) === 'ACTION') {
+                        this.client.sendIRCCommand('action', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing.substr(7, command.trailing.length - 2)});
+                    } else if (command.trailing.substr(1, 4) === 'KIWI') {
+                        tmp = msg.trailing.substr(6, msg.trailing.length - 2);
+                        namespace = tmp.split(' ', 1)[0];
+                        this.client.sendIRCCommand('kiwi', {server: this.con_num, namespace: namespace, data: tmp.substr(namespace.length + 1)});
+                    } else if (msg.trailing.substr(1, 7) === 'VERSION') {
+                        this.irc_connection.send('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1));
+                    } else {
+                        this.client.sendIRCCommand('ctcp_request', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing.substr(1, command.trailing.length - 2)});
+                    }
+                } else {
+                    //{nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing}
+                    this.client.sendIRCCommand('msg', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: command.params[0], msg: command.trailing});
+                }
+            },
+    'ERROR':                function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERROR';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'error', reason: command.trailing});
+            },
+    ERR_LINKCHANNEL:        function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_LINKCHANNEL';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
+                this.client.sendIRCCommand('channel_redirect', {server: this.con_num, from: command.params[1], to: command.params[2]});
+            },
+    ERR_NOSUCHNICK:         function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_NOSUCHNICK';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'no_such_nick', nick: command.params[1], reason: command.trailing});
+            },
+    ERR_CANNOTSENDTOCHAN:   function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_CANNOTSENDTOCHAN';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'cannot_send_to_chan', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_TOOMANYCHANNELS:    function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_TOOMANYCHANNELS';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'too_many_channels', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_USERNOTINCHANNEL:   function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_USERNOTINCHANNEL';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'user_not_in_channel', nick: command.params[0], channel: command.params[1], reason: command.trailing});
+            },
+    ERR_NOTONCHANNEL:       function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_NOTONCHANNEL';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'not_on_channel', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_CHANNELISFULL:      function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_CHANNELISFULL';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'channel_is_full', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_INVITEONLYCHAN:     function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_INVITEONLYCHAN';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'invite_only_channel', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_BANNEDFROMCHAN:     function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_BANNEDFROMCHAN';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'banned_from_channel', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_BADCHANNELKEY:      function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_BADCHANNELKEY';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'bad_channel_key', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_CHANOPRIVSNEEDED:   function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_CHANOPRIVSNEEDED';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'chanop_privs_needed', channel: command.params[1], reason: command.trailing});
+            },
+    ERR_NICKNAMEINUSE:      function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_NICKNAMEINUSE';
+                               this.client.sendIRCCommand(command);*/
+                //websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
+                this.client.sendIRCCommand('irc_error', {server: this.con_num, error: 'nickname_in_use', nick: command.params[1], reason: command.trailing});
+            },
+    ERR_NOTREGISTERED:      function (command) {
+                               /*command.server = this.con_num;
+                               command.command = 'ERR_NOTREGISTERED';
+                               this.client.sendIRCCommand(command);*/
+            }
+};
diff --git a/server/irc-connection.js b/server/irc-connection.js
new file mode 100755 (executable)
index 0000000..163d69e
--- /dev/null
@@ -0,0 +1,124 @@
+var net     = require('net'),
+    tls     = require('tls'),
+    events  = require('events'),
+    util    = require('util');
+
+var IRCConnection = function (hostname, port, ssl, nick, user, pass, webirc) {
+    var that = this;
+    events.EventEmitter.call(this);
+    
+    if (ssl) {
+        this.socket = tls.connect(port, hostname, {}, connect_handler);
+    } else {
+        this.socket = net.createConnection(port, hostname);
+        this.socket.on('connect', function () {
+            connect_handler.apply(that, arguments);
+        });
+    }
+    
+    this.socket.on('error', function () {
+        var a = Array.prototype.slice.call(arguments);
+        a.unshift('error');
+        that.emit.apply(this, a);
+    });
+    
+    this.socket.setEncoding('utf-8');
+    
+    this.socket.on('data', function () {
+        parse.apply(that, arguments);
+    });
+    
+    this.connected = false;
+    this.registered = false;
+    this.nick = nick;
+    this.user = user;
+    this.ssl = !(!ssl);
+    this.options = Object.create(null);
+    
+    this.webirc = webirc;
+    this.password = pass;
+    this.hold_last = false;
+    this.held_data = '';
+};
+util.inherits(IRCConnection, events.EventEmitter);
+
+IRCConnection.prototype.send = function (data, callback) {
+    console.log('S<--', data);
+    write.call(this, data + '\r\n', 'utf-8', callback);
+};
+
+var write = function (data, encoding, callback) {
+    this.socket.write(data, encoding, callback);
+};
+
+module.exports.IRCConnection = IRCConnection;
+
+var connect_handler = function () {
+    if (this.webirc) {
+        this.send('WEBIRC ' + webirc.pass + ' KiwiIRC ' + this.user.hostname + ' ' + this.user.address);
+    }
+    if (this.password) {
+        this.send('PASS ' + password);
+    }
+    //this.send('CAP LS');
+    this.send('NICK ' + this.nick);
+    this.send('USER kiwi_' + this.nick.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + this.nick);
+    
+    this.connected = true;
+    console.log("IRCConnection.emit('connected')");
+    this.emit('connected');
+};
+
+//parse_regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
+alt_regex   = /(?::(([0-9a-z][\x2d0-9a-z]*[0-9a-z]*(?:\x2e[0-9a-z][\x2d0-9a-z]*[0-9a-z]*)*|[\x5b-\x7d][\x2d0-9\x5b-\x7d]{0,8})(?:(?:!([\x01-\t\v\f\x0e-\x1f!-\x3f\x5b-\xff]+))?@([0-9a-z][\x2d0-9a-z]*[0-9a-z]*(?:\x2e[0-9a-z][\x2d0-9a-z]*[0-9a-z]*)*|\d{1,3}\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3}|[0-9a-f]+(?::[0-9a-f]+){7}|0:0:0:0:0:(?:0|ffff):\d{1,3}\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3}))?)\x20)?([a-z]+|\d{3})((?:\x20[\x01-\t\v\f\x0e-\x1f!-9;-@\x5b-\xff][\x01-\t\v\f\x0e-\x1f!-@\x5b-\xff]*){0,14}(?:\x20:[\x01-\t\v\f\x0e-@\x5b-\xff]*)?|(?:\x20[\x01-\t\v\f\x0e-\x1f!-9;-@\x5b-\xff][\x01-\t\v\f\x0e-\x1f!-@\x5b-\xff]*){14}(?:\x20:?[\x01-\t\v\f\x0e-@\x5b-\xff]*)?)?/i;
+
+var parse = function (data) {
+    var i,
+        msg,
+               msg2,
+               trm;
+    
+    if ((this.hold_last) && (this.held_data !== '')) {
+        data = this.held_data + data;
+        this.hold_last = false;
+        this.held_data = '';
+    }
+    if (data.substr(-1) !== '\n') {
+        this.hold_last = true;
+    }
+    data = data.split("\n");
+    for (i = 0; i < data.length; i++) {
+        if (data[i]) {
+            if ((this.hold_last) && (i === data.length - 1)) {
+                this.held_data = data[i];
+                break;
+            }
+
+            // We have a complete line of data, parse it!
+            //msg = parse_regex.exec(data[i].replace(/^\r+|\r+$/, ''));
+                       msg2 = alt_regex.exec(data[i].replace(/^\r+|\r+$/, ''));
+                       //console.log(msg2);
+            if (msg2) {
+                msg = {
+                    prefix:     msg2[1],
+                    nick:       msg2[2],
+                    ident:      msg2[3],
+                    hostname:   msg2[4],
+                    command:    msg2[5]
+                };
+                               trm = msg2[6].indexOf(':');
+                               if (trm !== -1){
+                                       msg.params = msg2[6].substr(0, trm - 1).trim().split(" ");
+                                       msg.trailing = msg2[6].substr(trm + 1).trim();
+                               } else {
+                                       msg.params = msg2[6].trim().split(" ");
+                               }
+                console.log('S-->', data[i]);
+                               //console.log(msg);
+                this.emit('irc_' + msg.command.toUpperCase(), msg);
+            } else {
+                console.log("Malformed IRC line: " + data[i].replace(/^\r+|\r+$/, ''));
+            }
+        }
+    }
+};
index a4afaa28644c6d89cb7f05820a75a8ad888b53bf..24c26d9d28afc52d8604c2f5ba04587b9a3c0861 100755 (executable)
-/*jslint continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
-"use strict";
-var tls = require('tls'),
-    net = require('net'),
-    http = require('http'),
-    https = require('https'),
-    fs = require('fs'),
-    url = require('url'),
-    dns = require('dns'),
-    crypto = require('crypto'),
-    events = require("events"),
-    util = require('util'),
-    ws = require('socket.io'),
-    jsp = require("uglify-js").parser,
-    pro = require("uglify-js").uglify,
-    _ = require('./lib/underscore.min.js'),
-    starttls = require('./lib/starttls.js'),
-    app = require(__dirname + '/app.js');
+var fs          = require('fs'),
+    WebListener = require('./web.js').WebListener;
 
+//load config
 
-// Libraries may need to know kiwi.js path as __dirname
-// only gives that librarys path. Set it here for usage later.
-this.kiwi_root = __dirname;
-
-
-
-// How to handle log output
-this.log = function(str, level) {
-    level = level || 0;
-    console.log(str);
-}
-
-
-/*
- * Configuration and rehashing routines
- */
 var config_filename = 'config.json',
-    config_dirs = ['/etc/kiwiirc/', this.kiwi_root + '/'];
-
-this.config = {};
-this.loadConfig = function () {
-    var i, j,
-        nconf = {},
-        cconf = {},
-        found_config = false;
-    
-    for (i in config_dirs) {
-        try {
-            if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) {
-                found_config = true;
-                nconf = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'ascii'));
-                for (j in nconf) {
-                    // If this has changed from the previous config, mark it as changed
-                    if (!_.isEqual(this.config[j], nconf[j])) {
-                        cconf[j] = nconf[j];
-                    }
-
-                    this.config[j] = nconf[j];
-                }
-
-                this.log('Loaded config file ' + config_dirs[i] + config_filename);
-                break;
-            }
-        } catch (e) {
-            switch (e.code) {
-            case 'ENOENT':      // No file/dir
-                break;
-            default:
-                this.log('An error occured parsing the config file ' + config_dirs[i] + config_filename + ': ' + e.message);
-                return false;
-            }
-            continue;
+    config_dirs = ['/etc/kiwiirc/', __dirname + '/'];
+
+var config = Object.create(null);
+for (var i in config_dirs) {
+    try {
+        if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) {
+            config = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'utf-8'));
+            console.log('Loaded config file ' + config_dirs[i] + config_filename);
+            break;
         }
-    }
-    if (Object.keys(this.config).length === 0) {
-        if (!found_config) {
-            this.log('Couldn\'t find a config file!');
+    } catch (e) {
+        switch (e.code) {
+        case 'ENOENT':      // No file/dir
+            break;
+        default:
+            console.log('An error occured parsing the config file ' + config_dirs[i] + config_filename + ': ' + e.message);
+            return false;
         }
-        return false;
-    }
-    return [nconf, cconf];
-};
-
-
-// Reloads the config during runtime
-this.rehash = function () {
-    return app.rehash();
-}
-
-// Reloads app.js during runtime for any recoding
-this.recode = function () {
-    if (typeof require.cache[this.kiwi_root + '/app.js'] !== 'undefined'){
-        delete require.cache[this.kiwi_root + '/app.js'];
+        continue;
     }
-
-    app = null;
-    app = require(__dirname + '/app.js');
-
-    var objs = {tls:tls, net:net, http:http, https:https, fs:fs, url:url, dns:dns, crypto:crypto, events:events, util:util, ws:ws, jsp:jsp, pro:pro, _:_, starttls:starttls};
-    app.init(objs);
-    app.rebindIRCCommands();
-
-    return true;
 }
 
-
-
-
-
-
-/*
- * Before we continue we need the config loaded
- */
-if (!this.loadConfig()) {
-    process.exit(0);
+if (Object.keys(config).length === 0) {
+    console.log('Couldn\'t find a valid config file!');
+    process.exit(1);
 }
 
-
-
-
-
-
-
-/*
- * HTTP file serving
- */
-if (this.config.handle_http) {
-    this.fileServer = new (require('node-static').Server)(__dirname + this.config.public_http);
-    this.jade = require('jade');
-    this.cache = {alljs: '', html: []};
-}
-this.httpServers = [];
-this.httpHandler = function (request, response, server) {
-    return app.httpHandler(request, response, server);
-}
-
-
-
-
-
-
-/*
- * Websocket handling
- */
-this.connections = {};
-this.io = [];
-this.websocketListen = function (servers, handler) {
-    return app.websocketListen(servers, handler);
-}
-this.websocketConnection = function (websocket) {
-    return app.websocketConnection(websocket);
-}
-this.websocketDisconnect = function () {
-    return app.websocketDisconnect(this);
-}
-this.websocketMessage = function (msg, callback) {
-    return app.websocketMessage(this, msg, callback);
-}
-this.websocketIRCConnect = function (nick, host, port, ssl, callback) {
-    return app.websocketIRCConnect(this, nick, host, port, ssl, callback);
+if ((!config.servers) || (config.servers.length < 1)) {
+    console.log('No servers defined in config file');
+    process.exit(2);
 }
 
+//Create web listeners
 
+var clients = [];
+_.each(config.servers, function (server) {
+    var wl = new WebListener(server, config.transports);
+    wl.on('connection', function (client) {
+        clients.push(client);
+    });
+    wl.on('destroy', function (client) {
+        clients = _.reject(clients, function (c) {
+            return client === c;
+        });
+    });
+});
 
 
-/*
- * IRC handling
- */
-this.parseIRCMessage = function (websocket, ircSocket, data) {
-    return app.parseIRCMessage(websocket, ircSocket, data);
-}
-this.ircSocketDataHandler = function (data, websocket, ircSocket) {
-    return app.ircSocketDataHandler(data, websocket, ircSocket);
-}
-this.IRCConnection = function (websocket, nick, host, port, ssl, password, callback) {
-    return app.IRCConnection.call(this, websocket, nick, host, port, ssl, password, callback);
-}
-util.inherits(this.IRCConnection, events.EventEmitter);
+//Set process title
+process.title = 'Kiwi IRC';
 
-this.bindIRCCommands = function (irc_connection, websocket) {
-    return app.bindIRCCommands.call(this, irc_connection, websocket);
+//Change UID/GID
+if ((config.user) && (config.user !== '')) {
+    process.setuid(config.user);
 }
-this.rebindIRCCommands = function () {
-    return app.rebindIRCCommands.call(this);
-}
-
-
-
-
-
-
-/*
- * Load up main application source
- */
-if (!this.recode()) {
-    process.exit(0);
+if ((config.group) && (config.group !== '')) {
+    process.setgid(config.group);
 }
 
-
-
-// Set the process title
-app.setTitle();
-
-
-
-/*
- * Load the modules as set in the config and print them out
- */
-this.kiwi_mod = require('./lib/kiwi_mod.js');
-this.kiwi_mod.loadModules(this.kiwi_root, this.config);
-this.kiwi_mod.printMods();
-
-
-// Make sure Kiwi doesn't simply quit on an exception
-process.on('uncaughtException', function (e) {
-    console.log('[Uncaught exception] ' + e);
-});
-
-// Start the server up
-this.websocketListen(this.config.servers, this.httpHandler);
-
-// Now we're listening on the network, set our UID/GIDs if required
-app.changeUser();
-
-// Listen for controll messages
+//Listen to STDIN
 process.stdin.resume();
-process.stdin.on('data', function (data) { app.manageControll(data); });
-
-
-
-
+process.stdin.on('data', function (data) {
+    console.log(data.toString());
+});
diff --git a/server/kiwi_modules/forcessl.js b/server/kiwi_modules/forcessl.js
deleted file mode 100644 (file)
index d510299..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * forcessl Kiwi module
- * Force clients to use an SSL port by redirecting them
- */
-
-var kiwi = require('../kiwi.js');
-
-
-exports.onhttp = function (ev, opts) {
-       var host, port = null, i;
-
-       if (!ev.ssl) {
-           host = ev.request.headers.host;
-
-           // Remove the port if one is set
-           if (host.search(/:/) > -1) {
-               host = host.substring(0, host.search(/:/));
-           }
-
-       for (i in kiwi.config.servers) {
-               if (kiwi.config.servers[i].secure) {
-                       port = kiwi.config.servers[i].port;
-                       break;
-               }
-       }
-
-           // If we didn't find an SSL listener, don't redirect
-           if (port == null) {
-               return ev;
-           }
-
-           // No need to specify port 443 since it's the standard
-           if (port !== 443) {
-               host += ':' + port.toString();
-           }
-           
-           ev.response.writeHead(302, {'Location': 'https://' + host + ev.request.url});
-           ev.response.end();
-
-           return null;
-       }
-
-       return ev;
-}
\ No newline at end of file
diff --git a/server/kiwi_modules/spamfilter.js b/server/kiwi_modules/spamfilter.js
deleted file mode 100644 (file)
index 5f3ff76..0000000
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * Example Kiwi module.
- * This is by no means is a production ready module.
- */
-
-var filters;
-var compiled_regex;
-
-exports.onload = function(){
-       filters = [];
-
-       if (filter.length > 0) {
-               compiled_regex = new RegExp(filters.join('|'), 'im');
-       }
-}
-
-
-exports.onmsg = function(msg){
-       if (typeof compiled_regex !== 'undefined' && msg.msg.search(compiled_regex) > -1) {
-               return null;
-       }
-
-       return msg;
-}
\ No newline at end of file
diff --git a/server/kiwi_modules/statistics.js b/server/kiwi_modules/statistics.js
deleted file mode 100644 (file)
index 09b4546..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Example Kiwi module.
- * This is by no means is a production ready module.
- */
-
-var kiwi = require('../kiwi.js');
-var stats = {msgs: 0, topic_changes: 0};
-
-exports.onmsgsend = function (msg, opts) {
-    stats.msgs++;
-
-    var connections_cnt = 0;
-    for (var i in kiwi.connections) {
-        connections_cnt = connections_cnt + parseInt(kiwi.connections[i].count, 10);
-    }
-
-    if (msg.msg === '!kiwistats') {
-        msg.msg = '';
-        msg.msg += 'Connections: ' + connections_cnt.toString() + '. ';
-        msg.msg += 'Messages sent: ' + stats.msgs.toString() + '. ';
-        msg.msg += 'Topics set: ' + stats.topic_changes.toString() + '. ';
-
-        opts.websocket.sendClientEvent('msg', {nick: msg.target, ident: '', hostname: '', channel: msg.target, msg: msg.msg});
-        return null;
-    }
-
-    return msg;
-}
-
-exports.ontopic = function (topic, opts) {
-    stats.topic_changes++;
-
-    return topic;
-}
diff --git a/server/lib/kiwi_mod.js b/server/lib/kiwi_mod.js
deleted file mode 100644 (file)
index bdac9c3..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*jslint node: true, sloppy: true, forin: true, maxerr: 50, indent: 4 */
-/*
- * Kiwi module handler
- *
- * To run module events:
- *     kiwi_mod.run(event_name, obj);
- *
- * - Each module call must return obj, with or without changes.
- * - If a module call returns null, the event is considered cancelled
- *   and null is passed back to the caller to take action.
- *   For example, if null is returned for onmsg, kiwi stops sending
- *   the message to any clients.
- */
-
-var kiwi = require('../kiwi.js');
-var fs = require('fs');
-this.loaded_modules = {};
-
-
-/*
- * Load any unloaded modules as set in config
- */
-exports.loadModules = function (kiwi_root, config) {
-    var i, mod_name;
-    // Warn each module it is about to be unloaded
-    //this.run('unload');
-    //this.loaded_modules = {};
-
-    // Load each module and run the onload event
-    for (i in kiwi.config.modules) {
-        mod_name = kiwi.config.modules[i];
-        if (typeof this.loaded_modules[mod_name] !== 'undefined') continue;
-
-        this.loaded_modules[mod_name] = require(kiwi.kiwi_root + '/' + kiwi.config.module_dir + mod_name);
-    }
-    this.run('load');
-};
-
-
-/*
- * Unload and reload a specific module
- */
-exports.reloadModule = function (mod_name) {
-    fs.realpath(kiwi.kiwi_root + '/' + kiwi.config.module_dir + mod_name + '.js', function(err, resolvedPath){
-        try {
-            var mod_path = resolvedPath;
-
-            if (typeof kiwi.kiwi_mod.loaded_modules[mod_name] !== 'undefined') {
-                delete kiwi.kiwi_mod.loaded_modules[mod_name];
-            }
-            if (typeof require.cache[mod_path] !== 'undefined') {
-                delete require.cache[mod_path];
-            }
-
-            kiwi.kiwi_mod.loaded_modules[mod_name] = null;
-            kiwi.kiwi_mod.loaded_modules[mod_name] = require(mod_path);
-
-            kiwi.log('Module ' + mod_name + ' reloaded.');
-        } catch (e) {
-            kiwi.log('reloadModule error!');
-            kiwi.log(e);
-            return false;
-        }
-    });
-
-    //return this.loaded_modules[mod_name] ? true : false;
-};
-
-
-/*
- * Run an event against all loaded modules
- */
-exports.run = function (event_name, event_data, opts) {
-    var ret = event_data,
-        ret_tmp, mod_name;
-    
-    event_data = (typeof event_data === 'undefined') ? {} : event_data;
-    opts = (typeof opts === 'undefined') ? {} : opts;
-    
-    for (mod_name in this.loaded_modules) {
-        if (typeof this.loaded_modules[mod_name]['on' + event_name] === 'function') {
-            try {
-                ret_tmp = this.loaded_modules[mod_name]['on' + event_name](ret, opts);
-                if (ret_tmp === null) {
-                    return null;
-                }
-                ret = ret_tmp;
-            } catch (e) {
-            }
-        }
-    }
-
-    return ret;
-};
-
-exports.printMods = function () {
-    var mod_name;
-    kiwi.log('Loaded Kiwi modules:');
-    for (mod_name in this.loaded_modules) {
-        kiwi.log(' - ' + mod_name);
-    }
-};
diff --git a/server/lib/starttls.js b/server/lib/starttls.js
deleted file mode 100644 (file)
index e7f2240..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-// Target API:
-//
-//  var s = require('net').createStream(25, 'smtp.example.com');
-//  s.on('connect', function() {
-//   require('starttls')(s, options, function() {
-//      if (!s.authorized) {
-//        s.destroy();
-//        return;
-//      }
-//
-//      s.end("hello world\n");
-//    });
-//  });
-//
-//
-module.exports = function starttls(socket, options, cb) {
-
-  var sslcontext = require('crypto').createCredentials(options);
-
-  var pair = require('tls').createSecurePair(sslcontext, false);
-
-  var cleartext = pipe(pair, socket);
-
-  pair.on('secure', function() {
-    var verifyError = pair.ssl.verifyError();
-
-    if (verifyError) {
-      cleartext.authorized = false;
-      cleartext.authorizationError = verifyError;
-    } else {
-      cleartext.authorized = true;
-    }
-
-    if (cb) cb();
-  });
-
-  cleartext._controlReleased = true;
-  return cleartext;
-};
-
-
-function pipe(pair, socket) {
-  pair.encrypted.pipe(socket);
-  socket.pipe(pair.encrypted);
-
-  pair.fd = socket.fd;
-  var cleartext = pair.cleartext;
-  cleartext.socket = socket;
-  cleartext.encrypted = pair.encrypted;
-  cleartext.authorized = false;
-
-  function onerror(e) {
-    if (cleartext._controlReleased) {
-      cleartext.emit('error', e);
-    }
-  }
-
-  function onclose() {
-    socket.removeListener('error', onerror);
-    socket.removeListener('close', onclose);
-  }
-
-  socket.on('error', onerror);
-  socket.on('close', onclose);
-
-  return cleartext;
-}
-
diff --git a/server/lib/underscore.min.js b/server/lib/underscore.min.js
deleted file mode 100644 (file)
index ad19500..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-// Underscore.js 1.2.2
-// (c) 2011 Jeremy Ashkenas, DocumentCloud Inc.
-// Underscore is freely distributable under the MIT license.
-// Portions of Underscore are inspired or borrowed from Prototype,
-// Oliver Steele's Functional, and John Resig's Micro-Templating.
-// For all details and documentation:
-// http://documentcloud.github.com/underscore
-(function(){function r(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(b.isFunction(a.isEqual))return a.isEqual(c);if(b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return String(a)==String(c);case "[object Number]":return a=+a,c=+c,a!=a?c!=c:a==0?1/a==1/c:a==c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
-c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&r(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(m.call(a,h)&&(f++,!(g=m.call(c,h)&&r(a[h],c[h],d))))break;if(g){for(h in c)if(m.call(c,
-h)&&!f--)break;g=!f}}d.pop();return g}var s=this,F=s._,o={},k=Array.prototype,p=Object.prototype,i=k.slice,G=k.unshift,l=p.toString,m=p.hasOwnProperty,v=k.forEach,w=k.map,x=k.reduce,y=k.reduceRight,z=k.filter,A=k.every,B=k.some,q=k.indexOf,C=k.lastIndexOf,p=Array.isArray,H=Object.keys,t=Function.prototype.bind,b=function(a){return new n(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else typeof define==="function"&&define.amd?
-define("underscore",function(){return b}):s._=b;b.VERSION="1.2.2";var j=b.each=b.forEach=function(a,c,b){if(a!=null)if(v&&a.forEach===v)a.forEach(c,b);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(b,a[e],e,a)===o)break}else for(e in a)if(m.call(a,e)&&c.call(b,a[e],e,a)===o)break};b.map=function(a,c,b){var e=[];if(a==null)return e;if(w&&a.map===w)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=
-d!==void 0;a==null&&(a=[]);if(x&&a.reduce===x)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){a==null&&(a=[]);if(y&&a.reduceRight===y)return e&&(c=b.bind(c,e)),d!==void 0?a.reduceRight(c,d):a.reduceRight(c);a=(b.isArray(a)?a.slice():b.toArray(a)).reverse();return b.reduce(a,c,d,e)};b.find=b.detect=function(a,c,b){var e;
-D(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(z&&a.filter===z)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(A&&a.every===A)return a.every(c,b);j(a,function(a,g,h){if(!(e=e&&c.call(b,a,g,h)))return o});
-return e};var D=b.some=b.any=function(a,c,d){var c=c||b.identity,e=false;if(a==null)return e;if(B&&a.some===B)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return o});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return q&&a.indexOf===q?a.indexOf(c)!=-1:b=D(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(c.call?c||a:a[c]).apply(a,d)})};b.pluck=function(a,c){return b.map(a,function(a){return a[c]})};
-b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});return e.value};b.shuffle=function(a){var c=[],b;
-j(a,function(a,f){f==0?c[0]=a:(b=Math.floor(Math.random()*(f+1)),c[f]=c[b],c[b]=a)});return c};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,c){var b=a.criteria,d=c.criteria;return b<d?-1:b>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,c){var b=e(a,c);(d[b]||(d[b]=[])).push(a)});return d};b.sortedIndex=function(a,c,d){d||(d=b.identity);for(var e=0,f=a.length;e<
-f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=b.tail=function(a,b,d){return i.call(a,b==null||
-d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);return e};b.union=function(){return b.uniq(b.flatten(arguments,
-true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a,c){return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(q&&a.indexOf===q)return a.indexOf(c);
-for(d=0,e=a.length;d<e;d++)if(a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(C&&a.lastIndexOf===C)return a.lastIndexOf(b);for(var d=a.length;d--;)if(a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};var E=function(){};b.bind=function(a,c){var d,e;if(a.bind===t&&t)return t.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;
-e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));E.prototype=a.prototype;var b=new E,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,c){var d={};c||(c=b.identity);return function(){var b=c.apply(this,arguments);return m.call(d,b)?d[b]:d[b]=a.apply(this,arguments)}};b.delay=
-function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=
-null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments));return b.apply(this,d)}};b.compose=function(){var a=i.call(arguments);return function(){for(var b=i.call(arguments),d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=H||function(a){if(a!==
-Object(a))throw new TypeError("Invalid object");var b=[],d;for(d in a)m.call(a,d)&&(b[b.length]=d);return b};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)b[d]!==void 0&&(a[d]=b[d])});return a};b.defaults=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?
-a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return r(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(m.call(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=p||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};b.isArguments=l.call(arguments)=="[object Arguments]"?function(a){return l.call(a)=="[object Arguments]"}:
-function(a){return!(!a||!m.call(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};
-b.isUndefined=function(a){return a===void 0};b.noConflict=function(){s._=F;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#x27;").replace(/\//g,"&#x2F;")};b.mixin=function(a){j(b.functions(a),function(c){I(c,b[c]=a[c])})};var J=0;b.uniqueId=function(a){var b=J++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,
-interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape,function(a,b){return"',_.escape("+b.replace(/\\'/g,"'")+"),'"}).replace(d.interpolate,function(a,b){return"',"+b.replace(/\\'/g,"'")+",'"}).replace(d.evaluate||null,function(a,b){return"');"+b.replace(/\\'/g,"'").replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,
-"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e(a,b)}};var n=function(a){this._wrapped=a};b.prototype=n.prototype;var u=function(a,c){return c?b(a).chain():a},I=function(a,c){n.prototype[a]=function(){var a=i.call(arguments);G.call(a,this._wrapped);return u(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];n.prototype[a]=function(){b.apply(this._wrapped,
-arguments);return u(this._wrapped,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];n.prototype[a]=function(){return u(b.apply(this._wrapped,arguments),this._chain)}});n.prototype.chain=function(){this._chain=true;return this};n.prototype.value=function(){return this._wrapped}}).call(this);
-
diff --git a/server/web.js b/server/web.js
new file mode 100755 (executable)
index 0000000..6751e00
--- /dev/null
@@ -0,0 +1,72 @@
+var ws          = require('socket.io'),
+    events      = require('events'),
+    http        = require('http'),
+    https       = require('https'),
+    util        = require('util'),
+    fs          = require('fs'),
+    dns         = require('dns'),
+    _           = require('underscore'),
+    Client   = require('./client.js').Client;
+    HTTPHandler = require('./http-handler.js').HTTPHandler;
+
+var WebListener = function (config, transports) {
+    var handler,
+        hs,
+        opts,
+        that = this;
+
+    events.EventEmitter.call(this);
+    
+    http_handler = new HTTPHandler(config);
+    
+    if (config.secure) {
+        opts = {
+            key: fs.readFileSync(__dirname + '/' + config.ssl_key),
+            cert: fs.readFileSync(__dirname + '/' + config.ssl_cert)
+        };
+        // Do we have an intermediate certificate?
+        if (typeof config.ssl_ca !== 'undefined') {
+            opts.ca = fs.readFileSync(__dirname + '/' + config.ssl_ca);
+        }
+        hs = https.createServer(opts, function (request, response) {
+            http_handler.handler(request, response);
+        });
+        
+        this.ws = ws.listen(hs, {secure: true});
+        hs.listen(config.port, config.address);
+        console.log('Listening on ' + config.address + ':' + config.port.toString() + ' with SSL');
+    } else {
+        // Start some plain-text server up
+        hs = http.createServer(function (request, response) {
+            http_handler.handler(request, response);
+        });
+        this.ws = ws.listen(hs, {secure: false});
+        hs.listen(config.port, config.address);
+        console.log('Listening on ' + config.address + ':' + config.port.toString() + ' without SSL');
+    }
+    
+    this.ws.set('log level', 1);
+    this.ws.enable('browser client minification');
+    this.ws.enable('browser client etag');
+    this.ws.set('transports', transports);
+
+    this.ws.of('/kiwi').authorization(authorisation).on('connection', function () {
+        connection.apply(that, arguments);
+    });
+    this.ws.of('/kiwi').on('error', console.log);
+};
+util.inherits(WebListener, events.EventEmitter);
+
+module.exports.WebListener = WebListener;
+
+var authorisation = function (handshakeData, callback) {
+    dns.reverse(handshakeData.address.address, function (err, domains) {
+        handshakeData.revdns = (err) ? handshakeData.address.address : _.first(domains);
+        callback(null, true);
+    });
+};
+
+var connection = function (websocket) {
+    //console.log(websocket);
+    this.emit('connection', new Client(websocket));
+};