Merge pull request #421 from CoryChaplin/development
[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 '353': 'RPL_NAMEREPLY',
38 '364': 'RPL_LINKS',
39 '365': 'RPL_ENDOFLINKS',
40 '366': 'RPL_ENDOFNAMES',
41 '367': 'RPL_BANLIST',
42 '368': 'RPL_ENDOFBANLIST',
43 '369': 'RPL_ENDOFWHOWAS',
44 '372': 'RPL_MOTD',
45 '375': 'RPL_MOTDSTART',
46 '376': 'RPL_ENDOFMOTD',
47 '378': 'RPL_WHOISHOST',
48 '379': 'RPL_WHOISMODES',
49 '401': 'ERR_NOSUCHNICK',
50 '404': 'ERR_CANNOTSENDTOCHAN',
51 '405': 'ERR_TOOMANYCHANNELS',
52 '406': 'ERR_WASNOSUCHNICK',
53 '421': 'ERR_UNKNOWNCOMMAND',
54 '422': 'ERR_NOMOTD',
55 '432': 'ERR_ERRONEUSNICKNAME',
56 '433': 'ERR_NICKNAMEINUSE',
57 '441': 'ERR_USERNOTINCHANNEL',
58 '442': 'ERR_NOTONCHANNEL',
59 '443': 'ERR_USERONCHANNEL',
60 '451': 'ERR_NOTREGISTERED',
61 '464': 'ERR_PASSWDMISMATCH',
62 '470': 'ERR_LINKCHANNEL',
63 '471': 'ERR_CHANNELISFULL',
64 '473': 'ERR_INVITEONLYCHAN',
65 '474': 'ERR_BANNEDFROMCHAN',
66 '475': 'ERR_BADCHANNELKEY',
67 '481': 'ERR_NOPRIVILEGES',
68 '482': 'ERR_CHANOPRIVSNEEDED',
69 '670': 'RPL_STARTTLS',
70 '671': 'RPL_WHOISSECURE',
71 '900': 'RPL_SASLAUTHENTICATED',
72 '903': 'RPL_SASLLOGGEDIN',
73 '904': 'ERR_SASLNOTAUTHORISED',
74 '906': 'ERR_SASLABORTED',
75 '907': 'ERR_SASLALREADYAUTHED'
76 };
77
78
79 IrcCommands = function (irc_connection) {
80 this.irc_connection = irc_connection;
81 };
82 module.exports = IrcCommands;
83
84 IrcCommands.prototype.dispatch = function (command, data) {
85 command += '';
86 if (irc_numerics[command]) {
87 command = irc_numerics[command];
88 }
89 if (handlers[command]) {
90 handlers[command].call(this, data);
91 } else {
92 unknownCommand.call(this, command, data);
93 }
94 };
95
96 IrcCommands.addHandler = function (command, handler) {
97 if (typeof handler !== 'function') {
98 return false;
99 }
100 handlers[command] = handler;
101 };
102
103 IrcCommands.addNumeric = function (numeric, handler_name) {
104 irc_numerics[numeric + ''] = handler_name +'';
105 };
106
107 unknownCommand = function (command, data) {
108 var params = _.clone(data.params);
109
110 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' unknown_command', {
111 command: command,
112 params: params,
113 trailing: data.trailing
114 });
115
116
117 /*
118 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' notice', {
119 from_server: command.prefix ? true : false,
120 nick: command.nick || command.prefix || undefined,
121 ident: command.ident,
122 hostname: command.hostname,
123 target: command.params[0],
124 msg: command.trailing
125 });
126 */
127 };
128
129
130 handlers = {
131 'RPL_WELCOME': function (command) {
132 var nick = command.params[0];
133 this.irc_connection.registered = true;
134 this.cap_negotation = false;
135 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' connect', {
136 nick: nick
137 });
138 },
139
140 'RPL_ISUPPORT': function (command) {
141 var options, i, option, matches, j;
142 options = command.params;
143 for (i = 1; i < options.length; i++) {
144 option = options[i].split("=", 2);
145 option[0] = option[0].toUpperCase();
146 this.irc_connection.options[option[0]] = (typeof option[1] !== 'undefined') ? option[1] : true;
147 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option[0])) {
148 if (option[0] === 'PREFIX') {
149 matches = /\(([^)]*)\)(.*)/.exec(option[1]);
150 if ((matches) && (matches.length === 3)) {
151 this.irc_connection.options.PREFIX = [];
152 for (j = 0; j < matches[2].length; j++) {
153 this.irc_connection.options.PREFIX.push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
154 }
155 }
156 } else if (option[0] === 'CHANTYPES') {
157 this.irc_connection.options.CHANTYPES = this.irc_connection.options.CHANTYPES.split('');
158 } else if (option[0] === 'CHANMODES') {
159 this.irc_connection.options.CHANMODES = option[1].split(',');
160 } else if ((option[0] === 'NAMESX') && (!_.contains(this.irc_connection.cap.enabled, 'multi-prefix'))) {
161 this.irc_connection.write('PROTOCTL NAMESX');
162 }
163 }
164 }
165 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' options', {
166 options: this.irc_connection.options,
167 cap: this.irc_connection.cap.enabled
168 });
169 },
170
171 'RPL_ENDOFWHOIS': function (command) {
172 this.irc_connection.emit('user ' + command.params[1] + ' endofwhois', {
173 nick: command.params[1],
174 msg: command.trailing
175 });
176 },
177
178 'RPL_AWAY': function (command) {
179 this.irc_connection.emit('user ' + command.params[1] + ' whoisaway', {
180 nick: command.params[1],
181 reason: command.trailing
182 });
183 },
184
185 'RPL_WHOISUSER': function (command) {
186 this.irc_connection.emit('user ' + command.params[1] + ' whoisuser', {
187 nick: command.params[1],
188 ident: command.params[2],
189 host: command.params[3],
190 msg: command.trailing
191 });
192 },
193
194 'RPL_WHOISSERVER': function (command) {
195 this.irc_connection.emit('user ' + command.params[1] + ' whoisserver', {
196 nick: command.params[1],
197 irc_server: command.params[2],
198 server_info: command.trailing
199 });
200 },
201
202 'RPL_WHOISOPERATOR': function (command) {
203 this.irc_connection.emit('user ' + command.params[1] + ' whoisoperator', {
204 nick: command.params[1],
205 msg: command.trailing
206 });
207 },
208
209 'RPL_WHOISCHANNELS': function (command) {
210 this.irc_connection.emit('user ' + command.params[1] + ' whoischannels', {
211 nick: command.params[1],
212 chans: command.trailing
213 });
214 },
215
216 'RPL_WHOISMODES': function (command) {
217 this.irc_connection.emit('user ' + command.params[1] + ' whoismodes', {
218 nick: command.params[1],
219 msg: command.trailing
220 });
221 },
222
223 'RPL_WHOISIDLE': function (command) {
224 this.irc_connection.emit('user ' + command.params[1] + ' whoisidle', {
225 nick: command.params[1],
226 idle: command.params[2],
227 logon: command.params[3] || undefined
228 });
229 },
230
231 'RPL_WHOISREGNICK': function (command) {
232 this.irc_connection.emit('user ' + command.params[1] + ' whoisregnick', {
233 nick: command.params[1],
234 msg: command.trailing
235 });
236 },
237
238 'RPL_WHOISHOST': function (command) {
239 this.irc_connection.emit('user ' + command.params[1] + ' whoishost', {
240 nick: command.params[1],
241 msg: command.trailing
242 });
243 },
244
245 'RPL_WHOISSECURE': function (command) {
246 this.irc_connection.emit('user ' + command.params[1] + ' whoissecure', {
247 nick: command.params[1]
248 });
249 },
250
251 'RPL_WHOISACCOUNT': function (command) {
252 this.irc_connection.emit('user ' + command.params[1] + ' whoisaccount', {
253 nick: command.params[1],
254 account: command.params[2]
255 });
256 },
257
258 'RPL_WHOWASUSER': function (command) {
259 this.irc_connection.emit('user ' + command.params[1] + ' whowas', {
260 nick: command.params[1],
261 ident: command.params[2],
262 host: command.params[3],
263 real_name: command.trailing
264 });
265 },
266
267 'RPL_ENDOFWHOWAS': function (command) {
268 this.irc_connection.emit('user ' + command.params[1] + ' endofwhowas', {
269 nick: command.params[1]
270 });
271 },
272
273 'ERR_WASNOSUCHNICK': function (command) {
274 this.irc_connection.emit('user ' + command.params[1] + ' wasnosucknick', {
275 nick: command.params[1]
276 });
277 },
278
279 'RPL_LISTSTART': function (command) {
280 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_start', {});
281 },
282
283 'RPL_LISTEND': function (command) {
284 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_end', {});
285 },
286
287 'RPL_LIST': function (command) {
288 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' list_channel', {
289 channel: command.params[1],
290 num_users: parseInt(command.params[2], 10),
291 topic: command.trailing
292 });
293 },
294
295 'RPL_MOTD': function (command) {
296 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd', {
297 motd: command.trailing + '\n'
298 });
299 },
300
301 'RPL_MOTDSTART': function (command) {
302 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_start', {});
303 },
304
305 'RPL_ENDOFMOTD': function (command) {
306 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' motd_end', {});
307 },
308
309 'RPL_NAMEREPLY': function (command) {
310 var members = command.trailing.split(' ');
311 var member_list = [];
312 var that = this;
313 _.each(members, function (member) {
314 var i = 0,
315 j = 0,
316 modes = [];
317
318 // Make sure we have some prefixes already
319 if (that.irc_connection.options.PREFIX) {
320 for (j = 0; j < that.irc_connection.options.PREFIX.length; j++) {
321 if (member.charAt(i) === that.irc_connection.options.PREFIX[j].symbol) {
322 modes.push(that.irc_connection.options.PREFIX[j].mode);
323 i++;
324 }
325 }
326 }
327
328 member_list.push({nick: member, modes: modes});
329 });
330
331 this.irc_connection.emit('channel ' + command.params[2] + ' userlist', {
332 users: member_list,
333 channel: command.params[2]
334 });
335 },
336
337 'RPL_ENDOFNAMES': function (command) {
338 this.irc_connection.emit('channel ' + command.params[1] + ' userlist_end', {
339 channel: command.params[1]
340 });
341 },
342
343 'RPL_BANLIST': function (command) {
344 this.irc_connection.emit('channel ' + command.params[1] + ' banlist', {
345 channel: command.params[1],
346 banned: command.params[2],
347 banned_by: command.params[3],
348 banned_at: command.params[4]
349 });
350 },
351
352 'RPL_ENDOFBANLIST': function (command) {
353 this.irc_connection.emit('channel ' + command.params[1] + ' banlist_end', {
354 channel: command.params[1]
355 });
356 },
357
358 'RPL_TOPIC': function (command) {
359 this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
360 channel: command.params[1],
361 topic: command.trailing
362 });
363 },
364
365 'RPL_NOTOPIC': function (command) {
366 this.irc_connection.emit('channel ' + command.params[1] + ' topic', {
367 channel: command.params[1],
368 topic: ''
369 });
370 },
371
372 'RPL_TOPICWHOTIME': function (command) {
373 this.irc_connection.emit('channel ' + command.params[1] + ' topicsetby', {
374 nick: command.params[2],
375 channel: command.params[1],
376 when: command.params[3]
377 });
378 },
379
380 'PING': function (command) {
381 this.irc_connection.write('PONG ' + command.trailing);
382 },
383
384 'JOIN': function (command) {
385 var channel, time;
386 if (typeof command.trailing === 'string' && command.trailing !== '') {
387 channel = command.trailing;
388 } else if (typeof command.params[0] === 'string' && command.params[0] !== '') {
389 channel = command.params[0];
390 }
391
392 // Check if we have a server-time
393 time = getServerTime.call(this, command);
394
395 this.irc_connection.emit('channel ' + channel + ' join', {
396 nick: command.nick,
397 ident: command.ident,
398 hostname: command.hostname,
399 channel: channel,
400 time: time
401 });
402 },
403
404 'PART': function (command) {
405 var time;
406
407 // Check if we have a server-time
408 time = getServerTime.call(this, command);
409
410 this.irc_connection.emit('channel ' + command.params[0] + ' part', {
411 nick: command.nick,
412 ident: command.ident,
413 hostname: command.hostname,
414 channel: command.params[0],
415 message: command.trailing,
416 time: time
417 });
418 },
419
420 'KICK': function (command) {
421 var time;
422
423 // Check if we have a server-time
424 time = getServerTime.call(this, command);
425
426 this.irc_connection.emit('channel ' + command.params[0] + ' kick', {
427 kicked: command.params[1],
428 nick: command.nick,
429 ident: command.ident,
430 hostname: command.hostname,
431 channel: command.params[0],
432 message: command.trailing,
433 time: time
434 });
435 },
436
437 'QUIT': function (command) {
438 var time;
439
440 // Check if we have a server-time
441 time = getServerTime.call(this, command);
442
443 this.irc_connection.emit('user ' + command.nick + ' quit', {
444 nick: command.nick,
445 ident: command.ident,
446 hostname: command.hostname,
447 message: command.trailing,
448 time: time
449 });
450 },
451
452 'NOTICE': function (command) {
453 var namespace,
454 time;
455
456 // Check if we have a server-time
457 time = getServerTime.call(this, command);
458
459
460 if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
461 // It's a CTCP response
462 namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase()) ? 'user' : 'channel';
463 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' ctcp_response', {
464 nick: command.nick,
465 ident: command.ident,
466 hostname: command.hostname,
467 channel: command.params[0],
468 msg: command.trailing.substring(1, command.trailing.length - 1),
469 time: time
470 });
471 } else {
472 namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase() || command.params[0] == '*') ?
473 'user' :
474 'channel';
475
476 this.irc_connection.emit(namespace + ' ' + command.params[0] + ' notice', {
477 from_server: command.prefix ? true : false,
478 nick: command.nick || command.prefix || undefined,
479 ident: command.ident,
480 hostname: command.hostname,
481 target: command.params[0],
482 msg: command.trailing,
483 time: time
484 });
485 }
486 },
487
488 'NICK': function (command) {
489 var time;
490
491 // Check if we have a server-time
492 time = getServerTime.call(this, command);
493
494 this.irc_connection.emit('user ' + command.nick + ' nick', {
495 nick: command.nick,
496 ident: command.ident,
497 hostname: command.hostname,
498 newnick: command.trailing || command.params[0],
499 time: time
500 });
501 },
502
503 'TOPIC': function (command) {
504 var time;
505
506 // If we don't have an associated channel, no need to continue
507 if (!command.params[0]) return;
508
509 // Check if we have a server-time
510 time = getServerTime.call(this, command);
511
512 var channel = command.params[0],
513 topic = command.trailing || '';
514
515 this.irc_connection.emit('channel ' + channel + ' topic', {
516 nick: command.nick,
517 channel: channel,
518 topic: topic,
519 time: time
520 });
521 },
522
523 'MODE': function (command) {
524 var chanmodes = this.irc_connection.options.CHANMODES || [],
525 prefixes = this.irc_connection.options.PREFIX || [],
526 always_param = (chanmodes[0] || '').concat((chanmodes[1] || '')),
527 modes = [],
528 has_param, i, j, add, event, time;
529
530 // Check if we have a server-time
531 time = getServerTime.call(this, command);
532
533 prefixes = _.reduce(prefixes, function (list, prefix) {
534 list.push(prefix.mode);
535 return list;
536 }, []);
537 always_param = always_param.split('').concat(prefixes);
538
539 has_param = function (mode, add) {
540 if (_.find(always_param, function (m) {
541 return m === mode;
542 })) {
543 return true;
544 } else if (add && _.find((chanmodes[2] || '').split(''), function (m) {
545 return m === mode;
546 })) {
547 return true;
548 } else {
549 return false;
550 }
551 };
552
553 if (!command.params[1]) {
554 command.params[1] = command.trailing;
555 }
556
557 j = 0;
558 for (i = 0; i < command.params[1].length; i++) {
559 switch (command.params[1][i]) {
560 case '+':
561 add = true;
562 break;
563 case '-':
564 add = false;
565 break;
566 default:
567 if (has_param(command.params[1][i], add)) {
568 modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: command.params[2 + j]});
569 j++;
570 } else {
571 modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: null});
572 }
573 }
574 }
575
576 event = (_.contains(this.irc_connection.options.CHANTYPES, command.params[0][0]) ? 'channel ' : 'user ') + command.params[0] + ' mode';
577
578 this.irc_connection.emit(event, {
579 target: command.params[0],
580 nick: command.nick || command.prefix || '',
581 modes: modes,
582 time: time
583 });
584 },
585
586 'PRIVMSG': function (command) {
587 var tmp, namespace, time;
588
589 // Check if we have a server-time
590 time = getServerTime.call(this, command);
591
592 if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
593 //CTCP request
594 if (command.trailing.substr(1, 6) === 'ACTION') {
595 this.irc_connection.clientEvent('action', {
596 nick: command.nick,
597 ident: command.ident,
598 hostname: command.hostname,
599 channel: command.params[0],
600 msg: command.trailing.substring(8, command.trailing.length - 1),
601 time: time
602 });
603 } else if (command.trailing.substr(1, 4) === 'KIWI') {
604 tmp = command.trailing.substring(6, command.trailing.length - 1);
605 namespace = tmp.split(' ', 1)[0];
606 this.irc_connection.clientEvent('kiwi', {
607 namespace: namespace,
608 data: tmp.substr(namespace.length + 1),
609 time: time
610 });
611 } else if (command.trailing.substr(1, 7) === 'VERSION') {
612 this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1));
613 } else if (command.trailing.substr(1, 6) === 'SOURCE') {
614 this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String.fromCharCode(1));
615 } else if (command.trailing.substr(1, 10) === 'CLIENTINFO') {
616 this.irc_connection.write('NOTICE ' + command.nick + ' :' + String.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String.fromCharCode(1));
617 } else {
618 namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase()) ? 'user' : 'channel';
619 this.irc_connection.emit(namespace + ' ' + command.nick + ' ctcp_request', {
620 nick: command.nick,
621 ident: command.ident,
622 hostname: command.hostname,
623 target: command.params[0],
624 type: (command.trailing.substring(1, command.trailing.length - 1).split(' ') || [null])[0],
625 msg: command.trailing.substring(1, command.trailing.length - 1),
626 time: time
627 });
628 }
629 } else {
630 // A message to a user (private message) or to a channel?
631 namespace = (command.params[0].toLowerCase() == this.irc_connection.nick.toLowerCase()) ? 'user ' + command.nick : 'channel ' + command.params[0];
632 this.irc_connection.emit(namespace + ' privmsg', {
633 nick: command.nick,
634 ident: command.ident,
635 hostname: command.hostname,
636 channel: command.params[0],
637 msg: command.trailing,
638 time: time
639 });
640 }
641 },
642
643 'CAP': function (command) {
644 // TODO: capability modifiers
645 // i.e. - for disable, ~ for requires ACK, = for sticky
646 var capabilities = command.trailing.replace(/(?:^| )[\-~=]/, '').split(' ');
647 var request;
648
649 // Which capabilities we want to enable
650 var want = ['multi-prefix', 'away-notify', 'server-time', 'znc.in/server-time-iso', 'znc.in/server-time'];
651
652 if (this.irc_connection.password) {
653 want.push('sasl');
654 }
655
656 switch (command.params[1]) {
657 case 'LS':
658 // Compute which of the available capabilities we want and request them
659 request = _.intersection(capabilities, want);
660 if (request.length > 0) {
661 this.irc_connection.cap.requested = request;
662 this.irc_connection.write('CAP REQ :' + request.join(' '));
663 } else {
664 this.irc_connection.write('CAP END');
665 this.irc_connection.cap_negotation = false;
666 }
667 break;
668 case 'ACK':
669 if (capabilities.length > 0) {
670 // Update list of enabled capabilities
671 this.irc_connection.cap.enabled = capabilities;
672 // Update list of capabilities we would like to have but that aren't enabled
673 this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
674 }
675 if (this.irc_connection.cap.enabled.length > 0) {
676 if (_.contains(this.irc_connection.cap.enabled, 'sasl')) {
677 this.irc_connection.sasl = true;
678 this.irc_connection.write('AUTHENTICATE PLAIN');
679 } else {
680 this.irc_connection.write('CAP END');
681 this.irc_connection.cap_negotation = false;
682 }
683 }
684 break;
685 case 'NAK':
686 if (capabilities.length > 0) {
687 this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
688 }
689 if (this.irc_connection.cap.requested.length > 0) {
690 this.irc_connection.write('CAP END');
691 this.irc_connection.cap_negotation = false;
692 }
693 break;
694 case 'LIST':
695 // should we do anything here?
696 break;
697 }
698 },
699
700 'AUTHENTICATE': function (command) {
701 var b = new Buffer(this.irc_connection.nick + "\0" + this.irc_connection.nick + "\0" + this.irc_connection.password, 'utf8');
702 var b64 = b.toString('base64');
703 if (command.params[0] === '+') {
704 while (b64.length >= 400) {
705 this.irc_connection.write('AUTHENTICATE ' + b64.slice(0, 399));
706 b64 = b64.slice(399);
707 }
708 if (b64.length > 0) {
709 this.irc_connection.write('AUTHENTICATE ' + b64);
710 } else {
711 this.irc_connection.write('AUTHENTICATE +');
712 }
713 } else {
714 this.irc_connection.write('CAP END');
715 this.irc_connection.cap_negotation = false;
716 }
717 },
718
719 'AWAY': function (command) {
720 var time;
721
722 // Check if we have a server-time
723 time = getServerTime.call(this, command);
724
725 this.irc_connection.emit('user ' + command.nick + ' away', {
726 nick: command.nick,
727 msg: command.trailing,
728 time: time
729 });
730 },
731
732 'RPL_SASLAUTHENTICATED': function (command) {
733 this.irc_connection.write('CAP END');
734 this.irc_connection.cap_negotation = false;
735 this.irc_connection.sasl = true;
736 },
737
738 'RPL_SASLLOGGEDIN': function (command) {
739 if (this.irc_connection.cap_negotation === false) {
740 this.irc_connection.write('CAP END');
741 }
742 },
743
744 'ERR_SASLNOTAUTHORISED': function (command) {
745 this.irc_connection.write('CAP END');
746 this.irc_connection.cap_negotation = false;
747 },
748
749 'ERR_SASLABORTED': function (command) {
750 this.irc_connection.write('CAP END');
751 this.irc_connection.cap_negotation = false;
752 },
753
754 'ERR_SASLALREADYAUTHED': function (command) {
755 // noop
756 },
757
758 'ERROR': function (command) {
759 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' error', {
760 reason: command.trailing
761 });
762 },
763 ERR_PASSWDMISMATCH: function (command) {
764 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' password_mismatch', {});
765 },
766
767 ERR_LINKCHANNEL: function (command) {
768 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' channel_redirect', {
769 from: command.params[1],
770 to: command.params[2]
771 });
772 },
773
774 ERR_NOSUCHNICK: function (command) {
775 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' no_such_nick', {
776 nick: command.params[1],
777 reason: command.trailing
778 });
779 },
780
781 ERR_CANNOTSENDTOCHAN: function (command) {
782 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' cannot_send_to_chan', {
783 channel: command.params[1],
784 reason: command.trailing
785 });
786 },
787
788 ERR_TOOMANYCHANNELS: function (command) {
789 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' too_many_channels', {
790 channel: command.params[1],
791 reason: command.trailing
792 });
793 },
794
795 ERR_USERNOTINCHANNEL: function (command) {
796 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' user_not_in_channel', {
797 nick: command.params[0],
798 channel: command.params[1],
799 reason: command.trailing
800 });
801 },
802
803 ERR_NOTONCHANNEL: function (command) {
804 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' not_on_channel', {
805 channel: command.params[1],
806 reason: command.trailing
807 });
808 },
809
810 ERR_USERONCHANNEL: function (command) {
811 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' user_on_channel', {
812 nick: command.params[1],
813 channel: command.params[2]
814 });
815 },
816
817 ERR_CHANNELISFULL: function (command) {
818 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' channel_is_full', {
819 channel: command.params[1],
820 reason: command.trailing
821 });
822 },
823
824 ERR_INVITEONLYCHAN: function (command) {
825 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' invite_only_channel', {
826 channel: command.params[1],
827 reason: command.trailing
828 });
829 },
830
831 ERR_BANNEDFROMCHAN: function (command) {
832 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' banned_from_channel', {
833 channel: command.params[1],
834 reason: command.trailing
835 });
836 },
837
838 ERR_BADCHANNELKEY: function (command) {
839 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' bad_channel_key', {
840 channel: command.params[1],
841 reason: command.trailing
842 });
843 },
844
845 ERR_CHANOPRIVSNEEDED: function (command) {
846 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' chanop_privs_needed', {
847 channel: command.params[1],
848 reason: command.trailing
849 });
850 },
851
852 ERR_NICKNAMEINUSE: function (command) {
853 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' nickname_in_use', {
854 nick: command.params[1],
855 reason: command.trailing
856 });
857 },
858
859 ERR_ERRONEUSNICKNAME: function(command) {
860 this.irc_connection.emit('server ' + this.irc_connection.irc_host.hostname + ' erroneus_nickname', {
861 nick: command.params[1],
862 reason: command.trailing
863 });
864 },
865
866 ERR_NOTREGISTERED: function (command) {
867 },
868
869 RPL_MAPMORE: function (command) {
870 var params = _.clone(command.params);
871 params.shift();
872 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
873 },
874
875 RPL_MAPEND: function (command) {
876 var params = _.clone(command.params);
877 params.shift();
878 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
879 },
880
881 RPL_LINKS: function (command) {
882 var params = _.clone(command.params);
883 params.shift();
884 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
885 },
886
887 RPL_ENDOFLINKS: function (command) {
888 var params = _.clone(command.params);
889 params.shift();
890 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
891 },
892
893 ERR_UNKNOWNCOMMAND: function (command) {
894 var params = _.clone(command.params);
895 params.shift();
896 genericNotice.call(this, command, '`' + params.join(', ') + '` ' + command.trailing);
897 },
898
899 ERR_NOMOTD: function (command) {
900 var params = _.clone(command.params);
901 params.shift();
902 genericNotice.call(this, command, command.trailing);
903 },
904
905 ERR_NOPRIVILEGES: function (command) {
906 var params = _.clone(command.params);
907 params.shift();
908 genericNotice.call(this, command, command.trailing);
909 },
910
911 RPL_STATSCONN: function (command) {
912 var params = _.clone(command.params);
913 params.shift();
914 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
915 },
916
917 RPL_LUSERCLIENT: function (command) {
918 var params = _.clone(command.params);
919 params.shift();
920 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
921 },
922
923 RPL_LUSEROP: function (command) {
924 var params = _.clone(command.params);
925 params.shift();
926 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
927 },
928
929 RPL_LUSERUNKNOWN: function (command) {
930 var params = _.clone(command.params);
931 params.shift();
932 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
933 },
934
935 RPL_LUSERCHANNELS: function (command) {
936 var params = _.clone(command.params);
937 params.shift();
938 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
939 },
940
941 RPL_LUSERME: function (command) {
942 var params = _.clone(command.params);
943 params.shift();
944 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
945 },
946
947 RPL_LOCALUSERS: function (command) {
948 var params = _.clone(command.params);
949 params.shift();
950 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
951 },
952
953 RPL_GLOBALUSERS: function (command) {
954 var params = _.clone(command.params);
955 params.shift();
956 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
957 }
958 };
959
960
961
962
963 function genericNotice (command, msg, is_error) {
964 // Default to being an error
965 if (typeof is_error !== 'boolean')
966 is_error = true;
967
968 this.irc_connection.clientEvent('notice', {
969 from_server: true,
970 nick: command.prefix,
971 ident: '',
972 hostname: '',
973 target: command.params[0],
974 msg: msg,
975 numeric: parseInt(command.command, 10)
976 });
977 }
978
979
980 function getServerTime(command) {
981 var time;
982
983 // No tags? No times.
984 if (!command.tags || command.tags.length === 0) {
985 return time;
986 }
987
988 if (capContainsAny.call(this, ['server-time', 'znc.in/server-time', 'znc.in/server-time-iso'])) {
989 time = _.find(command.tags, function (tag) {
990 return tag.tag === 'time';
991 });
992
993 time = time ? time.value : undefined;
994
995 // Convert the time value to a unixtimestamp
996 if (typeof time === 'string') {
997 if (time.indexOf('T') > -1) {
998 time = parseISO8601(opts.time);
999
1000 } else if(time.match(/^[0-9.]+$/)) {
1001 // A string formatted unix timestamp
1002 time = new Date(time * 1000);
1003 }
1004
1005 time = time.getTime();
1006
1007 } else if (typeof time === 'number') {
1008 time = new Date(time * 1000);
1009 time = time.getTime();
1010 }
1011 }
1012
1013 return time;
1014 }
1015
1016
1017 function capContainsAny (caps) {
1018 var intersection;
1019 if (!caps instanceof Array) {
1020 caps = [caps];
1021 }
1022 intersection = _.intersection(this.irc_connection.cap.enabled, caps);
1023 return intersection.length > 0;
1024 }
1025
1026
1027 // Code based on http://anentropic.wordpress.com/2009/06/25/javascript-iso8601-parser-and-pretty-dates/#comment-154
1028 function parseISO8601(str) {
1029 if (Date.prototype.toISOString) {
1030 return new Date(str);
1031 } else {
1032 var parts = str.split('T'),
1033 dateParts = parts[0].split('-'),
1034 timeParts = parts[1].split('Z'),
1035 timeSubParts = timeParts[0].split(':'),
1036 timeSecParts = timeSubParts[2].split('.'),
1037 timeHours = Number(timeSubParts[0]),
1038 _date = new Date();
1039
1040 _date.setUTCFullYear(Number(dateParts[0]));
1041 _date.setUTCDate(1);
1042 _date.setUTCMonth(Number(dateParts[1])-1);
1043 _date.setUTCDate(Number(dateParts[2]));
1044 _date.setUTCHours(Number(timeHours));
1045 _date.setUTCMinutes(Number(timeSubParts[1]));
1046 _date.setUTCSeconds(Number(timeSecParts[0]));
1047 if (timeSecParts[1]) {
1048 _date.setUTCMilliseconds(Number(timeSecParts[1]));
1049 }
1050
1051 return _date;
1052 }
1053 }