1 var _
= require('lodash');
9 RPL_WHOISREGNICK
: '307',
11 RPL_WHOISSERVER
: '312',
12 RPL_WHOISOPERATOR
: '313',
14 RPL_ENDOFWHOIS
: '318',
15 RPL_WHOISCHANNELS
: '319',
21 RPL_TOPICWHOTIME
: '333',
24 RPL_ENDOFLINKS
: '365',
25 RPL_ENDOFNAMES
: '366',
27 RPL_ENDOFBANLIST
: '368',
31 RPL_WHOISMODES
: '379',
32 ERR_NOSUCHNICK
: '401',
33 ERR_CANNOTSENDTOCHAN
: '404',
34 ERR_TOOMANYCHANNELS
: '405',
35 ERR_UNKNOWNCOMMAND
: '421',
37 ERR_NICKNAMEINUSE
: '433',
38 ERR_USERNOTINCHANNEL
: '441',
39 ERR_NOTONCHANNEL
: '442',
40 ERR_NOTREGISTERED
: '451',
41 ERR_LINKCHANNEL
: '470',
42 ERR_CHANNELISFULL
: '471',
43 ERR_INVITEONLYCHAN
: '473',
44 ERR_BANNEDFROMCHAN
: '474',
45 ERR_BADCHANNELKEY
: '475',
46 ERR_NOPRIVILEGES
: '481',
47 ERR_CHANOPRIVSNEEDED
: '482',
49 RPL_SASLAUTHENTICATED
: '900',
50 RPL_SASLLOGGEDIN
: '903',
51 ERR_SASLNOTAUTHORISED
: '904',
52 ERR_SASLABORTED
: '906',
53 ERR_SASLALREADYAUTHED
: '907'
58 var IrcCommands = function (irc_connection
, con_num
, client
) {
59 this.irc_connection
= irc_connection
;
60 this.con_num
= con_num
;
63 module
.exports
= IrcCommands
;
65 IrcCommands
.prototype.bindEvents = function () {
68 _
.each(listeners
, function (listener
, command
) {
69 var s
= command
.substr(0, 4);
70 if ((s
=== 'RPL_') || (s
=== 'ERR_')) {
71 command
= irc_numerics
[command
];
73 that
.irc_connection
.on('irc_' + command
, function () {
74 listener
.apply(that
, arguments
);
79 IrcCommands
.prototype.dispose = function () {
80 this.removeAllListeners();
86 'RPL_WELCOME': function (command
) {
87 var nick
= command
.params
[0];
88 this.irc_connection
.registered
= true;
89 this.cap_negotation
= false;
90 this.client
.sendIrcCommand('connect', {server
: this.con_num
, nick
: nick
});
92 'RPL_ISUPPORT': function (command
) {
93 var options
, i
, option
, matches
, j
;
94 options
= command
.params
;
95 for (i
= 1; i
< options
.length
; i
++) {
96 option
= options
[i
].split("=", 2);
97 option
[0] = option
[0].toUpperCase();
98 this.irc_connection
.options
[option
[0]] = (typeof option
[1] !== 'undefined') ? option
[1] : true;
99 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option
[0])) {
100 if (option
[0] === 'PREFIX') {
101 matches
= /\(([^)]*)\)(.*)/.exec(option
[1]);
102 if ((matches
) && (matches
.length
=== 3)) {
103 this.irc_connection
.options
.PREFIX
= [];
104 for (j
= 0; j
< matches
[2].length
; j
++) {
105 this.irc_connection
.options
.PREFIX
.push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
108 } else if (option
[0] === 'CHANTYPES') {
109 this.irc_connection
.options
.CHANTYPES
= this.irc_connection
.options
.CHANTYPES
.split('');
110 } else if (option
[0] === 'CHANMODES') {
111 this.irc_connection
.options
.CHANMODES
= option
[1].split(',');
112 } else if ((option
[0] === 'NAMESX') && (!_
.contains(this.irc_connection
.cap
.enabled
, 'multi-prefix'))) {
113 this.irc_connection
.write('PROTOCTL NAMESX');
117 this.client
.sendIrcCommand('options', {server
: this.con_num
, options
: this.irc_connection
.options
, cap
: this.irc_connection
.cap
.enabled
});
119 'RPL_ENDOFWHOIS': function (command
) {
120 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: true});
122 'RPL_WHOISUSER': function (command
) {
123 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], ident
: command
.params
[2], host
: command
.params
[3], msg
: command
.trailing
, end
: false});
125 'RPL_WHOISSERVER': function (command
) {
126 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], irc_server
: command
.params
[2], end
: false});
128 'RPL_WHOISOPERATOR': function (command
) {
129 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: false});
131 'RPL_WHOISCHANNELS': function (command
) {
132 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], chans
: command
.trailing
, end
: false});
134 'RPL_WHOISMODES': function (command
) {
135 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: false});
137 'RPL_WHOISIDLE': function (command
) {
138 if (command
.params
[3]) {
139 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], idle
: command
.params
[2], logon
: command
.params
[3], end
: false});
141 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], idle
: command
.params
[2], end
: false});
144 'RPL_WHOISREGNICK': function (command
) {
145 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: false});
147 'RPL_LISTSTART': function (command
) {
148 this.client
.sendIrcCommand('list_start', {server
: this.con_num
});
149 this.client
.buffer
.list
= [];
151 'RPL_LISTEND': function (command
) {
152 if (this.client
.buffer
.list
.length
> 0) {
153 this.client
.buffer
.list
= _
.sortBy(this.client
.buffer
.list
, function (channel
) {
154 return channel
.num_users
;
156 this.client
.sendIrcCommand('list_channel', {server
: this.con_num
, chans
: this.client
.buffer
.list
});
157 this.client
.buffer
.list
= [];
159 this.client
.sendIrcCommand('list_end', {server
: this.con_num
});
161 'RPL_LIST': function (command
) {
162 this.client
.buffer
.list
.push({server
: this.con_num
, channel
: command
.params
[1], num_users
: parseInt(command
.params
[2], 10), topic
: command
.trailing
});
163 if (this.client
.buffer
.list
.length
> 200){
164 this.client
.buffer
.list
= _
.sortBy(this.client
.buffer
.list
, function (channel
) {
165 return channel
.num_users
;
167 this.client
.sendIrcCommand('list_channel', {server
: this.con_num
, chans
: this.client
.buffer
.list
});
168 this.client
.buffer
.list
= [];
171 'RPL_MOTD': function (command
) {
172 this.client
.buffer
.motd
+= command
.trailing
+ '\n';
174 'RPL_MOTDSTART': function (command
) {
175 this.client
.buffer
.motd
= '';
177 'RPL_ENDOFMOTD': function (command
) {
178 this.client
.sendIrcCommand('motd', {server
: this.con_num
, msg
: this.client
.buffer
.motd
});
180 'RPL_NAMEREPLY': function (command
) {
181 var members
= command
.trailing
.split(' ');
182 var member_list
= [];
185 _
.each(members
, function (member
) {
186 var j
, k
, modes
= [];
188 // Make sure we have some prefixes already
189 if (that
.irc_connection
.options
.PREFIX
) {
190 for (j
= 0; j
< member
.length
; j
++) {
191 for (k
= 0; k
< that
.irc_connection
.options
.PREFIX
.length
; k
++) {
192 if (member
.charAt(j
) === that
.irc_connection
.options
.PREFIX
[k
].symbol
) {
193 modes
.push(that
.irc_connection
.options
.PREFIX
[k
].mode
);
200 member_list
.push({nick
: member
, modes
: modes
});
203 that
.irc_connection
.emit('channel:' + command
.params
[2] + ':userlist', {
205 channel
: command
.params
[2]
210 'RPL_ENDOFNAMES': function (command
) {
211 that
.irc_connection
.emit('channel:' + command
.params
[1] + ':userlist_end', {
212 channel
: command
.params
[1]
217 'RPL_BANLIST': function (command
) {
218 this.client
.sendIrcCommand('banlist', {server
: this.con_num
, channel
: command
.params
[1], banned
: command
.params
[2], banned_by
: command
.params
[3], banned_at
: command
.params
[4]});
220 'RPL_ENDOFBANLIST': function (command
) {
221 this.client
.sendIrcCommand('banlist_end', {server
: this.con_num
, channel
: command
.params
[1]});
223 'RPL_TOPIC': function (command
) {
224 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: '', channel
: command
.params
[1], topic
: command
.trailing
});
226 'RPL_NOTOPIC': function (command
) {
227 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: '', channel
: command
.params
[1], topic
: ''});
229 'RPL_TOPICWHOTIME': function (command
) {
230 this.client
.sendIrcCommand('topicsetby', {server
: this.con_num
, nick
: command
.params
[2], channel
: command
.params
[1], when
: command
.params
[3]});
232 'PING': function (command
) {
233 this.irc_connection
.write('PONG ' + command
.trailing
);
237 'JOIN': function (command
) {
239 if (typeof command
.trailing
=== 'string' && command
.trailing
!== '') {
240 channel
= command
.trailing
;
241 } else if (typeof command
.params
[0] === 'string' && command
.params
[0] !== '') {
242 channel
= command
.params
[0];
245 this.irc_connection
.emit('channel:' + channel
+ ':join', {
247 ident
: command
.ident
,
248 hostname
: command
.hostname
,
254 'PART': function (command
) {
255 this.irc_connection
.emit('channel:' + command
.params
[0] + ':part', {
257 ident
: command
.ident
,
258 hostname
: command
.hostname
,
259 channel
: command
.params
[0],
260 message
: command
.trailing
265 'KICK': function (command
) {
266 this.irc_connection
.emit('channel:' + command
.params
[0] + ':kick', {
267 kicked
: command
.params
[1],
269 ident
: command
.ident
,
270 hostname
: command
.hostname
,
271 channel
: command
.params
[0],
272 message
: command
.trailing
277 'QUIT': function (command
) {
278 this.irc_connection
.emit('user:' + command
.nick
+ ':quit', {
280 ident
: command
.ident
,
281 hostname
: command
.hostname
,
282 message
: command
.trailing
287 'NOTICE': function (command
) {
290 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
291 // It's a CTCP response
292 namespace = (command
.params
[0] == this.irc_connection
.nick
) ? 'user' : 'channel';
293 this.irc_connection
.emit(namespace + ':' + command
.params
[0] + ':ctcp_response', {
295 ident
: command
.ident
,
296 hostname
: command
.hostname
,
297 channel
: command
.params
[0],
298 msg
: command
.trailing
.substr(1, command
.trailing
.length
- 2)
301 namespace = (command
.params
[0] == this.irc_connection
.nick
) ? 'user' : 'channel';
302 this.irc_connection
.emit(namespace + ':' + command
.params
[0] + ':notice', {
304 ident
: command
.ident
,
305 hostname
: command
.hostname
,
306 target
: command
.params
[0],
307 msg
: command
.trailing
311 'NICK': function (command
) {
312 this.client
.sendIrcCommand('nick', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, newnick
: command
.trailing
|| command
.params
[0]});
314 'TOPIC': function (command
) {
315 // If we don't have an associated channel, no need to continue
316 if (!command
.params
[0]) return;
318 var channel
= command
.params
[0],
319 topic
= command
.trailing
|| '';
321 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: command
.nick
, channel
: channel
, topic
: topic
});
323 'MODE': function (command
) {
324 var chanmodes
= this.irc_connection
.options
.CHANMODES
|| [],
325 prefixes
= this.irc_connection
.options
.PREFIX
|| [],
326 always_param
= (chanmodes
[0] || '').concat((chanmodes
[1] || '')),
328 has_param
, i
, j
, add
;
330 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
331 list
.push(prefix
.mode
);
334 always_param
= always_param
.split('').concat(prefixes
);
336 has_param = function (mode
, add
) {
337 if (_
.find(always_param
, function (m
) {
341 } else if (add
&& _
.find((chanmodes
[2] || '').split(''), function (m
) {
350 if (!command
.params
[1]) {
351 command
.params
[1] = command
.trailing
;
354 for (i
= 0; i
< command
.params
[1].length
; i
++) {
355 switch (command
.params
[1][i
]) {
363 if (has_param(command
.params
[1][i
], add
)) {
364 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: command
.params
[2 + j
]});
367 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: null});
372 this.client
.sendIrcCommand('mode', {
373 server
: this.con_num
,
374 target
: command
.params
[0],
375 nick
: command
.nick
|| command
.prefix
|| '',
379 'PRIVMSG': function (command
) {
381 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
383 if (command
.trailing
.substr(1, 6) === 'ACTION') {
384 this.client
.sendIrcCommand('action', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], msg
: command
.trailing
.substr(7, command
.trailing
.length
- 2)});
385 } else if (command
.trailing
.substr(1, 4) === 'KIWI') {
386 tmp
= command
.trailing
.substr(6, command
.trailing
.length
- 2);
387 namespace = tmp
.split(' ', 1)[0];
388 this.client
.sendIrcCommand('kiwi', {server
: this.con_num
, namespace: namespace, data
: tmp
.substr(namespace.length
+ 1)});
389 } else if (command
.trailing
.substr(1, 7) === 'VERSION') {
390 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1));
391 } else if (command
.trailing
.substr(1, 6) === 'SOURCE') {
392 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String
.fromCharCode(1));
393 } else if (command
.trailing
.substr(1, 10) === 'CLIENTINFO') {
394 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String
.fromCharCode(1));
396 namespace = (command
.target
== this.irc_connection
.nick
) ? 'user' : 'channel';
397 this.irc_connection
.emit(namespace + ':' + command
.nick
+ ':ctcp_request', {
399 ident
: command
.ident
,
400 hostname
: command
.hostname
,
401 target
: command
.params
[0],
402 type
: (command
.trailing
.substr(1, command
.trailing
.length
- 2).split(' ') || [null])[0],
403 msg
: command
.trailing
.substr(1, command
.trailing
.length
- 2)
407 // A message to a user (private message) or to a channel?
408 namespace = (command
.target
== this.irc_connection
.nick
) ? 'user' : 'channel';
409 this.irc_connection
.emit(namespace + ':' + command
.nick
+ ':privmsg', {
411 ident
: command
.ident
,
412 hostname
: command
.hostname
,
413 channel
: command
.params
[0],
414 msg
: command
.trailing
418 'CAP': function (command
) {
419 // TODO: capability modifiers
420 // i.e. - for disable, ~ for requires ACK, = for sticky
421 var capabilities
= command
.trailing
.replace(/[\-~=]/, '').split(' ');
423 var want
= ['multi-prefix', 'away-notify'];
425 if (this.irc_connection
.password
) {
429 switch (command
.params
[1]) {
431 request
= _
.intersection(capabilities
, want
);
432 if (request
.length
> 0) {
433 this.irc_connection
.cap
.requested
= request
;
434 this.irc_connection
.write('CAP REQ :' + request
.join(' '));
436 this.irc_connection
.write('CAP END');
437 this.irc_connection
.cap_negotation
= false;
441 if (capabilities
.length
> 0) {
442 this.irc_connection
.cap
.enabled
= capabilities
;
443 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
445 if (this.irc_connection
.cap
.requested
.length
> 0) {
446 if (_
.contains(this.irc_connection
.cap
.enabled
, 'sasl')) {
447 this.irc_connection
.sasl
= true;
448 this.irc_connection
.write('AUTHENTICATE PLAIN');
450 this.irc_connection
.write('CAP END');
451 this.irc_connection
.cap_negotation
= false;
456 if (capabilities
.length
> 0) {
457 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
459 if (this.irc_connection
.cap
.requested
.length
> 0) {
460 this.irc_connection
.write('CAP END');
461 this.irc_connection
.cap_negotation
= false;
465 // should we do anything here?
469 'AUTHENTICATE': function (command
) {
470 var b
= new Buffer(this.irc_connection
.nick
+ "\0" + this.irc_connection
.nick
+ "\0" + this.irc_connection
.password
, 'utf8');
471 var b64
= b
.toString('base64');
472 if (command
.params
[0] === '+') {
473 while (b64
.length
>= 400) {
474 this.irc_connection
.write('AUTHENTICATE ' + b64
.slice(0, 399));
475 b64
= b64
.slice(399);
477 if (b64
.length
> 0) {
478 this.irc_connection
.write('AUTHENTICATE ' + b64
);
480 this.irc_connection
.write('AUTHENTICATE +');
483 this.irc_connection
.write('CAP END');
484 this.irc_connection
.cap_negotation
= false;
487 'AWAY': function (command
) {
488 this.client
.sendIrcCommand('away', {server
: this.con_num
, nick
: command
.nick
, msg
: command
.trailing
});
490 'RPL_SASLAUTHENTICATED': function (command
) {
491 this.irc_connection
.write('CAP END');
492 this.irc_connection
.cap_negotation
= false;
493 this.irc_connection
.sasl
= true;
495 'RPL_SASLLOGGEDIN': function (command
) {
496 if (this.irc_connection
.cap_negotation
=== false) {
497 this.irc_connection
.write('CAP END');
500 'ERR_SASLNOTAUTHORISED': function (command
) {
501 this.irc_connection
.write('CAP END');
502 this.irc_connection
.cap_negotation
= false;
504 'ERR_SASLABORTED': function (command
) {
505 this.irc_connection
.write('CAP END');
506 this.irc_connection
.cap_negotation
= false;
508 'ERR_SASLALREADYAUTHED': function (command
) {
511 'ERROR': function (command
) {
512 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'error', reason
: command
.trailing
});
514 ERR_LINKCHANNEL: function (command
) {
515 this.client
.sendIrcCommand('channel_redirect', {server
: this.con_num
, from: command
.params
[1], to
: command
.params
[2]});
517 ERR_NOSUCHNICK: function (command
) {
518 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'no_such_nick', nick
: command
.params
[1], reason
: command
.trailing
});
520 ERR_CANNOTSENDTOCHAN: function (command
) {
521 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'cannot_send_to_chan', channel
: command
.params
[1], reason
: command
.trailing
});
523 ERR_TOOMANYCHANNELS: function (command
) {
524 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'too_many_channels', channel
: command
.params
[1], reason
: command
.trailing
});
526 ERR_USERNOTINCHANNEL: function (command
) {
527 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'user_not_in_channel', nick
: command
.params
[0], channel
: command
.params
[1], reason
: command
.trailing
});
529 ERR_NOTONCHANNEL: function (command
) {
530 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'not_on_channel', channel
: command
.params
[1], reason
: command
.trailing
});
532 ERR_CHANNELISFULL: function (command
) {
533 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'channel_is_full', channel
: command
.params
[1], reason
: command
.trailing
});
535 ERR_INVITEONLYCHAN: function (command
) {
536 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'invite_only_channel', channel
: command
.params
[1], reason
: command
.trailing
});
538 ERR_BANNEDFROMCHAN: function (command
) {
539 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'banned_from_channel', channel
: command
.params
[1], reason
: command
.trailing
});
541 ERR_BADCHANNELKEY: function (command
) {
542 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'bad_channel_key', channel
: command
.params
[1], reason
: command
.trailing
});
544 ERR_CHANOPRIVSNEEDED: function (command
) {
545 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'chanop_privs_needed', channel
: command
.params
[1], reason
: command
.trailing
});
547 ERR_NICKNAMEINUSE: function (command
) {
548 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'nickname_in_use', nick
: command
.params
[1], reason
: command
.trailing
});
550 ERR_NOTREGISTERED: function (command
) {
553 RPL_MAPMORE: function (command
) {
554 var params
= _
.clone(command
.params
);
556 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
558 RPL_MAPEND: function (command
) {
559 var params
= _
.clone(command
.params
);
561 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
564 RPL_LINKS: function (command
) {
565 var params
= _
.clone(command
.params
);
567 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
569 RPL_ENDOFLINKS: function (command
) {
570 var params
= _
.clone(command
.params
);
572 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
575 ERR_UNKNOWNCOMMAND: function (command
) {
576 var params
= _
.clone(command
.params
);
578 genericNotice
.call(this, command
, '`' + params
.join(', ') + '` ' + command
.trailing
);
581 ERR_NOMOTD: function (command
) {
582 var params
= _
.clone(command
.params
);
584 genericNotice
.call(this, command
, command
.trailing
);
587 ERR_NOPRIVILEGES: function (command
) {
588 var params
= _
.clone(command
.params
);
590 genericNotice
.call(this, command
, command
.trailing
);
597 function genericNotice (command
, msg
, is_error
) {
598 // Default to being an error
599 if (typeof is_error
!== 'boolean')
602 this.client
.sendIrcCommand('notice', {
603 server
: this.con_num
,
604 nick
: command
.prefix
,
607 target
: command
.params
[0],
609 numeric
: parseInt(command
.command
, 10)