1 /*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
23 this.init = function (objs
) {
28 node_static
= objs
.node_static
;
39 starttls
= objs
.starttls
;
40 kiwi
= require('./kiwi.js');
42 util
.inherits(this.IRCConnection
, events
.EventEmitter
);
44 file_server
= new StaticFileServer();
53 * Some process changes
55 this.setTitle = function () {
56 process
.title
= 'kiwiirc';
59 this.changeUser = function () {
60 if (typeof kiwi
.config
.group
!== 'undefined' && kiwi
.config
.group
!== '') {
62 process
.setgid(kiwi
.config
.group
);
64 kiwi
.log('Failed to set gid: ' + err
);
69 if (typeof kiwi
.config
.user
!== 'undefined' && kiwi
.config
.user
!== '') {
71 process
.setuid(kiwi
.config
.user
);
73 kiwi
.log('Failed to set uid: ' + e
);
81 function StaticFileServer(public_html
) {
82 public_html
= public_html
|| 'client/';
83 this.file_server
= new node_static
.Server(public_html
);
86 StaticFileServer
.prototype.serve = function (request
, response
) {
87 // Any requests for /client to load the index file
88 if (request
.url
.match(/^\/client/)) {
92 this.file_server
.serve(request
, response
, function (err
) {
94 response
.writeHead(err
.status
, err
.headers
);
111 RPL_WHOISUSER
: '311',
112 RPL_WHOISSERVER
: '312',
113 RPL_WHOISOPERATOR
: '313',
114 RPL_WHOISIDLE
: '317',
115 RPL_ENDOFWHOIS
: '318',
116 RPL_WHOISCHANNELS
: '319',
117 RPL_LISTSTART
: '321',
122 RPL_TOPICWHOTIME
: '333',
123 RPL_NAMEREPLY
: '353',
124 RPL_ENDOFNAMES
: '366',
126 RPL_ENDOFBANLIST
: '368',
128 RPL_MOTDSTART
: '375',
129 RPL_ENDOFMOTD
: '376',
130 RPL_WHOISMODES
: '379',
131 ERR_NOSUCHNICK
: '401',
132 ERR_CANNOTSENDTOCHAN
: '404',
133 ERR_TOOMANYCHANNELS
: '405',
134 ERR_NICKNAMEINUSE
: '433',
135 ERR_USERNOTINCHANNEL
: '441',
136 ERR_NOTONCHANNEL
: '442',
137 ERR_NOTREGISTERED
: '451',
138 ERR_LINKCHANNEL
: '470',
139 ERR_CHANNELISFULL
: '471',
140 ERR_INVITEONLYCHAN
: '473',
141 ERR_BANNEDFROMCHAN
: '474',
142 ERR_BADCHANNELKEY
: '475',
143 ERR_CHANOPRIVSNEEDED
: '482',
147 this.bindIRCCommands = function (irc_connection
, websocket
) {
148 var bound_events
= [],
149 bindCommand = function (command
, listener
) {
150 command
= 'irc_' + command
;
151 irc_connection
.on(command
, listener
);
152 bound_events
.push({"command": command
, "listener": listener
});
155 bindCommand('PING', function (msg
) {
156 websocket
.sendServerLine('PONG ' + msg
.trailing
);
159 bindCommand(ircNumerics
.RPL_WELCOME
, function (msg
) {
160 if (irc_connection
.IRC
.CAP
.negotiating
) {
161 irc_connection
.IRC
.CAP
.negotiating
= false;
162 irc_connection
.IRC
.CAP
.enabled
= [];
163 irc_connection
.IRC
.CAP
.requested
= [];
164 irc_connection
.IRC
.registered
= true;
166 var nick
= msg
.params
.split(' ')[0];
167 websocket
.sendClientEvent('connect', {connected
: true, host
: null, nick
: nick
});
170 bindCommand(ircNumerics
.RPL_ISUPPORT
, function (msg
) {
171 var opts
= msg
.params
.split(" "),
177 for (i
= 0; i
< opts
.length
; i
++) {
178 opt
= opts
[i
].split("=", 2);
179 opt
[0] = opt
[0].toUpperCase();
180 irc_connection
.IRC
.options
[opt
[0]] = (typeof opt
[1] !== 'undefined') ? opt
[1] : true;
181 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt
[0])) {
182 if (opt
[0] === 'PREFIX') {
183 regex
= /\(([^)]*)\)(.*)/;
184 matches
= regex
.exec(opt
[1]);
185 if ((matches
) && (matches
.length
=== 3)) {
186 irc_connection
.IRC
.options
[opt
[0]] = [];
187 for (j
= 0; j
< matches
[2].length
; j
++) {
188 irc_connection
.IRC
.options
[opt
[0]].push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
192 } else if (opt
[0] === 'CHANTYPES') {
193 irc_connection
.IRC
.options
.CHANTYPES
= irc_connection
.IRC
.options
.CHANTYPES
.split('');
194 } else if (opt
[0] === 'CHANMODES') {
195 irc_connection
.IRC
.options
.CHANMODES
= option
[1].split(',');
196 } else if (opt
[0] === 'NAMESX') {
197 websocket
.sendServerLine('PROTOCTL NAMESX');
202 websocket
.sendClientEvent('options', {server
: '', "options": irc_connection
.IRC
.options
});
205 bindCommand(ircNumerics
.RPL_ENDOFWHOIS
, function (msg
) {
206 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: true});
209 bindCommand(ircNumerics
.RPL_WHOISUSER
, function (msg
) {
210 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
213 bindCommand(ircNumerics
.RPL_WHOISSERVER
, function (msg
) {
214 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
217 bindCommand(ircNumerics
.RPL_WHOISOPERATOR
, function (msg
) {
218 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
221 bindCommand(ircNumerics
.RPL_WHOISCHANNELS
, function (msg
) {
222 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
225 bindCommand(ircNumerics
.RPL_WHOISMODES
, function (msg
) {
226 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
229 bindCommand(ircNumerics
.RPL_LISTSTART
, function (msg
) {
230 websocket
.sendClientEvent('list_start', {server
: ''});
231 websocket
.kiwi
.buffer
.list
= [];
234 bindCommand(ircNumerics
.RPL_LISTEND
, function (msg
) {
235 if (websocket
.kiwi
.buffer
.list
.length
> 0) {
236 websocket
.kiwi
.buffer
.list
= _
.sortBy(websocket
.kiwi
.buffer
.list
, function (channel
) {
237 return channel
.num_users
;
239 websocket
.sendClientEvent('list_channel', {chans
: websocket
.kiwi
.buffer
.list
});
240 websocket
.kiwi
.buffer
.list
= [];
242 websocket
.sendClientEvent('list_end', {server
: ''});
245 bindCommand(ircNumerics
.RPL_LIST
, function (msg
) {
246 var parts
, channel
, num_users
, topic
;
248 parts
= msg
.params
.split(' ');
250 num_users
= parts
[2];
251 topic
= msg
.trailing
;
253 //websocket.sendClientEvent('list_channel', {
254 websocket
.kiwi
.buffer
.list
.push({
259 num_users
: parseInt(num_users
, 10)
262 if (websocket
.kiwi
.buffer
.list
.length
> 200) {
263 websocket
.kiwi
.buffer
.list
= _
.sortBy(websocket
.kiwi
.buffer
.list
, function (channel
) {
264 return channel
.num_users
;
266 websocket
.sendClientEvent('list_channel', {chans
: websocket
.kiwi
.buffer
.list
});
267 websocket
.kiwi
.buffer
.list
= [];
271 bindCommand(ircNumerics
.RPL_WHOISIDLE
, function (msg
) {
272 var params
= msg
.params
.split(" ", 4),
273 rtn
= {server
: '', nick
: params
[1], idle
: params
[2]};
275 rtn
.logon
= params
[3];
277 websocket
.sendClientEvent('whois', rtn
);
280 bindCommand(ircNumerics
.RPL_MOTD
, function (msg
) {
281 websocket
.kiwi
.buffer
.motd
+= msg
.trailing
+ '\n';
284 bindCommand(ircNumerics
.RPL_MOTDSTART
, function (msg
) {
285 websocket
.kiwi
.buffer
.motd
= '';
288 bindCommand(ircNumerics
.RPL_ENDOFMOTD
, function (msg
) {
289 websocket
.sendClientEvent('motd', {server
: '', 'msg': websocket
.kiwi
.buffer
.motd
});
292 bindCommand(ircNumerics
.RPL_NAMEREPLY
, function (msg
) {
293 var params
= msg
.params
.split(" "),
295 users
= msg
.trailing
.split(" "),
299 _
.each(users
, function (user
) {
300 var j
, k
, modes
= [];
301 for (j
= 0; j
< user
.length
; j
++) {
302 for (k
= 0; k
< irc_connection
.IRC
.options
.PREFIX
.length
; k
++) {
303 if (user
.charAt(j
) === irc_connection
.IRC
.options
.PREFIX
[k
].symbol
) {
304 modes
.push(irc_connection
.IRC
.options
.PREFIX
[k
].mode
);
308 nicklist
.push({nick
: user
, modes
: modes
});
310 websocket
.sendClientEvent('userlist', {server
: '', 'users': nicklist
, channel
: chan
});
316 websocket
.sendClientEvent('userlist', {server
: '', "users": nicklist
, channel
: chan
});
322 bindCommand(ircNumerics
.RPL_ENDOFNAMES
, function (msg
) {
323 websocket
.sendClientEvent('userlist_end', {server
: '', channel
: msg
.params
.split(" ")[1]});
326 bindCommand(ircNumerics
.ERR_LINKCHANNEL
, function (msg
) {
327 var params
= msg
.params
.split(" ");
328 websocket
.sendClientEvent('channel_redirect', {from: params
[1], to
: params
[2]});
331 bindCommand(ircNumerics
.ERR_NOSUCHNICK
, function (msg
) {
332 websocket
.sendClientEvent('irc_error', {error
: 'no_such_nick', nick
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
335 bindCommand(ircNumerics
.RPL_BANLIST
, function (msg
) {
336 var params
= msg
.params
.split(" ");
338 websocket
.sendClientEvent('banlist', {server
: '', channel
: params
[1], banned
: params
[2], banned_by
: params
[3], banned_at
: params
[4]});
341 bindCommand(ircNumerics
.RPL_ENDOFBANLIST
, function (msg
) {
342 websocket
.sendClientEvent('banlist_end', {server
: '', channel
: msg
.params
.split(" ")[1]});
345 bindCommand('JOIN', function (msg
) {
348 // Some BNC's send malformed JOIN causing the channel to be as a
349 // parameter instead of trailing.
350 if (typeof msg
.trailing
=== 'string' && msg
.trailing
!== '') {
351 channel
= msg
.trailing
;
352 } else if (typeof msg
.params
=== 'string' && msg
.params
!== '') {
353 channel
= msg
.params
;
356 websocket
.sendClientEvent('join', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: channel
});
357 if (msg
.nick
=== irc_connection
.IRC
.nick
) {
358 websocket
.sendServerLine('NAMES ' + msg
.trailing
);
362 bindCommand('PART', function (msg
) {
363 websocket
.sendClientEvent('part', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), message
: msg
.trailing
});
366 bindCommand('KICK', function (msg
) {
367 var params
= msg
.params
.split(" ");
368 websocket
.sendClientEvent('kick', {kicked
: params
[1], nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: params
[0].trim(), message
: msg
.trailing
});
371 bindCommand('QUIT', function (msg
) {
372 websocket
.sendClientEvent('quit', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, message
: msg
.trailing
});
375 bindCommand('NOTICE', function (msg
) {
376 if ((msg
.trailing
.charAt(0) === String
.fromCharCode(1)) && (msg
.trailing
.charAt(msg
.trailing
.length
- 1) === String
.fromCharCode(1))) {
377 // It's a CTCP response
378 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)});
380 websocket
.sendClientEvent('notice', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, target
: msg
.params
.trim(), msg
: msg
.trailing
});
384 bindCommand('NICK', function (msg
) {
385 websocket
.sendClientEvent('nick', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, newnick
: msg
.trailing
});
388 bindCommand('TOPIC', function (msg
) {
389 var obj
= {nick
: msg
.nick
, channel
: msg
.params
, topic
: msg
.trailing
};
390 websocket
.sendClientEvent('topic', obj
);
393 bindCommand(ircNumerics
.RPL_TOPIC
, function (msg
) {
394 var obj
= {nick
: '', channel
: msg
.params
.split(" ")[1], topic
: msg
.trailing
};
395 websocket
.sendClientEvent('topic', obj
);
398 bindCommand(ircNumerics
.RPL_NOTOPIC
, function (msg
) {
399 var obj
= {nick
: '', channel
: msg
.params
.split(" ")[1], topic
: ''};
400 websocket
.sendClientEvent('topic', obj
);
403 bindCommand(ircNumerics
.RPL_TOPICWHOTIME
, function (msg
) {
404 var parts
= msg
.params
.split(' '),
408 obj
= {nick
: nick
, channel
: channel
, when
: when
};
409 websocket
.sendClientEvent('topicsetby', obj
);
412 bindCommand('MODE', function (command
) {
414 var opts = msg.params.split(" "),
415 params = {nick: msg.nick};
417 switch (opts.length) {
419 params.effected_nick = opts[0];
420 params.mode = msg.trailing;
423 params.channel = opts[0];
424 params.mode = opts[1];
427 params.channel = opts[0];
428 params.mode = opts[1];
429 params.effected_nick = opts[2];
432 websocket.sendClientEvent('mode', params);
434 command
.params
= command
.params
.split(" ");
435 var chanmodes
= irc_connection
.IRC
.options
.CHANMODES
,
436 prefixes
= irc_connection
.IRC
.options
.PREFIX
,
437 always_param
= chanmodes
[0].concat(chanmodes
[1]),
439 has_param
, i
, j
, add
;
441 prefixes
= _
.reduce(prefixes
, function (list
, prefix
) {
442 list
.push(prefix
.mode
);
445 always_param
= always_param
.split('').concat(prefixes
);
447 has_param = function (mode
, add
) {
448 if (_
.find(always_param
, function (m
) {
452 } else if (add
&& _
.find(chanmodes
[2].split(''), function (m
) {
461 if (!command
.params
[1]) {
462 command
.params
[1] = command
.trailing
;
465 for (i
= 0; i
< command
.params
[1].length
; i
++) {
466 switch (command
.params
[1][i
]) {
474 if (has_param(command
.params
[1][i
], add
)) {
475 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: command
.params
[2 + j
]});
478 modes
.push({mode
: (add
? '+' : '-') + command
.params
[1][i
], param
: null});
483 websocket
.sendClientEvent('mode', {
484 target
: command
.params
[0],
485 nick
: command
.nick
|| command
.prefix
|| '',
490 bindCommand('PRIVMSG', function (msg
) {
491 var tmp
, namespace, obj
;
492 if ((msg
.trailing
.charAt(0) === String
.fromCharCode(1)) && (msg
.trailing
.charAt(msg
.trailing
.length
- 1) === String
.fromCharCode(1))) {
493 // It's a CTCP request
494 if (msg
.trailing
.substr(1, 6) === 'ACTION') {
495 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)});
496 } else if (msg
.trailing
.substr(1, 4) === 'KIWI') {
497 tmp
= msg
.trailing
.substr(6, msg
.trailing
.length
- 2);
498 namespace = tmp
.split(' ', 1)[0];
499 websocket
.sendClientEvent('kiwi', {namespace: namespace, data
: tmp
.substr(namespace.length
+ 1)});
501 } else if (msg
.trailing
.substr(1, 7) === 'VERSION') {
502 irc_connection
.write('NOTICE ' + msg
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1) + '\r\n');
504 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)});
507 obj
= {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), msg
: msg
.trailing
};
508 websocket
.sendClientEvent('msg', obj
);
512 bindCommand('CAP', function (msg
) {
513 var caps
= kiwi
.config
.cap_options
,
514 options
= msg
.trailing
.split(" "),
517 switch (_
.last(msg
.params
.split(" "))) {
520 _
.each(_
.intersect(caps
, options
), function (cap
) {
525 irc_connection
.IRC
.CAP
.requested
.push(cap
);
527 if (opts
.length
> 0) {
528 websocket
.sendServerLine('CAP REQ :' + opts
);
530 websocket
.sendServerLine('CAP END');
533 /*if (_.include(options, 'tls')) {
534 websocket.sendServerLine('STARTTLS');
535 ircSocket.IRC.CAP.requested.push('tls');
539 _
.each(options
, function (cap
) {
540 irc_connection
.IRC
.CAP
.enabled
.push(cap
);
542 if (_
.last(msg
.params
.split(" ")) !== '*') {
543 irc_connection
.IRC
.CAP
.requested
= [];
544 irc_connection
.IRC
.CAP
.negotiating
= false;
545 websocket
.sendServerLine('CAP END');
549 irc_connection
.IRC
.CAP
.requested
= [];
550 irc_connection
.IRC
.CAP
.negotiating
= false;
551 websocket
.sendServerLine('CAP END');
555 /*case ircNumerics.RPL_STARTTLS:
558 listeners = ircSocket.listeners('data');
559 ircSocket.removeAllListeners('data');
560 ssl_socket = starttls(ircSocket, {}, function () {
561 ssl_socket.on("data", function (data) {
562 ircSocketDataHandler(data, websocket, ssl_socket);
564 ircSocket = ssl_socket;
566 _.each(listeners, function (listener) {
567 ircSocket.addListener('data', listener);
575 bindCommand(ircNumerics
.ERR_CANNOTSENDTOCHAN
, function (msg
) {
576 websocket
.sendClientEvent('irc_error', {error
: 'cannot_send_to_chan', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
579 bindCommand(ircNumerics
.ERR_TOOMANYCHANNELS
, function (msg
) {
580 websocket
.sendClientEvent('irc_error', {error
: 'too_many_channels', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
583 bindCommand(ircNumerics
.ERR_USERNOTINCHANNEL
, function (msg
) {
584 var params
= msg
.params
.split(" ");
585 websocket
.sendClientEvent('irc_error', {error
: 'user_not_in_channel', nick
: params
[0], channel
: params
[1], reason
: msg
.trainling
});
588 bindCommand(ircNumerics
.ERR_NOTONCHANNEL
, function (msg
) {
589 websocket
.sendClientEvent('irc_error', {error
: 'not_on_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
592 bindCommand(ircNumerics
.ERR_CHANNELISFULL
, function (msg
) {
593 websocket
.sendClientEvent('irc_error', {error
: 'channel_is_full', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
596 bindCommand(ircNumerics
.ERR_INVITEONLYCHAN
, function (msg
) {
597 websocket
.sendClientEvent('irc_error', {error
: 'invite_only_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
600 bindCommand(ircNumerics
.ERR_BANNEDFROMCHAN
, function (msg
) {
601 websocket
.sendClientEvent('irc_error', {error
: 'banned_from_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
604 bindCommand(ircNumerics
.ERR_BADCHANNELKEY
, function (msg
) {
605 websocket
.sendClientEvent('irc_error', {error
: 'bad_channel_key', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
608 bindCommand(ircNumerics
.ERR_CHANOPRIVSNEEDED
, function (msg
) {
609 websocket
.sendClientEvent('irc_error', {error
: 'chanop_privs_needed', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
612 bindCommand(ircNumerics
.ERR_NICKNAMEINUSE
, function (msg
) {
613 websocket
.sendClientEvent('irc_error', {error
: 'nickname_in_use', nick
: _
.last(msg
.params
.split(" ")), reason
: msg
.trailing
});
616 bindCommand('ERROR', function (msg
) {
617 irc_connection
.end();
618 websocket
.sendClientEvent('irc_error', {error
: 'error', reason
: msg
.trailing
});
619 websocket
.disconnect();
622 bindCommand(ircNumerics
.ERR_NOTREGISTERED
, function (msg
) {
623 if (irc_connection
.IRC
.registered
) {
624 kiwi
.log('Kiwi thinks user is registered, but the IRC server thinks differently');
631 this.rebindIRCCommands = function () {
632 _
.each(kiwi
.connections
, function (con
) {
633 _
.each(con
.sockets
, function (sock
) {
634 sock
.ircConnection
.rebindIRCCommands();
640 this.httpHandler = function (request
, response
) {
643 uri
= url
.parse(request
.url
, true);
644 subs
= uri
.pathname
.substr(0, 4);
646 if (uri
.pathname
.substr(0, 10) === '/socket.io') {
649 file_server
.serve(request
, response
);
656 this.websocketListen = function (servers
, handler
) {
657 if (kiwi
.httpServers
.length
> 0) {
658 _
.each(kiwi
.httpServers
, function (hs
) {
661 kiwi
.httpsServers
= [];
664 _
.each(servers
, function (server
) {
666 if (server
.secure
=== true) {
667 // Start some SSL server up
669 key
: fs
.readFileSync(__dirname
+ '/' + server
.ssl_key
),
670 cert
: fs
.readFileSync(__dirname
+ '/' + server
.ssl_cert
)
673 // Do we have an intermediate certificate?
674 if (typeof server
.ssl_ca
!== 'undefined') {
675 opts
.ca
= fs
.readFileSync(__dirname
+ '/' + server
.ssl_ca
);
678 hs
= https
.createServer(opts
, handler
);
679 kiwi
.io
.push(ws
.listen(hs
, {secure
: true}));
680 hs
.listen(server
.port
, server
.address
);
681 kiwi
.log('Listening on ' + server
.address
+ ':' + server
.port
.toString() + ' with SSL');
683 // Start some plain-text server up
684 hs
= http
.createServer(handler
);
685 kiwi
.io
.push(ws
.listen(hs
, {secure
: false}));
686 hs
.listen(server
.port
, server
.address
);
687 kiwi
.log('Listening on ' + server
.address
+ ':' + server
.port
.toString() + ' without SSL');
690 kiwi
.httpServers
.push(hs
);
693 _
.each(kiwi
.io
, function (io
) {
694 io
.set('log level', 1);
695 io
.enable('browser client minification');
696 io
.enable('browser client etag');
697 io
.set('transports', kiwi
.config
.transports
);
699 io
.of('/kiwi').authorization(function (handshakeData
, callback
) {
700 var address
= handshakeData
.address
.address
;
701 if (typeof kiwi
.connections
[address
] === 'undefined') {
702 kiwi
.connections
[address
] = {count
: 0, sockets
: []};
704 callback(null, true);
705 }).on('connection', kiwi
.websocketConnection
);
706 io
.of('/kiwi').on('error', console
.log
);
715 this.websocketConnection = function (websocket
) {
717 kiwi
.log("New connection!");
718 websocket
.kiwi
= {address
: websocket
.handshake
.address
.address
, buffer
: {list
: []}};
719 con
= kiwi
.connections
[websocket
.kiwi
.address
];
721 if (con
.count
>= kiwi
.config
.max_client_conns
) {
722 websocket
.emit('too_many_connections');
723 websocket
.disconnect();
726 con
.sockets
.push(websocket
);
728 websocket
.sendClientEvent = function (event_name
, data
) {
729 var ev
= kiwi
.kiwi_mod
.run(event_name
, data
, {websocket
: this});
734 //data.event = event_name;
735 websocket
.emit('irc', {command
:event_name
, data
:data
});
738 websocket
.sendServerLine = function (data
, eol
, callback
) {
739 if ((arguments
.length
< 3) && (typeof eol
=== 'function')) {
742 eol
= (typeof eol
!== 'string') ? '\r\n' : eol
;
745 websocket
.ircConnection
.write(data
+ eol
, 'utf-8', callback
);
749 websocket
.on('kiwi', kiwi
.websocketKiwiMessage
);
750 websocket
.on('irc', kiwi
.websocketMessage
);
751 websocket
.on('disconnect', kiwi
.websocketDisconnect
);
752 websocket
.on('error', console
.log
);
757 this.IRCConnection = function (websocket
, nick
, host
, port
, ssl
, password
, callback
) {
764 events
.EventEmitter
.call(this);
766 onConnectHandler = function () {
767 that
.IRC
.nick
= nick
;
768 // Send the login data
769 dns
.reverse(websocket
.kiwi
.address
, function (err
, domains
) {
770 websocket
.kiwi
.hostname
= (err
) ? websocket
.kiwi
.address
: _
.first(domains
);
771 if ((kiwi
.config
.webirc
) && (kiwi
.config
.webirc_pass
[host
])) {
772 websocket
.sendServerLine('WEBIRC ' + kiwi
.config
.webirc_pass
[host
] + ' KiwiIRC ' + websocket
.kiwi
.hostname
+ ' ' + websocket
.kiwi
.address
);
775 websocket
.sendServerLine('PASS ' + password
);
777 websocket
.sendServerLine('CAP LS');
778 websocket
.sendServerLine('NICK ' + nick
);
779 websocket
.sendServerLine('USER kiwi_' + nick
.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick
);
781 that
.connected
= true;
782 that
.emit('connect');
787 ircSocket
= net
.createConnection(port
, host
);
788 ircSocket
.on('connect', onConnectHandler
);
790 ircSocket
= tls
.connect(port
, host
, {}, onConnectHandler
);
793 ircSocket
.setEncoding('utf-8');
794 this.IRC
= {options
: {}, CAP
: {negotiating
: true, requested
: [], enabled
: []}, registered
: false};
796 this.on('error', function (e
) {
797 if (that
.IRC
.registered
) {
798 websocket
.emit('disconnect');
800 websocket
.emit('error', e
.message
);
804 ircSocket
.on('error', function (e
) {
805 that
.connected
= false;
806 that
.emit('error', e
);
810 if (typeof callback
=== 'function') {
811 this.on('connect', callback
);
814 regex
= /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
815 ircSocket
.holdLast
= false;
817 ircSocket
.on('data', function (data
) {
819 if ((ircSocket
.holdLast
) && (ircSocket
.held
!== '')) {
820 data
= ircSocket
.held
+ data
;
821 ircSocket
.holdLast
= false;
824 if (data
.substr(-1) !== '\n') {
825 ircSocket
.holdLast
= true;
827 data
= data
.split("\n");
828 for (i
= 0; i
< data
.length
; i
++) {
830 if ((ircSocket
.holdLast
) && (i
=== data
.length
- 1)) {
831 ircSocket
.held
= data
[i
];
835 // We have a complete line of data, parse it!
836 msg
= regex
.exec(data
[i
].replace(/^\r+|\r+$/, ''));
842 hostname
: msg
[4] || '',
844 params
: msg
[6] || '',
845 trailing
: (msg
[7]) ? msg
[7].trim() : ''
847 that
.emit('irc_' + msg
.command
.toUpperCase(), msg
);
848 if (that
.listeners('irc_' + msg
.command
.toUpperCase()).length
< 1) {
849 kiwi
.log("Unknown command (" + String(msg
.command
).toUpperCase() + ")");
852 kiwi
.log("Malformed IRC line: " + data
[i
].replace(/^\r+|\r+$/, ''));
859 ircSocket
.on('connect', callback
);
862 ircSocket
.on('end', function () {
863 that
.connected
= false;
864 that
.emit('disconnect', false);
867 ircSocket
.on('close', function (had_error
) {
868 that
.connected
= false;
869 that
.emit('disconnect', had_error
);
872 ircSocket
.on('timeout', function () {
874 that
.connected
= false;
875 that
.emit('error', {message
: 'Connection timed out'});
878 ircSocket
.on('drain', function () {
882 this.write = function (data
, encoding
, callback
) {
883 ircSocket
.write(data
, encoding
, callback
);
886 this.end = function (data
, encoding
, callback
) {
887 that
.connected
= false;
888 ircSocket
.end(data
, encoding
, callback
);
891 this.destroySoon = function () {
892 ircSocket
.destroySoon();
895 bound_events
= kiwi
.bindIRCCommands(this, websocket
);
897 this.rebindIRCCommands = function () {
898 _
.each(bound_events
, function (event
) {
899 that
.removeListener(event
.command
, event
.listener
);
901 bound_events
= kiwi
.bindIRCCommands(that
, websocket
);
904 that
.on('error', console
.log
);
910 this.websocketKiwiMessage = function (websocket
, msg
, callback
) {
911 websocket
.ircConnection
= new kiwi
.IRCConnection(websocket
, msg
.nick
, msg
.hostname
, msg
.port
, msg
.ssl
, msg
.password
, callback
);
915 this.websocketMessage = function (websocket
, msg
, callback
) {
916 var args
, obj
, channels
, keys
;
918 if ((callback
) && (typeof (callback
) !== 'function')) {
922 msg
.data
= JSON
.parse(msg
.data
);
924 kiwi
.log('[app.websocketMessage] JSON parsing error ' + msg
.data
);
927 args
= msg
.data
.args
;
928 switch (msg
.data
.method
) {
930 if ((args
.target
) && (args
.msg
)) {
931 obj
= kiwi
.kiwi_mod
.run('msgsend', args
, {websocket
: websocket
});
933 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ' :' + args
.msg
, callback
);
938 if ((args
.target
) && (args
.type
)) {
940 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ' :' + String
.fromCharCode(1) + args
.type
.toUpperCase() + ' ' + args
.params
+ String
.fromCharCode(1), callback
);
942 websocket
.sendServerLine('NOTICE ' + args
.target
+ ' :' + String
.fromCharCode(1) + args
.type
.toUpperCase() + ' ' + args
.params
+ String
.fromCharCode(1), callback
);
948 websocket
.sendServerLine(args
.data
, callback
);
953 channels
= args
.channel
.split(",");
954 keys
= (args
.key
) ? args
.key
.split(",") : [];
955 _
.each(channels
, function (chan
, index
) {
956 websocket
.sendServerLine('JOIN ' + chan
+ ' ' + (keys
[index
] || ''), callback
);
963 _
.each(args
.channel
.split(","), function (chan
) {
964 websocket
.sendServerLine('PART ' + chan
, callback
);
972 websocket
.sendServerLine('TOPIC ' + args
.channel
+ ' :' + args
.topic
, callback
);
974 websocket
.sendServerLine('TOPIC ' + args
.channel
, callback
);
980 if ((args
.channel
) && (args
.nick
)) {
981 websocket
.sendServerLine('KICK ' + args
.channel
+ ' ' + args
.nick
+ ':' + args
.reason
, callback
);
986 websocket
.ircConnection
.end('QUIT :' + args
.message
+ '\r\n');
987 websocket
.sentQUIT
= true;
988 websocket
.ircConnection
.destroySoon();
989 websocket
.disconnect();
993 if ((args
.target
) && (args
.msg
)) {
994 websocket
.sendServerLine('NOTICE ' + args
.target
+ ' :' + args
.msg
, callback
);
999 if ((args
.target
) && (args
.mode
)) {
1000 websocket
.sendServerLine('MODE ' + args
.target
+ ' ' + args
.mode
+ ' ' + args
.params
, callback
);
1006 websocket
.sendServerLine('NICK ' + args
.nick
, callback
);
1011 if ((args
.target
) && (args
.data
)) {
1012 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ': ' + String
.fromCharCode(1) + 'KIWI ' + args
.data
+ String
.fromCharCode(1), callback
);
1018 // kiwi.log("Caught error (app.websocketMessage): " + e);
1024 this.websocketDisconnect = function (websocket
) {
1027 if ((!websocket
.sentQUIT
) && (websocket
.ircConnection
.connected
)) {
1029 websocket
.ircConnection
.end('QUIT :' + kiwi
.config
.quit_message
+ '\r\n');
1030 websocket
.sentQUIT
= true;
1031 websocket
.ircConnection
.destroySoon();
1035 con
= kiwi
.connections
[websocket
.kiwi
.address
];
1037 con
.sockets
= _
.reject(con
.sockets
, function (sock
) {
1038 return sock
=== websocket
;
1047 this.rehash = function () {
1049 reload_config
= kiwi
.loadConfig();
1051 // If loading the new config errored out, dont attempt any changes
1052 if (reload_config
=== false) {
1056 // We just want the settings that have been changed
1057 changes
= reload_config
[1];
1059 if (Object
.keys(changes
).length
!== 0) {
1060 kiwi
.log('%s config changes: \n', Object
.keys(changes
).length
, changes
);
1061 for (i
in changes
) {
1064 kiwi
.websocketListen(kiwi
.config
.servers
, kiwi
.httpHandler
);
1065 delete changes
.ports
;
1066 delete changes
.bind_address
;
1067 delete changes
.ssl_key
;
1068 delete changes
.ssl_cert
;
1073 delete changes
.user
;
1074 delete changes
.group
;
1078 kiwi
.kiwi_mod
.loadModules(kiwi_root
, kiwi
.config
);
1079 kiwi
.kiwi_mod
.printMods();
1080 delete changes
.module_dir
;
1081 delete changes
.modules
;
1087 // Also clear the kiwi.cached javascript and HTML
1088 if (kiwi
.config
.handle_http
) {
1089 kiwi
.cache
= {alljs
: '', html
: []};
1100 * KiwiIRC controlling via STDIN
1102 this.manageControll = function (data
) {
1103 var parts
= data
.toString().trim().split(' '),
1104 connections_cnt
= 0,
1108 kiwi
.log('Rehashing...');
1109 kiwi
.log(kiwi
.rehash() ? 'Rehash complete' : 'Rehash failed');
1113 kiwi
.log('Recoding...');
1114 kiwi
.log(kiwi
.recode() ? 'Recode complete' : 'Recode failed');
1118 if (parts
[1] === 'reload') {
1120 kiwi
.log('Usage: mod reload module_name');
1124 kiwi
.log('Reloading module (' + parts
[2] + ')..');
1125 kiwi
.kiwi_mod
.reloadModule(parts
[2]);
1126 } else if (parts
[1] === 'list') {
1127 kiwi
.kiwi_mod
.printMods();
1132 if (parts
[1] === 'clear') {
1133 kiwi
.cache
.html
= {};
1134 kiwi
.cache
.alljs
= '';
1135 kiwi
.log('HTML cache cleared');
1140 for (i
in kiwi
.connections
) {
1141 connections_cnt
= connections_cnt
+ parseInt(kiwi
.connections
[i
].count
, 10);
1143 kiwi
.log(connections_cnt
.toString() + ' connected clients');
1147 kiwi
.log('Unknown command \'' + parts
[0] + '\'');