TLS connection fixes
[KiwiIRC.git] / server / irc / commands.js
index 445cd5a5796eacaf8745dbf2d89438082b57f6ed..03899aa66e537cebb8e1fb0d66fc62508334b95a 100644 (file)
@@ -24,16 +24,22 @@ irc_numerics = {
     '312': 'RPL_WHOISSERVER',
     '313': 'RPL_WHOISOPERATOR',
     '314': 'RPL_WHOWASUSER',
+    '315': 'RPL_ENDOFWHO',
     '317': 'RPL_WHOISIDLE',
     '318': 'RPL_ENDOFWHOIS',
     '319': 'RPL_WHOISCHANNELS',
     '321': 'RPL_LISTSTART',
     '322': 'RPL_LIST',
     '323': 'RPL_LISTEND',
+    '324': 'RPL_CHANNELMODEIS',
+    '328': 'RPL_CHANNEL_URL',
+    '329': 'RPL_CREATIONTIME',
     '330': 'RPL_WHOISACCOUNT',
     '331': 'RPL_NOTOPIC',
     '332': 'RPL_TOPIC',
     '333': 'RPL_TOPICWHOTIME',
+    '341': 'RPL_INVITING',
+    '352': 'RPL_WHOREPLY',
     '353': 'RPL_NAMEREPLY',
     '364': 'RPL_LINKS',
     '365': 'RPL_ENDOFLINKS',
@@ -292,6 +298,34 @@ handlers = {
         });
     },
 
