]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/irc/ircclient.js
5d138a30f5a1f4d6e46992bf3732445d83ec3470
[irc/quakenet/qwebirc.git] / js / irc / ircclient.js
1 qwebirc.irc.IRCClient = new Class({
2 Extends: qwebirc.irc.BaseIRCClient,
3 options: {
4 nickname: "qwebirc",
5 autojoin: "",
6 maxnicks: 10
7 },
8 initialize: function(options, ui) {
9 this.parent(options);
10
11 this.ui = ui;
12
13 this.prefixes = "@+";
14 this.modeprefixes = "ov";
15 this.windows = {};
16
17 this.commandparser = new qwebirc.irc.CommandParser(this);
18 this.exec = this.commandparser.dispatch.bind(this.commandparser);
19
20 this.statusWindow = this.ui.newClient(this);
21 this.lastNicks = [];
22
23 this.inviteChanList = [];
24 this.activeTimers = {};
25 },
26 newLine: function(window, type, data) {
27 if(!data)
28 data = {};
29
30 var w = this.getWindow(window);
31 if(w) {
32 w.addLine(type, data);
33 } else {
34 this.statusWindow.addLine(type, data);
35 }
36 },
37 newChanLine: function(channel, type, user, extra) {
38 if(!extra)
39 extra = {};
40
41 extra["n"] = user.hostToNick();
42 extra["h"] = user.hostToHost();
43 extra["c"] = channel;
44 extra["-"] = this.nickname;
45
46 this.newLine(channel, type, extra);
47 },
48 newServerLine: function(type, data) {
49 this.statusWindow.addLine(type, data);
50 },
51 newActiveLine: function(type, data) {
52 this.getActiveWindow().addLine(type, data);
53 },
54 newTargetOrActiveLine: function(target, type, data) {
55 if(this.getWindow(target)) {
56 this.newLine(target, type, data);
57 } else {
58 this.newActiveLine(type, data);
59 }
60 },
61 updateNickList: function(channel) {
62 var n1 = this.tracker.getChannel(channel);
63 var names = new Array();
64 var tff = String.fromCharCode(255);
65 var nh = {}
66
67 /* MEGAHACK */
68 for(var n in n1) {
69 var nc = n1[n];
70 var nx;
71
72 if(nc.prefixes.length > 0) {
73 var c = nc.prefixes.charAt(0);
74 nx = String.fromCharCode(this.prefixes.indexOf(c)) + n.toIRCLower();
75 nh[nx] = c + n;
76 } else {
77 nx = tff + n.toIRCLower();
78 nh[nx] = n;
79 }
80 names.push(nx);
81 };
82
83 names.sort();
84
85 var sortednames = new Array();
86 names.each(function(name) {
87 sortednames.push(nh[name]);
88 });
89
90 var w = this.getWindow(channel);
91 if(w)
92 w.updateNickList(sortednames);
93 },
94 getWindow: function(name) {
95 return this.windows[name.toIRCLower()];
96 },
97 newWindow: function(name, type, select) {
98 var w = this.getWindow(name);
99 if(!w) {
100 w = this.windows[name.toIRCLower()] = this.ui.newWindow(this, type, name);
101
102 w.addEvent("close", function(w) {
103 delete this.windows[name.toIRCLower()];
104 }.bind(this));
105 }
106
107 if(select)
108 this.ui.selectWindow(w);
109
110 return w;
111 },
112 getQueryWindow: function(name) {
113 return this.ui.getWindow(this, qwebirc.ui.WINDOW_QUERY, name);
114 },
115 newQueryWindow: function(name, privmsg) {
116 var e;
117 if(privmsg) {
118 e = this.ui.uiOptions.DEDICATED_MSG_WINDOW;
119 } else {
120 e = this.ui.uiOptions.DEDICATED_NOTICE_WINDOW;
121 }
122 if(e && !this.ui.getWindow(this, qwebirc.ui.WINDOW_MESSAGES) && !this.getQueryWindow(name))
123 return this.ui.newWindow(this, qwebirc.ui.WINDOW_MESSAGES, "Messages");
124
125 if(privmsg)
126 return this.newWindow(name, qwebirc.ui.WINDOW_QUERY, false);
127 },
128 newQueryLine: function(window, type, data, privmsg, active) {
129 if(this.getQueryWindow(window))
130 return this.newLine(window, type, data);
131
132 var w = this.ui.getWindow(this, qwebirc.ui.WINDOW_MESSAGES);
133
134 var e;
135 if(privmsg) {
136 e = this.ui.uiOptions.DEDICATED_MSG_WINDOW;
137 } else {
138 e = this.ui.uiOptions.DEDICATED_NOTICE_WINDOW;
139 }
140 if(e && w) {
141 return w.addLine(type, data);
142 } else {
143 if(active) {
144 return this.newActiveLine(window, type, data);
145 } else {
146 return this.newLine(window, type, data);
147 }
148 }
149 },
150 newQueryOrActiveLine: function(window, type, data, privmsg) {
151 this.newQueryLine(window, type, data, privmsg, true);
152 },
153 getActiveWindow: function() {
154 return this.ui.getActiveIRCWindow(this);
155 },
156 getNickname: function() {
157 return this.nickname;
158 },
159 addPrefix: function(nickchanentry, prefix) {
160 var ncp = nickchanentry.prefixes + prefix;
161 var prefixes = [];
162
163 /* O(n^2) */
164 for(var i=0;i<this.prefixes.length;i++) {
165 var pc = this.prefixes.charAt(i);
166 var index = ncp.indexOf(pc);
167 if(index != -1)
168 prefixes.push(pc);
169 }
170
171 nickchanentry.prefixes = prefixes.join("");
172 },
173 stripPrefix: function(nick) {
174 var l = nick.charAt(0);
175 if(!l)
176 return nick;
177
178 if(this.prefixes.indexOf(l) != -1)
179 return nick.substring(1);
180
181 return nick;
182 },
183 removePrefix: function(nickchanentry, prefix) {
184 nickchanentry.prefixes = nickchanentry.prefixes.replaceAll(prefix, "");
185 },
186
187 /* from here down are events */
188 rawNumeric: function(numeric, prefix, params) {
189 this.newServerLine("RAW", {"n": "numeric", "m": params.slice(1).join(" ")});
190 },
191 signedOn: function(nickname) {
192 this.tracker = new qwebirc.irc.IRCTracker();
193 this.nickname = nickname;
194 this.newServerLine("SIGNON");
195
196 if(this.options.autojoin)
197 this.commandparser.dispatch("/JOIN " + this.options.autojoin);
198 },
199 userJoined: function(user, channel) {
200 var nick = user.hostToNick();
201 var host = user.hostToHost();
202
203 if((nick == this.nickname) && !this.getWindow(channel))
204 this.newWindow(channel, qwebirc.ui.WINDOW_CHANNEL, true);
205 this.tracker.addNickToChannel(nick, channel);
206
207 if(nick == this.nickname) {
208 this.newChanLine(channel, "OURJOIN", user);
209 } else {
210 this.newChanLine(channel, "JOIN", user);
211 }
212 this.updateNickList(channel);
213 },
214 userPart: function(user, channel, message) {
215 var nick = user.hostToNick();
216 var host = user.hostToHost();
217
218 if(nick == this.nickname) {
219 this.tracker.removeChannel(channel);
220 } else {
221 this.tracker.removeNickFromChannel(nick, channel);
222 this.newChanLine(channel, "PART", user, {"m": message});
223 }
224
225 this.updateNickList(channel);
226
227 if(nick == this.nickname) {
228 var w = this.getWindow(channel)
229 if(w)
230 w.close();
231 }
232 },
233 userKicked: function(kicker, channel, kickee, message) {
234 if(kickee == this.nickname) {
235 this.tracker.removeChannel(channel);
236 this.getWindow(channel).close();
237 } else {
238 this.tracker.removeNickFromChannel(kickee, channel);
239 this.updateNickList(channel);
240 }
241
242 this.newChanLine(channel, "KICK", kicker, {"v": kickee, "m": message});
243 },
244 channelMode: function(user, channel, modes, raw) {
245 modes.each(function(mo) {
246 var direction = mo[0];
247 var mode = mo[1];
248
249 var prefixindex = this.modeprefixes.indexOf(mode);
250 if(prefixindex == -1)
251 return;
252
253 var nick = mo[2];
254 var prefixchar = this.prefixes.charAt(prefixindex);
255
256 var nc = this.tracker.getOrCreateNickOnChannel(nick, channel);
257 if(direction == "-") {
258 this.removePrefix(nc, prefixchar);
259 } else {
260 this.addPrefix(nc, prefixchar);
261 }
262 }, this);
263
264 this.newChanLine(channel, "MODE", user, {"m": raw.join(" ")});
265
266 this.updateNickList(channel);
267 },
268 userQuit: function(user, message) {
269 var nick = user.hostToNick();
270
271 var channels = this.tracker.getNick(nick);
272
273 var clist = [];
274 for(var c in channels) {
275 clist.push(c);
276 this.newChanLine(c, "QUIT", user, {"m": message});
277 }
278
279 this.tracker.removeNick(nick);
280
281 clist.each(function(cli) {
282 this.updateNickList(cli);
283 }, this);
284 },
285 nickChanged: function(user, newnick) {
286 var oldnick = user.hostToNick();
287
288 if(oldnick == this.nickname)
289 this.nickname = newnick;
290
291 this.tracker.renameNick(oldnick, newnick);
292
293 var channels = this.tracker.getNick(newnick);
294 var found = false;
295
296 for(var c in channels) {
297 var found = true;
298
299 this.newChanLine(c, "NICK", user, {"w": newnick});
300 /* TODO: rename queries */
301 this.updateNickList(c);
302 }
303
304 if(!found)
305 this.newChanLine(undefined, "NICK", user, {"w": newnick});
306 },
307 channelTopic: function(user, channel, topic) {
308 this.newChanLine(channel, "TOPIC", user, {"m": topic});
309 this.getWindow(channel).updateTopic(topic);
310 },
311 initialTopic: function(channel, topic) {
312 this.getWindow(channel).updateTopic(topic);
313 },
314 channelCTCP: function(user, channel, type, args) {
315 if(args == undefined)
316 args = "";
317
318 var nick = user.hostToNick();
319 if(type == "ACTION") {
320 this.tracker.updateLastSpoke(nick, channel, new Date().getTime());
321 this.newChanLine(channel, "CHANACTION", user, {"m": args, "c": channel, "@": this.getNickStatus(channel, nick)});
322 return;
323 }
324
325 this.newChanLine(channel, "CHANCTCP", user, {"x": type, "m": args, "c": channel, "@": this.getNickStatus(channel, nick)});
326 },
327 userCTCP: function(user, type, args) {
328 var nick = user.hostToNick();
329 var host = user.hostToHost();
330 if(args == undefined)
331 args = "";
332
333 if(type == "ACTION") {
334 this.newQueryWindow(nick, true);
335 this.newQueryLine(nick, "PRIVACTION", {"m": args, "x": type, "h": host, "n": nick}, true);
336 return;
337 }
338
339 this.newTargetOrActiveLine(nick, "PRIVCTCP", {"m": args, "x": type, "h": host, "n": nick, "-": this.nickname});
340 },
341 userCTCPReply: function(user, type, args) {
342 var nick = user.hostToNick();
343 var host = user.hostToHost();
344 if(args == undefined)
345 args = "";
346
347 this.newTargetOrActiveLine(nick, "CTCPREPLY", {"m": args, "x": type, "h": host, "n": nick, "-": this.nickname});
348 },
349 getNickStatus: function(channel, nick) {
350 var n = this.tracker.getNickOnChannel(nick, channel);
351 if(!$defined(n))
352 return "";
353
354 if(n.prefixes.length == 0)
355 return "";
356
357 return n.prefixes.charAt(0);
358 },
359 channelPrivmsg: function(user, channel, message) {
360 var nick = user.hostToNick();
361
362 this.tracker.updateLastSpoke(nick, channel, new Date().getTime());
363 this.newChanLine(channel, "CHANMSG", user, {"m": message, "@": this.getNickStatus(channel, nick)});
364 },
365 channelNotice: function(user, channel, message) {
366 this.newChanLine(channel, "CHANNOTICE", user, {"m": message, "@": this.getNickStatus(channel, user.hostToNick())});
367 },
368 userPrivmsg: function(user, message) {
369 var nick = user.hostToNick();
370 var host = user.hostToHost();
371
372 this.newQueryWindow(nick, true);
373 this.pushLastNick(nick);
374 this.newQueryLine(nick, "PRIVMSG", {"m": message, "h": host, "n": nick}, true);
375 },
376 serverNotice: function(user, message) {
377 if(user == "") {
378 this.newServerLine("SERVERNOTICE", {"m": message});
379 } else {
380 this.newServerLine("PRIVNOTICE", {"m": message, "n": user});
381 }
382 },
383 userNotice: function(user, message) {
384 var nick = user.hostToNick();
385 var host = user.hostToHost();
386
387 if(this.ui.uiOptions.DEDICATED_NOTICE_WINDOW) {
388 this.newQueryWindow(nick, false);
389 this.newQueryOrActiveLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick}, false);
390 } else {
391 this.newTargetOrActiveLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick});
392 }
393 },
394 isNetworkService: function(user) {
395 /* TODO: refactor */
396 return user == "Q!TheQBot@CServe.quakenet.org";
397 },
398 __joinInvited: function() {
399 this.exec("/JOIN " + this.inviteChanList.join(","));
400 this.inviteChanList = [];
401 delete this.activeTimers["serviceInvite"];
402 },
403 userInvite: function(user, channel) {
404 var nick = user.hostToNick();
405 var host = user.hostToHost();
406
407 this.newServerLine("INVITE", {"c": channel, "h": host, "n": nick});
408 if(this.ui.uiOptions.ACCEPT_SERVICE_INVITES && this.isNetworkService(user)) {
409 if(this.activeTimers.serviceInvite)
410 $clear(this.activeTimers.serviceInvite);
411
412 /* we do this so we can batch the joins, i.e. instead of sending 5 JOIN comands we send 1 with 5 channels. */
413 this.activeTimers.serviceInvite = this.__joinInvited.delay(100, this);
414
415 this.inviteChanList.push(channel);
416 }
417 },
418 userMode: function(modes) {
419 this.newServerLine("UMODE", {"m": modes, "n": this.nickname});
420 },
421 channelNames: function(channel, names) {
422 if(names.length == 0) {
423 this.updateNickList(channel);
424 return;
425 }
426
427 names.each(function(nick) {
428 var prefixes = [];
429 var splitnick = nick.split("");
430
431 splitnick.every(function(c, i) {
432 if(this.prefixes.indexOf(c) == -1) {
433 nick = nick.substr(i);
434 return false;
435 }
436
437 prefixes.push(c);
438 return true;
439 }, this);
440
441 var nc = this.tracker.addNickToChannel(nick, channel);
442 prefixes.each(function(p) {
443 this.addPrefix(nc, p);
444 }, this);
445 }, this);
446 },
447 disconnected: function(message) {
448 for(var x in this.windows) {
449 var w = this.windows[x];
450 if(w.type == qwebirc.ui.WINDOW_CHANNEL)
451 w.close();
452 }
453 this.tracker = undefined;
454
455 this.newServerLine("DISCONNECT", {"m": message});
456 },
457 supported: function(key, value) {
458 if(key == "PREFIX") {
459 var l = (value.length - 2) / 2;
460
461 this.modeprefixes = value.substr(1, l);
462 this.prefixes = value.substr(l + 2, l);
463 }
464 },
465 connected: function() {
466 this.newServerLine("CONNECT");
467 },
468 serverError: function(message) {
469 this.newServerLine("ERROR", {"m": message});
470 },
471 quit: function(message) {
472 this.send("QUIT :" + message);
473 this.disconnect();
474 },
475 disconnect: function() {
476 for(var k in this.activeTimers) {
477 this.activeTimers[k].cancel();
478 };
479 this.activeTimers = {};
480
481 this.parent();
482 },
483 awayMessage: function(nick, message) {
484 this.newQueryLine(nick, "AWAY", {"n": nick, "m": message}, true);
485 },
486 whois: function(nick, type, data) {
487 var ndata = {"n": nick};
488 var mtype;
489
490 var xsend = function() {
491 this.newTargetOrActiveLine(nick, "WHOIS" + mtype, ndata);
492 }.bind(this);
493
494 if(type == "user") {
495 mtype = "USER";
496 ndata.h = data.ident + "@" + data.hostname;
497 xsend();
498 mtype = "REALNAME";
499 ndata.m = data.realname;
500 } else if(type == "server") {
501 mtype = "SERVER";
502 ndata.x = data.server;
503 ndata.m = data.serverdesc;
504 } else if(type == "oper") {
505 mtype = "OPER";
506 } else if(type == "idle") {
507 mtype = "IDLE";
508 ndata.x = qwebirc.util.longtoduration(data.idle);
509 ndata.m = qwebirc.irc.IRCDate(new Date(data.connected * 1000));
510 } else if(type == "channels") {
511 mtype = "CHANNELS";
512 ndata.m = data.channels;
513 } else if(type == "account") {
514 mtype = "ACCOUNT";
515 ndata.m = data.account;
516 } else if(type == "away") {
517 mtype = "AWAY";
518 ndata.m = data.away;
519 } else if(type == "opername") {
520 mtype = "OPERNAME";
521 ndata.m = data.opername;
522 } else if(type == "actually") {
523 mtype = "ACTUALLY";
524 ndata.m = data.hostname;
525 ndata.x = data.ip;
526 } else if(type == "end") {
527 mtype = "END";
528 } else {
529 return false;
530 }
531
532 xsend();
533 return true;
534 },
535 genericError: function(target, message) {
536 this.newTargetOrActiveLine(target, "GENERICERROR", {m: message, t: target});
537 },
538 genericQueryError: function(target, message) {
539 this.newQueryOrActiveLine(target, "GENERICERROR", {m: message, t: target}, true);
540 },
541 awayStatus: function(state, message) {
542 this.newActiveLine("GENERICMESSAGE", {m: message});
543 },
544 pushLastNick: function(nick) {
545 var i = this.lastNicks.indexOf(nick);
546 if(i != -1) {
547 this.lastNicks.splice(i, 1);
548 } else {
549 if(this.lastNicks.length == this.options.maxnicks)
550 this.lastNicks.pop();
551 }
552 this.lastNicks.unshift(nick);
553 }
554 });