jade template & jade npm install
[KiwiIRC.git] / node / kiwi.js
1 /*jslint 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 ws = require('socket.io'),
9 _ = require('./lib/underscore.min.js'),
10 starttls = require('./lib/starttls.js');
11
12 var config = JSON.parse(fs.readFileSync(__dirname + '/config.json', 'ascii'));
13
14 var ircNumerics = {
15 RPL_WELCOME: '001',
16 RPL_ISUPPORT: '005',
17 RPL_WHOISUSER: '311',
18 RPL_WHOISSERVER: '312',
19 RPL_WHOISOPERATOR: '313',
20 RPL_WHOISIDLE: '317',
21 RPL_ENDOFWHOIS: '318',
22 RPL_WHOISCHANNELS: '319',
23 RPL_TOPIC: '332',
24 RPL_NAMEREPLY: '353',
25 RPL_ENDOFNAMES: '366',
26 RPL_MOTD: '372',
27 RPL_WHOISMODES: '379',
28 ERR_NOSUCHNICK: '401',
29 ERR_LINKCHANNEL: '470',
30 RPL_STARTTLS: '670'
31 };
32
33
34 var parseIRCMessage = function (websocket, ircSocket, data) {
35 /*global ircSocketDataHandler */
36 var msg, regex, opts, options, opt, i, j, matches, nick, users, chan, params, prefix, prefixes, nicklist, caps;
37 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;
38 msg = regex.exec(data);
39 if (msg) {
40 msg = {
41 prefix: msg[1],
42 nick: msg[2],
43 ident: msg[3],
44 hostname: msg[4],
45 command: msg[5],
46 params: msg[6] || '',
47 trailing: (msg[7]) ? msg[7].trim() : ''
48 };
49 switch (msg.command.toUpperCase()) {
50 case 'PING':
51 ircSocket.write('PONG ' + msg.trailing + '\r\n');
52 break;
53 case ircNumerics.RPL_WELCOME:
54 if (ircSocket.IRC.CAP.negotiating) {
55 ircSocket.IRC.CAP.negotiating = false;
56 ircSocket.IRC.CAP.enabled = [];
57 ircSocket.IRC.CAP.requested = [];
58 }
59 websocket.emit('message', {event: 'connect', connected: true, host: null});
60 break;
61 case ircNumerics.RPL_ISUPPORT:
62 opts = msg.params.split(" ");
63 options = [];
64 for (i = 0; i < opts.length; i++) {
65 opt = opts[i].split("=", 2);
66 opt[0] = opt[0].toUpperCase();
67 ircSocket.IRC.options[opt[0]] = opt[1] || true;
68 if (_.include(['NETWORK', 'PREFIX', 'CHANTYPES'], opt[0])) {
69 if (opt[0] === 'PREFIX') {
70 regex = /\(([^)]*)\)(.*)/;
71 matches = regex.exec(opt[1]);
72 if ((matches) && (matches.length === 3)) {
73 ircSocket.IRC.options[opt[0]] = {};
74 for (j = 0; j < matches[2].length; j++) {
75 ircSocket.IRC.options[opt[0]][matches[2].charAt(j)] = matches[1].charAt(j);
76 }
77 }
78 }
79 }
80 }
81 websocket.emit('message', {event: 'options', server: '', "options": ircSocket.IRC.options});
82 break;
83 case ircNumerics.RPL_WHOISUSER:
84 case ircNumerics.RPL_WHOISSERVER:
85 case ircNumerics.RPL_WHOISOPERATOR:
86 case ircNumerics.RPL_WHOISIDLE:
87 case ircNumerics.RPL_ENDOFWHOIS:
88 case ircNumerics.RPL_WHOISCHANNELS:
89 case ircNumerics.RPL_WHOISMODES:
90 websocket.emit('message', {event: 'whois', server: '', nick: msg.params.split(" ", 3)[1], "msg": msg.trailing});
91 break;
92 case ircNumerics.RPL_MOTD:
93 websocket.emit('message', {event: 'motd', server: '', "msg": msg.trailing});
94 break;
95 case ircNumerics.RPL_NAMEREPLY:
96 params = msg.params.split(" ");
97 nick = params[0];
98 chan = params[2];
99 users = msg.trailing.split(" ");
100 prefixes = _.values(ircSocket.IRC.options.PREFIX);
101 nicklist = {};
102 i = 0;
103 _.each(users, function (user) {
104 if (_.include(prefix, user.charAt(0))) {
105 prefix = user.charAt(0);
106 user = user.substring(1);
107 nicklist[user] = prefix;
108 } else {
109 nicklist[user] = '';
110 }
111 if (i++ >= 50) {
112 websocket.emit('message', {event: 'userlist', server: '', "users": nicklist, channel: chan});
113 nicklist = {};
114 i = 0;
115 }
116 });
117 if (i > 0) {
118 websocket.emit('message', {event: 'userlist', server: '', "users": nicklist, channel: chan});
119 } else {
120 console.log("oops");
121 }
122 break;
123 case ircNumerics.RPL_ENDOFNAMES:
124 websocket.emit('message', {event: 'userlist_end', server: '', channel: msg.params.split(" ")[1]});
125 break;
126 case ircNumerics.ERR_LINKCHANNEL:
127 params = msg.params.split(" ");
128 websocket.emit('message', {event: 'channel_redirect', from: params[1], to: params[2]});
129 break;
130 case ircNumerics.ERR_NOSUCHNICK:
131 //TODO: shit
132 break;
133 case 'JOIN':
134 websocket.emit('message', {event: 'join', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.trailing});
135 if (msg.nick === ircSocket.IRC.nick) {
136 ircSocket.write('NAMES ' + msg.trailing + '\r\n');
137 }
138 break;
139 case 'PART':
140 websocket.emit('message', {event: 'part', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), message: msg.trailing});
141 break;
142 case 'KICK':
143 params = msg.params.split(" ");
144 websocket.emit('message', {event: 'kick', kicked: params[1], nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: params[0].trim(), message: msg.trailing});
145 break;
146 case 'QUIT':
147 websocket.emit('message', {event: 'quit', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, message: msg.trailing});
148 break;
149 case 'NOTICE':
150 websocket.emit('message', {event: 'notice', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing});
151 break;
152 case 'NICK':
153 websocket.emit('message', {event: 'nick', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, newnick: msg.trailing});
154 break;
155 case 'TOPIC':
156 websocket.emit('message', {event: 'topic', nick: msg.nick, channel: msg.params, topic: msg.trailing});
157 break;
158 case ircNumerics.RPL_TOPIC:
159 websocket.emit('message', {event: 'topic', nick: '', channel: msg.params.split(" ")[1], topic: msg.trailing});
160 break;
161 case 'MODE':
162 opts = msg.params.split(" ");
163 params = {event: 'mode', nick: msg.nick};
164 switch (opts.length) {
165 case 1:
166 params.effected_nick = opts[0];
167 params.mode = msg.trailing;
168 break;
169 case 2:
170 params.channel = opts[0];
171 params.mode = opts[1];
172 break;
173 default:
174 params.channel = opts[0];
175 params.mode = opts[1];
176 params.effected_nick = opts[2];
177 break;
178 }
179 websocket.emit('message', params);
180 break;
181 case 'PRIVMSG':
182 websocket.emit('message', {event: 'msg', nick: msg.nick, ident: msg.ident, hostname: msg.hostname, channel: msg.params.trim(), msg: msg.trailing});
183 break;
184 case 'CAP':
185 caps = config.cap_options;
186 options = msg.trailing.split(" ");
187 switch (_.first(msg.params.split(" "))) {
188 case 'LS':
189 opts = '';
190 _.each(_.intersect(caps, options), function (cap) {
191 if (opts !== '') {
192 opts += " ";
193 }
194 opts += cap;
195 ircSocket.IRC.CAP.requested.push(cap);
196 });
197 if (opts.length > 0) {
198 ircSocket.write('CAP REQ :' + opts + '\r\n');
199 } else {
200 ircSocket.write('CAP END\r\n');
201 }
202 // TLS is special
203 /*if (_.include(options, 'tls')) {
204 ircSocket.write('STARTTLS\r\n');
205 ircSocket.IRC.CAP.requested.push('tls');
206 }*/
207 break;
208 case 'ACK':
209 _.each(options, function (cap) {
210 ircSocket.IRC.CAP.enabled.push(cap);
211 });
212 if (_.last(msg.params.split(" ")) !== '*') {
213 ircSocket.IRC.CAP.requested = [];
214 ircSocket.IRC.CAP.negotiating = false;
215 ircSocket.write('CAP END\r\n');
216 }
217 break;
218 case 'NAK':
219 ircSocket.IRC.CAP.requested = [];
220 ircSocket.IRC.CAP.negotiating = false;
221 ircSocket.write('CAP END\r\n');
222 break;
223 }
224 break;
225 /*case ircNumerics.RPL_STARTTLS:
226 try {
227 IRC = ircSocket.IRC;
228 listeners = ircSocket.listeners('data');
229 ircSocket.removeAllListeners('data');
230 ssl_socket = starttls(ircSocket, {}, function () {
231 ssl_socket.on("data", function (data) {
232 ircSocketDataHandler(data, websocket, ssl_socket);
233 });
234 ircSocket = ssl_socket;
235 ircSocket.IRC = IRC;
236 _.each(listeners, function (listener) {
237 ircSocket.addListener('data', listener);
238 });
239 });
240 //console.log(ircSocket);
241 } catch (e) {
242 console.log(e);
243 }
244 break;*/
245 }
246 } else {
247 console.log("Unknown command.\r\n");
248 }
249 };
250
251 var ircSocketDataHandler = function (data, websocket, ircSocket) {
252 var i;
253 if ((ircSocket.holdLast) && (ircSocket.held !== '')) {
254 data = ircSocket.held + data;
255 ircSocket.holdLast = false;
256 ircSocket.held = '';
257 }
258 if (data.substr(-2) === '\r\n') {
259 ircSocket.holdLast = true;
260 }
261 data = data.split("\r\n");
262 for (i = 0; i < data.length; i++) {
263 if (data[i]) {
264 if ((ircSocket.holdLast) && (i === data.length - 1)) {
265 ircSocket.held = data[i];
266 break;
267 }
268 console.log("->" + data[i]);
269 parseIRCMessage(websocket, ircSocket, data[i]);
270 }
271 }
272 };
273
274 //setup websocket listener
275 if (config.listen_ssl) {
276 var httpServer = https.createServer({key: fs.readFileSync(__dirname + '/' + config.ssl_key), cert: fs.readFileSync(__dirname + '/' + config.ssl_cert)});
277 var io = ws.listen(httpServer, {secure: true});
278 httpServer.listen(config.port, config.bind_address);
279 } else {
280 var httpServer = http.createServer();
281 var io = ws.listen(httpServer, {secure: false});
282 httpServer.listen(config.port, config.bind_address);
283 }
284 io.sockets.on('connection', function (websocket) {
285 websocket.on('irc connect', function (nick, host, port, ssl, callback) {
286 var ircSocket;
287 //setup IRC connection
288 if (!ssl) {
289 ircSocket = net.createConnection(port, host);
290 } else {
291 ircSocket = tls.connect(port, host);
292 }
293 ircSocket.setEncoding('ascii');
294 ircSocket.IRC = {options: {}, CAP: {negotiating: true, requested: [], enabled: []}};
295 websocket.ircSocket = ircSocket;
296 ircSocket.holdLast = false;
297 ircSocket.held = '';
298
299 ircSocket.on('data', function (data) {
300 ircSocketDataHandler(data, websocket, ircSocket);
301 });
302
303 ircSocket.IRC.nick = nick;
304 // Send the login data
305 ircSocket.write('CAP LS\r\n');
306 ircSocket.write('NICK ' + nick + '\r\n');
307 ircSocket.write('USER ' + nick + '_kiwi 0 0 :' + nick + '\r\n');
308
309 if ((callback) && (typeof (callback) === 'function')) {
310 callback();
311 }
312 });
313 websocket.on('message', function (msg, callback) {
314 var args;
315 try {
316 msg.data = JSON.parse(msg.data);
317 args = msg.data.args;
318 switch (msg.data.method) {
319 case 'msg':
320 if ((args.target) && (args.msg)) {
321 websocket.ircSocket.write('PRIVMSG ' + args.target + ' :' + args.msg + '\r\n');
322 }
323 break;
324 case 'action':
325 if ((args.target) && (args.msg)) {
326 websocket.ircSocket.write('PRIVMSG ' + args.target + ' :\ 1ACTION ' + args.msg + '\ 1\r\n');
327 }
328 break;
329 case 'raw':
330 websocket.ircSocket.write(args.data + '\r\n');
331 break;
332 case 'join':
333 if (args.channel) {
334 _.each(args.channel.split(","), function (chan) {
335 websocket.ircSocket.write('JOIN ' + chan + '\r\n');
336 });
337 }
338 break;
339 case 'quit':
340 websocket.ircSocket.end('QUIT :' + args.message + '\r\n');
341 websocket.sentQUIT = true;
342 websocket.ircSocket.destroySoon();
343 websocket.disconnect();
344 break;
345 case 'notice':
346 if ((args.target) && (args.msg)) {
347 websocket.ircSocket.write('NOTICE ' + args.target + ' :' + args.msg + '\r\n');
348 }
349 break;
350 default:
351 }
352 if ((callback) && (typeof (callback) === 'function')) {
353 callback();
354 }
355 } catch (e) {
356 console.log("Caught error: " + e);
357 }
358 });
359 websocket.on('disconnect', function () {
360 if ((!websocket.sentQUIT) && (websocket.ircSocket)) {
361 websocket.ircSocket.end('QUIT :' + config.quit_message + '\r\n');
362 websocket.sentQUIT = true;
363 websocket.ircSocket.destroySoon();
364 }
365 });
366 });
367