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
});
202 that
.client
.sendIrcCommand('userlist', {server
: that
.con_num
, users
: member_list
, channel
: command
.params
[2]});
208 this.client
.sendIrcCommand('userlist', {server
: this.con_num
, users
: member_list
, channel
: command
.params
[2]});
211 'RPL_ENDOFNAMES': function (command
) {
212 this.client
.sendIrcCommand('userlist_end', {server
: this.con_num
, channel
: command
.params
[1]});
214 'RPL_BANLIST': function (command
) {
215 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]});
217 'RPL_ENDOFBANLIST': function (command
) {
218 this.client
.sendIrcCommand('banlist_end', {server
: this.con_num
, channel
: command
.params
[1]});
220 'RPL_TOPIC': function (command
) {
221 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: '', channel
: command
.params
[1], topic
: command
.trailing
});
223 'RPL_NOTOPIC': function (command
) {
224 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: '', channel
: command
.params
[1], topic
: ''});
226 'RPL_TOPICWHOTIME': function (command
) {
227 this.client
.sendIrcCommand('topicsetby', {server
: this.con_num
, nick
: command
.params
[2], channel
: command
.params
[1], when
: command
.params
[3]});
229 'PING': function (command
) {
230 this.irc_connection
.write('PONG ' + command
.trailing
);
232 'JOIN': function (command
) {
234 if (typeof command
.trailing
=== 'string' && command
.trailing
!== '') {
235 channel
= command
.trailing
;
236 } else if (typeof command
.params
[0] === 'string' && command
.params
[0] !== '') {
237 channel
= command
.params
[0];
240 this.client
.sendIrcCommand('join', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: channel
});
242 if (command
.nick
=== this.nick
) {
243 this.irc_connection
.write('NAMES ' + channel
);
246 'PART': function (command
) {
247 this.client
.sendIrcCommand('part', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], message
: command
.trailing
});
249 'KICK': function (command
) {
250 this.client
.sendIrcCommand('kick', {server
: this.con_num
, kicked
: command
.params
[1], nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], message
: command
.trailing
});
252 'QUIT': function (command
) {
253 this.client
.sendIrcCommand('quit', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, message
: command
.trailing
});
255 'NOTICE': function (command
) {
256 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
257 // It's a CTCP response
258 this.client
.sendIrcCommand('ctcp_response', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], msg
: command
.trailing
.substr(1, command
.trailing
.length
- 2)});
260 this.client
.sendIrcCommand('notice', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, target
: command
.params
[0], msg
: command
.trailing
});
263 'NICK': function (command
) {
264 this.client
.sendIrcCommand('nick', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, newnick
: command
.trailing
|| command
.params
[0]});
266 'TOPIC': function (command
) {
267 // If we don't have an associated channel, no need to continue
268 if (!command
.params
[0]) return;
270 var channel
= command
.params
[0],
271 topic
= command
.trailing
|| '';
273 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: command
.nick
, channel
: channel
, topic
: topic
});
275 'MODE': function (command
) {
276 var chanmodes
= this.irc_connection
.options
.CHANMODES
|| [],
277 prefixes
= this.irc_connection
.options
.PREFIX
|| [],
278 always_param
= (chanmodes
[0] || '').concat((chanmodes
[1] || '')),
280 has_param
, i
, j
, add
;
282 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
283 list
.push(prefix
.mode
);
286 always_param
= always_param
.split('').concat(prefixes
);
288 has_param = function (mode
, add
) {
289 if (_
.find(always_param
, function (m
) {
293 } else if (add
&& _
.find((chanmodes
[2] || '').split(''), function (m
) {
302 if (!command
.params
[1]) {
303 command
.params
[1] = command
.trailing
;
306 for (i
= 0; i
< command
.params
[1].length
; i
++) {
307 switch (command
.params
[1][i
]) {
315 if (has_param(command
.params
[1][i
], add
)) {
316 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: command
.params
[2 + j
]});
319 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: null});
324 this.client
.sendIrcCommand('mode', {
325 server
: this.con_num
,
326 target
: command
.params
[0],
327 nick
: command
.nick
|| command
.prefix
|| '',
331 'PRIVMSG': function (command
) {
333 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
335 if (command
.trailing
.substr(1, 6) === 'ACTION') {
336 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)});
337 } else if (command
.trailing
.substr(1, 4) === 'KIWI') {
338 tmp
= command
.trailing
.substr(6, command
.trailing
.length
- 2);
339 namespace = tmp
.split(' ', 1)[0];
340 this.client
.sendIrcCommand('kiwi', {server
: this.con_num
, namespace: namespace, data
: tmp
.substr(namespace.length
+ 1)});
341 } else if (command
.trailing
.substr(1, 7) === 'VERSION') {
342 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1));
343 } else if (command
.trailing
.substr(1, 6) === 'SOURCE') {
344 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String
.fromCharCode(1));
345 } else if (command
.trailing
.substr(1, 10) === 'CLIENTINFO') {
346 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String
.fromCharCode(1));
348 this.client
.sendIrcCommand('ctcp_request', {
349 server
: this.con_num
,
351 ident
: command
.ident
,
352 hostname
: command
.hostname
,
353 target
: command
.params
[0],
354 type
: (command
.trailing
.substr(1, command
.trailing
.length
- 2).split(' ') || [null])[0],
355 msg
: command
.trailing
.substr(1, command
.trailing
.length
- 2)
359 //{nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing}
360 this.client
.sendIrcCommand('msg', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], msg
: command
.trailing
});
363 'CAP': function (command
) {
364 // TODO: capability modifiers
365 // i.e. - for disable, ~ for requires ACK, = for sticky
366 var capabilities
= command
.trailing
.replace(/[\-~=]/, '').split(' ');
368 var want
= ['multi-prefix', 'away-notify'];
370 if (this.irc_connection
.password
) {
374 switch (command
.params
[1]) {
376 request
= _
.intersection(capabilities
, want
);
377 if (request
.length
> 0) {
378 this.irc_connection
.cap
.requested
= request
;
379 this.irc_connection
.write('CAP REQ :' + request
.join(' '));
381 this.irc_connection
.write('CAP END');
382 this.irc_connection
.cap_negotation
= false;
386 if (capabilities
.length
> 0) {
387 this.irc_connection
.cap
.enabled
= capabilities
;
388 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
390 if (this.irc_connection
.cap
.requested
.length
> 0) {
391 if (_
.contains(this.irc_connection
.cap
.enabled
, 'sasl')) {
392 this.irc_connection
.sasl
= true;
393 this.irc_connection
.write('AUTHENTICATE PLAIN');
395 this.irc_connection
.write('CAP END');
396 this.irc_connection
.cap_negotation
= false;
401 if (capabilities
.length
> 0) {
402 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
404 if (this.irc_connection
.cap
.requested
.length
> 0) {
405 this.irc_connection
.write('CAP END');
406 this.irc_connection
.cap_negotation
= false;
410 // should we do anything here?
414 'AUTHENTICATE': function (command
) {
415 var b
= new Buffer(this.irc_connection
.nick
+ "\0" + this.irc_connection
.nick
+ "\0" + this.irc_connection
.password
, 'utf8');
416 var b64
= b
.toString('base64');
417 if (command
.params
[0] === '+') {
418 while (b64
.length
>= 400) {
419 this.irc_connection
.write('AUTHENTICATE ' + b64
.slice(0, 399));
420 b64
= b64
.slice(399);
422 if (b64
.length
> 0) {
423 this.irc_connection
.write('AUTHENTICATE ' + b64
);
425 this.irc_connection
.write('AUTHENTICATE +');
428 this.irc_connection
.write('CAP END');
429 this.irc_connection
.cap_negotation
= false;
432 'AWAY': function (command
) {
433 this.client
.sendIrcCommand('away', {server
: this.con_num
, nick
: command
.nick
, msg
: command
.trailing
});
435 'RPL_SASLAUTHENTICATED': function (command
) {
436 this.irc_connection
.write('CAP END');
437 this.irc_connection
.cap_negotation
= false;
438 this.irc_connection
.sasl
= true;
440 'RPL_SASLLOGGEDIN': function (command
) {
441 if (this.irc_connection
.cap_negotation
=== false) {
442 this.irc_connection
.write('CAP END');
445 'ERR_SASLNOTAUTHORISED': function (command
) {
446 this.irc_connection
.write('CAP END');
447 this.irc_connection
.cap_negotation
= false;
449 'ERR_SASLABORTED': function (command
) {
450 this.irc_connection
.write('CAP END');
451 this.irc_connection
.cap_negotation
= false;
453 'ERR_SASLALREADYAUTHED': function (command
) {
456 'ERROR': function (command
) {
457 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'error', reason
: command
.trailing
});
459 ERR_LINKCHANNEL: function (command
) {
460 this.client
.sendIrcCommand('channel_redirect', {server
: this.con_num
, from: command
.params
[1], to
: command
.params
[2]});
462 ERR_NOSUCHNICK: function (command
) {
463 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'no_such_nick', nick
: command
.params
[1], reason
: command
.trailing
});
465 ERR_CANNOTSENDTOCHAN: function (command
) {
466 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'cannot_send_to_chan', channel
: command
.params
[1], reason
: command
.trailing
});
468 ERR_TOOMANYCHANNELS: function (command
) {
469 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'too_many_channels', channel
: command
.params
[1], reason
: command
.trailing
});
471 ERR_USERNOTINCHANNEL: function (command
) {
472 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
});
474 ERR_NOTONCHANNEL: function (command
) {
475 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'not_on_channel', channel
: command
.params
[1], reason
: command
.trailing
});
477 ERR_CHANNELISFULL: function (command
) {
478 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'channel_is_full', channel
: command
.params
[1], reason
: command
.trailing
});
480 ERR_INVITEONLYCHAN: function (command
) {
481 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'invite_only_channel', channel
: command
.params
[1], reason
: command
.trailing
});
483 ERR_BANNEDFROMCHAN: function (command
) {
484 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'banned_from_channel', channel
: command
.params
[1], reason
: command
.trailing
});
486 ERR_BADCHANNELKEY: function (command
) {
487 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'bad_channel_key', channel
: command
.params
[1], reason
: command
.trailing
});
489 ERR_CHANOPRIVSNEEDED: function (command
) {
490 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'chanop_privs_needed', channel
: command
.params
[1], reason
: command
.trailing
});
492 ERR_NICKNAMEINUSE: function (command
) {
493 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'nickname_in_use', nick
: command
.params
[1], reason
: command
.trailing
});
495 ERR_NOTREGISTERED: function (command
) {
498 RPL_MAPMORE: function (command
) {
499 var params
= _
.clone(command
.params
);
501 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
503 RPL_MAPEND: function (command
) {
504 var params
= _
.clone(command
.params
);
506 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
509 RPL_LINKS: function (command
) {
510 var params
= _
.clone(command
.params
);
512 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
514 RPL_ENDOFLINKS: function (command
) {
515 var params
= _
.clone(command
.params
);
517 genericNotice
.call(this, command
, params
.join(', ') + ' ' + command
.trailing
);
520 ERR_UNKNOWNCOMMAND: function (command
) {
521 var params
= _
.clone(command
.params
);
523 genericNotice
.call(this, command
, '`' + params
.join(', ') + '` ' + command
.trailing
);
526 ERR_NOMOTD: function (command
) {
527 var params
= _
.clone(command
.params
);
529 genericNotice
.call(this, command
, command
.trailing
);
532 ERR_NOPRIVILEGES: function (command
) {
533 var params
= _
.clone(command
.params
);
535 genericNotice
.call(this, command
, command
.trailing
);
542 function genericNotice (command
, msg
, is_error
) {
543 // Default to being an error
544 if (typeof is_error
!== 'boolean')
547 this.client
.sendIrcCommand('notice', {
548 server
: this.con_num
,
549 nick
: command
.prefix
,
552 target
: command
.params
[0],
554 numeric
: parseInt(command
.command
, 10)