fix crash when connection to IRC server failed. Added partial support for ERROR comma...
[KiwiIRC.git] / node / kiwi.js
1 /*jslint continue: true, forin: true, regexp: true, confusion: true, undef: false, node: true, sloppy: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
2
3 var tls = require('tls'),
4 net = require('net'),
5 http = require('http'),
6 https = require('https'),
7 fs = require('fs'),
8 url = require('url'),
9 ws = require('socket.io'),
10 _ = require('./lib/underscore.min.js'),
11 starttls = require('./lib/starttls.js');
12
13 // Libraries may need to know kiwi.js path as __dirname
14 // only gives that librarys path. Set it here for usage later.
15 var kiwi_root = __dirname;
16
17
18 /*
19 * Find a config file in the following order:
20 * - /etc/kiwi/config.json
21 * - ./config.json
22 */
23 var config = null,
24 config_filename = 'config.json',
25 config_dirs = ['/etc/kiwiirc/', __dirname + '/'];
26
27 (function () {
28 var i;
29 for (i in config_dirs) {
30 try {
31 if (fs.lstatSync(config_dirs[i] + config_filename).isDirectory() === false) {
32 config = JSON.parse(fs.readFileSync(config_dirs[i] + config_filename, 'ascii'));
33 console.log('Using config file ' + config_dirs[i] + config_filename);
34 break;
35 }
36 } catch (e) {
37 continue;
38 }
39 }
40
41 if (config === null) {
42 console.log('Couldn\'t find a config file!');
43 process.exit(0);
44 }
45 }());
46
47
48 /*
49 * Load the modules as set in the config and print them out
50 */
51 var kiwi_mod = require('./lib/kiwi_mod.js');
52 kiwi_mod.loadModules(kiwi_root, config);
53 kiwi_mod.printMods();
54
55
56
57
58 /*
59 * Some process changes
60 */
61 process.title = 'kiwiirc';
62 var changeUser = function () {
63 if (typeof config.group !== 'undefined' && config.group !== '') {
64 try {
65 process.setgid(config.group);
66 } catch (err) {
67 console.log('Failed to set gid: ' + err);
68 process.exit();
69 }
70 }
71
72 if (typeof config.user !== 'undefined' && config.user !== '') {
73 try {
74 process.setuid(config.user);
75 } catch (e) {
76 console.log('Failed to set uid: ' + e);
77 process.exit();
78 }
79 }
80 };
81
82
83 /*
84 * And now KiwiIRC, the server :)
85 *
86 */
87
88 var ircNumerics = {
89 RPL_WELCOME: '001',
90 RPL_ISUPPORT: '005',
91 RPL_WHOISUSER: '311',
92 RPL_WHOISSERVER: '312',
93 RPL_WHOISOPERATOR: '313',
94 RPL_WHOISIDLE: '317',
95 RPL_ENDOFWHOIS: '318',
96 RPL_WHOISCHANNELS: '319',
97 RPL_NOTOPIC: '331',
98 RPL_TOPIC: '332',
99 RPL_NAMEREPLY: '353',
100 RPL_ENDOFNAMES: '366',
101 RPL_MOTD: '372',
102 RPL_WHOISMODES: '379',
103 ERR_NOSUCHNICK: '401',
104 ERR_CANNOTSENDTOCHAN: '404',
105 ERR_TOOMANYCHANNELS: '405',
106 ERR_NICKNAMEINUSE: '433',
107 ERR_USERNOTINCHANNEL: '441',
108 ERR_NOTONCHANNEL: '442',
109 ERR_LINKCHANNEL: '470',
110 ERR_CHANNELISFULL: '471',
111 ERR_INVITEONLYCHAN: '473',
112 ERR_BANNEDFROMCHAN: '474',
113 ERR_BADCHANNELKEY: '475',
114 ERR_CHANOPRIVSNEEDED: '482',
115 RPL_STARTTLS: '670'
116 };
117
118
119
120
121 var parseIRCMessage = function (websocket, ircSocket, data) {
122 /*global ircSocketDataHandler */
123 var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, channel, params, prefix, prefixes, nicklist, caps, rtn, obj;
124 regex = /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:]+)?) )?([a-z0-9]+)(?:(?: ([^:]+))?(?: :(.+))?)$/i;
125 msg = regex.exec(data);
126 if (msg) {
127 msg = {
128 prefix: msg[1],
129 nick: msg[2],
130 ident: msg[3],
131 hostname: msg[4] || '',
132 command: msg[5],
133 params: msg[6] || '',
134 trailing: (msg[7]) ? msg[7].trim() : ''
135 };
136 switch (msg.command.toUpperCase()) {
137 case 'PING':
138 websocket.sendServerLine('PONG ' + msg.trailing);
139 break;
140 case ircNumerics.RPL_WELCOME:
141 if (ircSocket.IRC.CAP.negotiating) {
142 ircSocket.IRC.CAP.negotiating = false;
143 ircSocket.IRC.CAP.enabled = [];
144 ircSocket.IRC.CAP.requested = [];
145 ircSocket.IRC.registered = true;
146 }
147 websocket.sendClientEvent('connect', {connected: true, host: null});
148 break;
149 case ircNumerics.RPL_ISUPPORT:
150 opts = msg.params.split(" ");
151 options = [];
152 for (i = 0; i < opts.length; i++) {
153 opt = opts[i].split("=", 2);
154 opt[0] = opt[0].toUpperCase();
155 ircSocket.IRC.options[opt[0]] = opt[1] || true;
156 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) {
157 if (opt[0] === 'PREFIX') {
158 regex = /\(([^)]*)\)(.*)/;
159 matches = regex.exec(opt[1]);
160 if ((matches) && (matches.length === 3)) {
161 ircSocket.IRC.options[opt[0]] = [];
162 for (j = 0; j < matches[2].length; j++) {
163 //ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
164 ircSocket.IRC.options[opt[0]].push({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
165 //console.log({symbol: matches[2].charAt(j), mode: matches[1].charAt(j)});
166 }
167 console.log(ircSocket.IRC.options);
168 }
169 }
170 }
171 }
172 websocket.sendClientEvent('options', {server: '', "options": ircSocket.IRC.options});
173 break;
174 case ircNumerics.RPL_WHOISUSER:
175 case ircNumerics.RPL_WHOISSERVER:
176 case ircNumerics.RPL_WHOISOPERATOR:
177 case ircNumerics.RPL_ENDOFWHOIS:
178 case ircNumerics.RPL_WHOISCHANNELS:
179 case ircNumerics.RPL_WHOISMODES:
180 websocket.sendClientEvent('whois', {server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
181 break;
182 case ircNumerics.RPL_WHOISIDLE:
183 params = msg.params.split(" ", 4);
184 rtn = {server: '', nick: params[1], idle: params[2]};
185 if (params[3]) {
186 rtn.logon = params[3];
187 }
188 websocket.sendClientEvent('whois', rtn);
189 break;
190 case ircNumerics.RPL_MOTD:
191 websocket.sendClientEvent('motd', {server: '', "msg": msg.trailing});
192 break;
193 case ircNumerics.RPL_NAMEREPLY:
194 params = msg.params.split(" ");
195 nick = params[0];
196 chan = params[2];
197 users = msg.trailing.split(" ");
198 prefixes = _.values(ircSocket.IRC.options.PREFIX);
199 nicklist = {};
200 i = 0;
201 _.each(users, function (user) {
202 if (_.include(prefix, user.charAt(0))) {
203 prefix = user.charAt(0);
204 user = user.substring(1);
205 nicklist[user] = prefix;
206 } else {
207 nicklist[user] = '';
208 }
209 if (i++ >= 50) {
210 websocket.sendClientEvent('userlist', {server: '', 'users': nicklist, channel: chan});
211 nicklist = {};
212 i = 0;
213 }
214 });
215 if (i > 0) {
216 websocket.sendClientEvent('userlist', {server: '', "users": nicklist, channel: chan});
217 } else {
218 console.log("oops");
219 }
220 break;
221 case ircNumerics.RPL_ENDOFNAMES:
222 websocket.sendClientEvent('userlist_end', {server: '', channel: msg.params.split(" ")[1]});
223 break;
224 case ircNumerics.ERR_LINKCHANNEL:
225 params = msg.params.split(" ");
226 websocket.sendClientEvent('channel_redirect', {from: params[1], to: params[2]});
227 break;
228 case ircNumerics.ERR_NOSUCHNICK:
229 websocket.sendClientEvent('irc_error', {error: 'no_such_nick', nick: msg.params.split(" ")[1], reason: msg.trailing});
230 break;
231 case 'JOIN':
232 // Some BNC's send malformed JOIN causing the channel to be as a
233 // parameter instead of trailing.
234 if (typeof msg.trailing === 'string' && msg.trailing !== '') {
235 channel = msg.trailing;
236 } else if (typeof msg.params === 'string' && msg.params !== '') {
237 channel = msg.params;
238 }
239
240 websocket.sendClientEvent('join', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: channel});
241 if (msg.nick === ircSocket.IRC.nick) {
242 websocket.sendServerLine('NAMES ' + msg.trailing);
243 }
244 break;
245 case 'PART':
246 websocket.sendClientEvent('part', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
247 break;
248 case 'KICK':
249 params = msg.params.split(" ");
250 websocket.sendClientEvent('kick', {kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
251 break;
252 case 'QUIT':
253 websocket.sendClientEvent('quit', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
254 break;
255 case 'NOTICE':
256 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
257 // It's a CTCP response
258 websocket.sendClientEvent('ctcp_response', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
259 } else {
260 websocket.sendClientEvent('notice', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing});
261 }
262 break;
263 case 'NICK':
264 websocket.sendClientEvent('nick', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
265 break;
266 case 'TOPIC':
267 obj = {nick: msg.nick, channel: msg.params, topic: msg.trailing};
268 websocket.sendClientEvent('topic', obj);
269 break;
270 case ircNumerics.RPL_TOPIC:
271 obj = {nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing};
272 websocket.sendClientEvent('topic', obj);
273 break;
274 case ircNumerics.RPL_NOTOPIC:
275 obj = {nick: '', channel: msg.params.split(" ")[1], topic: ''};
276 websocket.sendClientEvent('topic', obj);
277 break;
278 case 'MODE':
279 opts = msg.params.split(" ");
280 params = {nick: msg.nick};
281 switch (opts.length) {
282 case 1:
283 params.effected_nick = opts[0];
284 params.mode = msg.trailing;
285 break;
286 case 2:
287 params.channel = opts[0];
288 params.mode = opts[1];
289 break;
290 default:
291 params.channel = opts[0];
292 params.mode = opts[1];
293 params.effected_nick = opts[2];
294 break;
295 }
296 websocket.sendClientEvent('mode', params);
297 break;
298 case 'PRIVMSG':
299 if ((msg.trailing.charAt(0) === String.fromCharCode(1)) && (msg.trailing.charAt(msg.trailing.length - 1) === String.fromCharCode(1))) {
300 // It's a CTCP request
301 if (msg.trailing.substr(1, 6) === 'ACTION') {
302 websocket.sendClientEvent('action', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(7, msg.trailing.length - 2)});
303 } else if (msg.trailing.substr(1, 7) === 'VERSION') {
304 ircSocket.write('NOTICE ' + msg.nick + ' :' + String.fromCharCode(1) + 'VERSION KiwiIRC' + String.fromCharCode(1) + '\r\n');
305 } else {
306 websocket.sendClientEvent('ctcp_request', {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing.substr(1, msg.trailing.length - 2)});
307 }
308 } else {
309 obj = {nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing};
310 websocket.sendClientEvent('msg', obj);
311 }
312 break;
313 case 'CAP':
314 caps = config.cap_options;
315 options = msg.trailing.split(" ");
316 switch (_.last(msg.params.split(" "))) {
317 case 'LS':
318 opts = '';
319 _.each(_.intersect(caps, options), function (cap) {
320 if (opts !== '') {
321 opts += " ";
322 }
323 opts += cap;
324 ircSocket.IRC.CAP.requested.push(cap);
325 });
326 if (opts.length > 0) {
327 websocket.sendServerLine('CAP REQ :' + opts);
328 } else {
329 websocket.sendServerLine('CAP END');
330 }
331 // TLS is special
332 /*if (_.include(options, 'tls')) {
333 websocket.sendServerLine('STARTTLS');
334 ircSocket.IRC.CAP.requested.push('tls');
335 }*/
336 break;
337 case 'ACK':
338 _.each(options, function (cap) {
339 ircSocket.IRC.CAP.enabled.push(cap);
340 });
341 if (_.last(msg.params.split(" ")) !== '*') {
342 ircSocket.IRC.CAP.requested = [];
343 ircSocket.IRC.CAP.negotiating = false;
344 websocket.sendServerLine('CAP END');
345 }
346 break;
347 case 'NAK':
348 ircSocket.IRC.CAP.requested = [];
349 ircSocket.IRC.CAP.negotiating = false;
350 websocket.sendServerLine('CAP END');
351 break;
352 }
353 break;
354 /*case ircNumerics.RPL_STARTTLS:
355 try {
356 IRC = ircSocket.IRC;
357 listeners = ircSocket.listeners('data');
358 ircSocket.removeAllListeners('data');
359 ssl_socket = starttls(ircSocket, {}, function () {
360 ssl_socket.on("data", function (data) {
361 ircSocketDataHandler(data, websocket, ssl_socket);
362 });
363 ircSocket = ssl_socket;
364 ircSocket.IRC = IRC;
365 _.each(listeners, function (listener) {
366 ircSocket.addListener('data', listener);
367 });
368 });
369 //console.log(ircSocket);
370 } catch (e) {
371 console.log(e);
372 }
373 break;*/
374 case ircNumerics.ERR_CANNOTSENDTOCHAN:
375 websocket.sendClientEvent('irc_error', {error: 'cannot_send_to_chan', channel: msg.params.split(" ")[1], reason: msg.trailing});
376 break;
377 case ircNumerics.ERR_TOOMANYCHANNELS:
378 websocket.sendClientEvent('irc_error', {error: 'too_many_channels', channel: msg.params.split(" ")[1], reason: msg.trailing});
379 break;
380 case ircNumerics.ERR_USERNOTINCHANNEL:
381 params = msg.params.split(" ");
382 websocket.sendClientEvent('irc_error', {error: 'user_not_in_channel', nick: params[0], channel: params[1], reason: msg.trainling});
383 break;
384 case ircNumerics.ERR_NOTONCHANNEL:
385 websocket.sendClientEvent('irc_error', {error: 'not_on_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
386 break;
387 case ircNumerics.ERR_CHANNELISFULL:
388 websocket.sendClientEvent('irc_error', {error: 'channel_is_full', channel: msg.params.split(" ")[1], reason: msg.trailing});
389 break;
390 case ircNumerics.ERR_INVITEONLYCHAN:
391 websocket.sendClientEvent('irc_error', {error: 'invite_only_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
392 break;
393 case ircNumerics.ERR_BANNEDFROMCHAN:
394 websocket.sendClientEvent('irc_error', {error: 'banned_from_channel', channel: msg.params.split(" ")[1], reason: msg.trailing});
395 break;
396 case ircNumerics.ERR_BADCHANNELKEY:
397 websocket.sendClientEvent('irc_error', {error: 'bad_channel_key', channel: msg.params.split(" ")[1], reason: msg.trailing});
398 break;
399 case ircNumerics.ERR_CHANOPRIVSNEEDED:
400 websocket.sendClientEvent('irc_error', {error: 'chanop_privs_needed', channel: msg.params.split(" ")[1], reason: msg.trailing});
401 break;
402 case ircNumerics.ERR_NICKNAMEINUSE:
403 websocket.sendClientEvent('irc_error', {error: 'nickname_in_use', nick: _.last(msg.params.split(" ")), reason: msg.trailing});
404 break;
405 case 'ERROR':
406 ircSocket.end();
407 websocket.sendClientEvent('irc_error', {error: 'error', reason: msg.trailing});
408 websocket.disconnect();
409 break;
410 default:
411 console.log("Unknown command (" + String(msg.command).toUpperCase() + ")");
412 }
413 } else {
414 console.log("Malformed IRC line");
415 }
416 };
417
418
419 /*
420 * NOTE: Some IRC servers or BNC's out there incorrectly use
421 * only \n as a line splitter.
422 */
423 var ircSocketDataHandler = function (data, websocket, ircSocket) {
424 var i;
425 if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
426 data = ircSocket.held + data;
427 ircSocket.holdLast = false;
428 ircSocket.held = '';
429 }
430 if (data.substr(-1) === '\n') {
431 ircSocket.holdLast = true;
432 }
433 data = data.split("\n");
434 for (i = 0; i < data.length; i++) {
435 if (data[i]) {
436 if ((ircSocket.holdLast) && (i === data.length - 1)) {
437 ircSocket.held = data[i];
438 break;
439 }
440 console.log("->" + data[i]);
441 parseIRCMessage(websocket, ircSocket, data[i].replace(/^\r+|\r+$/, ''));
442 }
443 }
444 };
445
446 if (config.handle_http) {
447 var fileServer = new (require('node-static').Server)(__dirname + config.public_http);
448 var jade = require('jade');
449 }
450
451 var httpHandler = function (request, response) {
452 var uri, subs, useragent, agent, server_set, server, nick, debug, touchscreen;
453 if (config.handle_http) {
454 uri = url.parse(request.url);
455 subs = uri.pathname.substr(0, 4);
456 if ((subs === '/js/') || (subs === '/css') || (subs === '/img')) {
457 request.addListener('end', function () {
458 fileServer.serve(request, response);
459 });
460 } else if (uri.pathname === '/') {
461 useragent = (response.headers) ? response.headers['user-agent'] : '';
462 if (useragent.indexOf('android') !== -1) {
463 agent = 'android';
464 touchscreen = true;
465 } else if (useragent.indexOf('iphone') !== -1) {
466 agent = 'iphone';
467 touchscreen = true;
468 } else if (useragent.indexOf('ipad') !== -1) {
469 agent = 'ipad';
470 touchscreen = true;
471 } else if (useragent.indexOf('ipod') !== -1) {
472 agent = 'ipod';
473 touchscreen = true;
474 } else {
475 agent = 'normal';
476 touchscreen = false;
477 }
478 if (uri.query) {
479 server_set = (uri.query.server !== '');
480 server = uri.query.server || 'irc.anonnet.org';
481 nick = uri.query.nick || '';
482 debug = (uri.query.debug !== '');
483 } else {
484 server = 'irc.anonnet.org';
485 nick = '';
486 }
487 response.setHeader('Connection', 'close');
488 response.setHeader('X-Generated-By', 'KiwiIRC');
489 jade.renderFile(__dirname + '/client/index.html.jade', { locals: { "touchscreen": touchscreen, "debug": debug, "server_set": server_set, "server": server, "nick": nick, "agent": agent, "config": config }}, function (err, html) {
490 if (!err) {
491 response.write(html);
492 } else {
493 response.statusCode = 500;
494 }
495 response.end();
496 });
497 } else if (uri.pathname.substr(0, 10) === '/socket.io') {
498 return;
499 } else {
500 response.statusCode = 404;
501 response.end();
502 }
503 }
504 };
505
506 //setup websocket listener
507 if (config.listen_ssl) {
508 var httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + config.ssl_key), cert: fs.readFileSync(__dirname + '/' + config.ssl_cert)}, httpHandler);
509 var io = ws.listen(httpServer, {secure: true});
510 httpServer.listen(config.port, config.bind_address);
511 } else {
512 var httpServer = http.createServer(httpHandler);
513 var io = ws.listen(httpServer, {secure: false});
514 httpServer.listen(config.port, config.bind_address);
515 }
516 io.set('log level', 1);
517
518 // Now we're listening on the network, set our UID/GIDs if required
519 changeUser();
520
521 var connections = {};
522
523 // The main socket listening/handling routines
524 io.of('/kiwi').authorization(function (handshakeData, callback) {
525 var address = handshakeData.address.address;
526 if (typeof connections[address] === 'undefined') {
527 connections[address] = {count: 0, sockets: []};
528 }
529 callback(null, true);
530 }).on('connection', function (websocket) {
531 var con;
532 websocket.kiwi = {address: websocket.handshake.address.address};
533 con = connections[websocket.kiwi.address];
534 if (con.count >= config.max_client_conns) {
535 websocket.emit('too_many_connections');
536 websocket.disconnect();
537 } else {
538 con.count += 1;
539 con.sockets.push(websocket);
540
541 websocket.sendClientEvent = function (event_name, data) {
542 kiwi_mod.run(event_name, data, {websocket: this});
543 data.event = event_name;
544 websocket.emit('message', data);
545 };
546
547 websocket.sendServerLine = function (data, eol) {
548 eol = (typeof eol === 'undefined') ? '\r\n' : eol;
549 //console.log('Out: -----\n' + data + '\n-----');
550 websocket.ircSocket.write(data + eol);
551 };
552
553 websocket.on('irc connect', function (nick, host, port, ssl, callback) {
554 var ircSocket;
555 //setup IRC connection
556 if (!ssl) {
557 ircSocket = net.createConnection(port, host);
558 } else {
559 ircSocket = tls.connect(port, host);
560 }
561 ircSocket.setEncoding('ascii');
562 ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}, registered: false};
563 ircSocket.on('error', function (e) {
564 if (ircSocket.IRC.registered) {
565 websocket.emit('disconnect');
566 } else {
567 websocket.emit('error', e.message);
568 }
569 });
570 websocket.ircSocket = ircSocket;
571 ircSocket.holdLast = false;
572 ircSocket.held = '';
573
574 ircSocket.on('data', function (data) {
575 //console.log('In: -----\n' + data + '\n-----');
576 ircSocketDataHandler(data, websocket, ircSocket);
577 });
578
579 ircSocket.IRC.nick = nick;
580 // Send the login data
581 websocket.sendServerLine('CAP LS');
582 websocket.sendServerLine('NICK ' + nick);
583 websocket.sendServerLine('USER ' + nick + '_kiwi 0 0 :' + nick);
584
585 if ((callback) && (typeof (callback) === 'function')) {
586 callback();
587 }
588 });
589 websocket.on('message', function (msg, callback) {
590 var args, obj;
591 try {
592 msg.data = JSON.parse(msg.data);
593 args = msg.data.args;
594 switch (msg.data.method) {
595 case 'msg':
596 if ((args.target) && (args.msg)) {
597 obj = kiwi_mod.run('msgsend', args, {websocket: websocket});
598 if (obj !== null) {
599 websocket.sendServerLine('PRIVMSG ' + args.target + ' :' + args.msg);
600 }
601 }
602 break;
603 case 'action':
604 if ((args.target) && (args.msg)) {
605 websocket.sendServerLine('PRIVMSG ' + args.target + ' :\ 1' + String.fromCharCode(1) + 'ACTION ' + args.msg + String.fromCharCode(1));
606 }
607 break;
608 case 'raw':
609 websocket.sendServerLine(args.data);
610 break;
611 case 'join':
612 if (args.channel) {
613 _.each(args.channel.split(","), function (chan) {
614 websocket.sendServerLine('JOIN ' + chan);
615 });
616 }
617 break;
618 case 'topic':
619 if (args.channel) {
620 websocket.sendServerLine('TOPIC ' + args.channel + ' :' + args.topic);
621 }
622 break;
623 case 'quit':
624 websocket.ircSocket.end('QUIT :' + args.message + '\r\n');
625 websocket.sentQUIT = true;
626 websocket.ircSocket.destroySoon();
627 websocket.disconnect();
628 break;
629 case 'notice':
630 if ((args.target) && (args.msg)) {
631 websocket.sendServerLine('NOTICE ' + args.target + ' :' + args.msg);
632 }
633 break;
634 default:
635 }
636 if ((callback) && (typeof (callback) === 'function')) {
637 callback();
638 }
639 } catch (e) {
640 console.log("Caught error: " + e);
641 }
642 });
643
644
645 websocket.on('disconnect', function () {
646 if ((!websocket.sentQUIT) && (websocket.ircSocket)) {
647 try {
648 websocket.ircSocket.end('QUIT :' + config.quit_message + '\r\n');
649 websocket.sentQUIT = true;
650 websocket.ircSocket.destroySoon();
651 } catch (e) {
652 }
653 }
654 con = connections[websocket.kiwi.address];
655 con.count -= 1;
656 con.sockets = _.reject(con.sockets, function (sock) {
657 return sock === websocket;
658 });
659 });
660 }
661 });
662