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 '317': 'RPL_WHOISIDLE',
28 '318': 'RPL_ENDOFWHOIS',
29 '319': 'RPL_WHOISCHANNELS',
30 '321': 'RPL_LISTSTART',
33 '330': 'RPL_WHOISACCOUNT',
36 '333': 'RPL_TOPICWHOTIME',
37 '341': 'RPL_INVITING',
38 '353': 'RPL_NAMEREPLY',
40 '365': 'RPL_ENDOFLINKS',
41 '366': 'RPL_ENDOFNAMES',
43 '368': 'RPL_ENDOFBANLIST',
44 '369': 'RPL_ENDOFWHOWAS',
46 '375': 'RPL_MOTDSTART',
47 '376': 'RPL_ENDOFMOTD',
48 '378': 'RPL_WHOISHOST',
49 '379': 'RPL_WHOISMODES',
50 '401': 'ERR_NOSUCHNICK',
51 '404': 'ERR_CANNOTSENDTOCHAN',
52 '405': 'ERR_TOOMANYCHANNELS',
53 '406': 'ERR_WASNOSUCHNICK',
54 '421': 'ERR_UNKNOWNCOMMAND',
56 '432': 'ERR_ERRONEUSNICKNAME',
57 '433': 'ERR_NICKNAMEINUSE',
58 '441': 'ERR_USERNOTINCHANNEL',
59 '442': 'ERR_NOTONCHANNEL',
60 '443': 'ERR_USERONCHANNEL',
61 '451': 'ERR_NOTREGISTERED',
62 '464': 'ERR_PASSWDMISMATCH',
63 '470': 'ERR_LINKCHANNEL',
64 '471': 'ERR_CHANNELISFULL',
65 '473': 'ERR_INVITEONLYCHAN',
66 '474': 'ERR_BANNEDFROMCHAN',
67 '475': 'ERR_BADCHANNELKEY',
68 '481': 'ERR_NOPRIVILEGES',
69 '482': 'ERR_CHANOPRIVSNEEDED',
70 '670': 'RPL_STARTTLS',
71 '671': 'RPL_WHOISSECURE',
72 '900': 'RPL_SASLAUTHENTICATED',
73 '903': 'RPL_SASLLOGGEDIN',
74 '904': 'ERR_SASLNOTAUTHORISED',
75 '906': 'ERR_SASLABORTED',
76 '907': 'ERR_SASLALREADYAUTHED'
80 IrcCommands = function (irc_connection
) {
81 this.irc_connection
= irc_connection
;
83 module
.exports
= IrcCommands
;
85 IrcCommands
.prototype.dispatch = function (command
, data
) {
87 if (irc_numerics
[command
]) {
88 command
= irc_numerics
[command
];
90 if (handlers
[command
]) {
91 handlers
[command
].call(this, data
);
93 unknownCommand
.call(this, command
, data
);
97 IrcCommands
.addHandler = function (command
, handler
) {
98 if (typeof handler
!== 'function') {
101 handlers
[command
] = handler
;
104 IrcCommands
.addNumeric = function (numeric
, handler_name
) {
105 irc_numerics
[numeric
+ ''] = handler_name
+'';
108 unknownCommand = function (command
, data
) {
109 var params
= _
.clone(data
.params
);
111 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' unknown_command', {
114 trailing
: data
.trailing
119 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' notice', {
120 from_server: command.prefix ? true : false,
121 nick: command.nick || command.prefix || undefined,
122 ident: command.ident,
123 hostname: command.hostname,
124 target: command.params[0],
125 msg: command.trailing
132 'RPL_WELCOME': function (command
) {
133 var nick
= command
.params
[0];
134 this.irc_connection
.registered
= true;
135 this.cap_negotation
= false;
136 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' connect', {
141 'RPL_ISUPPORT': function (command
) {
142 var options
, i
, option
, matches
, j
;
143 options
= command
.params
;
144 for (i
= 1; i
< options
.length
; i
++) {
145 option
= options
[i
].split("=", 2);
146 option
[0] = option
[0].toUpperCase();
147 this.irc_connection
.options
[option
[0]] = (typeof option
[1] !== 'undefined') ? option
[1] : true;
148 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option
[0])) {
149 if (option
[0] === 'PREFIX') {
150 matches
= /\(([^)]*)\)(.*)/.exec(option
[1]);
151 if ((matches
) && (matches
.length
=== 3)) {
152 this.irc_connection
.options
.PREFIX
= [];
153 for (j
= 0; j
< matches
[2].length
; j
++) {
154 this.irc_connection
.options
.PREFIX
.push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
157 } else if (option
[0] === 'CHANTYPES') {
158 this.irc_connection
.options
.CHANTYPES
= this.irc_connection
.options
.CHANTYPES
.split('');
159 } else if (option
[0] === 'CHANMODES') {
160 this.irc_connection
.options
.CHANMODES
= option
[1].split(',');
161 } else if ((option
[0] === 'NAMESX') && (!_
.contains(this.irc_connection
.cap
.enabled
, 'multi-prefix'))) {
162 this.irc_connection
.write('PROTOCTL NAMESX');
166 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' options', {
167 options
: this.irc_connection
.options
,
168 cap
: this.irc_connection
.cap
.enabled
172 'RPL_ENDOFWHOIS': function (command
) {
173 this.irc_connection
.emit('user ' + command
.params
[1] + ' endofwhois', {
174 nick
: command
.params
[1],
175 msg
: command
.trailing
179 'RPL_AWAY': function (command
) {
180 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisaway', {
181 nick
: command
.params
[1],
182 reason
: command
.trailing
186 'RPL_WHOISUSER': function (command
) {
187 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisuser', {
188 nick
: command
.params
[1],
189 ident
: command
.params
[2],
190 host
: command
.params
[3],
191 msg
: command
.trailing
195 'RPL_WHOISSERVER': function (command
) {
196 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisserver', {
197 nick
: command
.params
[1],
198 irc_server
: command
.params
[2],
199 server_info
: command
.trailing
203 'RPL_WHOISOPERATOR': function (command
) {
204 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisoperator', {
205 nick
: command
.params
[1],
206 msg
: command
.trailing
210 'RPL_WHOISCHANNELS': function (command
) {
211 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoischannels', {
212 nick
: command
.params
[1],
213 chans
: command
.trailing
217 'RPL_WHOISMODES': function (command
) {
218 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoismodes', {
219 nick
: command
.params
[1],
220 msg
: command
.trailing
224 'RPL_WHOISIDLE': function (command
) {
225 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisidle', {
226 nick
: command
.params
[1],
227 idle
: command
.params
[2],
228 logon
: command
.params
[3] || undefined
232 'RPL_WHOISREGNICK': function (command
) {
233 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisregnick', {
234 nick
: command
.params
[1],
235 msg
: command
.trailing
239 'RPL_WHOISHOST': function (command
) {
240 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoishost', {
241 nick
: command
.params
[1],
242 msg
: command
.trailing
246 'RPL_WHOISSECURE': function (command
) {
247 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoissecure', {
248 nick
: command
.params
[1]
252 'RPL_WHOISACCOUNT': function (command
) {
253 this.irc_connection
.emit('user ' + command
.params
[1] + ' whoisaccount', {
254 nick
: command
.params
[1],
255 account
: command
.params
[2]
259 'RPL_WHOWASUSER': function (command
) {
260 this.irc_connection
.emit('user ' + command
.params
[1] + ' whowas', {
261 nick
: command
.params
[1],
262 ident
: command
.params
[2],
263 host
: command
.params
[3],
264 real_name
: command
.trailing
268 'RPL_ENDOFWHOWAS': function (command
) {
269 this.irc_connection
.emit('user ' + command
.params
[1] + ' endofwhowas', {
270 nick
: command
.params
[1]
274 'ERR_WASNOSUCHNICK': function (command
) {
275 this.irc_connection
.emit('user ' + command
.params
[1] + ' wasnosucknick', {
276 nick
: command
.params
[1]
280 'RPL_LISTSTART': function (command
) {
281 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_start', {});
284 'RPL_LISTEND': function (command
) {
285 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_end', {});
288 'RPL_LIST': function (command
) {
289 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' list_channel', {
290 channel
: command
.params
[1],
291 num_users
: parseInt(command
.params
[2], 10),
292 topic
: command
.trailing
296 'RPL_MOTD': function (command
) {
297 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd', {
298 motd
: command
.trailing
+ '\n'
302 'RPL_MOTDSTART': function (command
) {
303 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd_start', {});
306 'RPL_ENDOFMOTD': function (command
) {
307 this.irc_connection
.emit('server ' + this.irc_connection
.irc_host
.hostname
+ ' motd_end', {});
310 'RPL_NAMEREPLY': function (command
) {
311 var members
= command
.trailing
.split(' ');
312 var member_list
= [];
314 _
.each(members
, function (member
) {
319 // Make sure we have some prefixes already
320 if (that
.irc_connection
.options
.PREFIX
) {
321 for (j
= 0; j
< that
.irc_connection
.options
.PREFIX
.length
; j
++) {
322 if (member
.charAt(i
) === that
.irc_connection
.options
.PREFIX
[j
].symbol
) {
323 modes
.push(that
.irc_connection
.options
.PREFIX
[j
].mode
);
329 member_list
.push({nick
: member
, modes
: modes
});
332 this.irc_connection
.emit('channel ' + command
.params
[2] + ' userlist', {
334 channel
: command
.params
[2]
338 'RPL_ENDOFNAMES': function (command
) {
339 this.irc_connection
.emit('channel ' + command
.params
[1] + ' userlist_end', {
340 channel
: command
.params
[1]
344 'RPL_BANLIST': function (command
) {
345 this.irc_connection
.emit('channel ' + command
.params
[1] + ' banlist', {
346 channel
: command
.params
[1],
347 banned
: command
.params
[2],
348 banned_by
: command
.params
[3],
349 banned_at
: command
.params
[4]
353 'RPL_ENDOFBANLIST': function (command
) {
354 this.irc_connection
.emit('channel ' + command
.params
[1] + ' banlist_end', {
355 channel
: command
.params
[1]
359 'RPL_TOPIC': function (command
) {
360 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topic', {
361 channel
: command
.params
[1],
362 topic
: command
.trailing
366 'RPL_NOTOPIC': function (command
) {
367 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topic', {
368 channel
: command
.params
[1],
373 'RPL_TOPICWHOTIME': function (command
) {
374 this.irc_connection
.emit('channel ' + command
.params
[1] + ' topicsetby', {
375 nick
: command
.params
[2],
376 channel
: command
.params
[1],
377 when
: command
.params
[3]
381 'RPL_INVITING': function (command
) {
382 this.irc_connection
.emit('channel ' + command
.params
[1] + ' invited', {
383 nick
: command
.params
[0],
384 channel
: command
.params
[1]
388 'PING': function (command
) {
389 this.irc_connection
.write('PONG ' + command
.trailing
);
392 'JOIN': function (command
) {
394 if (typeof command
.trailing
=== 'string' && command
.trailing
!== '') {
395 channel
= command
.trailing
;
396 } else if (typeof command
.params
[0] === 'string' && command
.params
[0] !== '') {
397 channel
= command
.params
[0];
400 // Check if we have a server-time
401 time
= getServerTime
.call(this, command
);
403 this.irc_connection
.emit('channel ' + channel
+ ' join', {
405 ident
: command
.ident
,
406 hostname
: command
.hostname
,
412 'PART': function (command
) {
415 // Check if we have a server-time
416 time
= getServerTime
.call(this, command
);
418 this.irc_connection
.emit('channel ' + command
.params
[0] + ' part', {
420 ident
: command
.ident
,
421 hostname
: command
.hostname
,
422 channel
: command
.params
[0],
423 message
: command
.trailing
,
428 'KICK': function (command
) {
431 // Check if we have a server-time
432 time
= getServerTime
.call(this, command
);
434 this.irc_connection
.emit('channel ' + command
.params
[0] + ' kick', {
435 kicked
: command
.params
[1],
437 ident
: command
.ident
,
438 hostname
: command
.hostname
,
439 channel
: command
.params
[0],
440 message
: command
.trailing
,
445 'QUIT': function (command
) {
448 // Check if we have a server-time
449 time
= getServerTime
.call(this, command
);
451 this.irc_connection
.emit('user ' + command
.nick
+ ' quit', {
453 ident
: command
.ident
,
454 hostname
: command
.hostname
,
455 message
: command
.trailing
,
460 'NOTICE': function (command
) {
464 // Check if we have a server-time
465 time
= getServerTime
.call(this, command
);
468 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
469 // It's a CTCP response
470 namespace = (command
.params
[0].toLowerCase() == this.irc_connection
.nick
.toLowerCase()) ? 'user' : 'channel';
471 this.irc_connection
.emit(namespace + ' ' + command
.params
[0] + ' ctcp_response', {
473 ident
: command
.ident
,
474 hostname
: command
.hostname
,
475 channel
: command
.params
[0],
476 msg
: command
.trailing
.substring(1, command
.trailing
.length
- 1),
480 namespace = (command
.params
[0].toLowerCase() == this.irc_connection
.nick
.toLowerCase() || command
.params
[0] == '*') ?
484 this.irc_connection
.emit(namespace + ' ' + command
.params
[0] + ' notice', {
485 from_server
: command
.prefix
? true : false,
486 nick
: command
.nick
|| command
.prefix
|| undefined,
487 ident
: command
.ident
,
488 hostname
: command
.hostname
,
489 target
: command
.params
[0],
490 msg
: command
.trailing
,
496 'NICK': function (command
) {
499 // Check if we have a server-time
500 time
= getServerTime
.call(this, command
);
502 this.irc_connection
.emit('user ' + command
.nick
+ ' nick', {
504 ident
: command
.ident
,
505 hostname
: command
.hostname
,
506 newnick
: command
.trailing
|| command
.params
[0],
511 'TOPIC': function (command
) {
514 // If we don't have an associated channel, no need to continue
515 if (!command
.params
[0]) return;
517 // Check if we have a server-time
518 time
= getServerTime
.call(this, command
);
520 var channel
= command
.params
[0],
521 topic
= command
.trailing
|| '';
523 this.irc_connection
.emit('channel ' + channel
+ ' topic', {
531 'MODE': function (command
) {
532 var chanmodes
= this.irc_connection
.options
.CHANMODES
|| [],
533 prefixes
= this.irc_connection
.options
.PREFIX
|| [],
534 always_param
= (chanmodes
[0] || '').concat((chanmodes
[1] || '')),
536 has_param
, i
, j
, add
, event
, time
;
538 // Check if we have a server-time
539 time
= getServerTime
.call(this, command
);
541 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
542 list
.push(prefix
.mode
);
545 always_param
= always_param
.split('').concat(prefixes
);
547 has_param = function (mode
, add
) {
548 if (_
.find(always_param
, function (m
) {
552 } else if (add
&& _
.find((chanmodes
[2] || '').split(''), function (m
) {
561 if (!command
.params
[1]) {
562 command
.params
[1] = command
.trailing
;
566 for (i
= 0; i
< command
.params
[1].length
; i
++) {
567 switch (command
.params
[1][i
]) {
575 if (has_param(command
.params
[1][i
], add
)) {
576 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: command
.params
[2 + j
]});
579 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: null});
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)
988 function getServerTime(command
) {
991 // No tags? No times.
992 if (!command
.tags
|| command
.tags
.length
=== 0) {
996 if (capContainsAny
.call(this, ['server-time', 'znc.in/server-time', 'znc.in/server-time-iso'])) {
997 time
= _
.find(command
.tags
, function (tag
) {
998 return tag
.tag
=== 'time';
1001 time
= time
? time
.value
: undefined;
1003 // Convert the time value to a unixtimestamp
1004 if (typeof time
=== 'string') {
1005 if (time
.indexOf('T') > -1) {
1006 time
= parseISO8601(opts
.time
);
1008 } else if(time
.match(/^[0-9.]+$/)) {
1009 // A string formatted unix timestamp
1010 time
= new Date(time
* 1000);
1013 time
= time
.getTime();
1015 } else if (typeof time
=== 'number') {
1016 time
= new Date(time
* 1000);
1017 time
= time
.getTime();
1025 function capContainsAny (caps
) {
1027 if (!caps
instanceof Array
) {
1030 intersection
= _
.intersection(this.irc_connection
.cap
.enabled
, caps
);
1031 return intersection
.length
> 0;
1035 // Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
1036 function parseISO8601(str
) {
1037 if (Date
.prototype.toISOString
) {
1038 return new Date(str
);
1040 var parts
= str
.split('T'),
1041 dateParts
= parts
[0].split('-'),
1042 timeParts
= parts
[1].split('Z'),
1043 timeSubParts
= timeParts
[0].split(':'),
1044 timeSecParts
= timeSubParts
[2].split('.'),
1045 timeHours
= Number(timeSubParts
[0]),
1048 _date
.setUTCFullYear(Number(dateParts
[0]));
1049 _date
.setUTCDate(1);
1050 _date
.setUTCMonth(Number(dateParts
[1])-1);
1051 _date
.setUTCDate(Number(dateParts
[2]));
1052 _date
.setUTCHours(Number(timeHours
));
1053 _date
.setUTCMinutes(Number(timeSubParts
[1]));
1054 _date
.setUTCSeconds(Number(timeSecParts
[0]));
1055 if (timeSecParts
[1]) {
1056 _date
.setUTCMilliseconds(Number(timeSecParts
[1]));