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