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