map/links/permission denied/unknown command errors shown
[KiwiIRC.git] / server / irc / commands.js
1 var _ = require('lodash');
2
3 var irc_numerics = {
4 RPL_WELCOME: '001',
5 RPL_MYINFO: '004',
6 RPL_ISUPPORT: '005',
7 RPL_MAPMORE: '006',
8 RPL_MAPEND: '007',
9 RPL_WHOISREGNICK: '307',
10 RPL_WHOISUSER: '311',
11 RPL_WHOISSERVER: '312',
12 RPL_WHOISOPERATOR: '313',
13 RPL_WHOISIDLE: '317',
14 RPL_ENDOFWHOIS: '318',
15 RPL_WHOISCHANNELS: '319',
16 RPL_LISTSTART: '321',
17 RPL_LIST: '322',
18 RPL_LISTEND: '323',
19 RPL_NOTOPIC: '331',
20 RPL_TOPIC: '332',
21 RPL_TOPICWHOTIME: '333',
22 RPL_NAMEREPLY: '353',
23 RPL_LINKS: '364',
24 RPL_ENDOFLINKS: '365',
25 RPL_ENDOFNAMES: '366',
26 RPL_BANLIST: '367',
27 RPL_ENDOFBANLIST: '368',
28 RPL_MOTD: '372',
29 RPL_MOTDSTART: '375',
30 RPL_ENDOFMOTD: '376',
31 RPL_WHOISMODES: '379',
32 ERR_NOSUCHNICK: '401',
33 ERR_CANNOTSENDTOCHAN: '404',
34 ERR_TOOMANYCHANNELS: '405',
35 ERR_UNKNOWNCOMMAND: '421',
36 ERR_NOMOTD: '422',
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',
48 RPL_STARTTLS: '670',
49 RPL_SASLAUTHENTICATED: '900',
50 RPL_SASLLOGGEDIN: '903',
51 ERR_SASLNOTAUTHORISED: '904',
52 ERR_SASLABORTED: '906',
53 ERR_SASLALREADYAUTHED: '907'
54
55 };
56
57
58 var IrcCommands = function (irc_connection, con_num, client) {
59 this.irc_connection = irc_connection;
60 this.con_num = con_num;
61 this.client = client;
62 };
63 module.exports = IrcCommands;
64
65 IrcCommands.prototype.bindEvents = function () {
66 var that = this;
67
68 _.each(listeners, function (listener, command) {
69 var s = command.substr(0, 4);
70 if ((s === 'RPL_') || (s === 'ERR_')) {
71 command = irc_numerics[command];
72 }
73 that.irc_connection.on('irc_' + command, function () {
74 listener.apply(that, arguments);
75 });
76 });
77 };
78
79 IrcCommands.prototype.dispose = function () {
80 this.removeAllListeners();
81 };
82
83
84
85 var listeners = {
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});
91 },
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)});
106 }
107 }
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');
114 }
115 }
116 }
117 this.client.sendIrcCommand('options', {server: this.con_num, options: this.irc_connection.options, cap: this.irc_connection.cap.enabled});
118 },
119 'RPL_ENDOFWHOIS': function (command) {
120 this.client.sendIrcCommand('whois', {server: this.con_num, nick: command.params[1], msg: command.trailing, end: true});
121 },
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});
124 },
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});
127 },
128 'RPL_WHOISOPERATOR': function (command) {
129 this.client.sendIrcCommand('whois', {server: this.con_num, nick: command.params[1], msg: command.trailing, end: false});
130 },
131 'RPL_WHOISCHANNELS': function (command) {
132 this.client.sendIrcCommand('whois', {server: this.con_num, nick: command.params[1], chans: command.trailing, end: false});
133 },
134 'RPL_WHOISMODES': function (command) {
135 this.client.sendIrcCommand('whois', {server: this.con_num, nick: command.params[1], msg: command.trailing, end: false});
136 },
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});
140 } else {
141 this.client.sendIrcCommand('whois', {server: this.con_num, nick: command.params[1], idle: command.params[2], end: false});
142 }
143 },
144 'RPL_WHOISREGNICK': function (command) {
145 this.client.sendIrcCommand('whois', {server: this.con_num, nick: command.params[1], msg: command.trailing, end: false});
146 },
147 'RPL_LISTSTART': function (command) {
148 this.client.sendIrcCommand('list_start', {server: this.con_num});
149 this.client.buffer.list = [];
150 },
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;
155 });
156 this.client.sendIrcCommand('list_channel', {server: this.con_num, chans: this.client.buffer.list});
157 this.client.buffer.list = [];
158 }
159 this.client.sendIrcCommand('list_end', {server: this.con_num});
160 },
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;
166 });
167 this.client.sendIrcCommand('list_channel', {server: this.con_num, chans: this.client.buffer.list});
168 this.client.buffer.list = [];
169 }
170 },
171 'RPL_MOTD': function (command) {
172 this.client.buffer.motd += command.trailing + '\n';
173 },
174 'RPL_MOTDSTART': function (command) {
175 this.client.buffer.motd = '';
176 },
177 'RPL_ENDOFMOTD': function (command) {
178 this.client.sendIrcCommand('motd', {server: this.con_num, msg: this.client.buffer.motd});
179 },
180 'RPL_NAMEREPLY': function (command) {
181 var members = command.trailing.split(' ');
182 var member_list = [];
183 var that = this;
184 var i = 0;
185 _.each(members, function (member) {
186 var j, k, modes = [];
187
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);
194 i++;
195 }
196 }
197 }
198 }
199
200 member_list.push({nick: member, modes: modes});
201 if (i++ >= 50) {
202 that.client.sendIrcCommand('userlist', {server: that.con_num, users: member_list, channel: command.params[2]});
203 member_list = [];
204 i = 0;
205 }
206 });
207 if (i > 0) {
208 this.client.sendIrcCommand('userlist', {server: this.con_num, users: member_list, channel: command.params[2]});
209 }
210 },
211 'RPL_ENDOFNAMES': function (command) {
212 this.client.sendIrcCommand('userlist_end', {server: this.con_num, channel: command.params[1]});
213 },
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]});
216 },
217 'RPL_ENDOFBANLIST': function (command) {
218 this.client.sendIrcCommand('banlist_end', {server: this.con_num, channel: command.params[1]});
219 },
220 'RPL_TOPIC': function (command) {
221 this.client.sendIrcCommand('topic', {server: this.con_num, nick: '', channel: command.params[1], topic: command.trailing});
222 },
223 'RPL_NOTOPIC': function (command) {
224 this.client.sendIrcCommand('topic', {server: this.con_num, nick: '', channel: command.params[1], topic: ''});
225 },
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]});
228 },
229 'PING': function (command) {
230 this.irc_connection.write('PONG ' + command.trailing);
231 },
232 'JOIN': function (command) {
233 var channel;
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];
238 }
239
240 this.client.sendIrcCommand('join', {server: this.con_num, nick: command.nick, ident: command.ident, hostname: command.hostname, channel: channel});
241
242 if (command.nick === this.nick) {
243 this.irc_connection.write('NAMES ' + channel);
244 }
245 },
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});
248 },
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});
251 },
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});
254 },
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)});
259 } else {
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});
261 }
262 },
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]});
265 },
266 'TOPIC': function (command) {
267 // If we don't have an associated channel, no need to continue
268 if (!command.params[0]) return;
269
270 var channel = command.params[0],
271 topic = command.trailing || '';
272
273 this.client.sendIrcCommand('topic', {server: this.con_num, nick: command.nick, channel: channel, topic: topic});
274 },
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] || '')),
279 modes = [],
280 has_param, i, j, add;
281
282 prefixes = _.reduce(prefixes, function (list, prefix) {
283 list.push(prefix.mode);
284 return list;
285 }, []);
286 always_param = always_param.split('').concat(prefixes);
287
288 has_param = function (mode, add) {
289 if (_.find(always_param, function (m) {
290 return m === mode;
291 })) {
292 return true;
293 } else if (add && _.find((chanmodes[2] || '').split(''), function (m) {
294 return m === mode;
295 })) {
296 return true;
297 } else {
298 return false;
299 }
300 };
301
302 if (!command.params[1]) {
303 command.params[1] = command.trailing;
304 }
305 j = 0;
306 for (i = 0; i < command.params[1].length; i++) {
307 switch (command.params[1][i]) {
308 case '+':
309 add = true;
310 break;
311 case '-':
312 add = false;
313 break;
314 default:
315 if (has_param(command.params[1][i], add)) {
316 modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: command.params[2 + j]});
317 j++;
318 } else {
319 modes.push({mode: (add ? '+' : '-') + command.params[1][i], param: null});
320 }
321 }
322 }
323
324 this.client.sendIrcCommand('mode', {
325 server: this.con_num,
326 target: command.params[0],
327 nick: command.nick || command.prefix || '',
328 modes: modes
329 });
330 },
331 'PRIVMSG': function (command) {
332 var tmp, namespace;
333 if ((command.trailing.charAt(0) === String.fromCharCode(1)) && (command.trailing.charAt(command.trailing.length - 1) === String.fromCharCode(1))) {
334 //CTCP request
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));
347 } else {
348 this.client.sendIrcCommand('ctcp_request', {
349 server: this.con_num,
350 nick: command.nick,
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)
356 });
357 }
358 } else {
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});
361 }
362 },
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(' ');
367 var request;
368 var want = ['multi-prefix', 'away-notify'];
369
370 if (this.irc_connection.password) {
371 want.push('sasl');
372 }
373
374 switch (command.params[1]) {
375 case 'LS':
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(' '));
380 } else {
381 this.irc_connection.write('CAP END');
382 this.irc_connection.cap_negotation = false;
383 }
384 break;
385 case 'ACK':
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);
389 }
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');
394 } else {
395 this.irc_connection.write('CAP END');
396 this.irc_connection.cap_negotation = false;
397 }
398 }
399 break;
400 case 'NAK':
401 if (capabilities.length > 0) {
402 this.irc_connection.cap.requested = _.difference(this.irc_connection.cap.requested, capabilities);
403 }
404 if (this.irc_connection.cap.requested.length > 0) {
405 this.irc_connection.write('CAP END');
406 this.irc_connection.cap_negotation = false;
407 }
408 break;
409 case 'LIST':
410 // should we do anything here?
411 break;
412 }
413 },
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);
421 }
422 if (b64.length > 0) {
423 this.irc_connection.write('AUTHENTICATE ' + b64);
424 } else {
425 this.irc_connection.write('AUTHENTICATE +');
426 }
427 } else {
428 this.irc_connection.write('CAP END');
429 this.irc_connection.cap_negotation = false;
430 }
431 },
432 'AWAY': function (command) {
433 this.client.sendIrcCommand('away', {server: this.con_num, nick: command.nick, msg: command.trailing});
434 },
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;
439 },
440 'RPL_SASLLOGGEDIN': function (command) {
441 if (this.irc_connection.cap_negotation === false) {
442 this.irc_connection.write('CAP END');
443 }
444 },
445 'ERR_SASLNOTAUTHORISED': function (command) {
446 this.irc_connection.write('CAP END');
447 this.irc_connection.cap_negotation = false;
448 },
449 'ERR_SASLABORTED': function (command) {
450 this.irc_connection.write('CAP END');
451 this.irc_connection.cap_negotation = false;
452 },
453 'ERR_SASLALREADYAUTHED': function (command) {
454 // noop
455 },
456 'ERROR': function (command) {
457 this.client.sendIrcCommand('irc_error', {server: this.con_num, error: 'error', reason: command.trailing});
458 },
459 ERR_LINKCHANNEL: function (command) {
460 this.client.sendIrcCommand('channel_redirect', {server: this.con_num, from: command.params[1], to: command.params[2]});
461 },
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});
464 },
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});
467 },
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});
470 },
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});
473 },
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});
476 },
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});
479 },
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});
482 },
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});
485 },
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});
488 },
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});
491 },
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});
494 },
495 ERR_NOTREGISTERED: function (command) {
496 },
497
498 RPL_MAPMORE: function (command) {
499 var params = _.clone(command.params);
500 params.shift();
501 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
502 },
503 RPL_MAPEND: function (command) {
504 var params = _.clone(command.params);
505 params.shift();
506 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
507 },
508
509 RPL_LINKS: function (command) {
510 var params = _.clone(command.params);
511 params.shift();
512 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
513 },
514 RPL_ENDOFLINKS: function (command) {
515 var params = _.clone(command.params);
516 params.shift();
517 genericNotice.call(this, command, params.join(', ') + ' ' + command.trailing);
518 },
519
520 ERR_UNKNOWNCOMMAND: function (command) {
521 var params = _.clone(command.params);
522 params.shift();
523 genericNotice.call(this, command, '`' + params.join(', ') + '` ' + command.trailing);
524 },
525
526 ERR_NOMOTD: function (command) {
527 var params = _.clone(command.params);
528 params.shift();
529 genericNotice.call(this, command, command.trailing);
530 },
531
532 ERR_NOPRIVILEGES: function (command) {
533 var params = _.clone(command.params);
534 params.shift();
535 genericNotice.call(this, command, command.trailing);
536 }
537 };
538
539
540
541
542 function genericNotice (command, msg, is_error) {
543 // Default to being an error
544 if (typeof is_error !== 'boolean')
545 is_error = true;
546
547 this.client.sendIrcCommand('notice', {
548 server: this.con_num,
549 nick: command.prefix,
550 ident: '',
551 hostname: '',
552 target: command.params[0],
553 msg: msg,
554 numeric: parseInt(command.command, 10)
555 });
556 }