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', {
124 'RPL_WELCOME': function (command
) {
125 var nick
= command
.params
[0];
126 this.irc_connection
.registered
= true;
127 this.cap_negotiation
= false;
128 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' connect', {
133 'RPL_ISUPPORT': function (command
) {
134 var options
, i
, option
, matches
, j
;
135 options
= command
.params
;
136 for (i
= 1; i
< options
.length
; i
++) {
137 option
= options
[i
].split("=", 2);
138 option
[0] = option
[0].toUpperCase();
139 this.irc_connection
.options
[option
[0]] = (typeof option
[1] !== 'undefined') ? option
[1] : true;
140 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option
[0])) {
141 if (option
[0] === 'PREFIX') {
142 matches
= /\(([^)]*)\)(.*)/.exec(option
[1]);
143 if ((matches
) && (matches
.length
=== 3)) {
144 this.irc_connection
.options
.PREFIX
= [];
145 for (j
= 0; j
< matches
[2].length
; j
++) {
146 this.irc_connection
.options
.PREFIX
.push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
149 } else if (option
[0] === 'CHANTYPES') {
150 this.irc_connection
.options
.CHANTYPES
= this.irc_connection
.options
.CHANTYPES
.split('');
151 } else if (option
[0] === 'CHANMODES') {
152 this.irc_connection
.options
.CHANMODES
= option
[1].split(',');
153 } else if ((option
[0] === 'NAMESX') && (!_
.contains(this.irc_connection
.cap
.enabled
, 'multi-prefix'))) {
154 this.irc_connection
.write('PROTOCTL NAMESX');
158 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' options', {
159 options
: this.irc_connection
.options
,
160 cap
: this.irc_connection
.cap
.enabled
164 'RPL_ENDOFWHOIS': function (command
) {
165 this.irc_connection
.emit('user ' + command
.params
[1] + ' endofwhois', {
166 nick
: command
.params
[1],
167 msg
: command
.params
[command
.params
.length
- 1]
171 'RPL_AWAY': function (command
) {
172 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisaway', {
173 nick
: command
.params
[1],
174 reason
: command
.params
[command
.params
.length
- 1]
178 'RPL_WHOISUSER': function (command
) {
179 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisuser', {
180 nick
: command
.params
[1],
181 ident
: command
.params
[2],
182 host
: command
.params
[3],
183 msg
: command
.params
[command
.params
.length
- 1]
187 'RPL_WHOISSERVER': function (command
) {
188 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisserver', {
189 nick
: command
.params
[1],
190 irc_server
: command
.params
[2],
191 server_info
: command
.params
[command
.params
.length
- 1]
195 'RPL_WHOISOPERATOR': function (command
) {
196 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisoperator', {
197 nick
: command
.params
[1],
198 msg
: command
.params
[command
.params
.length
- 1]
202 'RPL_WHOISCHANNELS': function (command
) {
203 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoischannels', {
204 nick
: command
.params
[1],
205 chans
: command
.params
[command
.params
.length
- 1]
209 'RPL_WHOISMODES': function (command
) {
210 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoismodes', {
211 nick
: command
.params
[1],
212 msg
: command
.params
[command
.params
.length
- 1]
216 'RPL_WHOISIDLE': function (command
) {
217 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisidle', {
218 nick
: command
.params
[1],
219 idle
: command
.params
[2],
220 logon
: command
.params
[3] || undefined
224 'RPL_WHOISREGNICK': function (command
) {
225 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisregnick', {
226 nick
: command
.params
[1],
227 msg
: command
.params
[command
.params
.length
- 1]
231 'RPL_WHOISHOST': function (command
) {
232 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoishost', {
233 nick
: command
.params
[1],
234 msg
: command
.params
[command
.params
.length
- 1]
238 'RPL_WHOISSECURE': function (command
) {
239 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoissecure', {
240 nick
: command
.params
[1]
244 'RPL_WHOISACCOUNT': function (command
) {
245 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisaccount', {
246 nick
: command
.params
[1],
247 account
: command
.params
[2]
251 'RPL_WHOWASUSER': function (command
) {
252 this.irc_connection
.emit('user ' + command
.params
[1] + ' whowas', {
253 nick
: command
.params
[1],
254 ident
: command
.params
[2],
255 host
: command
.params
[3],
256 real_name
: command
.params
[command
.params
.length
- 1]
260 'RPL_ENDOFWHOWAS': function (command
) {
261 this.irc_connection
.emit('user ' + command
.params
[1] + ' endofwhowas', {
262 nick
: command
.params
[1]
266 'ERR_WASNOSUCHNICK': function (command
) {
267 this.irc_connection
.emit('user ' + command
.params
[1] + ' wasnosucknick', {
268 nick
: command
.params
[1]
272 'RPL_LISTSTART': function (command
) {
273 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_start', {});
276 'RPL_LISTEND': function (command
) {
277 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_end', {});
280 'RPL_LIST': function (command
) {
281 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_channel', {
282 channel
: command
.params
[1],
283 num_users
: parseInt(command
.params
[2], 10),
284 topic
: command
.params
[command
.params
.length
- 1]
288 'RPL_CHANNELMODEIS': function (command
) {
289 var channel
= command
.params
[1],
290 modes
= parseModeList
.call(this, command
.params
[2], command
.params
.slice(3));
292 this.irc_connection
.emit('channel ' + channel
+ ' info', {
298 'RPL_CREATIONTIME': function (command
) {
299 var channel
= command
.params
[1];
301 this.irc_connection
.emit('channel ' + channel
+ ' info', {
303 created_at
: parseInt(command
.params
[2], 10)
307 'RPL_CHANNEL_URL': function (command
) {
308 var channel
= command
.params
[1];
310 this.irc_connection
.emit('channel ' + channel
+ ' info', {
312 url
: command
.params
[command
.params
.length
- 1]
316 'RPL_MOTD': function (command
) {
317 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd', {
318 motd
: command
.params
[command
.params
.length
- 1] + '\n'
322 'RPL_MOTDSTART': function (command
) {
323 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd_start', {});
326 'RPL_ENDOFMOTD': function (command
) {
327 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd_end', {});
330 'RPL_NAMEREPLY': function (command
) {
331 var members
= command
.params
[command
.params
.length
- 1].split(' ');
332 var member_list
= [];
334 _
.each(members
, function (member
) {
339 // Make sure we have some prefixes already
340 if (that
.irc_connection
.options
.PREFIX
) {
341 for (j
= 0; j
< that
.irc_connection
.options
.PREFIX
.length
; j
++) {
342 if (member
.charAt(i
) === that
.irc_connection
.options
.PREFIX
[j
].symbol
) {
343 modes
.push(that
.irc_connection
.options
.PREFIX
[j
].mode
);
349 member_list
.push({nick
: member
, modes
: modes
});
352 this.irc_connection
.emit('channel ' + command
.params
[2] + ' userlist', {
354 channel
: command
.params
[2]
358 'RPL_ENDOFNAMES': function (command
) {
359 this.irc_connection
.emit('channel ' + command
.params
[1] + ' userlist_end', {
360 channel
: command
.params
[1]
364 'RPL_WHOREPLY': function (command
) {
365 // For the time being, NOOP this command so they don't get passed
366 // down to the client. Waste of bandwidth since we do not use it yet
367 // TODO: Impliment RPL_WHOREPLY
370 'RPL_ENDOFWHO': function (command
) {
371 // For the time being, NOOP this command so they don't get passed
372 // down to the client. Waste of bandwidth since we do not use it yet
373 // TODO: Impliment RPL_ENDOFWHO
376 'RPL_BANLIST': function (command
) {
377 this.irc_connection
.emit('channel ' + command
.params
[1] + ' banlist', {
378 channel
: command
.params
[1],
379 banned
: command
.params
[2],
380 banned_by
: command
.params
[3],
381 banned_at
: command
.params
[4]
385 'RPL_ENDOFBANLIST': function (command
) {
386 this.irc_connection
.emit('channel ' + command
.params
[1] + ' banlist_end', {
387 channel
: command
.params
[1]
391 'RPL_TOPIC': function (command
) {
392 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topic', {
393 channel
: command
.params
[1],
394 topic
: command
.params
[command
.params
.length
- 1]
398 'RPL_NOTOPIC': function (command
) {
399 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topic', {
400 channel
: command
.params
[1],
405 'RPL_TOPICWHOTIME': function (command
) {
406 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topicsetby', {
407 nick
: command
.params
[2],
408 channel
: command
.params
[1],
409 when
: command
.params
[3]
413 'RPL_INVITING': function (command
) {
414 this.irc_connection
.emit('channel ' + command
.params
[1] + ' invited', {
415 nick
: command
.params
[0],
416 channel
: command
.params
[1]
420 'PING': function (command
) {
421 this.irc_connection
.write('PONG ' + command
.params
[command
.params
.length
- 1]);
424 'JOIN': function (command
) {
426 if (typeof command
.params
[0] === 'string' && command
.params
[0] !== '') {
427 channel
= command
.params
[0];
430 // Check if we have a server-time
431 time
= getServerTime
.call(this, command
);
433 this.irc_connection
.emit('channel ' + channel
+ ' join', {
435 ident
: command
.ident
,
436 hostname
: command
.hostname
,
442 'PART': function (command
) {
443 var time
, channel
, message
;
445 // Check if we have a server-time
446 time
= getServerTime
.call(this, command
);
448 channel
= command
.params
[0];
449 if (command
.params
.length
> 1) {
450 message
= command
.params
[command
.params
.length
- 1];
453 this.irc_connection
.emit('channel ' + channel
+ ' part', {
455 ident
: command
.ident
,
456 hostname
: command
.hostname
,
463 'KICK': function (command
) {
466 // Check if we have a server-time
467 time
= getServerTime
.call(this, command
);
469 this.irc_connection
.emit('channel ' + command
.params
[0] + ' kick', {
470 kicked
: command
.params
[1],
472 ident
: command
.ident
,
473 hostname
: command
.hostname
,
474 channel
: command
.params
[0],
475 message
: command
.params
[command
.params
.length
- 1],
480 'QUIT': function (command
) {
483 // Check if we have a server-time
484 time
= getServerTime
.call(this, command
);
486 this.irc_connection
.emit('user ' + command
.nick
+ ' quit', {
488 ident
: command
.ident
,
489 hostname
: command
.hostname
,
490 message
: command
.params
[command
.params
.length
- 1],
495 'NOTICE': function (command
) {
500 // Check if we have a server-time
501 time
= getServerTime
.call(this, command
);
503 msg
= command
.params
[command
.params
.length
- 1];
504 if ((msg
.charAt(0) === String
.fromCharCode(1)) && (msg
.charAt(msg
.length
- 1) === String
.fromCharCode(1))) {
505 // It's a CTCP response
506 namespace = (command
.params
[0].toLowerCase() === this.irc_connection
.nick
.toLowerCase()) ? 'user' : 'channel';
507 this.irc_connection
.emit(namespace + ' ' + command
.params
[0] + ' ctcp_response', {
509 ident
: command
.ident
,
510 hostname
: command
.hostname
,
511 channel
: command
.params
[0],
512 msg
: msg
.substring(1, msg
.length
- 1),
516 namespace = (command
.params
[0].toLowerCase() === this.irc_connection
.nick
.toLowerCase() || command
.params
[0] === '*') ?
520 this.irc_connection
.emit(namespace + ' ' + command
.params
[0] + ' notice', {
521 from_server
: command
.prefix
? true : false,
522 nick
: command
.nick
|| command
.prefix
|| undefined,
523 ident
: command
.ident
,
524 hostname
: command
.hostname
,
525 target
: command
.params
[0],
532 'NICK': function (command
) {
535 // Check if we have a server-time
536 time
= getServerTime
.call(this, command
);
538 this.irc_connection
.emit('user ' + command
.nick
+ ' nick', {
540 ident
: command
.ident
,
541 hostname
: command
.hostname
,
542 newnick
: command
.params
[0],
547 'TOPIC': function (command
) {
550 // If we don't have an associated channel, no need to continue
551 if (!command
.params
[0]) {
555 // Check if we have a server-time
556 time
= getServerTime
.call(this, command
);
558 var channel
= command
.params
[0],
559 topic
= command
.params
[command
.params
.length
- 1] || '';
561 this.irc_connection
.emit('channel ' + channel
+ ' topic', {
569 'MODE': function (command
) {
570 var modes
= [], event
, time
;
572 // Check if we have a server-time
573 time
= getServerTime
.call(this, command
);
575 // Get a JSON representation of the modes
576 modes
= parseModeList
.call(this, command
.params
[1], command
.params
.slice(2));
577 event
= (_
.contains(this.irc_connection
.options
.CHANTYPES
, command
.params
[0][0]) ? 'channel ' : 'user ') + command
.params
[0] + ' mode';
579 this.irc_connection
.emit(event
, {
580 target
: command
.params
[0],
581 nick
: command
.nick
|| command
.prefix
|| '',
587 'PRIVMSG': function (command
) {
588 var tmp
, namespace, time
, msg
;
590 // Check if we have a server-time
591 time
= getServerTime
.call(this, command
);
593 msg
= command
.params
[command
.params
.length
- 1];
594 if ((msg
.charAt(0) === String
.fromCharCode(1)) && (msg
.charAt(msg
.length
- 1) === String
.fromCharCode(1))) {
596 if (msg
.substr(1, 6) === 'ACTION') {
597 this.irc_connection
.clientEvent('action', {
599 ident
: command
.ident
,
600 hostname
: command
.hostname
,
601 channel
: command
.params
[0],
602 msg
: msg
.substring(8, msg
.length
- 1),
605 } else if (msg
.substr(1, 4) === 'KIWI') {
606 tmp
= msg
.substring(6, msg
.length
- 1);
607 namespace = tmp
.split(' ', 1)[0];
608 this.irc_connection
.clientEvent('kiwi', {
609 namespace: namespace,
610 data
: tmp
.substr(namespace.length
+ 1),
613 } else if (msg
.substr(1, 7) === 'VERSION') {
614 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1));
615 } else if (msg
.substr(1, 6) === 'SOURCE') {
616 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String
.fromCharCode(1));
617 } else if (msg
.substr(1, 10) === 'CLIENTINFO') {
618 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String
.fromCharCode(1));
620 namespace = (command
.params
[0].toLowerCase() === this.irc_connection
.nick
.toLowerCase()) ? 'user' : 'channel';
621 this.irc_connection
.emit(namespace + ' ' + command
.nick
+ ' ctcp_request', {
623 ident
: command
.ident
,
624 hostname
: command
.hostname
,
625 target
: command
.params
[0],
626 type
: (msg
.substring(1, msg
.length
- 1).split(' ') || [null])[0],
627 msg
: msg
.substring(1, msg
.length
- 1),
632 // A message to a user (private message) or to a channel?
633 namespace = (command
.params
[0].toLowerCase() === this.irc_connection
.nick
.toLowerCase()) ? 'user ' + command
.nick
: 'channel ' + command
.params
[0];
634 this.irc_connection
.emit(namespace + ' privmsg', {
636 ident
: command
.ident
,
637 hostname
: command
.hostname
,
638 channel
: command
.params
[0],
645 'CAP': function (command
) {
646 // TODO: capability modifiers
647 // i.e. - for disable, ~ for requires ACK, = for sticky
648 var capabilities
= command
.params
[command
.params
.length
- 1].replace(/(?:^| )[\-~=]/, '').split(' ');
651 // Which capabilities we want to enable
652 var want
= ['multi-prefix', 'away-notify', 'server-time', 'znc.in/server-time-iso', 'znc.in/server-time'];
654 if (this.irc_connection
.password
) {
658 switch (command
.params
[1]) {
660 // Compute which of the available capabilities we want and request them
661 request
= _
.intersection(capabilities
, want
);
662 if (request
.length
> 0) {
663 this.irc_connection
.cap
.requested
= request
;
664 this.irc_connection
.write('CAP REQ :' + request
.join(' '));
666 this.irc_connection
.write('CAP END');
667 this.irc_connection
.cap_negotiation
= false;
671 if (capabilities
.length
> 0) {
672 // Update list of enabled capabilities
673 this.irc_connection
.cap
.enabled
= capabilities
;
674 // Update list of capabilities we would like to have but that aren't enabled
675 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
677 if (this.irc_connection
.cap
.enabled
.length
> 0) {
678 if (_
.contains(this.irc_connection
.cap
.enabled
, 'sasl')) {
679 this.irc_connection
.sasl
= true;
680 this.irc_connection
.write('AUTHENTICATE PLAIN');
682 this.irc_connection
.write('CAP END');
683 this.irc_connection
.cap_negotiation
= false;
688 if (capabilities
.length
> 0) {
689 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
691 if (this.irc_connection
.cap
.requested
.length
> 0) {
692 this.irc_connection
.write('CAP END');
693 this.irc_connection
.cap_negotiation
= false;
697 // should we do anything here?
702 'AUTHENTICATE': function (command
) {
703 var b
= new Buffer(this.irc_connection
.nick
+ "\0" + this.irc_connection
.nick
+ "\0" + this.irc_connection
.password
, 'utf8');
704 var b64
= b
.toString('base64');
705 if (command
.params
[0] === '+') {
706 while (b64
.length
>= 400) {
707 this.irc_connection
.write('AUTHENTICATE ' + b64
.slice(0, 399));
708 b64
= b64
.slice(399);
710 if (b64
.length
> 0) {
711 this.irc_connection
.write('AUTHENTICATE ' + b64
);
713 this.irc_connection
.write('AUTHENTICATE +');
716 this.irc_connection
.write('CAP END');
717 this.irc_connection
.cap_negotiation
= false;
721 'AWAY': function (command
) {
724 // Check if we have a server-time
725 time
= getServerTime
.call(this, command
);
727 this.irc_connection
.emit('user ' + command
.nick
+ ' away', {
729 msg
: command
.params
[command
.params
.length
- 1],
734 'RPL_SASLAUTHENTICATED': function (command
) {
735 this.irc_connection
.write('CAP END');
736 this.irc_connection
.cap_negotiation
= false;
737 this.irc_connection
.sasl
= true;
740 'RPL_SASLLOGGEDIN': function (command
) {
741 if (this.irc_connection
.cap_negotiation
=== true) {
742 this.irc_connection
.write('CAP END');
743 this.irc_connection
.cap_negotiation
= false;
747 'ERR_SASLNOTAUTHORISED': function (command
) {
748 this.irc_connection
.write('CAP END');
749 this.irc_connection
.cap_negotiation
= false;
752 'ERR_SASLABORTED': function (command
) {
753 this.irc_connection
.write('CAP END');
754 this.irc_connection
.cap_negotiation
= false;
757 'ERR_SASLALREADYAUTHED': function (command
) {
761 'ERROR': function (command
) {
762 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' error', {
763 reason
: command
.params
[command
.params
.length
- 1]
766 ERR_PASSWDMISMATCH: function (command
) {
767 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' password_mismatch', {});
770 ERR_LINKCHANNEL: function (command
) {
771 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' channel_redirect', {
772 from: command
.params
[1],
773 to
: command
.params
[2]
777 ERR_NOSUCHNICK: function (command
) {
778 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' no_such_nick', {
779 nick
: command
.params
[1],
780 reason
: command
.params
[command
.params
.length
- 1]
784 ERR_CANNOTSENDTOCHAN: function (command
) {
785 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' cannot_send_to_chan', {
786 channel
: command
.params
[1],
787 reason
: command
.params
[command
.params
.length
- 1]
791 ERR_TOOMANYCHANNELS: function (command
) {
792 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' too_many_channels', {
793 channel
: command
.params
[1],
794 reason
: command
.params
[command
.params
.length
- 1]
798 ERR_USERNOTINCHANNEL: function (command
) {
799 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' user_not_in_channel', {
800 nick
: command
.params
[0],
801 channel
: command
.params
[1],
802 reason
: command
.params
[command
.params
.length
- 1]
806 ERR_NOTONCHANNEL: function (command
) {
807 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' not_on_channel', {
808 channel
: command
.params
[1],
809 reason
: command
.params
[command
.params
.length
- 1]
813 ERR_USERONCHANNEL: function (command
) {
814 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' user_on_channel', {
815 nick
: command
.params
[1],
816 channel
: command
.params
[2]
820 ERR_CHANNELISFULL: function (command
) {
821 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' channel_is_full', {
822 channel
: command
.params
[1],
823 reason
: command
.params
[command
.params
.length
- 1]
827 ERR_INVITEONLYCHAN: function (command
) {
828 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' invite_only_channel', {
829 channel
: command
.params
[1],
830 reason
: command
.params
[command
.params
.length
- 1]
834 ERR_BANNEDFROMCHAN: function (command
) {
835 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' banned_from_channel', {
836 channel
: command
.params
[1],
837 reason
: command
.params
[command
.params
.length
- 1]
841 ERR_BADCHANNELKEY: function (command
) {
842 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' bad_channel_key', {
843 channel
: command
.params
[1],
844 reason
: command
.params
[command
.params
.length
- 1]
848 ERR_CHANOPRIVSNEEDED: function (command
) {
849 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' chanop_privs_needed', {
850 channel
: command
.params
[1],
851 reason
: command
.params
[command
.params
.length
- 1]
855 ERR_NICKNAMEINUSE: function (command
) {
856 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' nickname_in_use', {
857 nick
: command
.params
[1],
858 reason
: command
.params
[command
.params
.length
- 1]
862 ERR_ERRONEUSNICKNAME: function(command
) {
863 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' erroneus_nickname', {
864 nick
: command
.params
[1],
865 reason
: command
.params
[command
.params
.length
- 1]
869 ERR_NOTREGISTERED: function (command
) {
872 RPL_MAPMORE: function (command
) {
873 var params
= _
.clone(command
.params
);
875 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
878 RPL_MAPEND: function (command
) {
879 var params
= _
.clone(command
.params
);
881 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
884 RPL_LINKS: function (command
) {
885 var params
= _
.clone(command
.params
);
887 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
890 RPL_ENDOFLINKS: function (command
) {
891 var params
= _
.clone(command
.params
);
893 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
896 ERR_UNKNOWNCOMMAND: function (command
) {
897 var params
= _
.clone(command
.params
);
899 genericNotice
.call(this, command
, '`' + params
.slice(0, -1).join(', ') + '` ' + command
.params
[command
.params
.length
- 1]);
902 ERR_NOMOTD: function (command
) {
903 var params
= _
.clone(command
.params
);
905 genericNotice
.call(this, command
, command
.params
[command
.params
.length
- 1]);
908 ERR_NOPRIVILEGES: function (command
) {
909 var params
= _
.clone(command
.params
);
911 genericNotice
.call(this, command
, command
.params
[command
.params
.length
- 1]);
914 RPL_STATSCONN: function (command
) {
915 var params
= _
.clone(command
.params
);
917 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
920 RPL_LUSERCLIENT: function (command
) {
921 var params
= _
.clone(command
.params
);
923 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
926 RPL_LUSEROP: function (command
) {
927 var params
= _
.clone(command
.params
);
929 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
932 RPL_LUSERUNKNOWN: function (command
) {
933 var params
= _
.clone(command
.params
);
935 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
938 RPL_LUSERCHANNELS: function (command
) {
939 var params
= _
.clone(command
.params
);
941 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
944 RPL_LUSERME: function (command
) {
945 var params
= _
.clone(command
.params
);
947 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
950 RPL_LOCALUSERS: function (command
) {
951 var params
= _
.clone(command
.params
);
953 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
956 RPL_GLOBALUSERS: function (command
) {
957 var params
= _
.clone(command
.params
);
959 genericNotice
.call(this, command
, params
.slice(0, -1).join(', ') + ' ' + command
.params
[command
.params
.length
- 1]);
966 function genericNotice (command
, msg
, is_error
) {
967 // Default to being an error
968 if (typeof is_error
!== 'boolean')
971 this.irc_connection
.clientEvent('notice', {
973 nick
: command
.prefix
,
976 target
: command
.params
[0],
978 numeric
: parseInt(command
.command
, 10)
984 * Convert a mode string such as '+k pass', or '-i' to a readable
986 * [ { mode: '+k', param: 'pass' } ]
987 * [ { mode: '-i', param: null } ]
989 function parseModeList(mode_string
, mode_params
) {
990 var chanmodes
= this.irc_connection
.options
.CHANMODES
|| [],
991 prefixes
= this.irc_connection
.options
.PREFIX
|| [],
992 always_param
= (chanmodes
[0] || '').concat((chanmodes
[1] || '')),
994 has_param
, i
, j
, add
;
996 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
997 list
.push(prefix
.mode
);
1000 always_param
= always_param
.split('').concat(prefixes
);
1002 has_param = function (mode
, add
) {
1003 if (_
.find(always_param
, function (m
) {
1007 } else if (add
&& _
.find((chanmodes
[2] || '').split(''), function (m
) {
1017 for (i
= 0; i
< mode_string
.length
; i
++) {
1018 switch (mode_string
[i
]) {
1026 if (has_param(mode_string
[i
], add
)) {
1027 modes
.push({mode
: (add
? '+' : '-') + mode_string
[i
], param
: mode_params
[j
]});
1030 modes
.push({mode
: (add
? '+' : '-') + mode_string
[i
], param
: null});
1039 function getServerTime(command
) {
1042 // No tags? No times.
1043 if (!command
.tags
|| command
.tags
.length
=== 0) {
1047 if (capContainsAny
.call(this, ['server-time', 'znc.in/server-time', 'znc.in/server-time-iso'])) {
1048 time
= _
.find(command
.tags
, function (tag
) {
1049 return tag
.tag
=== 'time';
1052 time
= time
? time
.value
: undefined;
1054 // Convert the time value to a unixtimestamp
1055 if (typeof time
=== 'string') {
1056 if (time
.indexOf('T') > -1) {
1057 time
= parseISO8601(time
);
1059 } else if(time
.match(/^[0-9.]+$/)) {
1060 // A string formatted unix timestamp
1061 time
= new Date(time
* 1000);
1064 time
= time
.getTime();
1066 } else if (typeof time
=== 'number') {
1067 time
= new Date(time
* 1000);
1068 time
= time
.getTime();
1076 function capContainsAny (caps
) {
1078 if (!caps
instanceof Array
) {
1081 intersection
= _
.intersection(this.irc_connection
.cap
.enabled
, caps
);
1082 return intersection
.length
> 0;
1086 // Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
1087 function parseISO8601(str
) {
1088 if (Date
.prototype.toISOString
) {
1089 return new Date(str
);
1091 var parts
= str
.split('T'),
1092 dateParts
= parts
[0].split('-'),
1093 timeParts
= parts
[1].split('Z'),
1094 timeSubParts
= timeParts
[0].split(':'),
1095 timeSecParts
= timeSubParts
[2].split('.'),
1096 timeHours
= Number(timeSubParts
[0]),
1099 _date
.setUTCFullYear(Number(dateParts
[0]));
1100 _date
.setUTCDate(1);
1101 _date
.setUTCMonth(Number(dateParts
[1])-1);
1102 _date
.setUTCDate(Number(dateParts
[2]));
1103 _date
.setUTCHours(Number(timeHours
));
1104 _date
.setUTCMinutes(Number(timeSubParts
[1]));
1105 _date
.setUTCSeconds(Number(timeSecParts
[0]));
1106 if (timeSecParts
[1]) {
1107 _date
.setUTCMilliseconds(Number(timeSecParts
[1]));