]>
jfr.im git - irc/quakenet/qwebirc.git/blob - js/irc/ircclient.js
1 qwebirc
.irc
.IRCClient
= new Class({
2 Extends: qwebirc
.irc
.BaseIRCClient
,
8 initialize: function(options
, ui
) {
14 this.modeprefixes
= "ov";
15 this.windows
= new QHash();
17 this.commandparser
= new qwebirc
.irc
.Commands(this);
18 this.exec
= this.commandparser
.dispatch
.bind(this.commandparser
);
20 this.statusWindow
= this.ui
.newClient(this);
23 this.inviteChanList
= [];
24 this.activeTimers
= {};
26 this.loginRegex
= new RegExp(this.ui
.options
.loginRegex
);
27 this.tracker
= new qwebirc
.irc
.IRCTracker(this);
29 this.__silenceSupported
= false;
30 this.__silenced
= null;
31 this.ignoreController
= new qwebirc
.irc
.IgnoreController(function(x
) { return this.toIRCLower(x
) }.bind(this));
35 this.newServerInfoLine("CONNECTING", "");
37 newLine: function(window
, type
, data
) {
41 var w
= this.getWindow(window
);
43 w
.addLine(type
, data
);
45 this.statusWindow
.addLine(type
, data
);
48 newChanLine: function(channel
, type
, user
, extra
) {
53 extra
["n"] = user
.hostToNick();
54 extra
["h"] = user
.hostToHost();
57 extra
["-"] = this.nickname
;
59 if(!(this.ui
.uiOptions
.NICK_OV_STATUS
))
62 this.newLine(channel
, type
, extra
);
64 newServerLine: function(type
, data
) {
65 this.statusWindow
.addLine(type
, data
);
67 newServerInfoLine: function(type
, data
) {
68 this.statusWindow
.infoMessage(type
, data
);
70 newActiveLine: function(type
, data
) {
71 this.getActiveWindow().addLine(type
, data
);
73 newTargetOrActiveLine: function(target
, type
, data
) {
74 if(this.getWindow(target
)) {
75 this.newLine(target
, type
, data
);
77 this.newActiveLine(type
, data
);
80 getPrefixPriority: function(prefixes
) {
81 if (prefixes
.length
> 0) {
82 var c
= prefixes
.charAt(0);
83 var p
= this.prefixes
.indexOf(c
);
89 updateNickList: function(channel
) {
93 var n1
= this.tracker
.getChannel(channel
);
95 n1
.each(function (n
, nc
) {
96 var prefix
, pri
= this.getPrefixPriority(nc
.prefixes
);
97 if(nc
.prefixes
.length
> 0) {
98 prefix
= nc
.prefixes
.charAt(0);
102 names
.push([pri
, this.toIRCLower(n
), prefix
+ n
]);
105 names
.sort(qwebirc
.util
.arrayCmp
);
107 var sortednames
= [];
108 names
.each(function(name
) {
109 sortednames
.push(name
[2]);
112 var w
= this.getWindow(channel
);
114 w
.updateNickList(sortednames
);
116 getWindow: function(name
) {
117 return this.windows
.get(this.toIRCLower(name
));
119 renameWindow: function(oldname
, newname
) {
120 var oldwindow
= this.getWindow(oldname
);
121 if(!oldwindow
|| this.getWindow(newname
))
124 var window
= this.ui
.renameWindow(oldwindow
, newname
);
126 this.windows
.put(this.toIRCLower(newname
), window
);
127 this.windows
.remove(this.toIRCLower(oldname
));
130 newWindow: function(name
, type
, select
) {
131 var w
= this.getWindow(name
);
133 w
= this.ui
.newWindow(this, type
, name
);
134 this.windows
.put(this.toIRCLower(name
), w
);
136 w
.addEvent("close", function(w
) {
137 this.windows
.remove(this.toIRCLower(name
));
142 this.ui
.selectWindow(w
);
146 getQueryWindow: function(name
) {
147 return this.ui
.getWindow(this, qwebirc
.ui
.WINDOW_QUERY
, name
);
149 newQueryWindow: function(name
, privmsg
) {
152 if(this.getQueryWindow(name
))
156 return this.newPrivmsgQueryWindow(name
);
157 return this.newNoticeQueryWindow(name
);
159 newPrivmsgQueryWindow: function(name
) {
160 if(this.ui
.uiOptions
.DEDICATED_MSG_WINDOW
) {
161 if(!this.ui
.getWindow(this, qwebirc
.ui
.WINDOW_MESSAGES
))
162 return this.ui
.newWindow(this, qwebirc
.ui
.WINDOW_MESSAGES
, "Messages");
164 return this.newWindow(name
, qwebirc
.ui
.WINDOW_QUERY
, false);
167 newNoticeQueryWindow: function(name
) {
168 if(this.ui
.uiOptions
.DEDICATED_NOTICE_WINDOW
)
169 if(!this.ui
.getWindow(this, qwebirc
.ui
.WINDOW_MESSAGES
))
170 return this.ui
.newWindow(this, qwebirc
.ui
.WINDOW_MESSAGES
, "Messages");
172 newQueryLine: function(window
, type
, data
, privmsg
, active
) {
173 if(this.getQueryWindow(window
))
174 return this.newLine(window
, type
, data
);
176 var w
= this.ui
.getWindow(this, qwebirc
.ui
.WINDOW_MESSAGES
);
180 e
= this.ui
.uiOptions
.DEDICATED_MSG_WINDOW
;
182 e
= this.ui
.uiOptions
.DEDICATED_NOTICE_WINDOW
;
185 return w
.addLine(type
, data
);
188 return this.newActiveLine(type
, data
);
190 return this.newLine(window
, type
, data
);
194 newQueryOrActiveLine: function(window
, type
, data
, privmsg
) {
195 this.newQueryLine(window
, type
, data
, privmsg
, true);
197 getActiveWindow: function() {
198 return this.ui
.getActiveIRCWindow(this);
200 getNickname: function() {
201 return this.nickname
;
203 addPrefix: function(nickchanentry
, prefix
) {
204 var ncp
= nickchanentry
.prefixes
+ prefix
;
208 for(var i
=0;i
<this.prefixes
.length
;i
++) {
209 var pc
= this.prefixes
.charAt(i
);
210 var index
= ncp
.indexOf(pc
);
215 nickchanentry
.prefixes
= prefixes
.join("");
217 stripPrefix: function(nick
) {
218 var l
= nick
.charAt(0);
222 if(this.prefixes
.indexOf(l
) != -1)
223 return nick
.substring(1);
227 removePrefix: function(nickchanentry
, prefix
) {
228 nickchanentry
.prefixes
= nickchanentry
.prefixes
.replaceAll(prefix
, "");
231 /* from here down are events */
232 rawNumeric: function(numeric
, prefix
, params
) {
233 this.newServerLine("RAW", {"n": "numeric", "m": params
.slice(1).join(" ")});
235 signedOn: function(nickname
) {
236 this.tracker
= new qwebirc
.irc
.IRCTracker(this);
237 this.nickname
= nickname
;
238 this.newServerLine("SIGNON");
240 /* we guarantee that +x is sent out before the joins */
241 if(this.ui
.uiOptions
.USE_HIDDENHOST
)
242 this.exec("/UMODE +x");
244 if(this.options
.autojoin
) {
245 if(qwebirc
.auth
.loggedin() && this.ui
.uiOptions
.USE_HIDDENHOST
) {
247 if($defined(this.activeTimers
.autojoin
))
248 this.ui
.getActiveWindow().infoMessage("Waiting for login before joining channels...");
250 this.activeTimers
.autojoin = function() {
251 var w
= this.ui
.getActiveWindow();
252 w
.errorMessage("No login response in 10 seconds.");
253 w
.errorMessage("You may want to try authing manually and then type: /autojoin (if you don't auth your host may be visible).");
254 }.delay(10000, this);
258 this.exec("/AUTOJOIN");
260 var d = function() { this.newServerInfoLine("CONNECTED", ""); }.delay(1000, this);
263 this.fireEvent("signedOn");
265 userJoined: function(user
, channel
) {
266 var nick
= user
.hostToNick();
267 var host
= user
.hostToHost();
269 if((nick
== this.nickname
) && !this.getWindow(channel
))
270 this.newWindow(channel
, qwebirc
.ui
.WINDOW_CHANNEL
, true);
271 this.tracker
.addNickToChannel(nick
, channel
);
273 if(nick
== this.nickname
) {
274 this.newChanLine(channel
, "OURJOIN", user
);
276 if(!this.ui
.uiOptions
.HIDE_JOINPARTS
) {
277 this.newChanLine(channel
, "JOIN", user
);
280 this.updateNickList(channel
);
282 userPart: function(user
, channel
, message
) {
283 var nick
= user
.hostToNick();
284 var host
= user
.hostToHost();
286 if(nick
== this.nickname
) {
287 this.tracker
.removeChannel(channel
);
289 this.tracker
.removeNickFromChannel(nick
, channel
);
290 if(!this.ui
.uiOptions
.HIDE_JOINPARTS
) {
291 this.newChanLine(channel
, "PART", user
, {"m": message
});
295 this.updateNickList(channel
);
297 if(nick
== this.nickname
) {
298 var w
= this.getWindow(channel
);
303 userKicked: function(kicker
, channel
, kickee
, message
) {
304 if(kickee
== this.nickname
) {
305 this.tracker
.removeChannel(channel
);
306 this.getWindow(channel
).close();
308 this.tracker
.removeNickFromChannel(kickee
, channel
);
309 this.updateNickList(channel
);
312 this.newChanLine(channel
, "KICK", kicker
, {"v": kickee
, "m": message
});
314 channelMode: function(user
, channel
, modes
, raw
) {
315 modes
.each(function(mo
) {
316 var direction
= mo
[0];
319 var prefixindex
= this.modeprefixes
.indexOf(mode
);
320 if(prefixindex
== -1)
324 var prefixchar
= this.prefixes
.charAt(prefixindex
);
326 var nc
= this.tracker
.getOrCreateNickOnChannel(nick
, channel
);
327 if(direction
== "-") {
328 this.removePrefix(nc
, prefixchar
);
330 this.addPrefix(nc
, prefixchar
);
334 this.newChanLine(channel
, "MODE", user
, {"m": raw
.join(" ")});
336 this.updateNickList(channel
);
338 userQuit: function(user
, message
) {
339 var nick
= user
.hostToNick();
342 var channels
= this.tracker
.getNick(nick
);
343 if($defined(channels
)) {
344 channels
.each(function (c
) {
346 if (!this.ui
.uiOptions
.HIDE_JOINPARTS
) {
347 this.newChanLine(c
, "QUIT", user
, {"m": message
});
351 this.tracker
.removeNick(nick
);
353 clist
.each(function(cli
) {
354 this.updateNickList(cli
);
357 nickChanged: function(user
, newnick
) {
358 var oldnick
= user
.hostToNick();
360 if(oldnick
== this.nickname
)
361 this.nickname
= newnick
;
363 this.tracker
.renameNick(oldnick
, newnick
);
367 var channels
= this.tracker
.getNick(newnick
);
368 if($defined(channels
)) {
369 channels
.each(function (c
) {
372 this.newChanLine(c
, "NICK", user
, {"w": newnick
});
373 this.updateNickList(c
);
377 if(this.getQueryWindow(oldnick
)) {
379 this.renameWindow(oldnick
, newnick
);
380 this.newLine(newnick
, "NICK", {"n": oldnick
, "w": newnick
});
383 /* this is quite horrible */
385 this.newServerLine("NICK", {"w": newnick
, n: user
.hostToNick(), h: user
.hostToHost(), "-": this.nickname
});
387 channelTopic: function(user
, channel
, topic
) {
388 this.newChanLine(channel
, "TOPIC", user
, {"m": topic
});
389 this.getWindow(channel
).updateTopic(topic
);
391 initialTopic: function(channel
, topic
) {
392 this.getWindow(channel
).updateTopic(topic
);
394 channelCTCP: function(user
, channel
, type
, args
) {
395 if(args
== undefined)
398 var nick
= user
.hostToNick();
399 if(type
== "ACTION") {
400 this.tracker
.updateLastSpoke(nick
, channel
, new Date().getTime());
401 this.newChanLine(channel
, "CHANACTION", user
, {"m": args
, "c": channel
, "@": this.getNickStatus(channel
, nick
)});
405 this.newChanLine(channel
, "CHANCTCP", user
, {"x": type
, "m": args
, "c": channel
, "@": this.getNickStatus(channel
, nick
)});
407 userCTCP: function(user
, type
, args
) {
408 var nick
= user
.hostToNick();
409 var host
= user
.hostToHost();
410 if(args
== undefined)
413 if(type
== "ACTION") {
414 this.newQueryWindow(nick
, true);
415 this.newQueryLine(nick
, "PRIVACTION", {"m": args
, "x": type
, "h": host
, "n": nick
}, true);
419 this.newTargetOrActiveLine(nick
, "PRIVCTCP", {"m": args
, "x": type
, "h": host
, "n": nick
, "-": this.nickname
});
421 userCTCPReply: function(user
, type
, args
) {
422 var nick
= user
.hostToNick();
423 var host
= user
.hostToHost();
424 if(args
== undefined)
427 this.newTargetOrActiveLine(nick
, "CTCPREPLY", {"m": args
, "x": type
, "h": host
, "n": nick
, "-": this.nickname
});
429 getNickStatus: function(channel
, nick
) {
430 var n
= this.tracker
.getNickOnChannel(nick
, channel
);
434 if(n
.prefixes
.length
== 0)
437 return n
.prefixes
.charAt(0);
439 channelPrivmsg: function(user
, channel
, message
) {
440 var nick
= user
.hostToNick();
441 var host
= user
.hostToHost();
443 if(this.isIgnored(nick
, host
))
446 this.tracker
.updateLastSpoke(nick
, channel
, new Date().getTime());
447 this.newChanLine(channel
, "CHANMSG", user
, {"m": message
, "@": this.getNickStatus(channel
, nick
)});
449 channelNotice: function(user
, channel
, message
) {
450 var nick
= user
.hostToNick();
451 var host
= user
.hostToHost();
453 if(this.isIgnored(nick
, host
))
456 this.newChanLine(channel
, "CHANNOTICE", user
, {"m": message
, "@": this.getNickStatus(channel
, user
.hostToNick())});
458 userPrivmsg: function(user
, message
) {
459 var nick
= user
.hostToNick();
460 var host
= user
.hostToHost();
462 if(this.isIgnored(nick
, host
))
465 this.newQueryWindow(nick
, true);
466 this.pushLastNick(nick
);
467 this.newQueryLine(nick
, "PRIVMSG", {"m": message
, "h": host
, "n": nick
}, true);
469 this.checkLogin(user
, message
);
471 checkLogin: function(user
, message
) {
472 if(this.isNetworkService(user
) && $defined(this.activeTimers
.autojoin
)) {
473 if($defined(this.loginRegex
) && message
.match(this.loginRegex
)) {
474 $clear(this.activeTimers
.autojoin
);
475 delete this.activeTimers
["autojoin"];
476 this.ui
.getActiveWindow().infoMessage("Joining channels...");
477 this.exec("/AUTOJOIN");
481 serverNotice: function(user
, message
) {
483 this.newServerLine("SERVERNOTICE", {"m": message
});
485 this.newServerLine("PRIVNOTICE", {"m": message
, "n": user
});
488 userNotice: function(user
, message
) {
489 var nick
= user
.hostToNick();
490 var host
= user
.hostToHost();
492 if(this.isIgnored(nick
, host
))
495 if(this.ui
.uiOptions
.DEDICATED_NOTICE_WINDOW
) {
496 this.newQueryWindow(nick
, false);
497 this.newQueryOrActiveLine(nick
, "PRIVNOTICE", {"m": message
, "h": host
, "n": nick
}, false);
499 this.newTargetOrActiveLine(nick
, "PRIVNOTICE", {"m": message
, "h": host
, "n": nick
});
502 this.checkLogin(user
, message
);
504 isNetworkService: function(user
) {
505 return this.ui
.options
.networkServices
.indexOf(user
) > -1;
507 __joinInvited: function() {
508 this.exec("/JOIN " + this.inviteChanList
.join(","));
509 this.inviteChanList
= [];
510 delete this.activeTimers
["serviceInvite"];
512 userInvite: function(user
, channel
) {
513 var nick
= user
.hostToNick();
514 var host
= user
.hostToHost();
516 if(this.isIgnored(nick
, host
))
519 this.newServerLine("INVITE", {"c": channel
, "h": host
, "n": nick
});
520 if(this.ui
.uiOptions
.ACCEPT_SERVICE_INVITES
&& this.isNetworkService(user
)) {
521 if(this.activeTimers
.serviceInvite
)
522 $clear(this.activeTimers
.serviceInvite
);
524 /* we do this so we can batch the joins, i.e. instead of sending 5 JOIN comands we send 1 with 5 channels. */
525 this.activeTimers
.serviceInvite
= this.__joinInvited
.delay(100, this);
527 this.inviteChanList
.push(channel
);
530 userMode: function(modes
) {
531 this.newServerLine("UMODE", {"m": modes
, "n": this.nickname
});
533 channelNames: function(channel
, names
) {
534 if(names
.length
== 0) {
535 this.updateNickList(channel
);
539 names
.each(function(nick
) {
541 var splitnick
= nick
.split("");
543 splitnick
.every(function(c
, i
) {
544 if(this.prefixes
.indexOf(c
) == -1) {
545 nick
= nick
.substr(i
);
553 var nc
= this.tracker
.addNickToChannel(nick
, channel
);
554 prefixes
.each(function(p
) {
555 this.addPrefix(nc
, p
);
559 disconnected: function(message
) {
561 this.windows
.each(function(k
, v
) {
562 if(v
.type
== qwebirc
.ui
.WINDOW_CHANNEL
)
565 for(var i
=0;i
<toClose
.length
;i
++)
568 this.tracker
= undefined;
570 qwebirc
.connected
= false;
571 this.newServerLine("DISCONNECT", {"m": message
});
573 nickOnChanHasPrefix: function(nick
, channel
, prefix
) {
574 var entry
= this.tracker
.getNickOnChannel(nick
, channel
);
576 return false; /* shouldn't happen */
578 return entry
.prefixes
.indexOf(prefix
) != -1;
580 nickOnChanHasAtLeastPrefix: function(nick
, channel
, prefix
) {
581 var entry
= this.tracker
.getNickOnChannel(nick
, channel
);
583 return false; /* shouldn't happen */
585 /* this array is sorted */
586 var pos
= this.prefixes
.indexOf(prefix
);
588 return false; /* shouldn't happen */
590 var modehash
= new QSet();
591 this.prefixes
.slice(0, pos
+ 1).split("").each(function(x
) {
595 var prefixes
= entry
.prefixes
;
596 for(var i
=0;i
<prefixes
.length
;i
++)
597 if(modehash
.contains(prefixes
.charAt(i
)))
602 supported: function(key
, value
) {
603 if(key
== "PREFIX") {
604 var l
= (value
.length
- 2) / 2;
606 this.modeprefixes
= value
.substr(1, l
);
607 this.prefixes
= value
.substr(l
+ 2, l
);
608 } else if(key
== "SILENCE") {
609 this.__silenceSupported
= true;
612 this.parent(key
, value
);
614 connected: function() {
615 qwebirc
.connected
= true;
616 this.newServerInfoLine("CONNECT", "");
618 serverError: function(message
) {
619 this.newServerLine("ERROR", {"m": message
});
621 quit: function(message
) {
622 this.send("QUIT :" + message
, true);
625 disconnect: function() {
626 for(var k
in this.activeTimers
) {
627 this.activeTimers
[k
].cancel();
629 this.activeTimers
= {};
633 awayMessage: function(nick
, message
) {
634 this.newQueryLine(nick
, "AWAY", {"n": nick
, "m": message
}, true);
636 whois: function(nick
, type
, data
) {
637 var ndata
= {"n": nick
};
640 var xsend = function() {
641 this.newTargetOrActiveLine(nick
, "WHOIS" + mtype
, ndata
);
646 ndata
.h
= data
.ident
+ "@" + data
.hostname
;
649 ndata
.m
= data
.realname
;
650 } else if(type
== "server") {
652 ndata
.x
= data
.server
;
653 ndata
.m
= data
.serverdesc
;
654 } else if(type
== "oper") {
656 } else if(type
== "idle") {
658 ndata
.x
= qwebirc
.util
.longtoduration(data
.idle
);
659 ndata
.m
= qwebirc
.irc
.IRCDate(new Date(data
.connected
* 1000));
660 } else if(type
== "channels") {
662 ndata
.m
= data
.channels
;
663 } else if(type
== "account") {
665 ndata
.m
= data
.account
;
666 } else if(type
== "away") {
669 } else if(type
== "opername") {
671 ndata
.m
= data
.opername
;
672 } else if(type
== "actually") {
674 ndata
.m
= data
.hostname
;
676 } else if(type
== "generictext") {
677 mtype
= "GENERICTEXT";
679 } else if(type
== "end") {
688 genericError: function(target
, message
) {
689 this.newTargetOrActiveLine(target
, "GENERICERROR", {m: message
, t: target
});
691 genericQueryError: function(target
, message
) {
692 this.newQueryOrActiveLine(target
, "GENERICERROR", {m: message
, t: target
}, true);
694 awayStatus: function(state
, message
) {
695 this.newActiveLine("GENERICMESSAGE", {m: message
});
697 pushLastNick: function(nick
) {
698 var i
= this.lastNicks
.indexOf(nick
);
700 this.lastNicks
.splice(i
, 1);
702 if(this.lastNicks
.length
== this.options
.maxnicks
)
703 this.lastNicks
.pop();
705 this.lastNicks
.unshift(nick
);
707 wallops: function(user
, text
) {
708 var nick
= user
.hostToNick();
709 var host
= user
.hostToHost();
711 this.newServerLine("WALLOPS", {t: text
, n: nick
, h: host
});
713 channelModeIs: function(channel
, modes
) {
714 this.newTargetOrActiveLine(channel
, "CHANNELMODEIS", {c: channel
, m: modes
.join(" ")});
716 channelCreationTime: function(channel
, time
) {
717 this.newTargetOrActiveLine(channel
, "CHANNELCREATIONTIME", {c: channel
, m: qwebirc
.irc
.IRCDate(new Date(time
* 1000))});
719 ignore: function(host
) {
720 var host
= this.ignoreController
.ignore(host
);
724 if(this.__silenceSupported
) {
725 this.__silenced
= "+" + host
;
726 this.exec("/SILENCE +" + host
);
731 unignore: function(host
) {
732 var host
= this.ignoreController
.unignore(host
);
736 if(this.__silenceSupported
) {
737 this.__silenced
= "-" + host
;
738 this.exec("/SILENCE -" + host
);
743 getIgnoreList: function() {
744 return this.ignoreController
.get();
746 silenced: function(host
) {
747 if (host
=== this.__silenced
) {
748 this.__silenced
= null;
752 this.newServerLine("SILENCE", {h: host
});
754 isIgnored: function(nick
, host
) {
755 return this.ignoreController
.isIgnored(nick
, host
);