Handle RPL_INVITING command
[KiwiIRC.git] / server / irc / commands.js
1 var _ = require('lodash'),
2 irc_numerics,
3 IrcCommands,
4 handlers,
5 unknownCommand;
6
7 irc_numerics = {
8 '001': 'RPL_WELCOME',
9 '004': 'RPL_MYINFO',
10 '005': 'RPL_ISUPPORT',
11 '006': 'RPL_MAPMORE',
12 '007': 'RPL_MAPEND',
13 '250': 'RPL_STATSCONN',
14 '251': 'RPL_LUSERCLIENT',
15 '252': 'RPL_LUSEROP',
16 '253': 'RPL_LUSERUNKNOWN',
17 '254': 'RPL_LUSERCHANNELS',
18 '255': 'RPL_LUSERME',
19 '265': 'RPL_LOCALUSERS',
20 '266': 'RPL_GLOBALUSERS',
21 '301': 'RPL_AWAY',
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',
31 '322': 'RPL_LIST',
32 '323': 'RPL_LISTEND',
33 '330': 'RPL_WHOISACCOUNT',
34 '331': 'RPL_NOTOPIC',
35 '332': 'RPL_TOPIC',
36 '333': 'RPL_TOPICWHOTIME',
37 '341': 'RPL_INVITING',
38 '353': 'RPL_NAMEREPLY',
39 '364': 'RPL_LINKS',
40 '365': 'RPL_ENDOFLINKS',
41 '366': 'RPL_ENDOFNAMES',
42 '367': 'RPL_BANLIST',
43 '368': 'RPL_ENDOFBANLIST',
44 '369': 'RPL_ENDOFWHOWAS',
45 '372': 'RPL_MOTD',
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',
55 '422': 'ERR_NOMOTD',
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'
77 };
78
79
80 IrcCommands = function (irc_connection) {
81 this.irc_connection = irc_connection;
82 };
83 module.exports = IrcCommands;
84
85 IrcCommands.prototype.dispatch = function (command, data) {
86 command += '';
87 if (irc_numerics[command]) {
88 command = irc_numerics[command];
89 }
90 if (handlers[command]) {
91 handlers[command].call(this, data);
92 } else {
93 unknownCommand.call(this, command, data);
94 }
95 };
96
97 IrcCommands.addHandler = function (command, handler) {
98 if (typeof handler !== 'function') {
99 return false;
100 }
101 handlers[command] = handler;
102 };
103
104 IrcCommands.addNumeric = function (numeric, handler_name) {
105 irc_numerics[numeric + ''] = handler_name +'';
106 };
107
108 unknownCommand = function (command, data) {
109 var params = _.clone(data.params);
110
111 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' unknown_command', {
112 command: command,
113 params: params,
114 trailing: data.trailing
115 });
116
117
118 /*
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
126 });
127 */
128 };
129
130
131 handlers = {
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', {
137 nick: nick
138 });
139 },
140
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)});
155 }
156 }
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');
163 }
164 }
165 }
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
169 });
170 },
171
172 'RPL_ENDOFWHOIS': function (command) {
173 this.irc_connection.emit('user ' + command.params[1] + ' endofwhois', {
174 nick: command.params[1],
175 msg: command.trailing
176 });
177 },
178
179 'RPL_AWAY': function (command) {
180 this.irc_connection.emit('user ' + command.params[1] + ' whoisaway', {
181 nick: command.params[1],
182 reason: command.trailing
183 });
184 },
185
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
192 });
193 },
194
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
200 });
201 },
202
203 'RPL_WHOISOPERATOR': function (command) {
204 this.irc_connection.emit('user ' + command.params[1] + ' whoisoperator', {
205 nick: command.params[1],
206 msg: command.trailing
207 });
208 },
209
210 'RPL_WHOISCHANNELS': function (command) {
211 this.irc_connection.emit('user ' + command.params[1] + ' whoischannels', {
212 nick: command.params[1],
213 chans: command.trailing
214 });
215 },
216
217 'RPL_WHOISMODES': function (command) {
218 this.irc_connection.emit('user ' + command.params[1] + ' whoismodes', {
219 nick: command.params[1],
220 msg: command.trailing
221 });
222 },
223
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
229 });
230 },
231
232 'RPL_WHOISREGNICK': function (command) {
233 this.irc_connection.emit('user ' + command.params[1] + ' whoisregnick', {
234 nick: command.params[1],
235 msg: command.trailing
236 });
237 },
238
239 'RPL_WHOISHOST': function (command) {
240 this.irc_connection.emit('user ' + command.params[1] + ' whoishost', {
241 nick: command.params[1],
242 msg: command.trailing
243 });
244 },
245
246 'RPL_WHOISSECURE': function (command) {
247 this.irc_connection.emit('user ' + command.params[1] + ' whoissecure', {
248 nick: command.params[1]
249 });
250 },
251
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]
256 });
257 },
258
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
265 });
266 },
267
268 'RPL_ENDOFWHOWAS': function (command) {
269 this.irc_connection.emit('user ' + command.params[1] + ' endofwhowas', {
270 nick: command.params[1]
271 });
272 },
273
274 'ERR_WASNOSUCHNICK': function (command) {
275 this.irc_connection.emit('user ' + command.params[1] + ' wasnosucknick', {
276 nick: command.params[1]
277 });
278 },
279
280 'RPL_LISTSTART': function (command) {
281 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_start', {});
282 },
283
284 'RPL_LISTEND': function (command) {
285 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_end', {});
286 },
287
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
293 });
294 },
295
296 'RPL_MOTD': function (command) {
297 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd', {
298 motd: command.trailing + '\n'
299 });
300 },
301
302 'RPL_MOTDSTART': function (command) {
303 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_start', {});
304 },
305
306 'RPL_ENDOFMOTD': function (command) {
307 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_end', {});
308 },
309
310 'RPL_NAMEREPLY': function (command) {
311 var members = command.trailing.split(' ');
312 var member_list = [];
313 var that = this;
314 _.each(members, function (member) {
315 var i = 0,
316 j = 0,
317 modes = [];
318
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);
324 i++;
325 }
326 }
327 }
328
329 member_list.push({nick: member, modes: modes});
330 });
331
332 this.irc_connection.emit('channel ' + command.params[2] + ' userlist', {
333 users: member_list,
334 channel: command.params[2]
335 });
336 },
337
338 'RPL_ENDOFNAMES': function (command) {
339 this.irc_connection.emit('channel ' + command.params[1] + ' userlist_end', {
340 channel: command.params[1]
341 });
342 },
343
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]
350 });
351 },
352
353 'RPL_ENDOFBANLIST': function (command) {
354 this.irc_connection.emit('channel ' + command.params[1] + ' banlist_end', {
355 channel: command.params[1]
356 });
357 },
358
359 'RPL_TOPIC': function (command) {
360 this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
361 channel: command.params[1],
362 topic: command.trailing
363 });
364 },
365
366 'RPL_NOTOPIC': function (command) {
367 this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
368 channel: command.params[1],
369 topic: ''
370 });
371 },
372
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]
378 });
379 },
380
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]
385 });
386 },
387
388 'PING': function (command) {
389 this.irc_connection.write('PONG ' + command.trailing);
390 },
391
392 'JOIN': function (command) {
393 var channel, time;
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];
398 }
399
400 // Check if we have a server-time
401 time = getServerTime.call(this, command);
402
403 this.irc_connection.emit('channel ' + channel + ' join', {
404 nick: command.nick,
405 ident: command.ident,
406 hostname: command.hostname,
407 channel: channel,
408 time: time
409 });
410 },
411
412 'PART': function (command) {
413 var time;
414
415 // Check if we have a server-time
416 time = getServerTime.call(this, command);
417
418 this.irc_connection.emit('channel ' + command.params[0] + ' part', {
419 nick: command.nick,
420 ident: command.ident,
421 hostname: command.hostname,
422 channel: command.params[0],
423 message: command.trailing,
424 time: time
425 });
426 },
427
428 'KICK': function (command) {
429 var time;
430
431 // Check if we have a server-time
432 time = getServerTime.call(this, command);
433
434 this.irc_connection.emit('channel ' + command.params[0] + ' kick', {
435 kicked: command.params[1],
436 nick: command.nick,
437 ident: command.ident,
438 hostname: command.hostname,
439 channel: command.params[0],
440 message: command.trailing,
441 time: time
442 });
443 },
444
445 'QUIT': function (command) {
446 var time;
447
448 // Check if we have a server-time
449 time = getServerTime.call(this, command);
450
451 this.irc_connection.emit('user ' + command.nick + ' quit', {
452 nick: command.nick,
453 ident: command.ident,
454 hostname: command.hostname,
455 message: command.trailing,
456 time: time
457 });
458 },
459
460 'NOTICE': function (command) {
461 var namespace,
462 time;
463
464 // Check if we have a server-time
465 time = getServerTime.call(this, command);
466
467
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', {
472 nick: command.nick,
473 ident: command.ident,
474 hostname: command.hostname,
475 channel: command.params[0],
476 msg: command.trailing.substring(1, command.trailing.length - 1),
477 time: time
478 });
479 } else {
480 namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase() || command.params[0] == '*') ?
481 'user' :
482 'channel';
483
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,
491 time: time
492 });
493 }
494 },
495
496 'NICK': function (command) {
497 var time;
498
499 // Check if we have a server-time
500 time = getServerTime.call(this, command);
501
502 this.irc_connection.emit('user ' + command.nick + ' nick', {
503 nick: command.nick,
504 ident: command.ident,
505 hostname: command.hostname,
506 newnick: command.trailing || command.params[0],
507 time: time
508 });
509 },
510
511 'TOPIC': function (command) {
512 var time;
513
514 // If we don't have an associated channel, no need to continue
515 if (!command.params[0]) return;
516
517 // Check if we have a server-time
518 time = getServerTime.call(this, command);
519
520 var channel = command.params[0],
521 topic = command.trailing || '';
522
523 this.irc_connection.emit('channel ' + channel + ' topic', {
524 nick: command.nick,
525 channel: channel,
526 topic: topic,
527 time: time
528 });
529 },
530
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] || '')),
535 modes = [],
536 has_param, i, j, add, event, time;
537
538 // Check if we have a server-time
539 time = getServerTime.call(this, command);
540
541 prefixes = _.reduce(prefixes, function (list, prefix) {
542 list.push(prefix.mode);
543 return list;
544 }, []);
545 always_param = always_param.split('').concat(prefixes);
546
547 has_param = function (mode, add) {
548 if (_.find(always_param, function (m) {
549 return m === mode;
550 })) {
551 return true;
552 } else if (add && _.find((chanmodes[2] || '').split(''), function (m) {
553 return m === mode;
554 })) {
555 return true;
556 } else {
557 return false;
558 }
559 };
560
561 if (!command.params[1]) {
562 command.params[1] = command.trailing;
563 }
564
565 j = 0;
566 for (i = 0; i < command.params[1].length; i++) {
567 switch (command.params[1][i]) {
568 case '+':
569 add = true;
570 break;
571 case '-':
572 add = false;
573 break;
574 default:
575 if (has_param(command.params[1][i], add)) {
576 modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: command.params[2 + j]});
577 j++;
578 } else {
579 modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: null});
580 }
581 }
582 }
583
584 event = (_.contains(this.irc_connection.options.CHANTYPES, command.params[0][0]) ? 'channel ' : 'user ') + command.params[0] + ' mode';
585
586 this.irc_connection.emit(event, {
587 target: command.params[0],
588 nick: command.nick || command.prefix || '',
589 modes: modes,
590 time: time
591 });
592 },
593
594 'PRIVMSG': function (command) {
595 var tmp, namespace, time;
596
597 // Check if we have a server-time
598 time = getServerTime.call(this, command);
599
600 if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
601 //CTCP request
602 if (command.trailing.substr(1, 6) === 'ACTION') {
603 this.irc_connection.clientEvent('action', {
604 nick: command.nick,
605 ident: command.ident,
606 hostname: command.hostname,
607 channel: command.params[0],
608 msg: command.trailing.substring(8, command.trailing.length - 1),
609 time: time
610 });
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),
617 time: time
618 });
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));
625 } else {
626 namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase()) ? 'user' : 'channel';
627 this.irc_connection.emit(namespace + ' ' + command.nick + ' ctcp_request', {
628 nick: command.nick,
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),
634 time: time
635 });
636 }
637 } else {
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', {
641 nick: command.nick,
642 ident: command.ident,
643 hostname: command.hostname,
644 channel: command.params[0],
645 msg: command.trailing,
646 time: time
647 });
648 }
649 },
650
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(' ');
655 var request;
656
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'];
659
660 if (this.irc_connection.password) {
661 want.push('sasl');
662 }
663
664 switch (command.params[1]) {
665 case 'LS':
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(' '));
671 } else {
672 this.irc_connection.write('CAP END');
673 this.irc_connection.cap_negotation = false;
674 }
675 break;
676 case 'ACK':
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);
682 }
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');
687 } else {
688 this.irc_connection.write('CAP END');
689 this.irc_connection.cap_negotation = false;
690 }
691 }
692 break;
693 case 'NAK':
694 if (capabilities.length > 0) {
695 this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
696 }
697 if (this.irc_connection.cap.requested.length > 0) {
698 this.irc_connection.write('CAP END');
699 this.irc_connection.cap_negotation = false;
700 }
701 break;
702 case 'LIST':
703 // should we do anything here?
704 break;
705 }
706 },
707
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);
715 }
716 if (b64.length > 0) {
717 this.irc_connection.write('AUTHENTICATE ' + b64);
718 } else {
719 this.irc_connection.write('AUTHENTICATE +');
720 }
721 } else {
722 this.irc_connection.write('CAP END');
723 this.irc_connection.cap_negotation = false;
724 }
725 },
726
727 'AWAY': function (command) {
728 var time;
729
730 // Check if we have a server-time
731 time = getServerTime.call(this, command);
732
733 this.irc_connection.emit('user ' + command.nick + ' away', {
734 nick: command.nick,
735 msg: command.trailing,
736 time: time
737 });
738 },
739
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;
744 },
745
746 'RPL_SASLLOGGEDIN': function (command) {
747 if (this.irc_connection.cap_negotation === false) {
748 this.irc_connection.write('CAP END');
749 }
750 },
751
752 'ERR_SASLNOTAUTHORISED': function (command) {
753 this.irc_connection.write('CAP END');
754 this.irc_connection.cap_negotation = false;
755 },
756
757 'ERR_SASLABORTED': function (command) {
758 this.irc_connection.write('CAP END');
759 this.irc_connection.cap_negotation = false;
760 },
761
762 'ERR_SASLALREADYAUTHED': function (command) {
763 // noop
764 },
765
766 'ERROR': function (command) {
767 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' error', {
768 reason: command.trailing
769 });
770 },
771 ERR_PASSWDMISMATCH: function (command) {
772 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' password_mismatch', {});
773 },
774
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]
779 });
780 },
781
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
786 });
787 },
788
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
793 });
794 },
795
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
800 });
801 },
802
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
808 });
809 },
810
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
815 });
816 },
817
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]
822 });
823 },
824
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
829 });
830 },
831
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
836 });
837 },
838
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
843 });
844 },
845
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
850 });
851 },
852
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
857 });
858 },
859
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
864 });
865 },
866
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
871 });
872 },
873
874 ERR_NOTREGISTERED: function (command) {
875 },
876
877 RPL_MAPMORE: function (command) {
878 var params = _.clone(command.params);
879 params.shift();
880 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
881 },
882
883 RPL_MAPEND: function (command) {
884 var params = _.clone(command.params);
885 params.shift();
886 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
887 },
888
889 RPL_LINKS: function (command) {
890 var params = _.clone(command.params);
891 params.shift();
892 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
893 },
894
895 RPL_ENDOFLINKS: function (command) {
896 var params = _.clone(command.params);
897 params.shift();
898 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
899 },
900
901 ERR_UNKNOWNCOMMAND: function (command) {
902 var params = _.clone(command.params);
903 params.shift();
904 genericNotice.call(this, command, '`' + params.join(', ') + '` ' + command.trailing);
905 },
906
907 ERR_NOMOTD: function (command) {
908 var params = _.clone(command.params);
909 params.shift();
910 genericNotice.call(this, command, command.trailing);
911 },
912
913 ERR_NOPRIVILEGES: function (command) {
914 var params = _.clone(command.params);
915 params.shift();
916 genericNotice.call(this, command, command.trailing);
917 },
918
919 RPL_STATSCONN: function (command) {
920 var params = _.clone(command.params);
921 params.shift();
922 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
923 },
924
925 RPL_LUSERCLIENT: function (command) {
926 var params = _.clone(command.params);
927 params.shift();
928 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
929 },
930
931 RPL_LUSEROP: function (command) {
932 var params = _.clone(command.params);
933 params.shift();
934 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
935 },
936
937 RPL_LUSERUNKNOWN: function (command) {
938 var params = _.clone(command.params);
939 params.shift();
940 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
941 },
942
943 RPL_LUSERCHANNELS: function (command) {
944 var params = _.clone(command.params);
945 params.shift();
946 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
947 },
948
949 RPL_LUSERME: function (command) {
950 var params = _.clone(command.params);
951 params.shift();
952 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
953 },
954
955 RPL_LOCALUSERS: function (command) {
956 var params = _.clone(command.params);
957 params.shift();
958 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
959 },
960
961 RPL_GLOBALUSERS: function (command) {
962 var params = _.clone(command.params);
963 params.shift();
964 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
965 }
966 };
967
968
969
970
971 function genericNotice (command, msg, is_error) {
972 // Default to being an error
973 if (typeof is_error !== 'boolean')
974 is_error = true;
975
976 this.irc_connection.clientEvent('notice', {
977 from_server: true,
978 nick: command.prefix,
979 ident: '',
980 hostname: '',
981 target: command.params[0],
982 msg: msg,
983 numeric: parseInt(command.command, 10)
984 });
985 }
986
987
988 function getServerTime(command) {
989 var time;
990
991 // No tags? No times.
992 if (!command.tags || command.tags.length === 0) {
993 return time;
994 }
995
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';
999 });
1000
1001 time = time ? time.value : undefined;
1002
1003 // Convert the time value to a unixtimestamp
1004 if (typeof time === 'string') {
1005 if (time.indexOf('T') > -1) {
1006 time = parseISO8601(opts.time);
1007
1008 } else if(time.match(/^[0-9.]+$/)) {
1009 // A string formatted unix timestamp
1010 time = new Date(time * 1000);
1011 }
1012
1013 time = time.getTime();
1014
1015 } else if (typeof time === 'number') {
1016 time = new Date(time * 1000);
1017 time = time.getTime();
1018 }
1019 }
1020
1021 return time;
1022 }
1023
1024
1025 function capContainsAny (caps) {
1026 var intersection;
1027 if (!caps instanceof Array) {
1028 caps = [caps];
1029 }
1030 intersection = _.intersection(this.irc_connection.cap.enabled, caps);
1031 return intersection.length > 0;
1032 }
1033
1034
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);
1039 } else {
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]),
1046 _date = new Date();
1047
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]));
1057 }
1058
1059 return _date;
1060 }
1061 }