+    'RPL_CHANNELMODEIS': function (command) {
+        var channel = command.params[1],
+            modes = parseModeList.call(this, command.params[2], command.params.slice(3));
+
+        this.irc_connection.emit('channel ' + channel + ' info', {
+            channel: channel,
+            modes: modes
+        });
+    },
+
+    'RPL_CREATIONTIME': function (command) {
+        var channel = command.params[1];
+
+        this.irc_connection.emit('channel ' + channel + ' info', {
+            channel: channel,
+            created_at: parseInt(command.params[2], 10)
+        });
+    },
+
+    'RPL_CHANNEL_URL': function (command) {
+        var channel = command.params[1];
+
+        this.irc_connection.emit('channel ' + channel + ' info', {
+            channel: channel,
+            url: command.trailing
+        });
+    },
+
     'RPL_MOTD': function (command) {
         this.irc_connection.emit('server '  + this.irc_connection.irc_host.hostname + ' motd', {
             motd: command.trailing + '\n'
@@ -340,6 +374,18 @@ handlers = {
         });
     },
 
+    'RPL_WHOREPLY': function (command) {
+        // For the time being, NOOP this command so they don't get passed
+        // down to the client. Waste of bandwidth since we do not use it yet
+        // TODO: Impliment RPL_WHOREPLY
+    },
+
+    'RPL_ENDOFWHO': function (command) {
+        // For the time being, NOOP this command so they don't get passed
+        // down to the client. Waste of bandwidth since we do not use it yet
+        // TODO: Impliment RPL_ENDOFWHO
+    },
+
     'RPL_BANLIST': function (command) {
         this.irc_connection.emit('channel ' + command.params[1] + ' banlist', {
             channel: command.params[1],
@@ -377,6 +423,13 @@ handlers = {
         });
     },
 
+    'RPL_INVITING': function (command) {
+        this.irc_connection.emit('channel ' + command.params[1] + ' invited', {
+            nick: command.params[0],
+            channel: command.params[1]
+        });
+    },
+
     'PING': function (command) {
         this.irc_connection.write('PONG ' + command.trailing);
     },
@@ -389,12 +442,8 @@ handlers = {
             channel = command.params[0];
         }
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         this.irc_connection.emit('channel ' + channel + ' join', {
             nick: command.nick,
@@ -408,12 +457,8 @@ handlers = {
     'PART': function (command) {
         var time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         this.irc_connection.emit('channel ' + command.params[0] + ' part', {
             nick: command.nick,
@@ -428,12 +473,8 @@ handlers = {
     'KICK': function (command) {
         var time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         this.irc_connection.emit('channel ' + command.params[0] + ' kick', {
             kicked: command.params[1],
@@ -449,12 +490,8 @@ handlers = {
     'QUIT': function (command) {
         var time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         this.irc_connection.emit('user ' + command.nick + ' quit', {
             nick: command.nick,
@@ -469,12 +506,8 @@ handlers = {
         var namespace,
             time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
 
         if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
@@ -508,12 +541,8 @@ handlers = {
     'NICK': function (command) {
         var time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         this.irc_connection.emit('user ' + command.nick + ' nick', {
             nick: command.nick,
@@ -530,12 +559,8 @@ handlers = {
         // If we don't have an associated channel, no need to continue
         if (!command.params[0]) return;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         var channel = command.params[0],
             topic = command.trailing || '';
@@ -549,62 +574,13 @@ handlers = {
     },
 
     'MODE': function (command) {
-        var chanmodes = this.irc_connection.options.CHANMODES || [],
-            prefixes = this.irc_connection.options.PREFIX || [],
-            always_param = (chanmodes[0] || '').concat((chanmodes[1] || '')),
-            modes = [],
-            has_param, i, j, add, event, time;
-
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        var modes = [], event, time;
 
-        prefixes = _.reduce(prefixes, function (list, prefix) {
-            list.push(prefix.mode);
-            return list;
-        }, []);
-        always_param = always_param.split('').concat(prefixes);
-
-        has_param = function (mode, add) {
-            if (_.find(always_param, function (m) {
-                return m === mode;
-            })) {
-                return true;
-            } else if (add && _.find((chanmodes[2] || '').split(''), function (m) {
-                return m === mode;
-            })) {
-                return true;
-            } else {
-                return false;
-            }
-        };
-
-        if (!command.params[1]) {
-            command.params[1] = command.trailing;
-        }
-
-        j = 0;
-        for (i = 0; i < command.params[1].length; i++) {
-            switch (command.params[1][i]) {
-                case '+':
-                    add = true;
-                    break;
-                case '-':
-                    add = false;
-                    break;
-                default:
-                    if (has_param(command.params[1][i], add)) {
-                        modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: command.params[2 + j]});
-                        j++;
-                    } else {
-                        modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: null});
-                    }
-            }
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
+        // Get a JSON representation of the modes
+        modes = parseModeList.call(this, command.params[1] || command.trailing, command.params.slice(2));
         event = (_.contains(this.irc_connection.options.CHANTYPES, command.params[0][0]) ? 'channel ' : 'user ') + command.params[0] + ' mode';
 
         this.irc_connection.emit(event, {
@@ -618,12 +594,8 @@ handlers = {
     'PRIVMSG': function (command) {
         var tmp, namespace, time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
             //CTCP request
@@ -683,7 +655,7 @@ handlers = {
         var request;
 
         // Which capabilities we want to enable
-        var want = ['multi-prefix', 'away-notify', 'server-time'];
+        var want = ['multi-prefix', 'away-notify', 'server-time', 'znc.in/server-time-iso', 'znc.in/server-time'];
 
         if (this.irc_connection.password) {
             want.push('sasl');
@@ -755,12 +727,8 @@ handlers = {
     'AWAY': function (command) {
         var time;
 
-        if (_.contains(this.irc_connection.cap.enabled, 'server-time') && command.tags && command.tags.length > 0) {
-            time = _.find(command.tags, function (tag) {
-                return tag.tag === 'time';
-            });
-            time = time ? time.value : undefined;
-        }
+        // Check if we have a server-time
+        time = getServerTime.call(this, command);
 
         this.irc_connection.emit('user ' + command.nick + ' away', {
             nick: command.nick,
@@ -1015,3 +983,135 @@ function genericNotice (command, msg, is_error) {
         numeric: parseInt(command.command, 10)
     });
 }
+
+
+/**
+ * Convert a mode string such as '+k pass', or '-i' to a readable
+ * format.
+ * [ { mode: '+k', param: 'pass' } ]
+ * [ { mode: '-i', param: null } ]
+ */
+function parseModeList(mode_string, mode_params) {
+    var chanmodes = this.irc_connection.options.CHANMODES || [],
+        prefixes = this.irc_connection.options.PREFIX || [],
+        always_param = (chanmodes[0] || '').concat((chanmodes[1] || '')),
+        modes = [],
+        has_param, i, j, add;
+
+    prefixes = _.reduce(prefixes, function (list, prefix) {
+        list.push(prefix.mode);
+        return list;
+    }, []);
+    always_param = always_param.split('').concat(prefixes);
+
+    has_param = function (mode, add) {
+        if (_.find(always_param, function (m) {
+            return m === mode;
+        })) {
+            return true;
+        } else if (add && _.find((chanmodes[2] || '').split(''), function (m) {
+            return m === mode;
+        })) {
+            return true;
+        } else {
+            return false;
+        }
+    };
+
+    j = 0;
+    for (i = 0; i < mode_string.length; i++) {
+        switch (mode_string[i]) {
+            case '+':
+                add = true;
+                break;
+            case '-':
+                add = false;
+                break;
+            default:
+                if (has_param(mode_string[i], add)) {
+                    modes.push({mode: (add ? '+' : '-') + mode_string[i], param: mode_params[j]});
+                    j++;
+                } else {
+                    modes.push({mode: (add ? '+' : '-') + mode_string[i], param: null});
+                }
+        }
+    }
+
+    return modes;
+}
+
+
+function getServerTime(command) {
+    var time;
+
+    // No tags? No times.
+    if (!command.tags || command.tags.length === 0) {
+        return time;
+    }
+
+    if (capContainsAny.call(this, ['server-time', 'znc.in/server-time', 'znc.in/server-time-iso'])) {
+        time = _.find(command.tags, function (tag) {
+            return tag.tag === 'time';
+        });
+
+        time = time ? time.value : undefined;
+
+        // Convert the time value to a unixtimestamp
+        if (typeof time === 'string') {
+            if (time.indexOf('T') > -1) {
+                time = parseISO8601(time);
+
+            } else if(time.match(/^[0-9.]+$/)) {
+                // A string formatted unix timestamp
+                time = new Date(time * 1000);
+            }
+
+            time = time.getTime();
+
+        } else if (typeof time === 'number') {
+            time = new Date(time * 1000);
+            time = time.getTime();
+        }
+    }
+
+    return time;
+}
+
+
+function capContainsAny (caps) {
+    var intersection;
+    if (!caps instanceof Array) {
+        caps = [caps];
+    }
+    intersection = _.intersection(this.irc_connection.cap.enabled, caps);
+    return intersection.length > 0;
+}
+
+
+// Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
+function parseISO8601(str) {
+    if (Date.prototype.toISOString) {
+        return new Date(str);
+    } else {
+        var parts = str.split('T'),
+            dateParts = parts[0].split('-'),
+            timeParts = parts[1].split('Z'),
+            timeSubParts = timeParts[0].split(':'),
+            timeSecParts = timeSubParts[2].split('.'),
+            timeHours = Number(timeSubParts[0]),
+            _date = new Date();
+
+        _date.setUTCFullYear(Number(dateParts[0]));
+        _date.setUTCDate(1);
+        _date.setUTCMonth(Number(dateParts[1])-1);
+        _date.setUTCDate(Number(dateParts[2]));
+        _date.setUTCHours(Number(timeHours));
+        _date.setUTCMinutes(Number(timeSubParts[1]));
+        _date.setUTCSeconds(Number(timeSecParts[0]));
+        if (timeSecParts[1]) {
+            _date.setUTCMilliseconds(Number(timeSecParts[1]));
+        }
+
+        return _date;
+    }
+}