1 /*jslint regexp: true, confusion: true, undef: false, node: true, sloppy: true, nomen: true, plusplus: true, maxerr: 50, indent: 4 */
3 var tls
= require('tls'),
5 http
= require('http'),
6 https
= require('https'),
9 static_server
= require('node-static'),
10 ws
= require('socket.io'),
11 jade
= require('jade'),
12 _
= require('./lib/underscore.min.js'),
13 starttls
= require('./lib/starttls.js');
15 var config
= JSON
.parse(fs
.readFileSync(__dirname
+ '/config.json', 'ascii'));
21 RPL_WHOISSERVER
: '312',
22 RPL_WHOISOPERATOR
: '313',
24 RPL_ENDOFWHOIS
: '318',
25 RPL_WHOISCHANNELS
: '319',
28 RPL_ENDOFNAMES
: '366',
30 RPL_WHOISMODES
: '379',
31 ERR_NOSUCHNICK
: '401',
32 ERR_LINKCHANNEL
: '470',
37 var parseIRCMessage = function (websocket
, ircSocket
, data
) {
38 /*global ircSocketDataHandler */
39 var msg
, regex
, opts
, options
, opt
, i
, j
, matches
, nick
, users
, chan
, params
, prefix
, prefixes
, nicklist
, caps
;
40 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;
41 msg
= regex
.exec(data
);
50 trailing
: (msg
[7]) ? msg
[7].trim() : ''
52 switch (msg
.command
.toUpperCase()) {
54 ircSocket
.write('PONG ' + msg
.trailing
+ '\r\n');
56 case ircNumerics
.RPL_WELCOME
:
57 if (ircSocket
.IRC
.CAP
.negotiating
) {
58 ircSocket
.IRC
.CAP
.negotiating
= false;
59 ircSocket
.IRC
.CAP
.enabled
= [];
60 ircSocket
.IRC
.CAP
.requested
= [];
62 websocket
.emit('message', {event
: 'connect', connected
: true, host
: null});
64 case ircNumerics
.RPL_ISUPPORT
:
65 opts
= msg
.params
.split(" ");
67 for (i
= 0; i
< opts
.length
; i
++) {
68 opt
= opts
[i
].split("=", 2);
69 opt
[0] = opt
[0].toUpperCase();
70 ircSocket
.IRC
.options
[opt
[0]] = opt
[1] || true;
71 if (_
.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt
[0])) {
72 if (opt
[0] === 'PREFIX') {
73 regex
= /\(([^)]*)\)(.*)/;
74 matches
= regex
.exec(opt
[1]);
75 if ((matches
) && (matches
.length
=== 3)) {
76 ircSocket
.IRC
.options
[opt
[0]] = {};
77 for (j
= 0; j
< matches
[2].length
; j
++) {
78 ircSocket
.IRC
.options
[opt
[0]][matches
[2].charAt(j
)] = matches
[1].charAt(j
);
84 websocket
.emit('message', {event
: 'options', server
: '', "options": ircSocket
.IRC
.options
});
86 case ircNumerics
.RPL_WHOISUSER
:
87 case ircNumerics
.RPL_WHOISSERVER
:
88 case ircNumerics
.RPL_WHOISOPERATOR
:
89 case ircNumerics
.RPL_ENDOFWHOIS
:
90 case ircNumerics
.RPL_WHOISCHANNELS
:
91 case ircNumerics
.RPL_WHOISMODES
:
92 websocket
.emit('message', {event
: 'whois', server
: '', nick
: msg
.params
.split(" ", 3)[1], "msg": msg
.trailing
});
94 case ircNumerics
.RPL_WHOISIDLE
:
95 params
= msg
.params
.split(" ", 3);
96 websocket
.emit('message', {event
: 'whois', server
: '', nick
: params
[1], "msg": params
[2] + ' ' + msg
.trailing
});
97 case ircNumerics
.RPL_MOTD
:
98 websocket
.emit('message', {event
: 'motd', server
: '', "msg": msg
.trailing
});
100 case ircNumerics
.RPL_NAMEREPLY
:
101 params
= msg
.params
.split(" ");
104 users
= msg
.trailing
.split(" ");
105 prefixes
= _
.values(ircSocket
.IRC
.options
.PREFIX
);
108 _
.each(users
, function (user
) {
109 if (_
.include(prefix
, user
.charAt(0))) {
110 prefix
= user
.charAt(0);
111 user
= user
.substring(1);
112 nicklist
[user
] = prefix
;
117 websocket
.emit('message', {event
: 'userlist', server
: '', "users": nicklist
, channel
: chan
});
123 websocket
.emit('message', {event
: 'userlist', server
: '', "users": nicklist
, channel
: chan
});
128 case ircNumerics
.RPL_ENDOFNAMES
:
129 websocket
.emit('message', {event
: 'userlist_end', server
: '', channel
: msg
.params
.split(" ")[1]});
131 case ircNumerics
.ERR_LINKCHANNEL
:
132 params
= msg
.params
.split(" ");
133 websocket
.emit('message', {event
: 'channel_redirect', from: params
[1], to
: params
[2]});
135 case ircNumerics
.ERR_NOSUCHNICK
:
139 websocket
.emit('message', {event
: 'join', nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.trailing
});
140 if (msg
.nick
=== ircSocket
.IRC
.nick
) {
141 ircSocket
.write('NAMES ' + msg
.trailing
+ '\r\n');
145 websocket
.emit('message', {event
: 'part', nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), message
: msg
.trailing
});
148 params
= msg
.params
.split(" ");
149 websocket
.emit('message', {event
: 'kick', kicked
: params
[1], nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: params
[0].trim(), message
: msg
.trailing
});
152 websocket
.emit('message', {event
: 'quit', nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, message
: msg
.trailing
});
155 websocket
.emit('message', {event
: 'notice', nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), msg
: msg
.trailing
});
158 websocket
.emit('message', {event
: 'nick', nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, newnick
: msg
.trailing
});
161 websocket
.emit('message', {event
: 'topic', nick
: msg
.nick
, channel
: msg
.params
, topic
: msg
.trailing
});
163 case ircNumerics
.RPL_TOPIC
:
164 websocket
.emit('message', {event
: 'topic', nick
: '', channel
: msg
.params
.split(" ")[1], topic
: msg
.trailing
});
167 opts
= msg
.params
.split(" ");
168 params
= {event
: 'mode', nick
: msg
.nick
};
169 switch (opts
.length
) {
171 params
.effected_nick
= opts
[0];
172 params
.mode
= msg
.trailing
;
175 params
.channel
= opts
[0];
176 params
.mode
= opts
[1];
179 params
.channel
= opts
[0];
180 params
.mode
= opts
[1];
181 params
.effected_nick
= opts
[2];
184 websocket
.emit('message', params
);
187 websocket
.emit('message', {event
: 'msg', nick
: msg
.nick
, ident
: msg
.ident
, hostname
: msg
.hostname
, channel
: msg
.params
.trim(), msg
: msg
.trailing
});
190 caps
= config
.cap_options
;
191 options
= msg
.trailing
.split(" ");
192 switch (_
.first(msg
.params
.split(" "))) {
195 _
.each(_
.intersect(caps
, options
), function (cap
) {
200 ircSocket
.IRC
.CAP
.requested
.push(cap
);
202 if (opts
.length
> 0) {
203 ircSocket
.write('CAP REQ :' + opts
+ '\r\n');
205 ircSocket
.write('CAP END\r\n');
208 /*if (_.include(options, 'tls')) {
209 ircSocket.write('STARTTLS\r\n');
210 ircSocket.IRC.CAP.requested.push('tls');
214 _
.each(options
, function (cap
) {
215 ircSocket
.IRC
.CAP
.enabled
.push(cap
);
217 if (_
.last(msg
.params
.split(" ")) !== '*') {
218 ircSocket
.IRC
.CAP
.requested
= [];
219 ircSocket
.IRC
.CAP
.negotiating
= false;
220 ircSocket
.write('CAP END\r\n');
224 ircSocket
.IRC
.CAP
.requested
= [];
225 ircSocket
.IRC
.CAP
.negotiating
= false;
226 ircSocket
.write('CAP END\r\n');
230 /*case ircNumerics.RPL_STARTTLS:
233 listeners = ircSocket.listeners('data');
234 ircSocket.removeAllListeners('data');
235 ssl_socket = starttls(ircSocket, {}, function () {
236 ssl_socket.on("data", function (data) {
237 ircSocketDataHandler(data, websocket, ssl_socket);
239 ircSocket = ssl_socket;
241 _.each(listeners, function (listener) {
242 ircSocket.addListener('data', listener);
245 //console.log(ircSocket);
252 console
.log("Unknown command.\r\n");
256 var ircSocketDataHandler = function (data
, websocket
, ircSocket
) {
258 if ((ircSocket
.holdLast
) && (ircSocket
.held
!== '')) {
259 data
= ircSocket
.held
+ data
;
260 ircSocket
.holdLast
= false;
263 if (data
.substr(-2) === '\r\n') {
264 ircSocket
.holdLast
= true;
266 data
= data
.split("\r\n");
267 for (i
= 0; i
< data
.length
; i
++) {
269 if ((ircSocket
.holdLast
) && (i
=== data
.length
- 1)) {
270 ircSocket
.held
= data
[i
];
273 console
.log("->" + data
[i
]);
274 parseIRCMessage(websocket
, ircSocket
, data
[i
]);
279 var fileServer
= new (static_server
.Server
)(__dirname
+ '/client');
281 var httpHandler = function (request
, response
) {
282 var uri
, subs
, useragent
, agent
, server_set
, server
, nick
, debug
, touchscreen
;
283 if (config
.handle_http
) {
284 uri
= url
.parse(request
.url
);
285 subs
= uri
.pathname
.substr(0, 4);
286 if ((subs
=== '/js/') || (subs
=== '/css') || (subs
=== '/img')) {
287 request
.addListener('end', function () {
288 fileServer
.serve(request
, response
);
290 } else if (uri
.pathname
=== '/') {
291 useragent
= (response
.headers
) ? response
.headers
['user-agent']: '';
292 if (useragent
.indexOf('android') !== -1) {
295 } else if (useragent
.indexOf('iphone') !== -1) {
298 } else if (useragent
.indexOf('ipad') !== -1) {
301 } else if (useragent
.indexOf('ipod') !== -1) {
309 server_set
= (uri
.query
.server
!== '');
310 server
= uri
.query
.server
|| 'irc.anonnet.org';
311 nick
= uri
.query
.nick
|| '';
312 debug
= (uri
.query
.debug
!== '');
314 server
= 'irc.anonnet.org';
317 response
.setHeader('Connection', 'close');
318 response
.setHeader('X-Generated-By', 'KiwiIRC');
319 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
) {
321 response
.write(html
);
323 response
.statusCode
= 500;
327 } else if (uri
.pathname
.substr(0, 10) === '/socket.io') {
330 response
.statusCode
= 404;
336 //setup websocket listener
337 if (config
.listen_ssl
) {
338 var httpServer
= https
.createServer({key
: fs
.readFileSync(__dirname
+ '/' + config
.ssl_key
), cert
: fs
.readFileSync(__dirname
+ '/' + config
.ssl_cert
)}, httpHandler
);
339 var io
= ws
.listen(httpServer
, {secure
: true});
340 httpServer
.listen(config
.port
, config
.bind_address
);
342 var httpServer
= http
.createServer(httpHandler
);
343 var io
= ws
.listen(httpServer
, {secure
: false});
344 httpServer
.listen(config
.port
, config
.bind_address
);
346 io
.of('/kiwi').on('connection', function (websocket
) {
347 websocket
.on('irc connect', function (nick
, host
, port
, ssl
, callback
) {
349 //setup IRC connection
351 ircSocket
= net
.createConnection(port
, host
);
353 ircSocket
= tls
.connect(port
, host
);
355 ircSocket
.setEncoding('ascii');
356 ircSocket
.IRC
= {options
: {}, CAP
: {negotiating
: true, requested
: [], enabled
: []}};
357 websocket
.ircSocket
= ircSocket
;
358 ircSocket
.holdLast
= false;
361 ircSocket
.on('data', function (data
) {
362 ircSocketDataHandler(data
, websocket
, ircSocket
);
365 ircSocket
.IRC
.nick
= nick
;
366 // Send the login data
367 ircSocket
.write('CAP LS\r\n');
368 ircSocket
.write('NICK ' + nick
+ '\r\n');
369 ircSocket
.write('USER ' + nick
+ '_kiwi 0 0 :' + nick
+ '\r\n');
371 if ((callback
) && (typeof (callback
) === 'function')) {
375 websocket
.on('message', function (msg
, callback
) {
378 msg
.data
= JSON
.parse(msg
.data
);
379 args
= msg
.data
.args
;
380 switch (msg
.data
.method
) {
382 if ((args
.target
) && (args
.msg
)) {
383 websocket
.ircSocket
.write('PRIVMSG ' + args
.target
+ ' :' + args
.msg
+ '\r\n');
387 if ((args
.target
) && (args
.msg
)) {
388 websocket
.ircSocket
.write('PRIVMSG ' + args
.target
+ ' :\ 1ACTION ' + args
.msg
+ '\ 1\r\n');
392 websocket
.ircSocket
.write(args
.data
+ '\r\n');
396 _
.each(args
.channel
.split(","), function (chan
) {
397 websocket
.ircSocket
.write('JOIN ' + chan
+ '\r\n');
402 websocket
.ircSocket
.end('QUIT :' + args
.message
+ '\r\n');
403 websocket
.sentQUIT
= true;
404 websocket
.ircSocket
.destroySoon();
405 websocket
.disconnect();
408 if ((args
.target
) && (args
.msg
)) {
409 websocket
.ircSocket
.write('NOTICE ' + args
.target
+ ' :' + args
.msg
+ '\r\n');
414 if ((callback
) && (typeof (callback
) === 'function')) {
418 console
.log("Caught error: " + e
);
421 websocket
.on('disconnect', function () {
422 if ((!websocket
.sentQUIT
) && (websocket
.ircSocket
)) {
423 websocket
.ircSocket
.end('QUIT :' + config
.quit_message
+ '\r\n');
424 websocket
.sentQUIT
= true;
425 websocket
.ircSocket
.destroySoon();