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