1 var _
= require('lodash');
7 RPL_WHOISREGNICK
: '307',
9 RPL_WHOISSERVER
: '312',
10 RPL_WHOISOPERATOR
: '313',
12 RPL_ENDOFWHOIS
: '318',
13 RPL_WHOISCHANNELS
: '319',
19 RPL_TOPICWHOTIME
: '333',
21 RPL_ENDOFNAMES
: '366',
23 RPL_ENDOFBANLIST
: '368',
27 RPL_WHOISMODES
: '379',
28 ERR_NOSUCHNICK
: '401',
29 ERR_CANNOTSENDTOCHAN
: '404',
30 ERR_TOOMANYCHANNELS
: '405',
31 ERR_NICKNAMEINUSE
: '433',
32 ERR_USERNOTINCHANNEL
: '441',
33 ERR_NOTONCHANNEL
: '442',
34 ERR_NOTREGISTERED
: '451',
35 ERR_LINKCHANNEL
: '470',
36 ERR_CHANNELISFULL
: '471',
37 ERR_INVITEONLYCHAN
: '473',
38 ERR_BANNEDFROMCHAN
: '474',
39 ERR_BADCHANNELKEY
: '475',
40 ERR_CHANOPRIVSNEEDED
: '482',
42 RPL_SASLAUTHENTICATED
: '900',
43 RPL_SASLLOGGEDIN
: '903',
44 ERR_SASLNOTAUTHORISED
: '904',
45 ERR_SASLABORTED
: '906',
46 ERR_SASLALREADYAUTHED
: '907'
51 var IrcCommands = function (irc_connection
, con_num
, client
) {
52 this.irc_connection
= irc_connection
;
53 this.con_num
= con_num
;
56 module
.exports
= IrcCommands
;
58 IrcCommands
.prototype.bindEvents = function () {
61 _
.each(listeners
, function (listener
, command
) {
62 var s
= command
.substr(0, 4);
63 if ((s
=== 'RPL_') || (s
=== 'ERR_')) {
64 command
= irc_numerics
[command
];
66 that
.irc_connection
.on('irc_' + command
, function () {
67 listener
.apply(that
, arguments
);
72 IrcCommands
.prototype.dispose = function () {
73 this.removeAllListeners();
79 'RPL_WELCOME': function (command
) {
80 var nick
= command
.params
[0];
81 this.irc_connection
.registered
= true;
82 this.cap_negotation
= false;
83 this.client
.sendIrcCommand('connect', {server
: this.con_num
, nick
: nick
});
85 'RPL_ISUPPORT': function (command
) {
86 var options
, i
, option
, matches
, j
;
87 options
= command
.params
;
88 for (i
= 1; i
< options
.length
; i
++) {
89 option
= options
[i
].split("=", 2);
90 option
[0] = option
[0].toUpperCase();
91 this.irc_connection
.options
[option
[0]] = (typeof option
[1] !== 'undefined') ? option
[1] : true;
92 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'CHANMODES', 'NAMESX'], option
[0])) {
93 if (option
[0] === 'PREFIX') {
94 matches
= /\(([^)]*)\)(.*)/.exec(option
[1]);
95 if ((matches
) && (matches
.length
=== 3)) {
96 this.irc_connection
.options
.PREFIX
= [];
97 for (j
= 0; j
< matches
[2].length
; j
++) {
98 this.irc_connection
.options
.PREFIX
.push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
101 } else if (option
[0] === 'CHANTYPES') {
102 this.irc_connection
.options
.CHANTYPES
= this.irc_connection
.options
.CHANTYPES
.split('');
103 } else if (option
[0] === 'CHANMODES') {
104 this.irc_connection
.options
.CHANMODES
= option
[1].split(',');
105 } else if ((option
[0] === 'NAMESX') && (!_
.contains(this.irc_connection
.cap
.enabled
, 'multi-prefix'))) {
106 this.irc_connection
.write('PROTOCTL NAMESX');
110 this.client
.sendIrcCommand('options', {server
: this.con_num
, options
: this.irc_connection
.options
, cap
: this.irc_connection
.cap
.enabled
});
112 'RPL_ENDOFWHOIS': function (command
) {
113 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: true});
115 'RPL_WHOISUSER': function (command
) {
116 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});
118 'RPL_WHOISSERVER': function (command
) {
119 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], irc_server
: command
.params
[2], end
: false});
121 'RPL_WHOISOPERATOR': function (command
) {
122 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: false});
124 'RPL_WHOISCHANNELS': function (command
) {
125 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], chans
: command
.trailing
, end
: false});
127 'RPL_WHOISMODES': function (command
) {
128 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: false});
130 'RPL_WHOISIDLE': function (command
) {
131 if (command
.params
[3]) {
132 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], idle
: command
.params
[2], logon
: command
.params
[3], end
: false});
134 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], idle
: command
.params
[2], end
: false});
137 'RPL_WHOISREGNICK': function (command
) {
138 this.client
.sendIrcCommand('whois', {server
: this.con_num
, nick
: command
.params
[1], msg
: command
.trailing
, end
: false});
140 'RPL_LISTSTART': function (command
) {
141 this.client
.sendIrcCommand('list_start', {server
: this.con_num
});
142 this.client
.buffer
.list
= [];
144 'RPL_LISTEND': function (command
) {
145 if (this.client
.buffer
.list
.length
> 0) {
146 this.client
.buffer
.list
= _
.sortBy(this.client
.buffer
.list
, function (channel
) {
147 return channel
.num_users
;
149 this.client
.sendIrcCommand('list_channel', {server
: this.con_num
, chans
: this.client
.buffer
.list
});
150 this.client
.buffer
.list
= [];
152 this.client
.sendIrcCommand('list_end', {server
: this.con_num
});
154 'RPL_LIST': function (command
) {
155 this.client
.buffer
.list
.push({server
: this.con_num
, channel
: command
.params
[1], num_users
: parseInt(command
.params
[2], 10), topic
: command
.trailing
});
156 if (this.client
.buffer
.list
.length
> 200){
157 this.client
.buffer
.list
= _
.sortBy(this.client
.buffer
.list
, function (channel
) {
158 return channel
.num_users
;
160 this.client
.sendIrcCommand('list_channel', {server
: this.con_num
, chans
: this.client
.buffer
.list
});
161 this.client
.buffer
.list
= [];
164 'RPL_MOTD': function (command
) {
165 this.client
.buffer
.motd
+= command
.trailing
+ '\n';
167 'RPL_MOTDSTART': function (command
) {
168 this.client
.buffer
.motd
= '';
170 'RPL_ENDOFMOTD': function (command
) {
171 this.client
.sendIrcCommand('motd', {server
: this.con_num
, msg
: this.client
.buffer
.motd
});
173 'RPL_NAMEREPLY': function (command
) {
174 var members
= command
.trailing
.split(' ');
175 var member_list
= [];
178 _
.each(members
, function (member
) {
179 var j
, k
, modes
= [];
181 // Make sure we have some prefixes already
182 if (that
.irc_connection
.options
.PREFIX
) {
183 for (j
= 0; j
< member
.length
; j
++) {
184 for (k
= 0; k
< that
.irc_connection
.options
.PREFIX
.length
; k
++) {
185 if (member
.charAt(j
) === that
.irc_connection
.options
.PREFIX
[k
].symbol
) {
186 modes
.push(that
.irc_connection
.options
.PREFIX
[k
].mode
);
193 member_list
.push({nick
: member
, modes
: modes
});
195 that
.client
.sendIrcCommand('userlist', {server
: that
.con_num
, users
: member_list
, channel
: command
.params
[2]});
201 this.client
.sendIrcCommand('userlist', {server
: this.con_num
, users
: member_list
, channel
: command
.params
[2]});
204 'RPL_ENDOFNAMES': function (command
) {
205 this.client
.sendIrcCommand('userlist_end', {server
: this.con_num
, channel
: command
.params
[1]});
207 'RPL_BANLIST': function (command
) {
208 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]});
210 'RPL_ENDOFBANLIST': function (command
) {
211 this.client
.sendIrcCommand('banlist_end', {server
: this.con_num
, channel
: command
.params
[1]});
213 'RPL_TOPIC': function (command
) {
214 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: '', channel
: command
.params
[1], topic
: command
.trailing
});
216 'RPL_NOTOPIC': function (command
) {
217 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: '', channel
: command
.params
[1], topic
: ''});
219 'RPL_TOPICWHOTIME': function (command
) {
220 this.client
.sendIrcCommand('topicsetby', {server
: this.con_num
, nick
: command
.params
[2], channel
: command
.params
[1], when
: command
.params
[3]});
222 'PING': function (command
) {
223 this.irc_connection
.write('PONG ' + command
.trailing
);
225 'JOIN': function (command
) {
227 if (typeof command
.trailing
=== 'string' && command
.trailing
!== '') {
228 channel
= command
.trailing
;
229 } else if (typeof command
.params
[0] === 'string' && command
.params
[0] !== '') {
230 channel
= command
.params
[0];
233 this.client
.sendIrcCommand('join', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: channel
});
235 if (command
.nick
=== this.nick
) {
236 this.irc_connection
.write('NAMES ' + channel
);
239 'PART': function (command
) {
240 this.client
.sendIrcCommand('part', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], message
: command
.trailing
});
242 'KICK': function (command
) {
243 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
});
245 'QUIT': function (command
) {
246 this.client
.sendIrcCommand('quit', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, message
: command
.trailing
});
248 'NOTICE': function (command
) {
249 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
250 // It's a CTCP response
251 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)});
253 this.client
.sendIrcCommand('notice', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, target
: command
.params
[0], msg
: command
.trailing
});
256 'NICK': function (command
) {
257 this.client
.sendIrcCommand('nick', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, newnick
: command
.trailing
|| command
.params
[0]});
259 'TOPIC': function (command
) {
260 // If we don't have an associated channel, no need to continue
261 if (!command
.params
[0]) return;
263 var channel
= command
.params
[0],
264 topic
= command
.trailing
|| '';
266 this.client
.sendIrcCommand('topic', {server
: this.con_num
, nick
: command
.nick
, channel
: channel
, topic
: topic
});
268 'MODE': function (command
) {
269 var chanmodes
= this.irc_connection
.options
.CHANMODES
|| [],
270 prefixes
= this.irc_connection
.options
.PREFIX
|| [],
271 always_param
= (chanmodes
[0] || '').concat((chanmodes
[1] || '')),
273 has_param
, i
, j
, add
;
275 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
276 list
.push(prefix
.mode
);
279 always_param
= always_param
.split('').concat(prefixes
);
281 has_param = function (mode
, add
) {
282 if (_
.find(always_param
, function (m
) {
286 } else if (add
&& _
.find((chanmodes
[2] || '').split(''), function (m
) {
295 if (!command
.params
[1]) {
296 command
.params
[1] = command
.trailing
;
299 for (i
= 0; i
< command
.params
[1].length
; i
++) {
300 switch (command
.params
[1][i
]) {
308 if (has_param(command
.params
[1][i
], add
)) {
309 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: command
.params
[2 + j
]});
312 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: null});
317 this.client
.sendIrcCommand('mode', {
318 server
: this.con_num
,
319 target
: command
.params
[0],
320 nick
: command
.nick
|| command
.prefix
|| '',
324 'PRIVMSG': function (command
) {
326 if ((command
.trailing
.charAt(0) === String
.fromCharCode(1)) && (command
.trailing
.charAt(command
.trailing
.length
- 1) === String
.fromCharCode(1))) {
328 if (command
.trailing
.substr(1, 6) === 'ACTION') {
329 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)});
330 } else if (command
.trailing
.substr(1, 4) === 'KIWI') {
331 tmp
= command
.trailing
.substr(6, command
.trailing
.length
- 2);
332 namespace = tmp
.split(' ', 1)[0];
333 this.client
.sendIrcCommand('kiwi', {server
: this.con_num
, namespace: namespace, data
: tmp
.substr(namespace.length
+ 1)});
334 } else if (command
.trailing
.substr(1, 7) === 'VERSION') {
335 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1));
336 } else if (command
.trailing
.substr(1, 6) === 'SOURCE') {
337 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'SOURCE http://www.kiwiirc.com/' + String
.fromCharCode(1));
338 } else if (command
.trailing
.substr(1, 10) === 'CLIENTINFO') {
339 this.irc_connection
.write('NOTICE ' + command
.nick
+ ' :' + String
.fromCharCode(1) + 'CLIENTINFO SOURCE VERSION TIME' + String
.fromCharCode(1));
341 this.client
.sendIrcCommand('ctcp_request', {
342 server
: this.con_num
,
344 ident
: command
.ident
,
345 hostname
: command
.hostname
,
346 target
: command
.params
[0],
347 type
: (command
.trailing
.substr(1, command
.trailing
.length
- 2).split(' ') || [null])[0],
348 msg
: command
.trailing
.substr(1, command
.trailing
.length
- 2)
352 //{nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing}
353 this.client
.sendIrcCommand('msg', {server
: this.con_num
, nick
: command
.nick
, ident
: command
.ident
, hostname
: command
.hostname
, channel
: command
.params
[0], msg
: command
.trailing
});
356 'CAP': function (command
) {
357 // TODO: capability modifiers
358 // i.e. - for disable, ~ for requires ACK, = for sticky
359 var capabilities
= command
.trailing
.replace(/[\-~=]/, '').split(' ');
361 var want
= ['multi-prefix', 'away-notify'];
363 if (this.irc_connection
.password
) {
367 switch (command
.params
[1]) {
369 request
= _
.intersection(capabilities
, want
);
370 if (request
.length
> 0) {
371 this.irc_connection
.cap
.requested
= request
;
372 this.irc_connection
.write('CAP REQ :' + request
.join(' '));
374 this.irc_connection
.write('CAP END');
375 this.irc_connection
.cap_negotation
= false;
379 if (capabilities
.length
> 0) {
380 this.irc_connection
.cap
.enabled
= capabilities
;
381 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
383 if (this.irc_connection
.cap
.requested
.length
> 0) {
384 if (_
.contains(this.irc_connection
.cap
.enabled
, 'sasl')) {
385 this.irc_connection
.sasl
= true;
386 this.irc_connection
.write('AUTHENTICATE PLAIN');
388 this.irc_connection
.write('CAP END');
389 this.irc_connection
.cap_negotation
= false;
394 if (capabilities
.length
> 0) {
395 this.irc_connection
.cap
.requested
= _
.difference(this.irc_connection
.cap
.requested
, capabilities
);
397 if (this.irc_connection
.cap
.requested
.length
> 0) {
398 this.irc_connection
.write('CAP END');
399 this.irc_connection
.cap_negotation
= false;
403 // should we do anything here?
407 'AUTHENTICATE': function (command
) {
408 var b
= new Buffer(this.irc_connection
.nick
+ "\0" + this.irc_connection
.nick
+ "\0" + this.irc_connection
.password
, 'utf8');
409 var b64
= b
.toString('base64');
410 if (command
.params
[0] === '+') {
411 while (b64
.length
>= 400) {
412 this.irc_connection
.write('AUTHENTICATE ' + b64
.slice(0, 399));
413 b64
= b64
.slice(399);
415 if (b64
.length
> 0) {
416 this.irc_connection
.write('AUTHENTICATE ' + b64
);
418 this.irc_connection
.write('AUTHENTICATE +');
421 this.irc_connection
.write('CAP END');
422 this.irc_connection
.cap_negotation
= false;
425 'AWAY': function (command
) {
426 this.client
.sendIrcCommand('away', {server
: this.con_num
, nick
: command
.nick
, msg
: command
.trailing
});
428 'RPL_SASLAUTHENTICATED': function (command
) {
429 this.irc_connection
.write('CAP END');
430 this.irc_connection
.cap_negotation
= false;
431 this.irc_connection
.sasl
= true;
433 'RPL_SASLLOGGEDIN': function (command
) {
434 if (this.irc_connection
.cap_negotation
=== false) {
435 this.irc_connection
.write('CAP END');
438 'ERR_SASLNOTAUTHORISED': function (command
) {
439 this.irc_connection
.write('CAP END');
440 this.irc_connection
.cap_negotation
= false;
442 'ERR_SASLABORTED': function (command
) {
443 this.irc_connection
.write('CAP END');
444 this.irc_connection
.cap_negotation
= false;
446 'ERR_SASLALREADYAUTHED': function (command
) {
449 'ERROR': function (command
) {
450 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'error', reason
: command
.trailing
});
452 ERR_LINKCHANNEL: function (command
) {
453 this.client
.sendIrcCommand('channel_redirect', {server
: this.con_num
, from: command
.params
[1], to
: command
.params
[2]});
455 ERR_NOSUCHNICK: function (command
) {
456 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'no_such_nick', nick
: command
.params
[1], reason
: command
.trailing
});
458 ERR_CANNOTSENDTOCHAN: function (command
) {
459 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'cannot_send_to_chan', channel
: command
.params
[1], reason
: command
.trailing
});
461 ERR_TOOMANYCHANNELS: function (command
) {
462 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'too_many_channels', channel
: command
.params
[1], reason
: command
.trailing
});
464 ERR_USERNOTINCHANNEL: function (command
) {
465 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
});
467 ERR_NOTONCHANNEL: function (command
) {
468 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'not_on_channel', channel
: command
.params
[1], reason
: command
.trailing
});
470 ERR_CHANNELISFULL: function (command
) {
471 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'channel_is_full', channel
: command
.params
[1], reason
: command
.trailing
});
473 ERR_INVITEONLYCHAN: function (command
) {
474 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'invite_only_channel', channel
: command
.params
[1], reason
: command
.trailing
});
476 ERR_BANNEDFROMCHAN: function (command
) {
477 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'banned_from_channel', channel
: command
.params
[1], reason
: command
.trailing
});
479 ERR_BADCHANNELKEY: function (command
) {
480 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'bad_channel_key', channel
: command
.params
[1], reason
: command
.trailing
});
482 ERR_CHANOPRIVSNEEDED: function (command
) {
483 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'chanop_privs_needed', channel
: command
.params
[1], reason
: command
.trailing
});
485 ERR_NICKNAMEINUSE: function (command
) {
486 this.client
.sendIrcCommand('irc_error', {server
: this.con_num
, error
: 'nickname_in_use', nick
: command
.params
[1], reason
: command
.trailing
});
488 ERR_NOTREGISTERED: function (command
) {