]> jfr.im git - irc/quakenet/qwebirc.git/blob - js/irc/ircclient.js
75bd25976f7cc2d3132911a585f275bbccb04172
[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.Commands(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 this.loginRegex = new RegExp(this.ui.options.loginRegex);
27 this.tracker = new qwebirc.irc.IRCTracker(this);
28 },
29 newLine: function(window, type, data) {
30 if(!data)
31 data = {};
32
33 var w = this.getWindow(window);
34 if(w) {
35 w.addLine(type, data);
36 } else {
37 this.statusWindow.addLine(type, data);
38 }
39 },
40 newChanLine: function(channel, type, user, extra) {
41 if(!extra)
42 extra = {};
43
44 if($defined(user)) {
45 extra["n"] = user.hostToNick();
46 extra["h"] = user.hostToHost();
47 }
48 extra["c"] = channel;
49 extra["-"] = this.nickname;
50
51 if(!(this.ui.uiOptions.NICK_OV_STATUS))
52 delete extra["@"];
53
54 this.newLine(channel, type, extra);
55 },
56 newServerLine: function(type, data) {
57 this.statusWindow.addLine(type, data);
58 },
59 newActiveLine: function(type, data) {
60 this.getActiveWindow().addLine(type, data);
61 },
62 newTargetOrActiveLine: function(target, type, data) {
63 if(this.getWindow(target)) {
64 this.newLine(target, type, data);
65 } else {
66 this.newActiveLine(type, data);
67 }
68 },
69 updateNickList: function(channel) {
70 var n1 = this.tracker.getChannel(channel);
71 var names = new Array();
72 var tff = String.fromCharCode(255);
73 var nh = {}
74
75 /* MEGAHACK */
76 for(var n in n1) {
77 var nc = n1[n];
78 var nx;
79
80 if(nc.prefixes.length > 0) {
81 var c = nc.prefixes.charAt(0);
82 nx = String.fromCharCode(this.prefixes.indexOf(c)) + this.toIRCLower(n);
83 nh[nx] = c + n;
84 } else {
85 nx = tff + this.toIRCLower(n);
86 nh[nx] = n;
87 }
88 names.push(nx);
89 };
90
91 names.sort();
92
93 var sortednames = new Array();
94 names.each(function(name) {
95 sortednames.push(nh[name]);
96 });
97
98 var w = this.getWindow(channel);
99 if(w)
100 w.updateNickList(sortednames);
101 },
102 getWindow: function(name) {
103 return this.windows[this.toIRCLower(name)];
104 },
105 renameWindow: function(oldname, newname) {
106 var oldwindow = this.getWindow(oldname);
107 if(!oldwindow || this.getWindow(newname))
108 return;
109
110 var window = this.ui.renameWindow(oldwindow, newname);
111 if(window) {
112 this.windows[this.toIRCLower(newname)] = window;
113 delete this.windows[this.toIRCLower(oldname)];
114 }
115 },
116 newWindow: function(name, type, select) {
117 var w = this.getWindow(name);
118 if(!w) {
119 w = this.windows[this.toIRCLower(name)] = this.ui.newWindow(this, type, name);
120
121 w.addEvent("close", function(w) {
122 delete this.windows[this.toIRCLower(name)];
123 }.bind(this));
124 }
125
126 if(select)
127 this.ui.selectWindow(w);
128
129 return w;
130 },
131 getQueryWindow: function(name) {
132 return this.ui.getWindow(this, qwebirc.ui.WINDOW_QUERY, name);
133 },
134 newQueryWindow: function(name, privmsg) {
135 var e;
136
137 if(this.getQueryWindow(name))
138 return;
139
140 if(privmsg)
141 return this.newPrivmsgQueryWindow(name);
142 return this.newNoticeQueryWindow(name);
143 },
144 newPrivmsgQueryWindow: function(name) {
145 if(this.ui.uiOptions.DEDICATED_MSG_WINDOW) {
146 if(!this.ui.getWindow(this, qwebirc.ui.WINDOW_MESSAGES))
147 return this.ui.newWindow(this, qwebirc.ui.WINDOW_MESSAGES, "Messages");
148 } else {
149 return this.newWindow(name, qwebirc.ui.WINDOW_QUERY, false);
150 }
151 },
152 newNoticeQueryWindow: function(name) {
153 if(this.ui.uiOptions.DEDICATED_NOTICE_WINDOW)
154 if(!this.ui.getWindow(this, qwebirc.ui.WINDOW_MESSAGES))
155 return this.ui.newWindow(this, qwebirc.ui.WINDOW_MESSAGES, "Messages");
156 },
157 newQueryLine: function(window, type, data, privmsg, active) {
158 if(this.getQueryWindow(window))
159 return this.newLine(window, type, data);
160
161 var w = this.ui.getWindow(this, qwebirc.ui.WINDOW_MESSAGES);
162
163 var e;
164 if(privmsg) {
165 e = this.ui.uiOptions.DEDICATED_MSG_WINDOW;
166 } else {
167 e = this.ui.uiOptions.DEDICATED_NOTICE_WINDOW;
168 }
169 if(e && w) {
170 return w.addLine(type, data);
171 } else {
172 if(active) {
173 return this.newActiveLine(type, data);
174 } else {
175 return this.newLine(window, type, data);
176 }
177 }
178 },
179 newQueryOrActiveLine: function(window, type, data, privmsg) {
180 this.newQueryLine(window, type, data, privmsg, true);
181 },
182 getActiveWindow: function() {
183 return this.ui.getActiveIRCWindow(this);
184 },
185 getNickname: function() {
186 return this.nickname;
187 },
188 addPrefix: function(nickchanentry, prefix) {
189 var ncp = nickchanentry.prefixes + prefix;
190 var prefixes = [];
191
192 /* O(n^2) */
193 for(var i=0;i<this.prefixes.length;i++) {
194 var pc = this.prefixes.charAt(i);
195 var index = ncp.indexOf(pc);
196 if(index != -1)
197 prefixes.push(pc);
198 }
199
200 nickchanentry.prefixes = prefixes.join("");
201 },
202 stripPrefix: function(nick) {
203 var l = nick.charAt(0);
204 if(!l)
205 return nick;
206
207 if(this.prefixes.indexOf(l) != -1)
208 return nick.substring(1);
209
210 return nick;
211 },
212 removePrefix: function(nickchanentry, prefix) {
213 nickchanentry.prefixes = nickchanentry.prefixes.replaceAll(prefix, "");
214 },
215
216 /* from here down are events */
217 rawNumeric: function(numeric, prefix, params) {
218 this.newServerLine("RAW", {"n": "numeric", "m": params.slice(1).join(" ")});
219 },
220 signedOn: function(nickname) {
221 this.tracker = new qwebirc.irc.IRCTracker(this);
222 this.nickname = nickname;
223 this.newServerLine("SIGNON");
224
225 /* we guarantee that +x is sent out before the joins */
226 if(this.ui.uiOptions.USE_HIDDENHOST)
227 this.exec("/UMODE +x");
228
229 if(this.options.autojoin) {
230 if(qwebirc.auth.loggedin() && this.ui.uiOptions.USE_HIDDENHOST) {
231 var d = function() {
232 if($defined(this.activeTimers.autojoin))
233 this.ui.getActiveWindow().infoMessage("Waiting for login before joining channels...");
234 }.delay(5, this);
235 this.activeTimers.autojoin = function() {
236 var w = this.ui.getActiveWindow();
237 w.errorMessage("No login response in 10 seconds.");
238 w.errorMessage("You may want to try authing manually and then type: /autojoin (if you don't auth your host may be visible).");
239 }.delay(10000, this);
240 return;
241 }
242
243 this.exec("/AUTOJOIN");
244 }
245 },
246 userJoined: function(user, channel) {
247 var nick = user.hostToNick();
248 var host = user.hostToHost();
249
250 if((nick == this.nickname) && !this.getWindow(channel))
251 this.newWindow(channel, qwebirc.ui.WINDOW_CHANNEL, true);
252 this.tracker.addNickToChannel(nick, channel);
253
254 if(nick == this.nickname) {
255 this.newChanLine(channel, "OURJOIN", user);
256 } else {
257 if(!this.ui.uiOptions.HIDE_JOINPARTS) {
258 this.newChanLine(channel, "JOIN", user);
259 }
260 }
261 this.updateNickList(channel);
262 },
263 userPart: function(user, channel, message) {
264 var nick = user.hostToNick();
265 var host = user.hostToHost();
266
267 if(nick == this.nickname) {
268 this.tracker.removeChannel(channel);
269 } else {
270 this.tracker.removeNickFromChannel(nick, channel);
271 if(!this.ui.uiOptions.HIDE_JOINPARTS) {
272 this.newChanLine(channel, "PART", user, {"m": message});
273 }
274 }
275
276 this.updateNickList(channel);
277
278 if(nick == this.nickname) {
279 var w = this.getWindow(channel)
280 if(w)
281 w.close();
282 }
283 },
284 userKicked: function(kicker, channel, kickee, message) {
285 if(kickee == this.nickname) {
286 this.tracker.removeChannel(channel);
287 this.getWindow(channel).close();
288 } else {
289 this.tracker.removeNickFromChannel(kickee, channel);
290 this.updateNickList(channel);
291 }
292
293 this.newChanLine(channel, "KICK", kicker, {"v": kickee, "m": message});
294 },
295 channelMode: function(user, channel, modes, raw) {
296 modes.each(function(mo) {
297 var direction = mo[0];
298 var mode = mo[1];
299
300 var prefixindex = this.modeprefixes.indexOf(mode);
301 if(prefixindex == -1)
302 return;
303
304 var nick = mo[2];
305 var prefixchar = this.prefixes.charAt(prefixindex);
306
307 var nc = this.tracker.getOrCreateNickOnChannel(nick, channel);
308 if(direction == "-") {
309 this.removePrefix(nc, prefixchar);
310 } else {
311 this.addPrefix(nc, prefixchar);
312 }
313 }, this);
314
315 this.newChanLine(channel, "MODE", user, {"m": raw.join(" ")});
316
317 this.updateNickList(channel);
318 },
319 userQuit: function(user, message) {
320 var nick = user.hostToNick();
321
322 var channels = this.tracker.getNick(nick);
323
324 var clist = [];
325 for(var c in channels) {
326 clist.push(c);
327 if(!this.ui.uiOptions.HIDE_JOINPARTS) {
328 this.newChanLine(c, "QUIT", user, {"m": message});
329 }
330 }
331
332 this.tracker.removeNick(nick);
333
334 clist.each(function(cli) {
335 this.updateNickList(cli);
336 }, this);
337 },
338 nickChanged: function(user, newnick) {
339 var oldnick = user.hostToNick();
340
341 if(oldnick == this.nickname)
342 this.nickname = newnick;
343
344 this.tracker.renameNick(oldnick, newnick);
345
346 var channels = this.tracker.getNick(newnick);
347 var found = false;
348
349 for(var c in channels) {
350 var found = true;
351
352 this.newChanLine(c, "NICK", user, {"w": newnick});
353 this.updateNickList(c);
354 }
355
356 if(this.getQueryWindow(oldnick)) {
357 var found = true;
358 this.renameWindow(oldnick, newnick);
359 this.newLine(newnick, "NICK", {"n": oldnick, "w": newnick});
360 }
361
362 /* this is quite horrible */
363 if(!found)
364 this.newServerLine("NICK", {"w": newnick, n: user.hostToNick(), h: user.hostToHost(), "-": this.nickname});
365 },
366 channelTopic: function(user, channel, topic) {
367 this.newChanLine(channel, "TOPIC", user, {"m": topic});
368 this.getWindow(channel).updateTopic(topic);
369 },
370 initialTopic: function(channel, topic) {
371 this.getWindow(channel).updateTopic(topic);
372 },
373 channelCTCP: function(user, channel, type, args) {
374 if(args == undefined)
375 args = "";
376
377 var nick = user.hostToNick();
378 if(type == "ACTION") {
379 this.tracker.updateLastSpoke(nick, channel, new Date().getTime());
380 this.newChanLine(channel, "CHANACTION", user, {"m": args, "c": channel, "@": this.getNickStatus(channel, nick)});
381 return;
382 }
383
384 this.newChanLine(channel, "CHANCTCP", user, {"x": type, "m": args, "c": channel, "@": this.getNickStatus(channel, nick)});
385 },
386 userCTCP: function(user, type, args) {
387 var nick = user.hostToNick();
388 var host = user.hostToHost();
389 if(args == undefined)
390 args = "";
391
392 if(type == "ACTION") {
393 this.newQueryWindow(nick, true);
394 this.newQueryLine(nick, "PRIVACTION", {"m": args, "x": type, "h": host, "n": nick}, true);
395 return;
396 }
397
398 this.newTargetOrActiveLine(nick, "PRIVCTCP", {"m": args, "x": type, "h": host, "n": nick, "-": this.nickname});
399 },
400 userCTCPReply: function(user, type, args) {
401 var nick = user.hostToNick();
402 var host = user.hostToHost();
403 if(args == undefined)
404 args = "";
405
406 this.newTargetOrActiveLine(nick, "CTCPREPLY", {"m": args, "x": type, "h": host, "n": nick, "-": this.nickname});
407 },
408 getNickStatus: function(channel, nick) {
409 var n = this.tracker.getNickOnChannel(nick, channel);
410 if(!$defined(n))
411 return "";
412
413 if(n.prefixes.length == 0)
414 return "";
415
416 return n.prefixes.charAt(0);
417 },
418 channelPrivmsg: function(user, channel, message) {
419 var nick = user.hostToNick();
420
421 this.tracker.updateLastSpoke(nick, channel, new Date().getTime());
422 this.newChanLine(channel, "CHANMSG", user, {"m": message, "@": this.getNickStatus(channel, nick)});
423 },
424 channelNotice: function(user, channel, message) {
425 this.newChanLine(channel, "CHANNOTICE", user, {"m": message, "@": this.getNickStatus(channel, user.hostToNick())});
426 },
427 userPrivmsg: function(user, message) {
428 var nick = user.hostToNick();
429 var host = user.hostToHost();
430 this.newQueryWindow(nick, true);
431 this.pushLastNick(nick);
432 this.newQueryLine(nick, "PRIVMSG", {"m": message, "h": host, "n": nick}, true);
433
434 this.checkLogin(user, message);
435 },
436 checkLogin: function(user, message) {
437 if(this.isNetworkService(user) && $defined(this.activeTimers.autojoin)) {
438 if($defined(this.loginRegex) && message.match(this.loginRegex)) {
439 $clear(this.activeTimers.autojoin);
440 delete this.activeTimers["autojoin"];
441 this.ui.getActiveWindow().infoMessage("Joining channels...");
442 this.exec("/AUTOJOIN");
443 }
444 }
445 },
446 serverNotice: function(user, message) {
447 if(user == "") {
448 this.newServerLine("SERVERNOTICE", {"m": message});
449 } else {
450 this.newServerLine("PRIVNOTICE", {"m": message, "n": user});
451 }
452 },
453 userNotice: function(user, message) {
454 var nick = user.hostToNick();
455 var host = user.hostToHost();
456
457 if(this.ui.uiOptions.DEDICATED_NOTICE_WINDOW) {
458 this.newQueryWindow(nick, false);
459 this.newQueryOrActiveLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick}, false);
460 } else {
461 this.newTargetOrActiveLine(nick, "PRIVNOTICE", {"m": message, "h": host, "n": nick});
462 }
463
464 this.checkLogin(user, message);
465 },
466 isNetworkService: function(user) {
467 return this.ui.options.networkServices.indexOf(user) > -1;
468 },
469 __joinInvited: function() {
470 this.exec("/JOIN " + this.inviteChanList.join(","));
471 this.inviteChanList = [];
472 delete this.activeTimers["serviceInvite"];
473 },
474 userInvite: function(user, channel) {
475 var nick = user.hostToNick();
476 var host = user.hostToHost();
477
478 this.newServerLine("INVITE", {"c": channel, "h": host, "n": nick});
479 if(this.ui.uiOptions.ACCEPT_SERVICE_INVITES && this.isNetworkService(user)) {
480 if(this.activeTimers.serviceInvite)
481 $clear(this.activeTimers.serviceInvite);
482
483 /* we do this so we can batch the joins, i.e. instead of sending 5 JOIN comands we send 1 with 5 channels. */
484 this.activeTimers.serviceInvite = this.__joinInvited.delay(100, this);
485
486 this.inviteChanList.push(channel);
487 }
488 },
489 userMode: function(modes) {
490 this.newServerLine("UMODE", {"m": modes, "n": this.nickname});
491 },
492 channelNames: function(channel, names) {
493 if(names.length == 0) {
494 this.updateNickList(channel);
495 return;
496 }
497
498 names.each(function(nick) {
499 var prefixes = [];
500 var splitnick = nick.split("");
501
502 splitnick.every(function(c, i) {
503 if(this.prefixes.indexOf(c) == -1) {
504 nick = nick.substr(i);
505 return false;
506 }
507
508 prefixes.push(c);
509 return true;
510 }, this);
511
512 var nc = this.tracker.addNickToChannel(nick, channel);
513 prefixes.each(function(p) {
514 this.addPrefix(nc, p);
515 }, this);
516 }, this);
517 },
518 disconnected: function(message) {
519 for(var x in this.windows) {
520 var w = this.windows[x];
521 if(w.type == qwebirc.ui.WINDOW_CHANNEL)
522 w.close();
523 }
524 this.tracker = undefined;
525
526 this.newServerLine("DISCONNECT", {"m": message});
527 },
528 nickOnChanHasPrefix: function(nick, channel, prefix) {
529 var entry = this.tracker.getNickOnChannel(nick, channel);
530 if(!$defined(entry))
531 return false; /* shouldn't happen */
532
533 return entry.prefixes.indexOf(prefix) != -1;
534 },
535 nickOnChanHasAtLeastPrefix: function(nick, channel, prefix) {
536 var entry = this.tracker.getNickOnChannel(nick, channel);
537 if(!$defined(entry))
538 return false; /* shouldn't happen */
539
540 /* this array is sorted */
541 var pos = this.prefixes.indexOf(prefix);
542 if(pos == -1)
543 return false; /* shouldn't happen */
544
545 var modehash = {};
546 this.prefixes.slice(0, pos + 1).split("").each(function(x) {
547 modehash[x] = true;
548 });
549
550 var prefixes = entry.prefixes;
551 for(var i=0;i<prefixes.length;i++)
552 if(modehash[prefixes.charAt(i)])
553 return true;
554
555 return false;
556 },
557 supported: function(key, value) {
558 if(key == "PREFIX") {
559 var l = (value.length - 2) / 2;
560
561 this.modeprefixes = value.substr(1, l);
562 this.prefixes = value.substr(l + 2, l);
563 }
564
565 this.parent(key, value);
566 },
567 connected: function() {
568 this.newServerLine("CONNECT");
569 },
570 serverError: function(message) {
571 this.newServerLine("ERROR", {"m": message});
572 },
573 quit: function(message) {
574 this.send("QUIT :" + message, true);
575 this.disconnect();
576 },
577 disconnect: function() {
578 for(var k in this.activeTimers) {
579 this.activeTimers[k].cancel();
580 };
581 this.activeTimers = {};
582
583 this.parent();
584 },
585 awayMessage: function(nick, message) {
586 this.newQueryLine(nick, "AWAY", {"n": nick, "m": message}, true);
587 },
588 whois: function(nick, type, data) {
589 var ndata = {"n": nick};
590 var mtype;
591
592 var xsend = function() {
593 this.newTargetOrActiveLine(nick, "WHOIS" + mtype, ndata);
594 }.bind(this);
595
596 if(type == "user") {
597 mtype = "USER";
598 ndata.h = data.ident + "@" + data.hostname;
599 xsend();
600 mtype = "REALNAME";
601 ndata.m = data.realname;
602 } else if(type == "server") {
603 mtype = "SERVER";
604 ndata.x = data.server;
605 ndata.m = data.serverdesc;
606 } else if(type == "oper") {
607 mtype = "OPER";
608 } else if(type == "idle") {
609 mtype = "IDLE";
610 ndata.x = qwebirc.util.longtoduration(data.idle);
611 ndata.m = qwebirc.irc.IRCDate(new Date(data.connected * 1000));
612 } else if(type == "channels") {
613 mtype = "CHANNELS";
614 ndata.m = data.channels;
615 } else if(type == "account") {
616 mtype = "ACCOUNT";
617 ndata.m = data.account;
618 } else if(type == "away") {
619 mtype = "AWAY";
620 ndata.m = data.away;
621 } else if(type == "opername") {
622 mtype = "OPERNAME";
623 ndata.m = data.opername;
624 } else if(type == "actually") {
625 mtype = "ACTUALLY";
626 ndata.m = data.hostname;
627 ndata.x = data.ip;
628 } else if(type == "generictext") {
629 mtype = "GENERICTEXT";
630 ndata.m = data.text;
631 } else if(type == "end") {
632 mtype = "END";
633 } else {
634 return false;
635 }
636
637 xsend();
638 return true;
639 },
640 genericError: function(target, message) {
641 this.newTargetOrActiveLine(target, "GENERICERROR", {m: message, t: target});
642 },
643 genericQueryError: function(target, message) {
644 this.newQueryOrActiveLine(target, "GENERICERROR", {m: message, t: target}, true);
645 },
646 awayStatus: function(state, message) {
647 this.newActiveLine("GENERICMESSAGE", {m: message});
648 },
649 pushLastNick: function(nick) {
650 var i = this.lastNicks.indexOf(nick);
651 if(i != -1) {
652 this.lastNicks.splice(i, 1);
653 } else {
654 if(this.lastNicks.length == this.options.maxnicks)
655 this.lastNicks.pop();
656 }
657 this.lastNicks.unshift(nick);
658 },
659 wallops: function(user, text) {
660 var nick = user.hostToNick();
661 var host = user.hostToHost();
662
663 this.newServerLine("WALLOPS", {t: text, n: nick, h: host});
664 },
665 channelModeIs: function(channel, modes) {
666 this.newTargetOrActiveLine(channel, "CHANNELMODEIS", {c: channel, m: modes.join(" ")});
667 },
668 channelCreationTime: function(channel, time) {
669 this.newTargetOrActiveLine(channel, "CHANNELCREATIONTIME", {c: channel, m: qwebirc.irc.IRCDate(new Date(time * 1000))});
670 }
671 });