1 /*jslint sloppy: true, continue: true, forin: true, regexp: true, undef: false, node: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
21 this.init = function (objs
) {
36 starttls
= objs
.starttls
;
37 kiwi
= require('./kiwi.js');
39 util
.inherits(this.IRCConnection
, events
.EventEmitter
);
48 * Some process changes
50 this.setTitle = function () {
51 process
.title
= 'kiwiirc';
54 this.changeUser = function () {
55 if (typeof kiwi
.config
.group
!== 'undefined' && kiwi
.config
.group
!== '') {
57 process
.setgid(kiwi
.config
.group
);
59 kiwi
.log('Failed to set gid: ' + err
);
64 if (typeof kiwi
.config
.user
!== 'undefined' && kiwi
.config
.user
!== '') {
66 process
.setuid(kiwi
.config
.user
);
68 kiwi
.log('Failed to set uid: ' + e
);
87 RPL_WHOISSERVER
: '312',
88 RPL_WHOISOPERATOR
: '313',
90 RPL_ENDOFWHOIS
: '318',
91 RPL_WHOISCHANNELS
: '319',
97 RPL_TOPICWHOTIME
: '333',
99 RPL_ENDOFNAMES
: '366',
101 RPL_ENDOFBANLIST
: '368',
103 RPL_MOTDSTART
: '375',
104 RPL_ENDOFMOTD
: '376',
105 RPL_WHOISMODES
: '379',
106 ERR_NOSUCHNICK
: '401',
107 ERR_CANNOTSENDTOCHAN
: '404',
108 ERR_TOOMANYCHANNELS
: '405',
109 ERR_NICKNAMEINUSE
: '433',
110 ERR_USERNOTINCHANNEL
: '441',
111 ERR_NOTONCHANNEL
: '442',
112 ERR_NOTREGISTERED
: '451',
113 ERR_LINKCHANNEL
: '470',
114 ERR_CHANNELISFULL
: '471',
115 ERR_INVITEONLYCHAN
: '473',
116 ERR_BANNEDFROMCHAN
: '474',
117 ERR_BADCHANNELKEY
: '475',
118 ERR_CHANOPRIVSNEEDED
: '482',
124 this.bindIRCCommands = function (irc_connection
, websocket
) {
125 var bound_events
= [];
127 irc_connection
.on('irc_PING', function (msg
) {
128 websocket
.sendServerLine('PONG ' + msg
.trailing
);
130 bound_events
.push('irc_PING');
132 irc_connection
.on('irc_' + ircNumerics
.RPL_WELCOME
, function (msg
) {
133 if (irc_connection
.IRC
.CAP
.negotiating
) {
134 irc_connection
.IRC
.CAP
.negotiating
= false;
135 irc_connection
.IRC
.CAP
.enabled
= [];
136 irc_connection
.IRC
.CAP
.requested
= [];
137 irc_connection
.IRC
.registered
= true;
139 var nick
= msg
.params
.split(' ')[0];
140 websocket
.sendClientEvent('connect', {connected
: true, host
: null, nick
: nick
});
142 bound_events
.push('irc_' + ircNumerics
.RPL_WELCOME
);
144 irc_connection
.on('irc_' + ircNumerics
.RPL_ISUPPORT
, function (msg
) {
145 var opts
= msg
.params
.split(" "),
151 for (i
= 0; i
< opts
.length
; i
++) {
152 opt
= opts
[i
].split("=", 2);
153 opt
[0] = opt
[0].toUpperCase();
154 irc_connection
.IRC
.options
[opt
[0]] = (typeof opt
[1] !== 'undefined') ? opt
[1] : true;
155 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES', 'NAMESX'], opt
[0])) {
156 if (opt
[0] === 'PREFIX') {
157 regex
= /\(([^)]*)\)(.*)/;
158 matches
= regex
.exec(opt
[1]);
159 if ((matches
) && (matches
.length
=== 3)) {
160 irc_connection
.IRC
.options
[opt
[0]] = [];
161 for (j
= 0; j
< matches
[2].length
; j
++) {
162 irc_connection
.IRC
.options
[opt
[0]].push({symbol
: matches
[2].charAt(j
), mode
: matches
[1].charAt(j
)});
167 if (opt
[0] === 'NAMESX') {
168 websocket
.sendServerLine('PROTOCTL NAMESX');
173 websocket
.sendClientEvent('options', {server
: '', "options": irc_connection
.IRC
.options
});
175 bound_events
.push('irc_' + ircNumerics
.RPL_ISUPPORT
);
177 irc_connection
.on('irc_' + ircNumerics
.RPL_ENDOFWHOIS
, function (msg
) {
178 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: true});
180 bound_events
.push('irc_' + ircNumerics
.RPL_ENDOFWHOIS
);
182 irc_connection
.on('irc_' + ircNumerics
.RPL_WHOISUSER
, function (msg
) {
183 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
185 bound_events
.push('irc_' + ircNumerics
.RPL_WHOISUSER
);
187 irc_connection
.on('irc_' + ircNumerics
.RPL_WHOISSERVER
, function (msg
) {
188 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
190 bound_events
.push('irc_' + ircNumerics
.RPL_WHOISSERVER
);
192 irc_connection
.on('irc_' + ircNumerics
.RPL_WHOISOPERATOR
, function (msg
) {
193 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
195 bound_events
.push('irc_' + ircNumerics
.RPL_WHOISOPERATOR
);
197 irc_connection
.on('irc_' + ircNumerics
.RPL_WHOISCHANNELS
, function (msg
) {
198 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
200 bound_events
.push('irc_' + ircNumerics
.RPL_WHOISCHANNELS
);
202 irc_connection
.on('irc_' + ircNumerics
.RPL_WHOISMODES
, function (msg
) {
203 websocket
.sendClientEvent('whois', {server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
, end
: false});
205 bound_events
.push('irc_' + ircNumerics
.RPL_WHOISMODES
);
207 irc_connection
.on('irc_' + ircNumerics
.RPL_LISTSTART
, function (msg
) {
208 websocket
.sendClientEvent('list_start', {server
: ''});
209 websocket
.kiwi
.buffer
.list
= [];
211 bound_events
.push('irc_' + ircNumerics
.RPL_LISTSTART
);
213 irc_connection
.on('irc_' + ircNumerics
.RPL_LISTEND
, function (msg
) {
214 if (websocket
.kiwi
.buffer
.list
.length
> 0) {
215 websocket
.kiwi
.buffer
.list
= _
.sortBy(websocket
.kiwi
.buffer
.list
, function (channel
) {
216 return channel
.num_users
;
218 websocket
.sendClientEvent('list_channel', {chans
: websocket
.kiwi
.buffer
.list
});
219 websocket
.kiwi
.buffer
.list
= [];
221 websocket
.sendClientEvent('list_end', {server
: ''});
223 bound_events
.push('irc_' + ircNumerics
.RPL_LISTEND
);
225 irc_connection
.on('irc_' + ircNumerics
.RPL_LIST
, function (msg
) {
226 var parts
, channel
, num_users
, topic
;
228 parts
= msg
.params
.split(' ');
230 num_users
= parts
[2];
231 topic
= msg
.trailing
;
233 //websocket.sendClientEvent('list_channel', {
234 websocket
.kiwi
.buffer
.list
.push({
239 num_users
: parseInt(num_users
, 10)
242 if (websocket
.kiwi
.buffer
.list
.length
> 200) {
243 websocket
.kiwi
.buffer
.list
= _
.sortBy(websocket
.kiwi
.buffer
.list
, function (channel
) {
244 return channel
.num_users
;
246 websocket
.sendClientEvent('list_channel', {chans
: websocket
.kiwi
.buffer
.list
});
247 websocket
.kiwi
.buffer
.list
= [];
250 bound_events
.push('irc_' + ircNumerics
.RPL_LIST
);
252 irc_connection
.on('irc_' + ircNumerics
.RPL_WHOISIDLE
, function (msg
) {
253 var params
= msg
.params
.split(" ", 4),
254 rtn
= {server
: '', nick
: params
[1], idle
: params
[2]};
256 rtn
.logon
= params
[3];
258 websocket
.sendClientEvent('whois', rtn
);
260 bound_events
.push('irc_' + ircNumerics
.RPL_WHOISIDLE
);
262 irc_connection
.on('irc_' + ircNumerics
.RPL_MOTD
, function (msg
) {
263 websocket
.kiwi
.buffer
.motd
+= msg
.trailing
+ '\n';
265 bound_events
.push('irc_' + ircNumerics
.RPL_MOTD
);
267 irc_connection
.on('irc_' + ircNumerics
.RPL_MOTDSTART
, function (msg
) {
268 websocket
.kiwi
.buffer
.motd
= '';
270 bound_events
.push('irc_' + ircNumerics
.RPL_MOTDSTART
);
272 irc_connection
.on('irc_' + ircNumerics
.RPL_ENDOFMOTD
, function (msg
) {
273 websocket
.sendClientEvent('motd', {server
: '', 'msg': websocket
.kiwi
.buffer
.motd
});
275 bound_events
.push('irc_' + ircNumerics
.RPL_ENDOFMOTD
);
277 irc_connection
.on('irc_' + ircNumerics
.RPL_NAMEREPLY
, function (msg
) {
278 var params
= msg
.params
.split(" "),
280 users
= msg
.trailing
.split(" "),
284 _
.each(users
, function (user
) {
285 var j
, k
, modes
= [];
286 for (j
= 0; j
< user
.length
; j
++) {
287 for (k
= 0; k
< irc_connection
.IRC
.options
.PREFIX
.length
; k
++) {
288 if (user
.charAt(j
) === irc_connection
.IRC
.options
.PREFIX
[k
].symbol
) {
289 modes
.push(irc_connection
.IRC
.options
.PREFIX
[k
].mode
);
293 nicklist
.push({nick
: user
, modes
: modes
});
295 websocket
.sendClientEvent('userlist', {server
: '', 'users': nicklist
, channel
: chan
});
301 websocket
.sendClientEvent('userlist', {server
: '', "users": nicklist
, channel
: chan
});
306 bound_events
.push('irc_' + ircNumerics
.RPL_NAMEREPLY
);
308 irc_connection
.on('irc_' + ircNumerics
.RPL_ENDOFNAMES
, function (msg
) {
309 websocket
.sendClientEvent('userlist_end', {server
: '', channel
: msg
.params
.split(" ")[1]});
311 bound_events
.push('irc_' + ircNumerics
.RPL_ENDOFNAMES
);
313 irc_connection
.on('irc_' + ircNumerics
.ERR_LINKCHANNEL
, function (msg
) {
314 var params
= msg
.params
.split(" ");
315 websocket
.sendClientEvent('channel_redirect', {from: params
[1], to
: params
[2]});
317 bound_events
.push('irc_' + ircNumerics
.ERR_LINKCHANNEL
);
319 irc_connection
.on('irc_' + ircNumerics
.ERR_NOSUCHNICK
, function (msg
) {
320 websocket
.sendClientEvent('irc_error', {error
: 'no_such_nick', nick
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
322 bound_events
.push('irc_' + ircNumerics
.ERR_NOSUCHNICK
);
324 irc_connection
.on('irc_' + ircNumerics
.RPL_BANLIST
, function (msg
) {
325 var params
= msg
.params
.split(" ");
327 websocket
.sendClientEvent('banlist', {server
: '', channel
: params
[1], banned
: params
[2], banned_by
: params
[3], banned_at
: params
[4]});
329 bound_events
.push('irc_' + ircNumerics
.RPL_BANLIST
);
331 irc_connection
.on('irc_' + ircNumerics
.RPL_ENDOFBANLIST
, function (msg
) {
332 websocket
.sendClientEvent('banlist_end', {server
: '', channel
: msg
.params
.split(" ")[1]});
334 bound_events
.push('irc_' + ircNumerics
.RPL_ENDOFBANLIST
);
336 irc_connection
.on('irc_JOIN', function (msg
) {
339 // Some BNC's send malformed JOIN causing the channel to be as a
340 // parameter instead of trailing.
341 if (typeof msg
.trailing
=== 'string' && msg
.trailing
!== '') {
342 channel
= msg
.trailing
;
343 } else if (typeof msg
.params
=== 'string' && msg
.params
!== '') {
344 channel
= msg
.params
;
347 websocket
.sendClientEvent('join', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: channel
});
348 if (msg
.nick
=== irc_connection
.IRC
.nick
) {
349 websocket
.sendServerLine('NAMES ' + msg
.trailing
);
352 bound_events
.push('irc_JOIN');
354 irc_connection
.on('irc_PART', function (msg
) {
355 websocket
.sendClientEvent('part', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), message
: msg
.trailing
});
357 bound_events
.push('irc_PART');
359 irc_connection
.on('irc_KICK', function (msg
) {
360 var params
= msg
.params
.split(" ");
361 websocket
.sendClientEvent('kick', {kicked
: params
[1], nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: params
[0].trim(), message
: msg
.trailing
});
363 bound_events
.push('irc_KICK');
365 irc_connection
.on('irc_QUIT', function (msg
) {
366 websocket
.sendClientEvent('quit', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, message
: msg
.trailing
});
368 bound_events
.push('irc_QUIT');
370 irc_connection
.on('irc_NOTICE', function (msg
) {
371 if ((msg
.trailing
.charAt(0) === String
.fromCharCode(1)) && (msg
.trailing
.charAt(msg
.trailing
.length
- 1) === String
.fromCharCode(1))) {
372 // It's a CTCP response
373 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)});
375 websocket
.sendClientEvent('notice', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, target
: msg
.params
.trim(), msg
: msg
.trailing
});
378 bound_events
.push('irc_NOTICE');
380 irc_connection
.on('irc_NICK', function (msg
) {
381 websocket
.sendClientEvent('nick', {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, newnick
: msg
.trailing
});
383 bound_events
.push('irc_NICK');
385 irc_connection
.on('irc_TOPIC', function (msg
) {
386 var obj
= {nick
: msg
.nick
, channel
: msg
.params
, topic
: msg
.trailing
};
387 websocket
.sendClientEvent('topic', obj
);
389 bound_events
.push('irc_TOPIC');
391 irc_connection
.on('irc_' + ircNumerics
.RPL_TOPIC
, function (msg
) {
392 var obj
= {nick
: '', channel
: msg
.params
.split(" ")[1], topic
: msg
.trailing
};
393 websocket
.sendClientEvent('topic', obj
);
395 bound_events
.push('irc_' + ircNumerics
.RPL_TOPIC
);
397 irc_connection
.on('irc_' + ircNumerics
.RPL_NOTOPIC
, function (msg
) {
398 var obj
= {nick
: '', channel
: msg
.params
.split(" ")[1], topic
: ''};
399 websocket
.sendClientEvent('topic', obj
);
401 bound_events
.push('irc_' + ircNumerics
.RPL_NOTOPIC
);
403 irc_connection
.on('irc_' + ircNumerics
.RPL_TOPICWHOTIME
, function (msg
) {
404 var parts
= msg
.params
.split(' '),
408 obj
= {nick
: nick
, channel
: channel
, when
: when
};
409 websocket
.sendClientEvent('topicsetby', obj
);
411 bound_events
.push('irc_' + ircNumerics
.RPL_TOPICWHOTIME
);
413 irc_connection
.on('irc_MODE', function (msg
) {
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 bound_events
.push('irc_MODE');
436 irc_connection
.on('irc_PRIVMSG', function (msg
) {
437 var tmp
, namespace, obj
;
438 if ((msg
.trailing
.charAt(0) === String
.fromCharCode(1)) && (msg
.trailing
.charAt(msg
.trailing
.length
- 1) === String
.fromCharCode(1))) {
439 // It's a CTCP request
440 if (msg
.trailing
.substr(1, 6) === 'ACTION') {
441 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)});
442 } else if (msg
.trailing
.substr(1, 4) === 'KIWI') {
443 tmp
= msg
.trailing
.substr(6, msg
.trailing
.length
- 2);
444 namespace = tmp
.split(' ', 1)[0];
445 websocket
.sendClientEvent('kiwi', {namespace: namespace, data
: tmp
.substr(namespace.length
+ 1)});
447 } else if (msg
.trailing
.substr(1, 7) === 'VERSION') {
448 irc_connection
.write('NOTICE ' + msg
.nick
+ ' :' + String
.fromCharCode(1) + 'VERSION KiwiIRC' + String
.fromCharCode(1) + '\r\n');
450 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)});
453 obj
= {nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), msg
: msg
.trailing
};
454 websocket
.sendClientEvent('msg', obj
);
457 bound_events
.push('irc_PRIVMSG');
459 irc_connection
.on('irc_CAP', function (msg
) {
460 var caps
= kiwi
.config
.cap_options
,
461 options
= msg
.trailing
.split(" "),
464 switch (_
.last(msg
.params
.split(" "))) {
467 _
.each(_
.intersect(caps
, options
), function (cap
) {
472 irc_connection
.IRC
.CAP
.requested
.push(cap
);
474 if (opts
.length
> 0) {
475 websocket
.sendServerLine('CAP REQ :' + opts
);
477 websocket
.sendServerLine('CAP END');
480 /*if (_.include(options, 'tls')) {
481 websocket.sendServerLine('STARTTLS');
482 ircSocket.IRC.CAP.requested.push('tls');
486 _
.each(options
, function (cap
) {
487 irc_connection
.IRC
.CAP
.enabled
.push(cap
);
489 if (_
.last(msg
.params
.split(" ")) !== '*') {
490 irc_connection
.IRC
.CAP
.requested
= [];
491 irc_connection
.IRC
.CAP
.negotiating
= false;
492 websocket
.sendServerLine('CAP END');
496 irc_connection
.IRC
.CAP
.requested
= [];
497 irc_connection
.IRC
.CAP
.negotiating
= false;
498 websocket
.sendServerLine('CAP END');
502 bound_events
.push('irc_CAP');
503 /*case ircNumerics.RPL_STARTTLS:
506 listeners = ircSocket.listeners('data');
507 ircSocket.removeAllListeners('data');
508 ssl_socket = starttls(ircSocket, {}, function () {
509 ssl_socket.on("data", function (data) {
510 ircSocketDataHandler(data, websocket, ssl_socket);
512 ircSocket = ssl_socket;
514 _.each(listeners, function (listener) {
515 ircSocket.addListener('data', listener);
523 irc_connection
.on('irc_' + ircNumerics
.ERR_CANNOTSENDTOCHAN
, function (msg
) {
524 websocket
.sendClientEvent('irc_error', {error
: 'cannot_send_to_chan', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
526 bound_events
.push('irc_' + ircNumerics
.ERR_CANNOTSENDTOCHAN
);
528 irc_connection
.on('irc_' + ircNumerics
.ERR_TOOMANYCHANNELS
, function (msg
) {
529 websocket
.sendClientEvent('irc_error', {error
: 'too_many_channels', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
531 bound_events
.push('irc_' + ircNumerics
.ERR_TOOMANYCHANNELS
);
533 irc_connection
.on('irc_' + ircNumerics
.ERR_USERNOTINCHANNEL
, function (msg
) {
534 var params
= msg
.params
.split(" ");
535 websocket
.sendClientEvent('irc_error', {error
: 'user_not_in_channel', nick
: params
[0], channel
: params
[1], reason
: msg
.trainling
});
537 bound_events
.push('irc_' + ircNumerics
.ERR_USERNOTINCHANNEL
);
539 irc_connection
.on('irc_' + ircNumerics
.ERR_NOTONCHANNEL
, function (msg
) {
540 websocket
.sendClientEvent('irc_error', {error
: 'not_on_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
542 bound_events
.push('irc_' + ircNumerics
.ERR_NOTONCHANNEL
);
544 irc_connection
.on('irc_' + ircNumerics
.ERR_CHANNELISFULL
, function (msg
) {
545 websocket
.sendClientEvent('irc_error', {error
: 'channel_is_full', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
547 bound_events
.push('irc_' + ircNumerics
.ERR_CHANNELISFULL
);
549 irc_connection
.on('irc_' + ircNumerics
.ERR_INVITEONLYCHAN
, function (msg
) {
550 websocket
.sendClientEvent('irc_error', {error
: 'invite_only_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
552 bound_events
.push('irc_' + ircNumerics
.ERR_INVITEONLYCHAN
);
554 irc_connection
.on('irc_' + ircNumerics
.ERR_BANNEDFROMCHAN
, function (msg
) {
555 websocket
.sendClientEvent('irc_error', {error
: 'banned_from_channel', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
557 bound_events
.push('irc_' + ircNumerics
.ERR_BANNEDFROMCHAN
);
559 irc_connection
.on('irc_' + ircNumerics
.ERR_BADCHANNELKEY
, function (msg
) {
560 websocket
.sendClientEvent('irc_error', {error
: 'bad_channel_key', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
562 bound_events
.push('irc_' + ircNumerics
.ERR_BADCHANNELKEY
);
564 irc_connection
.on('irc_' + ircNumerics
.ERR_CHANOPRIVSNEEDED
, function (msg
) {
565 websocket
.sendClientEvent('irc_error', {error
: 'chanop_privs_needed', channel
: msg
.params
.split(" ")[1], reason
: msg
.trailing
});
567 bound_events
.push('irc_' + ircNumerics
.ERR_CHANOPRIVSNEEDED
);
569 irc_connection
.on('irc_' + ircNumerics
.ERR_NICKNAMEINUSE
, function (msg
) {
570 websocket
.sendClientEvent('irc_error', {error
: 'nickname_in_use', nick
: _
.last(msg
.params
.split(" ")), reason
: msg
.trailing
});
572 bound_events
.push('irc_' + ircNumerics
.ERR_NICKNAMEINUSE
);
574 irc_connection
.on('irc_ERROR', function (msg
) {
575 irc_connection
.end();
576 websocket
.sendClientEvent('irc_error', {error
: 'error', reason
: msg
.trailing
});
577 websocket
.disconnect();
579 bound_events
.push('irc_ERROR');
581 irc_connection
.on('irc_' + ircNumerics
.ERR_NOTREGISTERED
, function (msg
) {
582 if (irc_connection
.IRC
.registered
) {
583 kiwi
.log('Kiwi thinks user is registered, but the IRC server thinks differently');
586 bound_events
.push('irc_' + ircNumerics
.ERR_NOTREGISTERED
);
591 this.rebindIRCCommands = function () {
592 _
.each(kiwi
.connections
, function (con
) {
593 _
.each(con
.sockets
, function (sock
) {
594 sock
.ircConnection
.rebindIRCCommands();
600 this.httpHandler = function (request
, response
) {
601 var uri
, uri_parts
, subs
, useragent
, agent
, server_set
, server
, nick
, debug
, touchscreen
, hash
,
602 min
= {}, public_http_path
, port
, ssl
, obj
, args
, ircuri
, target
, modifiers
, query
,
603 secure
= (typeof request
.client
.encrypted
=== 'object');
606 if (kiwi
.config
.handle_http
) {
607 // Run through any plugins..
608 args
= {request
: request
, response
: response
, ssl
: secure
};
609 obj
= kiwi
.kiwi_mod
.run('http', args
);
613 response
= args
.response
;
615 uri
= url
.parse(request
.url
, true);
616 uri_parts
= uri
.pathname
.split('/');
618 subs
= uri
.pathname
.substr(0, 4);
619 public_http_path
= kiwi
.kiwi_root
+ '/' + kiwi
.config
.public_http
;
621 if (typeof uri
.query
.ircuri
!== 'undefined') {
622 ircuri
= url
.parse(uri
.query
.ircuri
, true);
623 if (ircuri
.protocol
=== 'irc:') {
624 uri_parts
= /^\/([^,\?]*)((,[^,\?]*)*)?$/.exec(ircuri
.pathname
);
625 target
= uri_parts
[1];
626 modifiers
= (typeof uri_parts
[2] !== 'undefined') ? uri_parts
[2].split(',') : [];
627 query
= ircuri
.query
;
629 nick
= _
.detect(modifiers
, function (mod
) {
630 return mod
=== ',isnick';
632 kiwi
.log(request
.headers
);
633 response
.statusCode
= 303;
634 response
.setHeader('Location', 'http' + ((secure
) ? 's' : '') + '://' + request
.headers
.host
+ '/client/' + ircuri
.host
+ '/' + ((!nick
) ? target
: ''));
637 } else if (uri
.pathname
=== '/js/all.js') {
638 if (kiwi
.cache
.alljs
=== '') {
640 min
.underscore
= fs
.readFileSync(public_http_path
+ 'js/underscore.min.js');
641 min
.util
= fs
.readFileSync(public_http_path
+ 'js/util.js');
642 min
.gateway
= fs
.readFileSync(public_http_path
+ 'js/gateway.js');
643 min
.front
= fs
.readFileSync(public_http_path
+ 'js/front.js');
644 min
.front_events
= fs
.readFileSync(public_http_path
+ 'js/front.events.js');
645 min
.front_ui
= fs
.readFileSync(public_http_path
+ 'js/front.ui.js');
646 min
.iscroll
= fs
.readFileSync(public_http_path
+ 'js/iscroll.js');
647 min
.ast
= jsp
.parse(min
.underscore
+ min
.util
+ min
.gateway
+ min
.front
+ min
.front_events
+ min
.front_ui
+ min
.iscroll
);
648 min
.ast
= pro
.ast_mangle(min
.ast
);
649 min
.ast
= pro
.ast_squeeze(min
.ast
);
650 min
.final_code
= pro
.gen_code(min
.ast
);
651 kiwi
.cache
.alljs
= min
.final_code
;
652 hash
= crypto
.createHash('md5').update(kiwi
.cache
.alljs
);
653 kiwi
.cache
.alljs_hash
= hash
.digest('base64');
655 if (request
.headers
['if-none-match'] === kiwi
.cache
.alljs_hash
) {
656 response
.statusCode
= 304;
658 response
.setHeader('Content-type', 'application/javascript');
659 response
.setHeader('ETag', kiwi
.cache
.alljs_hash
);
660 response
.write(kiwi
.cache
.alljs
);
663 } else if ((subs
=== '/js/') || (subs
=== '/css') || (subs
=== '/img')) {
664 request
.addListener('end', function () {
665 kiwi
.fileServer
.serve(request
, response
);
667 } else if (uri
.pathname
=== '/' || uri_parts
[1] === 'client') {
668 useragent
= (typeof request
.headers
=== 'string') ? request
.headers
['user-agent'] : '';
669 if (useragent
.match(/android/i) !== -1) {
672 } else if (useragent
.match(/iphone/) !== -1) {
675 } else if (useragent
.match(/ipad/) !== -1) {
678 } else if (useragent
.match(/ipod/) !== -1) {
688 debug
= (typeof uri
.query
.debug
!== 'undefined');
690 ssl
= secure
; // ssl is passed to the client
691 port
= ssl
? kiwi
.config
.client_defaults
.port_ssl
: kiwi
.config
.client_defaults
.port
;
692 if (uri_parts
[1] !== 'client') {
694 server_set
= ((typeof uri
.query
.server
!== 'undefined') && (uri
.query
.server
!== ''));
695 server
= uri
.query
.server
|| kiwi
.config
.client_defaults
.server
;
696 nick
= uri
.query
.nick
|| '';
699 server
= kiwi
.config
.client_defaults
.server
;
703 server_set
= ((typeof uri_parts
[2] !== 'undefined') && (uri_parts
[2] !== ''));
704 server
= server_set
? uri_parts
[2] : kiwi
.config
.client_defaults
.server
;
705 if (server
.search(/:/) > 0) {
706 port
= server
.substring(server
.search(/:/) + 1);
707 server
= server
.substring(0, server
.search(/:/));
708 if (port
[0] === '+') {
709 port
= port
.substring(1);
715 nick
= uri
.query
.nick
|| '';
718 // Set the default nick if one isn't provided
723 // Set any random numbers if needed
724 nick
= nick
.replace('?', Math
.floor(Math
.random() * 100000).toString());
726 response
.setHeader('X-Generated-By', 'KiwiIRC');
727 hash
= crypto
.createHash('md5').update(touchscreen
? 't' : 'f')
728 .update(debug
? 't' : 'f')
729 .update(server_set
? 't' : 'f')
730 .update(secure
? 't' : 'f')
732 .update(port
.toString())
733 .update(ssl
? 't' : 'f')
736 .update(JSON
.stringify(kiwi
.config
))
738 if (kiwi
.cache
.html
[hash
]) {
739 if (request
.headers
['if-none-match'] === kiwi
.cache
.html
[hash
].hash
) {
740 response
.statusCode
= 304;
742 response
.setHeader('Etag', kiwi
.cache
.html
[hash
].hash
);
743 response
.setHeader('Content-type', 'text/html');
744 response
.write(kiwi
.cache
.html
[hash
].html
);
748 fs
.readFile(public_http_path
+ 'index.html.jade', 'utf8', function (err
, str
) {
752 html
= kiwi
.jade
.compile(str
)({ "touchscreen": touchscreen
, "debug": debug
, "secure": secure
, "server_set": server_set
, "server": server
, "port": port
, "ssl": ssl
, "nick": nick
, "agent": agent
, "config": kiwi
.config
});
753 hash2
= crypto
.createHash('md5').update(html
).digest('base64');
754 kiwi
.cache
.html
[hash
] = {"html": html
, "hash": hash2
};
755 if (request
.headers
['if-none-match'] === hash2
) {
756 response
.statusCode
= 304;
758 response
.setHeader('Etag', hash2
);
759 response
.setHeader('Content-type', 'text/html');
760 response
.write(html
);
763 response
.statusCode
= 500;
768 response
.statusCode
= 500;
773 } else if (uri
.pathname
.substr(0, 10) === '/socket.io') {
776 response
.statusCode
= 404;
782 kiwi
.log('ERROR app.httpHandler()');
790 this.websocketListen = function (servers
, handler
) {
791 if (kiwi
.httpServers
.length
> 0) {
792 _
.each(kiwi
.httpServers
, function (hs
) {
795 kiwi
.httpsServers
= [];
798 _
.each(servers
, function (server
) {
800 if (server
.secure
=== true) {
801 // Start some SSL server up
803 key
: fs
.readFileSync(__dirname
+ '/' + server
.ssl_key
),
804 cert
: fs
.readFileSync(__dirname
+ '/' + server
.ssl_cert
)
807 // Do we have an intermediate certificate?
808 if (typeof server
.ssl_ca
!== 'undefined') {
809 opts
.ca
= fs
.readFileSync(__dirname
+ '/' + server
.ssl_ca
);
812 hs
= https
.createServer(opts
, handler
);
813 kiwi
.io
.push(ws
.listen(hs
, {secure
: true}));
814 hs
.listen(server
.port
, server
.address
);
815 kiwi
.log('Listening on ' + server
.address
+ ':' + server
.port
.toString() + ' with SSL');
817 // Start some plain-text server up
818 hs
= http
.createServer(handler
);
819 kiwi
.io
.push(ws
.listen(hs
, {secure
: false}));
820 hs
.listen(server
.port
, server
.address
);
821 kiwi
.log('Listening on ' + server
.address
+ ':' + server
.port
.toString() + ' without SSL');
824 kiwi
.httpServers
.push(hs
);
827 _
.each(kiwi
.io
, function (io
) {
828 io
.set('log level', 1);
829 io
.enable('browser client minification');
830 io
.enable('browser client etag');
831 io
.set('transports', kiwi
.config
.transports
);
833 io
.of('/kiwi').authorization(function (handshakeData
, callback
) {
834 var address
= handshakeData
.address
.address
;
835 if (typeof kiwi
.connections
[address
] === 'undefined') {
836 kiwi
.connections
[address
] = {count
: 0, sockets
: []};
838 callback(null, true);
839 }).on('connection', kiwi
.websocketConnection
);
848 this.websocketConnection = function (websocket
) {
850 kiwi
.log("New connection!");
851 websocket
.kiwi
= {address
: websocket
.handshake
.address
.address
, buffer
: {list
: []}};
852 con
= kiwi
.connections
[websocket
.kiwi
.address
];
854 if (con
.count
>= kiwi
.config
.max_client_conns
) {
855 websocket
.emit('too_many_connections');
856 websocket
.disconnect();
859 con
.sockets
.push(websocket
);
861 websocket
.sendClientEvent = function (event_name
, data
) {
862 var ev
= kiwi
.kiwi_mod
.run(event_name
, data
, {websocket
: this});
867 data
.event
= event_name
;
868 websocket
.emit('message', data
);
871 websocket
.sendServerLine = function (data
, eol
, callback
) {
872 if ((arguments
.length
< 3) && (typeof eol
=== 'function')) {
876 eol
= ((typeof eol
=== 'undefined') || (eol
=== null)) ? '\r\n' : eol
;
879 websocket
.ircConnection
.write(data
+ eol
, 'utf-8', callback
);
883 websocket
.on('irc connect', function (nick
, host
, port
, ssl
, password
, callback
) {
884 websocket
.ircConnection
= new kiwi
.IRCConnection(this, nick
, host
, port
, ssl
, password
, callback
);
886 websocket
.on('message', kiwi
.websocketMessage
);
887 websocket
.on('disconnect', kiwi
.websocketDisconnect
);
892 this.IRCConnection = function (websocket
, nick
, host
, port
, ssl
, password
, callback
) {
899 events
.EventEmitter
.call(this);
901 onConnectHandler = function () {
902 that
.IRC
.nick
= nick
;
903 // Send the login data
904 dns
.reverse(websocket
.kiwi
.address
, function (err
, domains
) {
905 websocket
.kiwi
.hostname
= (err
) ? websocket
.kiwi
.address
: _
.first(domains
);
906 if ((kiwi
.config
.webirc
) && (kiwi
.config
.webirc_pass
[host
])) {
907 websocket
.sendServerLine('WEBIRC ' + kiwi
.config
.webirc_pass
[host
] + ' KiwiIRC ' + websocket
.kiwi
.hostname
+ ' ' + websocket
.kiwi
.address
);
910 websocket
.sendServerLine('PASS ' + password
);
912 websocket
.sendServerLine('CAP LS');
913 websocket
.sendServerLine('NICK ' + nick
);
914 websocket
.sendServerLine('USER kiwi_' + nick
.replace(/[^0-9a-zA-Z\-_.]/, '') + ' 0 0 :' + nick
);
916 that
.connected
= true;
917 that
.emit('connect');
922 ircSocket
= net
.createConnection(port
, host
);
923 ircSocket
.on('connect', onConnectHandler
);
925 ircSocket
= tls
.connect(port
, host
, {}, onConnectHandler
);
928 ircSocket
.setEncoding('utf-8');
929 this.IRC
= {options
: {}, CAP
: {negotiating
: true, requested
: [], enabled
: []}, registered
: false};
931 this.on('error', function (e
) {
932 if (that
.IRC
.registered
) {
933 websocket
.emit('disconnect');
935 websocket
.emit('error', e
.message
);
939 ircSocket
.on('error', function (e
) {
940 that
.connected
= false;
941 that
.emit('error', e
);
945 if (typeof callback
=== 'function') {
946 this.on('connect', callback
);
949 regex
= /^(?::(?:([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)|([a-z0-9\x5B-\x60\x7B-\x7D\.\-]+)!([a-z0-9~\.\-_|]+)@?([a-z0-9\.\-:\/]+)?) )?(\S+)(?: (?!:)(.+?))?(?: :(.+))?$/i;
950 ircSocket
.holdLast
= false;
952 ircSocket
.on('data', function (data
) {
954 if ((ircSocket
.holdLast
) && (ircSocket
.held
!== '')) {
955 data
= ircSocket
.held
+ data
;
956 ircSocket
.holdLast
= false;
959 if (data
.substr(-1) !== '\n') {
960 ircSocket
.holdLast
= true;
962 data
= data
.split("\n");
963 for (i
= 0; i
< data
.length
; i
++) {
965 if ((ircSocket
.holdLast
) && (i
=== data
.length
- 1)) {
966 ircSocket
.held
= data
[i
];
970 // We have a complete line of data, parse it!
971 msg
= regex
.exec(data
[i
].replace(/^\r+|\r+$/, ''));
977 hostname
: msg
[4] || '',
979 params
: msg
[6] || '',
980 trailing
: (msg
[7]) ? msg
[7].trim() : ''
982 that
.emit('irc_' + msg
.command
.toUpperCase(), msg
);
983 if (that
.listeners('irc_' + msg
.command
.toUpperCase()).length
< 1) {
984 kiwi
.log("Unknown command (" + String(msg
.command
).toUpperCase() + ")");
987 kiwi
.log("Malformed IRC line: " + data
[i
].replace(/^\r+|\r+$/, ''));
994 ircSocket
.on('connect', callback
);
997 ircSocket
.on('end', function () {
998 that
.connected
= false;
999 that
.emit('disconnect', false);
1002 ircSocket
.on('close', function (had_error
) {
1003 that
.connected
= false;
1004 that
.emit('disconnect', had_error
);
1007 ircSocket
.on('timeout', function () {
1008 ircSocket
.destroy();
1009 that
.connected
= false;
1010 that
.emit('error', {message
: 'Connection timed out'});
1013 ircSocket
.on('drain', function () {
1017 this.write = function (data
, encoding
, callback
) {
1018 ircSocket
.write(data
, encoding
, callback
);
1021 this.end = function (data
, encoding
, callback
) {
1022 that
.connected
= false;
1023 ircSocket
.end(data
, encoding
, callback
);
1026 this.destroySoon = function () {
1027 ircSocket
.destroySoon();
1030 bound_events
= kiwi
.bindIRCCommands(this, websocket
);
1032 this.rebindIRCCommands = function () {
1033 _
.each(bound_events
, function (event
) {
1034 that
.removeAllListeners(event
);
1036 bound_events
= kiwi
.bindIRCCommands(that
, websocket
);
1042 this.websocketMessage = function (websocket
, msg
, callback
) {
1043 var args
, obj
, channels
, keys
;
1045 if ((callback
) && (typeof (callback
) !== 'function')) {
1048 msg
.data
= JSON
.parse(msg
.data
);
1049 args
= msg
.data
.args
;
1050 switch (msg
.data
.method
) {
1052 if ((args
.target
) && (args
.msg
)) {
1053 obj
= kiwi
.kiwi_mod
.run('msgsend', args
, {websocket
: websocket
});
1055 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ' :' + args
.msg
, callback
);
1060 if ((args
.target
) && (args
.type
)) {
1062 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ' :' + String
.fromCharCode(1) + args
.type
.toUpperCase() + ' ' + args
.params
+ String
.fromCharCode(1), callback
);
1064 websocket
.sendServerLine('NOTICE ' + args
.target
+ ' :' + String
.fromCharCode(1) + args
.type
.toUpperCase() + ' ' + args
.params
+ String
.fromCharCode(1), callback
);
1070 websocket
.sendServerLine(args
.data
, callback
);
1075 channels
= args
.channel
.split(",");
1076 keys
= (args
.key
) ? args
.key
.split(",") : [];
1077 _
.each(channels
, function (chan
, index
) {
1078 websocket
.sendServerLine('JOIN ' + chan
+ ' ' + (keys
[index
] || ''), callback
);
1085 _
.each(args
.channel
.split(","), function (chan
) {
1086 websocket
.sendServerLine('PART ' + chan
, callback
);
1094 websocket
.sendServerLine('TOPIC ' + args
.channel
+ ' :' + args
.topic
, callback
);
1096 websocket
.sendServerLine('TOPIC ' + args
.channel
, callback
);
1102 if ((args
.channel
) && (args
.nick
)) {
1103 websocket
.sendServerLine('KICK ' + args
.channel
+ ' ' + args
.nick
+ ':' + args
.reason
, callback
);
1108 websocket
.ircConnection
.end('QUIT :' + args
.message
+ '\r\n');
1109 websocket
.sentQUIT
= true;
1110 websocket
.ircConnection
.destroySoon();
1111 websocket
.disconnect();
1115 if ((args
.target
) && (args
.msg
)) {
1116 websocket
.sendServerLine('NOTICE ' + args
.target
+ ' :' + args
.msg
, callback
);
1121 if ((args
.target
) && (args
.mode
)) {
1122 websocket
.sendServerLine('MODE ' + args
.target
+ ' ' + args
.mode
+ ' ' + args
.params
, callback
);
1128 websocket
.sendServerLine('NICK ' + args
.nick
, callback
);
1133 if ((args
.target
) && (args
.data
)) {
1134 websocket
.sendServerLine('PRIVMSG ' + args
.target
+ ': ' + String
.fromCharCode(1) + 'KIWI ' + args
.data
+ String
.fromCharCode(1), callback
);
1140 kiwi
.log("Caught error: " + e
);
1146 this.websocketDisconnect = function (websocket
) {
1149 if ((!websocket
.sentQUIT
) && (websocket
.ircConnection
.connected
)) {
1151 websocket
.ircConnection
.end('QUIT :' + kiwi
.config
.quit_message
+ '\r\n');
1152 websocket
.sentQUIT
= true;
1153 websocket
.ircConnection
.destroySoon();
1157 con
= kiwi
.connections
[websocket
.kiwi
.address
];
1159 con
.sockets
= _
.reject(con
.sockets
, function (sock
) {
1160 return sock
=== websocket
;
1169 this.rehash = function () {
1171 reload_config
= kiwi
.loadConfig();
1173 // If loading the new config errored out, dont attempt any changes
1174 if (reload_config
=== false) {
1178 // We just want the settings that have been changed
1179 changes
= reload_config
[1];
1181 if (Object
.keys(changes
).length
!== 0) {
1182 kiwi
.log('%s config changes: \n', Object
.keys(changes
).length
, changes
);
1183 for (i
in changes
) {
1186 case 'bind_address':
1189 kiwi
.websocketListen(kiwi
.config
.servers
, kiwi
.httpHandler
);
1190 delete changes
.ports
;
1191 delete changes
.bind_address
;
1192 delete changes
.ssl_key
;
1193 delete changes
.ssl_cert
;
1198 delete changes
.user
;
1199 delete changes
.group
;
1203 kiwi
.kiwi_mod
.loadModules(kiwi_root
, kiwi
.config
);
1204 kiwi
.kiwi_mod
.printMods();
1205 delete changes
.module_dir
;
1206 delete changes
.modules
;
1212 // Also clear the kiwi.cached javascript and HTML
1213 if (kiwi
.config
.handle_http
) {
1214 kiwi
.cache
= {alljs
: '', html
: []};
1225 * KiwiIRC controlling via STDIN
1227 this.manageControll = function (data
) {
1228 var parts
= data
.toString().trim().split(' '),
1229 connections_cnt
= 0,
1233 kiwi
.log('Rehashing...');
1234 kiwi
.log(kiwi
.rehash() ? 'Rehash complete' : 'Rehash failed');
1238 kiwi
.log('Recoding...');
1239 kiwi
.log(kiwi
.recode() ? 'Recode complete' : 'Recode failed');
1243 if (parts
[1] === 'reload') {
1245 kiwi
.log('Usage: mod reload module_name');
1249 kiwi
.log('Reloading module (' + parts
[2] + ')..');
1250 kiwi
.kiwi_mod
.reloadModule(parts
[2]);
1251 } else if (parts
[1] === 'list') {
1252 kiwi
.kiwi_mod
.printMods();
1257 if (parts
[1] === 'clear') {
1258 kiwi
.cache
.html
= {};
1259 kiwi
.cache
.alljs
= '';
1260 kiwi
.log('HTML cache cleared');
1265 for (i
in kiwi
.connections
) {
1266 connections_cnt
= connections_cnt
+ parseInt(kiwi
.connections
[i
].count
, 10);
1268 kiwi
.log(connections_cnt
.toString() + ' connected clients');
1272 kiwi
.log('Unknown command \'' + parts
[0] + '\'');