1 var _
= require('lodash'),
10 '005': 'RPL_ISUPPORT',
13 '250': 'RPL_STATSCONN',
14 '251': 'RPL_LUSERCLIENT',
16 '253': 'RPL_LUSERUNKNOWN',
17 '254': 'RPL_LUSERCHANNELS',
19 '265': 'RPL_LOCALUSERS',
20 '266': 'RPL_GLOBALUSERS',
22 '307': 'RPL_WHOISREGNICK',
23 '311': 'RPL_WHOISUSER',
24 '312': 'RPL_WHOISSERVER',
25 '313': 'RPL_WHOISOPERATOR',
26 '314': 'RPL_WHOWASUSER',
27 '315': 'RPL_ENDOFWHO',
28 '317': 'RPL_WHOISIDLE',
29 '318': 'RPL_ENDOFWHOIS',
30 '319': 'RPL_WHOISCHANNELS',
31 '321': 'RPL_LISTSTART',
34 '324': 'RPL_CHANNELMODEIS',
35 '328': 'RPL_CHANNEL_URL',
36 '329': 'RPL_CREATIONTIME',
37 '330': 'RPL_WHOISACCOUNT',
40 '333': 'RPL_TOPICWHOTIME',
41 '341': 'RPL_INVITING',
42 '352': 'RPL_WHOREPLY',
43 '353': 'RPL_NAMEREPLY',
45 '365': 'RPL_ENDOFLINKS',
46 '366': 'RPL_ENDOFNAMES',
48 '368': 'RPL_ENDOFBANLIST',
49 '369': 'RPL_ENDOFWHOWAS',
51 '375': 'RPL_MOTDSTART',
52 '376': 'RPL_ENDOFMOTD',
53 '378': 'RPL_WHOISHOST',
54 '379': 'RPL_WHOISMODES',
55 '401': 'ERR_NOSUCHNICK',
56 '404': 'ERR_CANNOTSENDTOCHAN',
57 '405': 'ERR_TOOMANYCHANNELS',
58 '406': 'ERR_WASNOSUCHNICK',
59 '421': 'ERR_UNKNOWNCOMMAND',
61 '432': 'ERR_ERRONEUSNICKNAME',
62 '433': 'ERR_NICKNAMEINUSE',
63 '441': 'ERR_USERNOTINCHANNEL',
64 '442': 'ERR_NOTONCHANNEL',
65 '443': 'ERR_USERONCHANNEL',
66 '451': 'ERR_NOTREGISTERED',
67 '464': 'ERR_PASSWDMISMATCH',
68 '470': 'ERR_LINKCHANNEL',
69 '471': 'ERR_CHANNELISFULL',
70 '473': 'ERR_INVITEONLYCHAN',
71 '474': 'ERR_BANNEDFROMCHAN',
72 '475': 'ERR_BADCHANNELKEY',
73 '481': 'ERR_NOPRIVILEGES',
74 '482': 'ERR_CHANOPRIVSNEEDED',
75 '670': 'RPL_STARTTLS',
76 '671': 'RPL_WHOISSECURE',
77 '900': 'RPL_SASLAUTHENTICATED',
78 '903': 'RPL_SASLLOGGEDIN',
79 '904': 'ERR_SASLNOTAUTHORISED',
80 '906': 'ERR_SASLABORTED',
81 '907': 'ERR_SASLALREADYAUTHED'
85 IrcCommands = function (irc_connection
) {
86 this.irc_connection
= irc_connection
;
88 module
.exports
= IrcCommands
;
90 IrcCommands
.prototype.dispatch = function (command
, data
) {
92 if (irc_numerics
[command
]) {
93 command
= irc_numerics
[command
];
95 if (handlers
[command
]) {
96 handlers
[command
].call(this, data
);
98 unknownCommand
.call(this, command
, data
);
102 IrcCommands
.addHandler = function (command
, handler
) {
103 if (typeof handler
!== 'function') {
106 handlers
[command
] = handler
;
109 IrcCommands
.addNumeric = function (numeric
, handler_name
) {
110 irc_numerics
[numeric
+ ''] = handler_name
+'';
113 unknownCommand = function (command
, data
) {
114 var params
= _
.clone(data
.params
);
116 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' unknown_command', {
119 trailing
: data
.trailing
124 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' notice', {
125 from_server: command.prefix ? true : false,
126 nick: command.nick || command.prefix || undefined,
127 ident: command.ident,
128 hostname: command.hostname,
129 target: command.params[0],
130 msg: command.trailing
137 'RPL_WELCOME': function (command
) {
138 var nick
= command
.params
[0];
139 this.irc_connection
.registered
= true;
140 this.cap_negotation
= false;
141 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' connect', {
146 'RPL_ISUPPORT': function (command
) {
147 var options
, i
, option
, matches
, j
;
148 options
= command
.params
;
149 for (i
= 1; i
< options
.length
; i
++) {
150 option
= options
[i
].split("=", 2);
151 option
[0] = option
[0].toUpperCase();
152 this.irc_connection
.options
[option
[0]] = (typeof option
[1] !== 'undefined') ? option
[1] : true;
153 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option
[0])) {
154 if (option
[0] === 'PREFIX') {
155 matches
= /\(([^)]*)\)(.*)/.exec(option
[1]);
156 if ((matches
) && (matches
.length
=== 3)) {
157 this.irc_connection
.options
.PREFIX
= [];
158 for (j
= 0; j
< matches
[2].length
; j
++) {
159 this.irc_connection
.options
.PREFIX
.push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
162 } else if (option
[0] === 'CHANTYPES') {
163 this.irc_connection
.options
.CHANTYPES
= this.irc_connection
.options
.CHANTYPES
.split('');
164 } else if (option
[0] === 'CHANMODES') {
165 this.irc_connection
.options
.CHANMODES
= option
[1].split(',');
166 } else if ((option
[0] === 'NAMESX') && (!_
.contains(this.irc_connection
.cap
.enabled
, 'multi-prefix'))) {
167 this.irc_connection
.write('PROTOCTL NAMESX');
171 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' options', {
172 options
: this.irc_connection
.options
,
173 cap
: this.irc_connection
.cap
.enabled
177 'RPL_ENDOFWHOIS': function (command
) {
178 this.irc_connection
.emit('user ' + command
.params
[1] + ' endofwhois', {
179 nick
: command
.params
[1],
180 msg
: command
.trailing
184 'RPL_AWAY': function (command
) {
185 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisaway', {
186 nick
: command
.params
[1],
187 reason
: command
.trailing
191 'RPL_WHOISUSER': function (command
) {
192 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisuser', {
193 nick
: command
.params
[1],
194 ident
: command
.params
[2],
195 host
: command
.params
[3],
196 msg
: command
.trailing
200 'RPL_WHOISSERVER': function (command
) {
201 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisserver', {
202 nick
: command
.params
[1],
203 irc_server
: command
.params
[2],
204 server_info
: command
.trailing
208 'RPL_WHOISOPERATOR': function (command
) {
209 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisoperator', {
210 nick
: command
.params
[1],
211 msg
: command
.trailing
215 'RPL_WHOISCHANNELS': function (command
) {
216 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoischannels', {
217 nick
: command
.params
[1],
218 chans
: command
.trailing
222 'RPL_WHOISMODES': function (command
) {
223 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoismodes', {
224 nick
: command
.params
[1],
225 msg
: command
.trailing
229 'RPL_WHOISIDLE': function (command
) {
230 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisidle', {
231 nick
: command
.params
[1],
232 idle
: command
.params
[2],
233 logon
: command
.params
[3] || undefined
237 'RPL_WHOISREGNICK': function (command
) {
238 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisregnick', {
239 nick
: command
.params
[1],
240 msg
: command
.trailing
244 'RPL_WHOISHOST': function (command
) {
245 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoishost', {
246 nick
: command
.params
[1],
247 msg
: command
.trailing
251 'RPL_WHOISSECURE': function (command
) {
252 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoissecure', {
253 nick
: command
.params
[1]
257 'RPL_WHOISACCOUNT': function (command
) {
258 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisaccount', {
259 nick
: command
.params
[1],
260 account
: command
.params
[2]
264 'RPL_WHOWASUSER': function (command
) {
265 this.irc_connection
.emit('user ' + command
.params
[1] + ' whowas', {
266 nick
: command
.params
[1],
267 ident
: command
.params
[2],
268 host
: command
.params
[3],
269 real_name
: command
.trailing
273 'RPL_ENDOFWHOWAS': function (command
) {
274 this.irc_connection
.emit('user ' + command
.params
[1] + ' endofwhowas', {
275 nick
: command
.params
[1]
279 'ERR_WASNOSUCHNICK': function (command
) {
280 this.irc_connection
.emit('user ' + command
.params
[1] + ' wasnosucknick', {
281 nick
: command
.params
[1]
285 'RPL_LISTSTART': function (command
) {
286 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_start', {});
289 'RPL_LISTEND': function (command
) {
290 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_end', {});
293 'RPL_LIST': function (command
) {
294 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_channel', {
295 channel
: command
.params
[1],
296 num_users
: parseInt(command
.params
[2], 10),
297 topic
: command
.trailing
301 'RPL_CHANNELMODEIS': function (command
) {
302 var channel
= command
.params
[1],
303 modes
= parseModeList
.call(this, command
.params
[2], command
.params
.slice(3));
305 this.irc_connection
.emit('channel ' + channel
+ ' info', {
311 'RPL_CREATIONTIME': function (command
) {
312 var channel
= command
.params
[1];
314 this.irc_connection
.emit('channel ' + channel
+ ' info', {
316 created_at
: parseInt(command
.params
[2], 10)
320 'RPL_CHANNEL_URL': function (command
) {
321 var channel
= command
.params
[1];
323 this.irc_connection
.emit('channel ' + channel
+ ' info', {
325 url
: command
.trailing
329 'RPL_MOTD': function (command
) {
330 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd', {
331 motd
: command
.trailing
+ '\n'
335 'RPL_MOTDSTART': function (command
) {
336 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd_start', {});
339 'RPL_ENDOFMOTD': function (command
) {
340 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd_end', {});
343 'RPL_NAMEREPLY': function (command
) {
344 var members
= command
.trailing
.split(' ');
345 var member_list
= [];
347 _
.each(members
, function (member
) {
352 // Make sure we have some prefixes already
353 if (that
.irc_connection
.options
.PREFIX
) {
354 for (j
= 0; j
< that
.irc_connection
.options
.PREFIX
.length
; j
++) {
355 if (member
.charAt(i
) === that
.irc_connection
.options
.PREFIX
[j
].symbol
) {
356 modes
.push(that
.irc_connection
.options
.PREFIX
[j
].mode
);
362 member_list
.push({nick
: member
, modes
: modes
});
365 this.irc_connection
.emit('channel ' + command
.params
[2] + ' userlist', {
367 channel
: command
.params
[2]
371 'RPL_ENDOFNAMES': function (command
) {
372 this.irc_connection
.emit('channel ' + command
.params
[1] + ' userlist_end', {
373 channel
: command
.params
[1]
377 'RPL_WHOREPLY': function (command
) {
378 // For the time being, NOOP this command so they don't get passed
379 // down to the client. Waste of bandwidth since we do not use it yet
380 // TODO: Impliment RPL_WHOREPLY
383 'RPL_ENDOFWHO': function (command
) {
384 // For the time being, NOOP this command so they don't get passed
385 // down to the client. Waste of bandwidth since we do not use it yet
386 // TODO: Impliment RPL_ENDOFWHO
389 'RPL_BANLIST': function (command
) {
390 this.irc_connection
.emit('channel ' + command
.params
[1] + ' banlist', {
391 channel
: command
.params
[1],
392 banned
: command
.params
[2],
393 banned_by
: command
.params
[3],
394 banned_at
: command
.params
[4]
398 'RPL_ENDOFBANLIST': function (command
) {
399 this.irc_connection
.emit('channel ' + command
.params
[1] + ' banlist_end', {
400 channel
: command
.params
[1]
404 'RPL_TOPIC': function (command
) {
405 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topic', {
406 channel
: command
.params
[1],
407 topic
: command
.trailing
411 'RPL_NOTOPIC': function (command
) {
412 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topic', {
413 channel
: command
.params
[1],
418 'RPL_TOPICWHOTIME': function (command
) {
419 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topicsetby', {
420 nick
: command
.params
[2],
421 channel
: command
.params
[1],
422 when
: command
.params
[3]
426 'RPL_INVITING': function (command
) {
427 this.irc_connection
.emit('channel ' + command
.params
[1] + ' invited', {
428 nick
: command
.params
[0],
429 channel
: command
.params
[1]
433 'PING': function (command
) {
434 this.irc_connection
.write('PONG ' + command
.trailing
);
437 'JOIN': function (command
) {
439 if (typeof command
.trailing
=== 'string' && command
.trailing
!== '') {
440 channel
= command
.trailing
;
441 } else if (typeof command
.params
[0] === 'string' && command
.params
[0] !== '') {
442 channel
= command
.params
[0];
445 // Check if we have a server-time
446 time
= getServerTime
.call(this, command
);
448 this.irc_connection
.emit('channel ' + channel
+ ' join', {
450 ident
: command
.ident
,
451 hostname
: command
.hostname
,
457 'PART': function (command
) {
460 // Check if we have a server-time
461 time
= getServerTime
.call(this, command
);
463 this.irc_connection
.emit('channel ' + command
.params
[0] + ' part', {
465 ident
: command
.ident
,
466 hostname
: command
.hostname
,
467 channel
: command
.params
[0],
468 message
: command
.trailing
,
473 'KICK': function (command
) {
476 // Check if we have a server-time
477 time
= getServerTime
.call(this, command
);
479 this.irc_connection
.emit('channel ' + command
.params
[0] + ' kick', {
480 kicked
: command
.params
[1],
482 ident
: command
.ident
,
483 hostname
: command
.hostname
,
484 channel
: command
.params
[0],
485 message
: command
.trailing
,
490 'QUIT': function (command
) {
493 // Check if we have a server-time
494 time
= getServerTime
.call(this, command
);
496 this.irc_connection
.emit('user ' + command
.nick
+ ' quit', {
498 ident
: command
.ident
,
499 hostname
: command
.hostname
,
500 message
: command
.trailing
,
505 'NOTICE': function (command
) {
509 // Check if we have a server-time
510 time
= getServerTime
.call(this, command
);
513 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
514 // It's a CTCP response
515 namespace = (command
.params
[0].toLowerCase() == this.irc_connection
.nick
.toLowerCase()) ? 'user' : 'channel';
516 this.irc_connection
.emit(namespace + ' ' + command
.params
[0] + ' ctcp_response', {
518 ident
: command
.ident
,
519 hostname
: command
.hostname
,
520 channel
: command
.params
[0],
521 msg
: command
.trailing
.substring(1, command
.trailing
.length
- 1),
525 namespace = (command
.params
[0].toLowerCase() == this.irc_connection
.nick
.toLowerCase() || command
.params
[0] == '*') ?
529 this.irc_connection
.emit(namespace + ' ' + command
.params
[0] + ' notice', {
530 from_server
: command
.prefix
? true : false,
531 nick
: command
.nick
|| command
.prefix
|| undefined,
532 ident
: command
.ident
,
533 hostname
: command
.hostname
,
534 target
: command
.params
[0],
535 msg
: command
.trailing
,
541 'NICK': function (command
) {
544 // Check if we have a server-time
545 time
= getServerTime
.call(this, command
);
547 this.irc_connection
.emit('user ' + command
.nick
+ ' nick', {
549 ident
: command
.ident
,
550 hostname
: command
.hostname
,
551 newnick
: command
.trailing
|| command
.params
[0],
556 'TOPIC': function (command
) {
559 // If we don't have an associated channel, no need to continue
560 if (!command
.params
[0]) return;
562 // Check if we have a server-time
563 time
= getServerTime
.call(this, command
);
565 var channel
= command
.params
[0],
566 topic
= command
.trailing
|| '';
568 this.irc_connection
.emit('channel ' + channel
+ ' topic', {
576 'MODE': function (command
) {
577 var modes
= [], event
, time
;
579 // Check if we have a server-time
580 time
= getServerTime
.call(this, command
);
582 // Get a JSON representation of the modes
583 modes
= parseModeList
.call(this, command
.params
[1] || command
.trailing
, command
.params
.slice(2));
584 event
= (_
.contains(this.irc_connection
.options
.CHANTYPES
, command
.params
[0][0]) ? 'channel ' : 'user ') + command
.params
[0] + ' mode';
586 this.irc_connection
.emit(event
, {
587 target
: command
.params
[0],
588 nick
: command
.nick
|| command
.prefix
|| '',
594 'PRIVMSG': function (command
) {
595 var tmp
, namespace, time
;
597 // Check if we have a server-time
598 time
= getServerTime
.call(this, command
);
600 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
602 if (command
.trailing
.substr(1, 6) === 'ACTION') {
603 this.irc_connection
.clientEvent('action', {
605 ident
: command
.ident
,
606 hostname
: command
.hostname
,
607 channel
: command
.params
[0],
608 msg
: command
.trailing
.substring(8, command
.trailing
.length
- 1),
611 } else if (command
.trailing
.substr(1, 4) === 'KIWI') {
612 tmp
= command
.trailing
.substring(6, command
.trailing
.length
- 1);
613 namespace = tmp
.split(' ', 1)[0];
614 this.irc_connection
.clientEvent('kiwi', {
615 namespace: namespace,
616 data
: tmp
.substr(namespace.length
+ 1),
619 } else if (command
.trailing
.substr(1, 7) === 'VERSION') {
620 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1));
621 } else if (command
.trailing
.substr(1, 6) === 'SOURCE') {
622 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String
.fromCharCode(1));
623 } else if (command
.trailing
.substr(1, 10) === 'CLIENTINFO') {
624 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String
.fromCharCode(1));
626 namespace = (command
.params
[0].toLowerCase() == this.irc_connection
.nick
.toLowerCase()) ? 'user' : 'channel';
627 this.irc_connection
.emit(namespace + ' ' + command
.nick
+ ' ctcp_request', {
629 ident
: command
.ident
,
630 hostname
: command
.hostname
,
631 target
: command
.params
[0],
632 type
: (command
.trailing
.substring(1, command
.trailing
.length
- 1).split(' ') || [null])[0],
633 msg
: command
.trailing
.substring(1, command
.trailing
.length
- 1),
638 // A message to a user (private message) or to a channel?
639 namespace = (command
.params
[0].toLowerCase() == this.irc_connection
.nick
.toLowerCase()) ? 'user ' + command
.nick
: 'channel ' + command
.params
[0];
640 this.irc_connection
.emit(namespace + ' privmsg', {
642 ident
: command
.ident
,
643 hostname
: command
.hostname
,
644 channel
: command
.params
[0],
645 msg
: command
.trailing
,
651 'CAP': function (command
) {
652 // TODO: capability modifiers
653 // i.e. - for disable, ~ for requires ACK, = for sticky
654 var capabilities
= command
.trailing
.replace(/(?:^| )[\-~=]/, '').split(' ');
657 // Which capabilities we want to enable
658 var want
= ['multi-prefix', 'away-notify', 'server-time', 'znc.in/server-time-iso', 'znc.in/server-time'];
660 if (this.irc_connection
.password
) {
664 switch (command
.params
[1]) {
666 // Compute which of the available capabilities we want and request them
667 request
= _
.intersection(capabilities
, want
);
668 if (request
.length
> 0) {
669 this.irc_connection
.cap
.requested
= request
;
670 this.irc_connection
.write('CAP REQ :' + request
.join(' '));
672 this.irc_connection
.write('CAP END');
673 this.irc_connection
.cap_negotation
= false;
677 if (capabilities
.length
> 0) {
678 // Update list of enabled capabilities
679 this.irc_connection
.cap
.enabled
= capabilities
;
680 // Update list of capabilities we would like to have but that aren't enabled
681 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
683 if (this.irc_connection
.cap
.enabled
.length
> 0) {
684 if (_
.contains(this.irc_connection
.cap
.enabled
, 'sasl')) {
685 this.irc_connection
.sasl
= true;
686 this.irc_connection
.write('AUTHENTICATE PLAIN');
688 this.irc_connection
.write('CAP END');
689 this.irc_connection
.cap_negotation
= false;
694 if (capabilities
.length
> 0) {
695 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
697 if (this.irc_connection
.cap
.requested
.length
> 0) {
698 this.irc_connection
.write('CAP END');
699 this.irc_connection
.cap_negotation
= false;
703 // should we do anything here?
708 'AUTHENTICATE': function (command
) {
709 var b
= new Buffer(this.irc_connection
.nick
+ "\0" + this.irc_connection
.nick
+ "\0" + this.irc_connection
.password
, 'utf8');
710 var b64
= b
.toString('base64');
711 if (command
.params
[0] === '+') {
712 while (b64
.length
>= 400) {
713 this.irc_connection
.write('AUTHENTICATE ' + b64
.slice(0, 399));
714 b64
= b64
.slice(399);
716 if (b64
.length
> 0) {
717 this.irc_connection
.write('AUTHENTICATE ' + b64
);
719 this.irc_connection
.write('AUTHENTICATE +');
722 this.irc_connection
.write('CAP END');
723 this.irc_connection
.cap_negotation
= false;
727 'AWAY': function (command
) {
730 // Check if we have a server-time
731 time
= getServerTime
.call(this, command
);
733 this.irc_connection
.emit('user ' + command
.nick
+ ' away', {
735 msg
: command
.trailing
,
740 'RPL_SASLAUTHENTICATED': function (command
) {
741 this.irc_connection
.write('CAP END');
742 this.irc_connection
.cap_negotation
= false;
743 this.irc_connection
.sasl
= true;
746 'RPL_SASLLOGGEDIN': function (command
) {
747 if (this.irc_connection
.cap_negotation
=== false) {
748 this.irc_connection
.write('CAP END');
752 'ERR_SASLNOTAUTHORISED': function (command
) {
753 this.irc_connection
.write('CAP END');
754 this.irc_connection
.cap_negotation
= false;
757 'ERR_SASLABORTED': function (command
) {
758 this.irc_connection
.write('CAP END');
759 this.irc_connection
.cap_negotation
= false;
762 'ERR_SASLALREADYAUTHED': function (command
) {
766 'ERROR': function (command
) {
767 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' error', {
768 reason
: command
.trailing
771 ERR_PASSWDMISMATCH: function (command
) {
772 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' password_mismatch', {});
775 ERR_LINKCHANNEL: function (command
) {
776 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' channel_redirect', {
777 from: command
.params
[1],
778 to
: command
.params
[2]
782 ERR_NOSUCHNICK: function (command
) {
783 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' no_such_nick', {
784 nick
: command
.params
[1],
785 reason
: command
.trailing
789 ERR_CANNOTSENDTOCHAN: function (command
) {
790 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' cannot_send_to_chan', {
791 channel
: command
.params
[1],
792 reason
: command
.trailing
796 ERR_TOOMANYCHANNELS: function (command
) {
797 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' too_many_channels', {
798 channel
: command
.params
[1],
799 reason
: command
.trailing
803 ERR_USERNOTINCHANNEL: function (command
) {
804 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' user_not_in_channel', {
805 nick
: command
.params
[0],
806 channel
: command
.params
[1],
807 reason
: command
.trailing
811 ERR_NOTONCHANNEL: function (command
) {
812 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' not_on_channel', {
813 channel
: command
.params
[1],
814 reason
: command
.trailing
818 ERR_USERONCHANNEL: function (command
) {
819 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' user_on_channel', {
820 nick
: command
.params
[1],
821 channel
: command
.params
[2]
825 ERR_CHANNELISFULL: function (command
) {
826 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' channel_is_full', {
827 channel
: command
.params
[1],
828 reason
: command
.trailing
832 ERR_INVITEONLYCHAN: function (command
) {
833 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' invite_only_channel', {
834 channel
: command
.params
[1],
835 reason
: command
.trailing
839 ERR_BANNEDFROMCHAN: function (command
) {
840 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' banned_from_channel', {
841 channel
: command
.params
[1],
842 reason
: command
.trailing
846 ERR_BADCHANNELKEY: function (command
) {
847 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' bad_channel_key', {
848 channel
: command
.params
[1],
849 reason
: command
.trailing
853 ERR_CHANOPRIVSNEEDED: function (command
) {
854 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' chanop_privs_needed', {
855 channel
: command
.params
[1],
856 reason
: command
.trailing
860 ERR_NICKNAMEINUSE: function (command
) {
861 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' nickname_in_use', {
862 nick
: command
.params
[1],
863 reason
: command
.trailing
867 ERR_ERRONEUSNICKNAME: function(command
) {
868 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' erroneus_nickname', {
869 nick
: command
.params
[1],
870 reason
: command
.trailing
874 ERR_NOTREGISTERED: function (command
) {
877 RPL_MAPMORE: function (command
) {
878 var params
= _
.clone(command
.params
);
880 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
883 RPL_MAPEND: function (command
) {
884 var params
= _
.clone(command
.params
);
886 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
889 RPL_LINKS: function (command
) {
890 var params
= _
.clone(command
.params
);
892 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
895 RPL_ENDOFLINKS: function (command
) {
896 var params
= _
.clone(command
.params
);
898 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
901 ERR_UNKNOWNCOMMAND: function (command
) {
902 var params
= _
.clone(command
.params
);
904 genericNotice
.call(this, command
, '`' + params
.join(', ') + '` ' + command
.trailing
);
907 ERR_NOMOTD: function (command
) {
908 var params
= _
.clone(command
.params
);
910 genericNotice
.call(this, command
, command
.trailing
);
913 ERR_NOPRIVILEGES: function (command
) {
914 var params
= _
.clone(command
.params
);
916 genericNotice
.call(this, command
, command
.trailing
);
919 RPL_STATSCONN: function (command
) {
920 var params
= _
.clone(command
.params
);
922 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
925 RPL_LUSERCLIENT: function (command
) {
926 var params
= _
.clone(command
.params
);
928 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
931 RPL_LUSEROP: function (command
) {
932 var params
= _
.clone(command
.params
);
934 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
937 RPL_LUSERUNKNOWN: function (command
) {
938 var params
= _
.clone(command
.params
);
940 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
943 RPL_LUSERCHANNELS: function (command
) {
944 var params
= _
.clone(command
.params
);
946 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
949 RPL_LUSERME: function (command
) {
950 var params
= _
.clone(command
.params
);
952 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
955 RPL_LOCALUSERS: function (command
) {
956 var params
= _
.clone(command
.params
);
958 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
961 RPL_GLOBALUSERS: function (command
) {
962 var params
= _
.clone(command
.params
);
964 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
971 function genericNotice (command
, msg
, is_error
) {
972 // Default to being an error
973 if (typeof is_error
!== 'boolean')
976 this.irc_connection
.clientEvent('notice', {
978 nick
: command
.prefix
,
981 target
: command
.params
[0],
983 numeric
: parseInt(command
.command
, 10)
989 * Convert a mode string such as '+k pass', or '-i' to a readable
991 * [ { mode: '+k', param: 'pass' } ]
992 * [ { mode: '-i', param: null } ]
994 function parseModeList(mode_string
, mode_params
) {
995 var chanmodes
= this.irc_connection
.options
.CHANMODES
|| [],
996 prefixes
= this.irc_connection
.options
.PREFIX
|| [],
997 always_param
= (chanmodes
[0] || '').concat((chanmodes
[1] || '')),
999 has_param
, i
, j
, add
;
1001 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
1002 list
.push(prefix
.mode
);
1005 always_param
= always_param
.split('').concat(prefixes
);
1007 has_param = function (mode
, add
) {
1008 if (_
.find(always_param
, function (m
) {
1012 } else if (add
&& _
.find((chanmodes
[2] || '').split(''), function (m
) {
1022 for (i
= 0; i
< mode_string
.length
; i
++) {
1023 switch (mode_string
[i
]) {
1031 if (has_param(mode_string
[i
], add
)) {
1032 modes
.push({mode
: (add
? '+' : '-') + mode_string
[i
], param
: mode_params
[j
]});
1035 modes
.push({mode
: (add
? '+' : '-') + mode_string
[i
], param
: null});
1044 function getServerTime(command
) {
1047 // No tags? No times.
1048 if (!command
.tags
|| command
.tags
.length
=== 0) {
1052 if (capContainsAny
.call(this, ['server-time', 'znc.in/server-time', 'znc.in/server-time-iso'])) {
1053 time
= _
.find(command
.tags
, function (tag
) {
1054 return tag
.tag
=== 'time';
1057 time
= time
? time
.value
: undefined;
1059 // Convert the time value to a unixtimestamp
1060 if (typeof time
=== 'string') {
1061 if (time
.indexOf('T') > -1) {
1062 time
= parseISO8601(time
);
1064 } else if(time
.match(/^[0-9.]+$/)) {
1065 // A string formatted unix timestamp
1066 time
= new Date(time
* 1000);
1069 time
= time
.getTime();
1071 } else if (typeof time
=== 'number') {
1072 time
= new Date(time
* 1000);
1073 time
= time
.getTime();
1081 function capContainsAny (caps
) {
1083 if (!caps
instanceof Array
) {
1086 intersection
= _
.intersection(this.irc_connection
.cap
.enabled
, caps
);
1087 return intersection
.length
> 0;
1091 // Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
1092 function parseISO8601(str
) {
1093 if (Date
.prototype.toISOString
) {
1094 return new Date(str
);
1096 var parts
= str
.split('T'),
1097 dateParts
= parts
[0].split('-'),
1098 timeParts
= parts
[1].split('Z'),
1099 timeSubParts
= timeParts
[0].split(':'),
1100 timeSecParts
= timeSubParts
[2].split('.'),
1101 timeHours
= Number(timeSubParts
[0]),
1104 _date
.setUTCFullYear(Number(dateParts
[0]));
1105 _date
.setUTCDate(1);
1106 _date
.setUTCMonth(Number(dateParts
[1])-1);
1107 _date
.setUTCDate(Number(dateParts
[2]));
1108 _date
.setUTCHours(Number(timeHours
));
1109 _date
.setUTCMinutes(Number(timeSubParts
[1]));
1110 _date
.setUTCSeconds(Number(timeSecParts
[0]));
1111 if (timeSecParts
[1]) {
1112 _date
.setUTCMilliseconds(Number(timeSecParts
[1]));