diff options
author | Brian Sherson <caretaker82@euclid.shersonb.net> | 2014-01-10 20:38:51 -0800 |
---|---|---|
committer | Brian Sherson <caretaker82@euclid.shersonb.net> | 2014-01-10 20:38:51 -0800 |
commit | 2bc7e36fa2e33f8ddbb1aaeebbf4e2e788a9602b (patch) | |
tree | 388771cff941e1305f0dddc3c5e064b8e3fbd5a8 | |
parent | f85a25ca2d46544de3ca5613eebdef903e9340ee (diff) |
New Release
-rw-r--r-- | bouncer.py | 783 | ||||
-rw-r--r-- | irc.py | 2680 | ||||
-rw-r--r-- | logger.py | 308 |
3 files changed, 2131 insertions, 1640 deletions
@@ -9,16 +9,29 @@ import string import hashlib import traceback import irc +import getpass from threading import Thread, Lock import Queue +def BouncerReload(BNC): + if BNC.isAlive(): + BNC.stop() + newBNC = Bouncer(addr=BNC.addr, port=BNC.port, ssl=BNC.ssl, ipv6=BNC.ipv6, + certfile=BNC.certfile, keyfile=BNC.keyfile, timeout=BNC.timeout, autoaway=BNC.autoaway) + for label, (IRC, passwd, hashtype) in BNC.servers.items(): + IRC.rmAddon(BNC) + IRC.addAddon(newBNC, label=label, passwd=passwd, hashtype=hashtype) + return newBNC + + class Bouncer (Thread): - def __init__(self, addr="", port=16667, ssl=False, ipv6=False, certfile=None, keyfile=None, ignore=None, debug=False, log=sys.stderr, timeout=300, BNC=None, autoaway=None): + + def __init__(self, addr="", port=16667, ssl=False, ipv6=False, certfile=None, keyfile=None, ignore=None, debug=False, timeout=300, autoaway=None): self.__name__ = "Bouncer for pyIRC" - self.__version__ = "1.1" + self.__version__ = "1.2" self.__author__ = "Brian Sherson" - self.__date__ = "December 1, 2013" + self.__date__ = "December 26, 2013" self.addr = addr self.port = port @@ -37,9 +50,10 @@ class Bouncer (Thread): self.debug = debug self.timeout = timeout self.autoaway = autoaway + self._stopexpected = False - ### Keep track of what extensions/connections are requesting WHO, WHOIS, and LIST, because we don't want to spam every bouncer connection with the server's replies. - ### In the future, MAY implement this idea in the irc module. + # Keep track of what extensions/connections are requesting WHO, WHOIS, and LIST, because we don't want to spam every bouncer connection with the server's replies. + # In the future, MAY implement this idea in the irc module. self.whoexpected = {} self.whoisexpected = {} self.listexpected = {} @@ -59,13 +73,19 @@ class Bouncer (Thread): try: (connection, addr) = self.socket.accept() if self.ssl: - connection = ssl.wrap_socket(connection, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23) + connection = ssl.wrap_socket( + connection, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23) #print ((self,"New client connecting from %s:%s"%addr)) except socket.error: - #print "Shutting down Listener" + # print "Shutting down Listener" self.socket.close() - #raise + if not self._stopexpected: + raise sys.exit() + except: + tb = traceback.format_exc() + print >>sys.stderr, tb + continue connection.settimeout(self.timeout) bouncer = BouncerConnection( self, connection, addr, debug=self.debug) @@ -75,15 +95,23 @@ class Bouncer (Thread): except: pass - def onAddonAdd(self, IRC, label, passwd, hashtype="md5"): - if IRC in [connection for (connection, passwd, hashtype) in self.servers.values()]: + def onAddonAdd(self, IRC, label, passwd=None, hashtype="sha512"): + if passwd == None: + while True: + passwd = getpass.getpass("Enter new password: ") + if passwd == getpass.getpass("Confirm new password: "): + break + print "Passwords do not match!" + passwd = hashlib.new(hashtype, passwd).hexdigest() + if IRC in [connection for (connection, p, h) in self.servers.values()]: return # Silently do nothing if label in self.servers.keys(): return self.servers[label] = (IRC, passwd, hashtype) self.whoexpected[IRC] = [] if self.debug: - IRC.logwrite("dbg [Bouncer.onAddonAdd] Clearing WHO expected list." % vars()) + IRC.logwrite( + "dbg [Bouncer.onAddonAdd] Clearing WHO expected list." % vars()) self.whoisexpected[IRC] = [] self.listexpected[IRC] = [] @@ -96,6 +124,7 @@ class Bouncer (Thread): del self.servers[label] def stop(self): + self._stopexpected = True self.socket.shutdown(0) def disconnectall(self, quitmsg="Disconnecting all sessions"): @@ -104,248 +133,259 @@ class Bouncer (Thread): def onDisconnect(self, IRC, expected=False): self.whoexpected[IRC] = [] - if self.debug: - IRC.logwrite("dbg [Bouncer.onDisconnect] Clearing WHO expected list.") self.whoisexpected[IRC] = [] self.listexpected[IRC] = [] for bouncerconnection in self.connections: if bouncerconnection.IRC == IRC: - bouncerconnection.quit(quitmsg="IRC connection lost") + #bouncerconnection.quit(quitmsg="IRC connection lost") + for channel in IRC.identity.channels: + bouncerconnection.send(":%s!%s@%s PART %s :Bouncer Connection Lost\n" % ( + IRC.identity.nick, IRC.identity.username, IRC.identity.host, channel.name)) + bouncerconnection.send( + ":%s!%s@%s QUIT :Bouncer Connection Lost\n" % + (IRC.identity.nick, IRC.identity.username, IRC.identity.host)) + bouncerconnection.send( + ":*Bouncer* NOTICE %s :Connection to %s:%s has been lost.\n" % + (bouncerconnection.nick, IRC.server, IRC.port)) + + def onQuit(self, IRC, user, quitmsg): + # For some odd reason, certain networks (*cough*Freenode*cough*) will send a quit message for the user, causing IRC.identity.channels to be cleared + # before onDisconnect can be executed. This is the remedy. + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + if quitmsg: + bouncerconnection.send(":%s!%s@%s QUIT :%s\n" % ( + user.nick, user.username, user.host, quitmsg)) + else: + bouncerconnection.send( + ":%s!%s@%s QUIT\n" % (user.nick, user.username, user.host)) + if user == IRC.identity: + for channel in IRC.identity.channels: + bouncerconnection.send(":%s!%s@%s PART %s :Bouncer Connection Lost\n" % ( + IRC.identity.nick, IRC.identity.username, IRC.identity.host, channel.name)) + + def onConnectAttempt(self, IRC): + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + bouncerconnection.send( + ":*Bouncer* NOTICE %s :Attempting connection to %s:%s.\n" % + (bouncerconnection.nick, IRC.server, IRC.port)) + + def onConnect(self, IRC): + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + bouncerconnection.send( + ":*Bouncer* NOTICE %s :Connection to %s:%s established.\n" % + (bouncerconnection.nick, IRC.server, IRC.port)) + + def onMeNickChange(self, IRC, newnick): + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + bouncerconnection.send(":%s!%s@%s NICK %s\n" % + (IRC.identity.nick, IRC.identity.username, IRC.identity.host, newnick)) + bouncerconnection.nick = newnick + + def onRegistered(self, IRC): + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + if bouncerconnection.nick != IRC.identity.nick: + bouncerconnection.send(":%s!%s@%s NICK %s\n" % ( + bouncerconnection.nick, bouncerconnection.username, bouncerconnection.host, IRC.identity.nick)) + bouncerconnection.nick = IRC.identity.nick + + def onConnectFail(self, IRC, exc, excmsg, tb): + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + bouncerconnection.send( + ":*Bouncer* NOTICE %s :Connection to %s:%s failed: %s.\n" % + (bouncerconnection.nick, IRC.server, IRC.port, excmsg)) def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg): - ### Called when bot sends a PRIVMSG to channel. - ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent. + # Called when bot sends a PRIVMSG to channel. + # The variable origin refers to a class instance voluntarily + # identifying itself as that which requested data be sent. for bouncerconnection in self.connections: if IRC == bouncerconnection.IRC and origin != bouncerconnection: - bouncerconnection.send(":%s!%s@%s PRIVMSG %s%s :%s\n"%(IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg)) + bouncerconnection.send(":%s!%s@%s PRIVMSG %s%s :%s\n" % + (IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg)) def onSendChanAction(self, IRC, origin, channel, targetprefix, action): - self.onSendChanMsg(IRC, origin, channel, targetprefix, - "\x01ACTION %s\x01"%action) + self.onSendChanMsg( + IRC, origin, channel, targetprefix, "\x01ACTION %s\x01" % action) def onSendChanNotice(self, IRC, origin, channel, targetprefix, msg): - ### Called when bot sends a NOTICE to channel. - ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent. + # Called when bot sends a NOTICE to channel. + # The variable origin refers to a class instance voluntarily + # identifying itself as that which requested data be sent. for bouncerconnection in self.connections: if IRC == bouncerconnection.IRC and origin != bouncerconnection: - bouncerconnection.send(":%s!%s@%s NOTICE %s%s :%s\n"%(IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg)) + bouncerconnection.send(":%s!%s@%s NOTICE %s%s :%s\n" % ( + IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg)) def onSend(self, IRC, origin, line, cmd, target, params, extinfo): if cmd.upper() == "WHO": self.whoexpected[IRC].append(origin) if self.debug: - with IRC.loglock: - timestamp = irc.timestamp() - if issubclass(type(origin), Thread): - name = origin.name - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] Adding %(origin)s (%(name)s) to WHO expected list." % vars() - else: - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] Adding %(origin)s to WHO expected list." % vars() - for obj in self.whoexpected[IRC]: - if issubclass(type(obj), Thread): - name = obj.name - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] WHO expected: %(obj)s (%(name)s)" % vars() - else: - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] WHO expected: %(obj)s" % vars() - IRC.log.flush() + if issubclass(type(origin), Thread): + name = origin.name + IRC.logwrite( + "dbg [Bouncer.onSend] Adding %(origin)s (%(name)s) to WHO expected list." % vars()) + else: + IRC.logwrite( + "dbg [Bouncer.onSend] Adding %(origin)s to WHO expected list." % vars()) + IRC.logwrite( + "dbg [Bouncer.onSend] WHO expected list size: %d" % + len(self.whoexpected[IRC])) elif cmd.upper() == "WHOIS": self.whoisexpected[IRC].append(origin) elif cmd.upper() == "LIST": self.listexpected[IRC].append(origin) def onWhoEntry(self, IRC, origin, channel, user, channame, username, host, serv, nick, flags, hops, realname): - ### Called when a WHO list is received. + # Called when a WHO list is received. if len(self.whoexpected[IRC]) and self.whoexpected[IRC][0] in self.connections: bncconnection = self.whoexpected[IRC][0] - try: - bncconnection.send(":%s 352 %s %s %s %s %s %s %s :%s %s\n"%(origin, IRC.identity.nick, channame, username, host, serv, nick, flags, hops, realname)) - except socket.error: - pass + bncconnection.send(":%s 352 %s %s %s %s %s %s %s :%s %s\n" % + (origin, IRC.identity.nick, channame, username, host, serv, nick, flags, hops, realname)) def onWhoEnd(self, IRC, origin, param, endmsg): - ### Called when a WHO list is received. + # Called when a WHO list is received. if len(self.whoexpected[IRC]) and self.whoexpected[IRC][0] in self.connections: bncconnection = self.whoexpected[IRC][0] - try: - bncconnection.send(":%s 315 %s %s :%s\n"%( - origin, IRC.identity.nick, param, endmsg)) - except socket.error: - pass - finally: - del self.whoexpected[IRC][0] - - def onWho(self, IRC, params, wholist, endmsg): - ### Called when a WHO list is received. - if len(self.whoexpected[IRC]): - try: - if self.whoexpected[IRC][0] in self.connections: - bncconnection = self.whoexpected[IRC][0] - lines = [":%s 352 %s %s %s %s %s %s %s :%s %s\n"%(IRC.serv, IRC.identity.nick, channame, username, host, serv, nick, flags, hops, realname) for (channel, user, channame, username, host, serv, nick, flags, hops, realname) in wholist]+[":%s 315 %s %s :%s\n"%(IRC.serv, IRC.identity.nick, params, endmsg)] - bncconnection.send("".join(lines)) - finally: - del self.whoexpected[IRC][0] + bncconnection.send(":%s 315 %s %s :%s\n" % + (origin, IRC.identity.nick, param, endmsg)) + if self.debug: + if issubclass(type(self.whoexpected[IRC][0]), Thread): + name = self.whoexpected[IRC][0].name + IRC.logwrite( + "dbg [Bouncer.onWhoEnd] Removing %s (%s) from WHO expected list." % + (self.whoexpected[IRC][0], name)) + else: + IRC.logwrite( + "dbg [Bouncer.onWhoEnd] Removing %s from WHO expected list." % self.whoexpected[IRC][0]) + del self.whoexpected[IRC][0] + if self.debug: + IRC.logwrite("dbg [Bouncer.onWhoEnd] WHO expected list size: %d" % + len(self.whoexpected[IRC])) def onListStart(self, IRC, origin, params, extinfo): - ### Called when a WHO list is received. + # Called when a WHO list is received. if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections: bncconnection = self.listexpected[IRC][0] - try: - bncconnection.send(":%s 321 %s %s :%s\n"%(origin, - IRC.identity.nick, params, extinfo)) - except socket.error: - pass + bncconnection.send(":%s 321 %s %s :%s\n" % + (origin, IRC.identity.nick, params, extinfo)) def onListEntry(self, IRC, origin, channel, population, extinfo): - ### Called when a WHO list is received. + # Called when a WHO list is received. if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections: bncconnection = self.listexpected[IRC][0] - try: - bncconnection.send(":%s 322 %s %s %d :%s\n"%(origin, IRC.identity.nick, channel.name, population, extinfo)) - except socket.error: - pass + bncconnection.send(":%s 322 %s %s %d :%s\n" % + (origin, IRC.identity.nick, channel.name, population, extinfo)) def onListEnd(self, IRC, origin, endmsg): - ### Called when a WHO list is received. + # Called when a WHO list is received. if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections: bncconnection = self.listexpected[IRC][0] - try: - bncconnection.send(":%s 323 %s :%s\n"%( - origin, IRC.identity.nick, endmsg)) - except socket.error: - pass - finally: - del self.listexpected[IRC][0] + bncconnection.send(":%s 323 %s :%s\n" % + (origin, IRC.identity.nick, endmsg)) + del self.listexpected[IRC][0] def onWhoisStart(self, IRC, origin, user, nickname, username, host, realname): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if len(self.whoisexpected[IRC]): if self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 311 %s %s %s %s * :%s\n" % (origin, IRC.identity.nick, nickname, username, host, realname)) - except socket.error: - pass + bncconnection.send(":%s 311 %s %s %s %s * :%s\n" % + (origin, IRC.identity.nick, nickname, username, host, realname)) def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 307 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass + bncconnection.send(":%s 307 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 378 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass + bncconnection.send(":%s 378 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) def onWhoisChannels(self, IRC, origin, user, nickname, chanlist): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 319 %s %s :%s\n" % (origin, IRC.identity.nick, nickname, " ".join(chanlist))) - except socket.error: - pass + bncconnection.send(":%s 319 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, " ".join(chanlist))) def onWhoisAvailability(self, IRC, origin, user, nickname, msg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 310 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass + bncconnection.send(":%s 310 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) def onWhoisServer(self, IRC, origin, user, nickname, server, servername): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 312 %s %s %s :%s\n" % (origin, IRC.identity.nick, nickname, server, servername)) - except socket.error: - pass + bncconnection.send(":%s 312 %s %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, server, servername)) def onWhoisOp(self, IRC, origin, user, nickname, msg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 313 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass + bncconnection.send(":%s 313 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) def onWhoisAway(self, IRC, origin, user, nickname, awaymsg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 301 %s %s :%s\n" % (origin, - IRC.identity.nick, nickname, awaymsg)) - except socket.error: - pass + bncconnection.send(":%s 301 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, awaymsg)) def onWhoisTimes(self, IRC, origin, user, nickname, idletime, signontime, msg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 317 %s %s %d %d :%s\n" % (origin, IRC.identity.nick, nickname, idletime, signontime, msg)) - except socket.error: - pass + bncconnection.send(":%s 317 %s %s %d %d :%s\n" % + (origin, IRC.identity.nick, nickname, idletime, signontime, msg)) def onWhoisSSL(self, IRC, origin, user, nickname, msg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 671 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass + bncconnection.send(":%s 671 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) def onWhoisModes(self, IRC, origin, user, nickname, msg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 339 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass + bncconnection.send(":%s 339 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) def onWhoisLoggedInAs(self, IRC, origin, user, nickname, loggedinas, msg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 330 %s %s %s :%s\n" % (origin, IRC.identity.nick, nickname, loggedinas, msg)) - except socket.error: - pass + bncconnection.send(":%s 330 %s %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, loggedinas, msg)) def onWhoisEnd(self, IRC, origin, user, nickname, msg): if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: bncconnection = self.whoisexpected[IRC][0] - try: - bncconnection.send(":%s 318 %s %s :%s\n" % ( - origin, IRC.identity.nick, nickname, msg)) - except socket.error: - pass - finally: - del self.whoisexpected[IRC][0] + bncconnection.send(":%s 318 %s %s :%s\n" % + (origin, IRC.identity.nick, nickname, msg)) + del self.whoisexpected[IRC][0] def onUnhandled(self, IRC, line, origin, cmd, target, params, extinfo): for bouncerconnection in self.connections: if bouncerconnection.IRC == IRC: - bouncerconnection.send("%s\n"%line) + bouncerconnection.send("%s\n" % line) class BouncerConnection (Thread): + def __init__(self, bouncer, connection, addr, debug=False): - #print "Initializing ListenThread..." + # print "Initializing ListenThread..." self.bouncer = bouncer self.connection = connection self.host, self.port = self.addr = addr[:2] @@ -371,7 +411,8 @@ class BouncerConnection (Thread): self.connection.send(data) except socket.error: exc, excmsg, tb = sys.exc_info() - print >>self.IRC.logwrite(*["!!! [BouncerConnection.send] Exception in thread %(self)s" % vars()]+["!!! [BouncerConnection.send] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")]) + print >>self.IRC.logwrite(*["!!! [BouncerConnection.send] Exception in thread %(self)s" % vars()] + [ + "!!! [BouncerConnection.send] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")]) self.quit(quitmsg=excmsg.message) def __repr__(self): @@ -401,18 +442,19 @@ class BouncerConnection (Thread): self.quitmsg = quitmsg with self.lock: try: - self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % (self.IRC.identity.nick if self.IRC else "*", self.host, quitmsg)) + self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % ( + self.IRC.identity.nick if self.IRC else "*", self.host, quitmsg)) except: pass try: - self.connection.shutdown(1) + self.connection.shutdown(socket.SHUT_WR) self.connection.close() except: pass self.quitting = True def run(self): - ### Name loopup should happen here instead + # Name loopup should happen here instead ipv4match = re.findall( r"^::ffff:((\d+)\.(\d+)\.(\d+)\.(\d+))$", self.host) if self.bouncer.ipv6 and ipv4match: @@ -428,7 +470,7 @@ class BouncerConnection (Thread): except: pass - ### Add connection to connection list. + # Add connection to connection list. listnumerics = dict(b=(367, 368, "channel ban list"), e=(348, 349, "Channel Exception List"), @@ -447,7 +489,8 @@ class BouncerConnection (Thread): try: while True: - ### Read data (appending) into readbuf, then break lines and append lines to linebuf + # Read data (appending) into readbuf, then break lines and + # append lines to linebuf while len(linebuf) == 0: timestamp = irc.timestamp() read = self.connection.recv(512) @@ -460,10 +503,11 @@ class BouncerConnection (Thread): if lastlf >= 0: linebuf.extend(string.split(readbuf[0:lastlf], "\n")) - readbuf = readbuf[lastlf+1:] + readbuf = readbuf[lastlf + 1:] line = string.rstrip(linebuf.pop(0)) - match = re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) + match = re.findall( + "^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) if len(match) == 0: continue @@ -474,146 +518,165 @@ class BouncerConnection (Thread): passwd = target if target else extinfo else: self.quit("Access Denied") + print "*** [BouncerConnection] Incoming connection from %s failed: Expected PASS." % (self.host) break - elif not nick: # Bouncer expects a NICK command + elif not self.nick: # Bouncer expects a NICK command if cmd.upper() == "NICK": - nick = target if target else extinfo + self.nick = target if target else extinfo else: self.quit("Access Denied") + print "*** [BouncerConnection] Incoming connection from %s failed: Expected NICK." % (self.host) break elif not self.username: # Bouncer expects a USER command to finish registration if cmd.upper() == "USER": self.username = target - #print self.username + # print self.username if self.username in self.bouncer.servers.keys(): - self.IRC, passwdhash, hashtype = self.bouncer.servers[self.username] - passmatch = hashlib.new(hashtype, passwd).hexdigest() == passwdhash + self.IRC, passwdhash, hashtype = self.bouncer.servers[ + self.username] + passmatch = hashlib.new( + hashtype, passwd).hexdigest() == passwdhash with self.IRC.lock: - self.IRC.logwrite("*** [BouncerConnection] Incoming connection from %s to %s." % (self.host, self.IRC)) + if not passmatch: + self.quit("Access Denied") + self.IRC.logwrite( + "*** [BouncerConnection] Incoming connection from %s to %s denied: Invalid password." % (self.host, self.IRC)) + for bouncerconnection in self.bouncer.connections: + if bouncerconnection.IRC != self.IRC: + continue + if not bouncerconnection.quitting: + bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s dened: Invalid password.\n" % ( + bouncerconnection.IRC.identity.nick, self.host, self.IRC)) + break + + self.IRC.logwrite( + "*** [BouncerConnection] Incoming connection from %s to %s." % (self.host, self.IRC)) with self.bouncer.lock: - ### Announce connection to all other bouncer connections. - if self.debug: - self.IRC.logwrite("dbg [BouncerConnection] Attempting to broadcast incoming connection %(self)s." % vars()) + # Announce connection to all other bouncer + # connections. for bouncerconnection in self.bouncer.connections: if bouncerconnection.IRC != self.IRC: continue - if self.debug: - self.IRC.logwrite("dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars()) if not bouncerconnection.quitting: - bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s\n" % (bouncerconnection.IRC.identity.nick, self.host, self.IRC)) - if self.debug: - self.IRC.logwrite("dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars()) - if len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.IRC == self.IRC]) == 0 and self.IRC.identity.away: - ### Bouncer connection should automatically return from away status. + bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s\n" % ( + bouncerconnection.IRC.identity.nick, self.host, self.IRC)) + if len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.IRC == self.IRC]) == 0 and self.IRC.registered and type(self.IRC.identity) == irc.User and self.IRC.identity.away: + # Bouncer connection should + # automatically return from away + # status. self.IRC.raw("AWAY") self.bouncer.connections.append(self) - if not (self.IRC.connected and self.IRC.registered and type(self.IRC.supports) == dict and "CHANMODES" in self.IRC.supports.keys() and passmatch): - self.quit("Access Denied") - break - - ### If we have made it to this point, then access has been granted. - labels = [bouncerconnection.label for bouncerconnection in self.bouncer.connections if bouncerconnection.IRC == self.IRC and bouncerconnection.label] - n = 1 - while "*%s_%d"%(self.username, n) in labels: - n += 1 - self.label = "*%s_%d"%(self.username, n) - - ### Request Version info. - #self.connection.send(":$bouncer PRIVMSG %s :\x01VERSION\x01\n" % (self.IRC.identity.nick)) - - ### Log incoming connection - - ### Send Greeting. - with self.lock: - self.connection.send(":%s 001 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.welcome)) - self.connection.send(":%s 002 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.hostinfo)) - self.connection.send(":%s 003 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.servcreated)) - self.connection.send(":%s 004 %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.servinfo)) - - ### Send 005 response. - supports = ["CHANMODES=%s"%(",".join(value)) if name == "CHANMODES" else "PREFIX=(%s)%s"%value if name == "PREFIX" else "%s=%s"%(name, value) if value else name for name, value in self.IRC.supports.items()] - supports.sort() - supportsreply = [] - supportsstr = " ".join(supports) - index = 0 - while True: - if len(supportsstr)-index > 196: - nextindex = supportsstr.rfind(" ", index, index+196) - supportsreply.append(supportsstr[index:nextindex]) - index = nextindex+1 + if self.IRC.registered: + # Send Greeting. + with self.lock: + if self.IRC.welcome: + self.connection.send(":%s 001 %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.welcome)) + if self.IRC.hostinfo: + self.connection.send(":%s 002 %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.hostinfo)) + if self.IRC.servcreated: + self.connection.send(":%s 003 %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.servcreated)) + if self.IRC.servinfo: + self.connection.send(":%s 004 %s %s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.servinfo)) + + # Send 005 response. + if self.IRC.supports: + supports = ["CHANMODES=%s" % (",".join(value)) if name == "CHANMODES" else "PREFIX=(%s)%s" % value if name == "PREFIX" else "%s=%s" % ( + name, value) if value else name for name, value in self.IRC.supports.items()] + supports.sort() + supportsreply = [] + supportsstr = " ".join(supports) + index = 0 + while True: + if len(supportsstr) - index > 196: + nextindex = supportsstr.rfind( + " ", index, index + 196) + supportsreply.append( + supportsstr[index:nextindex]) + index = nextindex + 1 + else: + supportsreply.append( + supportsstr[index:]) + break + for support in supportsreply: + self.connection.send(":%s 005 %s %s :are supported by this server\n" % ( + self.IRC.serv, self.IRC.identity.nick, support)) + + # Send MOTD + if self.IRC.motdgreet and self.IRC.motd and self.IRC.motdend: + self.connection.send(":%s 375 %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.motdgreet)) + for motdline in self.IRC.motd: + self.connection.send(":%s 372 %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, motdline)) + try: + self.connection.send(":%s 376 %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.motdend)) + except AttributeError: + self.connection.send( + ":%s 376 %s\n" % (self.IRC.serv, self.IRC.identity.nick)) else: - supportsreply.append(supportsstr[index:]) - break - for support in supportsreply: - self.connection.send(":%s 005 %s %s :are supported by this server\n" % (self.IRC.serv, self.IRC.identity.nick, support)) - - ### Send MOTD - if self.IRC.motdgreet and self.IRC.motd and self.IRC.motdend: - self.connection.send(":%s 375 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdgreet)) - for motdline in self.IRC.motd: - self.connection.send(":%s 372 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, motdline)) - try: - self.connection.send(":%s 376 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdend)) - except AttributeError: - self.connection.send(":%s 376 %s\n" % (self.IRC.serv, self.IRC.identity.nick)) - else: - self.connection.send(":%s 422 %s :MOTD File is missing\n" % (self.IRC.serv, self.IRC.identity.nick)) - - ### Send user modes and snomasks. - self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) - if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: - self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.server, self.IRC.identity.nick, self.IRC.identity.snomask)) - - # ### Join user to internal bouncer channel. - # self.connection.send(":%s!%s@%s JOIN :$bouncer\n" % (self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host)) - - # ### Set internal bouncer topic. - # self.connection.send(":$bouncer 332 %s $bouncer :Bouncer internal channel. Enter bouncer commands here.\n" % (self.IRC.identity.nick)) - # self.connection.send(":$bouncer 333 %s $bouncer $bouncer %s\n" % (self.IRC.identity.nick, self.bouncer.starttime)) - - # ### Send NAMES for internal bouncer channel. - # self.connection.send(":$bouncer 353 %s @ $bouncer :%s\n" % ( - # self.IRC.identity.nick, - # string.join(["@*Bouncer*"]+["@%s"%bouncerconnection.label for bouncerconnection in self.bouncer.connections])) - # ) - # self.connection.send(":$bouncer 366 %s $bouncer :End of /NAMES list.\n" % (self.IRC.identity.nick)) - - # ### Give operator mode to user. - # self.connection.send(":*Bouncer* MODE $bouncer +o %s\n" % (self.IRC.identity.nick)) - - ### Join user to channels. - for channel in self.IRC.identity.channels: - ### JOIN command - self.connection.send(":%s!%s@%s JOIN :%s\n" % (self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host, channel.name)) - - ### Topic - self.connection.send(":%s 332 %s %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topic)) - self.connection.send(":%s 333 %s %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topicsetby, channel.topictime)) - - ### Determine if +s or +p modes are set in channel - secret = "s" in channel.modes.keys() and channel.modes["s"] - private = "p" in channel.modes.keys() and channel.modes["p"] - - ### Construct NAMES for channel. - namesusers = [] - modes, symbols = self.IRC.supports["PREFIX"] - self.connection.send(":%s 353 %s %s %s :%s\n" % ( - self.IRC.serv, - self.IRC.identity.nick, - "@" if secret else ("*" if private else "="), - channel.name, - string.join([string.join([symbols[k] if modes[k] in channel.modes.keys() and user in channel.modes[modes[k]] else "" for k in xrange(len(modes))], "")+user.nick for user in channel.users])) - ) - self.connection.send(":%s 366 %s %s :End of /NAMES list.\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name)) - + self.connection.send( + ":%s 422 %s :MOTD File is missing\n" % (self.IRC.serv, self.IRC.identity.nick)) + + # Send user modes and snomasks. + self.connection.send(":%s 221 %s +%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) + + if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: + self.connection.send(":%s 008 %s +%s :Server notice mask\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.snomask)) + + # Join user to channels. + for channel in self.IRC.identity.channels: + # JOIN command + self.connection.send(":%s!%s@%s JOIN :%s\n" % ( + self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host, channel.name)) + + # Topic + self.connection.send(":%s 332 %s %s :%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topic)) + self.connection.send(":%s 333 %s %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topicsetby.nick if type( + channel.topicsetby) == irc.User else channel.topicsetby, channel.topictime)) + + # Determine if +s or +p modes are + # set in channel + secret = "s" in channel.modes.keys() and channel.modes[ + "s"] + private = "p" in channel.modes.keys( + ) and channel.modes["p"] + + # Construct NAMES for channel. + namesusers = [] + modes, symbols = self.IRC.supports[ + "PREFIX"] + self.connection.send(":%s 353 %s %s %s :%s\n" % ( + self.IRC.serv, + self.IRC.identity.nick, + "@" if secret else ( + "*" if private else "="), + channel.name, + string.join([string.join([symbols[k] if modes[k] in channel.modes.keys() and user in channel.modes[modes[k]] else "" for k in xrange(len(modes))], "") + user.nick for user in channel.users])) + ) + self.connection.send(":%s 366 %s %s :End of /NAMES list.\n" % ( + self.IRC.serv, self.IRC.identity.nick, channel.name)) + else: + self.send( + ":*Bouncer* NOTICE %s :Not connected to server. Type /bncconnect to attempt connection.\n" % self.nick) + self.send(":%s 001 %s :Welcome to the Bouncer IRC Network %s!%s@%s\n" % ( + "*Bouncer*", self.nick, self.nick, self.username, self.host)) else: # User not found self.quit("Access Denied") break else: # Client did not send USER command when expected self.quit("Access Denied") + print "*** [BouncerConnection] Incoming connection from %s failed: Expected USER." % (self.host) break elif cmd.upper() == "QUIT": @@ -621,60 +684,98 @@ class BouncerConnection (Thread): break elif cmd.upper() == "PING": - self.send(":%s PONG %s :%s\n" % (self.IRC.serv, self.IRC.serv, self.IRC.identity.nick)) - - elif cmd.upper() in ("PRIVMSG", "NOTICE"): - ### Check if CTCP - ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", - extinfo) - - if ctcp: # If CTCP, only want to - (ctcptype, ext) = ctcp[0] # Unpack CTCP info - - if ctcptype == "LAGCHECK": # Client is doing a lag check. No need to send to IRC network, just reply back. - self.send(":%s!%s@%s %s\n" % (self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host, line)) + self.send(":%s PONG %s :%s\n" % + (self.IRC.serv, self.IRC.serv, self.IRC.identity.nick if type(self.IRC.identity) == irc.User else "***")) + + elif cmd.upper() == "BNCCONNECT": + with self.IRC.lock: + if self.IRC.isAlive() and self.IRC.connected: + self.send( + ":*Bouncer* NOTICE %s :Bouncer is already connected.\n" % self.nick) else: - self.IRC.raw(line, origin=self) - else: - self.IRC.raw(line, origin=self) - - elif cmd.upper() == "MODE": # Will want to determine is requesting modes, or attempting to modify modes. - if target and "CHANTYPES" in self.IRC.supports.keys() and target[0] in self.IRC.supports["CHANTYPES"]: - if params == "": - channel = self.IRC.channel(target) - modes = channel.modes.keys() - modestr = "".join([mode for mode in modes if mode not in self.IRC.supports["CHANMODES"][0]+self.IRC.supports["PREFIX"][0] and channel.modes[mode]]) - params = " ".join([channel.modes[mode] for mode in modes if mode in self.IRC.supports["CHANMODES"][1]+self.IRC.supports["CHANMODES"][2] and channel.modes[mode]]) - with self.lock: - if len(modestr): - self.connection.send(":%s 324 %s %s +%s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, modestr, params)) - if channel.created: - self.connection.send(":%s 329 %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.created)) - elif re.match("^\\+?[%s]+$"%self.IRC.supports["CHANMODES"][0], params) and extinfo == "": - #print "ddd Mode List Request", params - channel = self.IRC.channel(target) - redundant = [] - for mode in params.lstrip("+"): - if mode in redundant or mode not in listnumerics.keys(): - continue - i, e, l = listnumerics[mode] + self.IRC.start() + + elif cmd.upper() == "BNCQUIT": + with self.IRC.lock: + if self.IRC.isAlive() and self.IRC.connected and self.IRC.registered: + quitmsg = " ".join( + [word for word in [target, params, extinfo] if word]) + self.IRC.quit(quitmsg) + else: + self.send( + ":*Bouncer* NOTICE %s :Bouncer is already disconnected.\n" % self.nick) + + else: + with self.IRC.lock: + if not self.IRC.connected: + self.send( + ":*Bouncer* NOTICE %s :Not connected to server. Type /bncconnect to attempt connection.\n" % self.nick) + break + elif not self.IRC.registered: + self.send( + ":*Bouncer* NOTICE %s :Not registered.\n" % self.nick) + break + elif cmd.upper() in ("PRIVMSG", "NOTICE"): + # Check if CTCP + ctcp = re.findall( + "^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo) + + if ctcp: # If CTCP, only want to + (ctcptype, ext) = ctcp[0] # Unpack CTCP info + + if ctcptype == "LAGCHECK": # Client is doing a lag check. No need to send to IRC network, just reply back. + self.send(":%s!%s@%s %s\n" % ( + self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host, line)) + else: + self.IRC.raw(line, origin=self) + else: + self.IRC.raw(line, origin=self) + + elif cmd.upper() == "MODE": # Will want to determine is requesting modes, or attempting to modify modes. + if target and "CHANTYPES" in self.IRC.supports.keys() and target[0] in self.IRC.supports["CHANTYPES"]: + if params == "": + channel = self.IRC.channel(target) + modes = channel.modes.keys() + modestr = "".join([mode for mode in modes if mode not in self.IRC.supports[ + "CHANMODES"][0] + self.IRC.supports["PREFIX"][0] and channel.modes[mode]]) + params = " ".join([channel.modes[mode] for mode in modes if mode in self.IRC.supports[ + "CHANMODES"][1] + self.IRC.supports["CHANMODES"][2] and channel.modes[mode]]) + with self.lock: + if len(modestr): + self.connection.send(":%s 324 %s %s +%s %s\n" % ( + self.IRC.serv, self.IRC.identity.nick, channel.name, modestr, params)) + if channel.created: + self.connection.send(":%s 329 %s %s %s\n" % ( + self.IRC.serv, self.IRC.identity.nick, channel.name, channel.created)) + elif re.match("^\\+?[%s]+$" % self.IRC.supports["CHANMODES"][0], params) and extinfo == "": + # print "ddd Mode List Request", params + channel = self.IRC.channel(target) + redundant = [] + for mode in params.lstrip("+"): + if mode in redundant or mode not in listnumerics.keys(): + continue + i, e, l = listnumerics[mode] + with self.lock: + if mode in channel.modes.keys(): + for (mask, setby, settime) in channel.modes[mode]: + self.connection.send(":%s %d %s %s %s %s %s\n" % ( + self.IRC.serv, i, channel.context.identity.nick, channel.name, mask, setby, settime)) + self.connection.send(":%s %d %s %s :End of %s\n" % ( + self.IRC.serv, e, channel.context.identity.nick, channel.name, l)) + redundant.append(mode) + else: + self.IRC.raw(line, origin=self) + elif params == "" and target.lower() == self.IRC.identity.nick.lower(): with self.lock: - if mode in channel.modes.keys(): - for (mask, setby, settime) in channel.modes[mode]: - self.connection.send(":%s %d %s %s %s %s %s\n" % (self.IRC.serv, i, channel.context.identity.nick, channel.name, mask, setby, settime)) - self.connection.send(":%s %d %s %s :End of %s\n" % (self.IRC.serv, e, channel.context.identity.nick, channel.name, l)) - redundant.append(mode) + self.connection.send(":%s 221 %s +%s\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) + if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: + self.connection.send(":%s 008 %s +%s :Server notice mask\n" % ( + self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.snomask)) + else: + self.IRC.raw(line, origin=self) else: self.IRC.raw(line, origin=self) - elif params == "" and target.lower() == self.IRC.identity.nick.lower(): - with self.lock: - self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes)) - if "s" in self.IRC.identity.modes and self.IRC.identity.snomask: - self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.snomask)) - else: - self.IRC.raw(line, origin=self) - else: - self.IRC.raw(line, origin=self) except SystemExit: pass # No need to pass error message if break resulted from sys.exit() @@ -683,9 +784,10 @@ class BouncerConnection (Thread): self.quitmsg = str(excmsg) if self.IRC: exc, excmsg, tb = sys.exc_info() - self.IRC.logwrite(*["!!! [BouncerConnection] Exception in thread %(self)s" % vars()]+["!!! [BouncerConnection] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")]) + self.IRC.logwrite(*["!!! [BouncerConnection] Exception in thread %(self)s" % vars()] + [ + "!!! [BouncerConnection] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")]) finally: - ### Juuuuuuust in case. + # Juuuuuuust in case. with self.lock: try: self.connection.shutdown(1) @@ -694,28 +796,33 @@ class BouncerConnection (Thread): pass if self.IRC: - self.IRC.logwrite("*** [BouncerConnection] Connection from %s terminated (%s)." % (self.host, self.quitmsg)) + self.IRC.logwrite( + "*** [BouncerConnection] Connection from %s terminated (%s)." % (self.host, self.quitmsg)) if self in self.bouncer.connections: with self.bouncer.lock: self.bouncer.connections.remove(self) - if self.IRC.connected and self.IRC.identity and len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.IRC == self.IRC]) == 0 and not self.IRC.identity.away and self.bouncer.autoaway: - ### Bouncer automatically sets away status. - self.IRC.raw("AWAY :%s"%self.bouncer.autoaway) + if self.IRC.connected and self.IRC.identity and len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.IRC == self.IRC]) == 0 and self.IRC.registered and type(self.IRC.identity) == irc.User and not self.IRC.identity.away and self.bouncer.autoaway: + # Bouncer automatically sets away status. + self.IRC.raw("AWAY :%s" % self.bouncer.autoaway) if self.debug: - self.IRC.logwrite("dbg [BouncerConnection] Attempting to broadcast terminated connection %(self)s." % vars()) + self.IRC.logwrite( + "dbg [BouncerConnection] Attempting to broadcast terminated connection %(self)s." % vars()) for bouncerconnection in self.bouncer.connections: if bouncerconnection.IRC == self.IRC: if self.debug: - self.IRC.logwrite("dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars()) + self.IRC.logwrite( + "dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars()) if not bouncerconnection.quitting: - bouncerconnection.connection.send(":*Bouncer* NOTICE %s :Connection from %s to %s terminated (%s)\n" % (bouncerconnection.IRC.identity.nick, self.host, self.IRC, self.quitmsg)) + bouncerconnection.connection.send(":*Bouncer* NOTICE %s :Connection from %s to %s terminated (%s)\n" % ( + bouncerconnection.IRC.identity.nick, self.host, self.IRC, self.quitmsg)) if self.debug: - self.IRC.logwrite("dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars()) - -# ### Announce QUIT to other bouncer connections. -# for bouncerconnection in self.bouncer.connections: -# try: -# bouncerconnection.connection.send(":%s!%s@%s QUIT :%s\n" % (self.label, self.username, self.host, self.quitmsg)) -# except: -# pass + self.IRC.logwrite( + "dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars()) + +# Announce QUIT to other bouncer connections. +# for bouncerconnection in self.bouncer.connections: +# try: +# bouncerconnection.connection.send(":%s!%s@%s QUIT :%s\n" % (self.label, self.username, self.host, self.quitmsg)) +# except: +# pass @@ -1,5 +1,5 @@ #!/usr/bin/python -from threading import Thread, Condition, Lock +from threading import Thread, Condition, Lock, currentThread import re import time import sys @@ -10,6 +10,7 @@ import platform import traceback import ssl import glob +from collections import deque import iqueue as Queue @@ -105,6 +106,10 @@ class AlreadyJoined(BaseException): pass +class AlreadyConnected(BaseException): + pass + + class RegistrationRequired(BaseException): pass @@ -115,102 +120,164 @@ class RejoinDelay(BaseException): _rfc1459casemapping = string.maketrans(string.ascii_uppercase + r'\[]~', string.ascii_lowercase + r'|{}^') -exceptcodes = {489: SSLOnly, 384: Cbaned, 403: NoSuchChannel, 405: TooManyChannels, 442: NotOnChannel, 470: RedirectedJoin, 471: ChannelFull, 473: InviteOnly, 474: BannedFromChannel, 475: BadChannelKey, 476: BadChannelMask, 520: OpersOnly, 437: Unavailable, 477: RegistrationRequired, 495: RejoinDelay, 530: OperCreateOnly} +# The IRC RFC does not permit the first character in a nickname to be a +# numeral. However, this is not always adhered to. +_nickmatch = r"^[A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]+$" + +_intmatch = r"^\d+$" +_chanmatch = r"^[%%s][^%s\s\n]*$" % re.escape("\x07,") +_targchanmatch = r"^([%%s]?)([%%s][^%s\s\n]*)$" % re.escape("\x07,") +_usermatch = r"^[A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]+$" +_realnamematch = r"^[^\n]*$" +_ircrecvmatch = r"^:(.+?)(?:!(.+?)@(.+?))?\s+(.+?)(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$" +_ircsendmatch = r"^(.+?)(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$" +_ctcpmatch = "^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$" +_prefixmatch = r"\((.*)\)(.*)" + +_privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=( + "Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice")) +_maskmodeeventnames = dict(b=("Ban", "Unban"), e=( + "BanExcept", "UnbanExcept"), I=("Invite", "Uninvite")) + +exceptcodes = {489: SSLOnly, 384: Cbaned, 403: NoSuchChannel, 405: TooManyChannels, 442: NotOnChannel, 470: RedirectedJoin, 471: ChannelFull, 473: InviteOnly, 474: + BannedFromChannel, 475: BadChannelKey, 476: BadChannelMask, 520: OpersOnly, 437: Unavailable, 477: RegistrationRequired, 495: RejoinDelay, 530: OperCreateOnly} def timestamp(): t = time.time() - ms = 1000*t%1000 + ms = 1000 * t % 1000 ymdhms = time.localtime(t) tz = time.altzone if ymdhms.tm_isdst else time.timezone sgn = "-" if tz >= 0 else "+" - return "%04d-%02d-%02d %02d:%02d:%02d.%03d%s%02d:%02d"%(ymdhms[:6]+(1000*t%1000, sgn, abs(tz)/3600, abs(tz)/60%60)) + return "%04d-%02d-%02d %02d:%02d:%02d.%03d%s%02d:%02d" % (ymdhms[:6] + (1000 * t % 1000, sgn, abs(tz) / 3600, abs(tz) / 60 % 60)) + +class Connection(object): -class Connection(Thread): def __init__(self, server, nick="ircbot", username="python", realname="Python IRC Library", passwd=None, port=None, ipv6=False, ssl=False, autoreconnect=True, log=sys.stderr, timeout=300, retrysleep=5, maxretries=15, onlogin=None): self.__name__ = "pyIRC" - self.__version__ = "1.1" + self.__version__ = "1.2" self.__author__ = "Brian Sherson" - self.__date__ = "December 1, 2013" + self.__date__ = "December 28, 2013" - if port is None: + if port == None: self.port = 6667 if not ssl else 6697 else: self.port = port if type(nick) in (str, unicode): - self.nick = [nick] + if re.match(_nickmatch, nick): + self.nick = [nick] + else: + raise InvalidCharacter elif type(nick) in (list, tuple): - self.nick = nick + if all([re.match(_nickmatch, n) for n in nick]): + self.nick = nick + else: + raise InvalidCharacter + + if re.match(_realnamematch, realname): + self.realname = realname + else: + raise InvalidCharacter + + if re.match(_usermatch, username): + self.username = username + else: + raise InvalidCharacter + + if passwd == None or "\n" not in passwd: + self.passwd = passwd + else: + raise InvalidCharacter - self.realname = realname - self.username = username - self.passwd = passwd self.server = server self.ssl = ssl self.ipv6 = ipv6 - self.connected = False - self.registered = False - self.connection = None - self.autoreconnect = autoreconnect self.maxretries = maxretries self.timeout = timeout self.retrysleep = retrysleep - self.quitexpected = False + self._quitexpected = False self.log = log self.addons = [] - self.trusted = [] + self.trusted = [] # To be implemented later + + self.lock = Lock() + + self._loglock = Lock() + self._outlock = Lock() + self._sendline = Condition(self._outlock) + self._outgoing = deque() + + self._sendhandlerthread = None + self._recvhandlerthread = None + + # Initialize IRC environment variables + self._init() + + def _init(self): + self._connected = False + self._registered = False + self._connection = None + self.trynick = 0 - ### Initialize IRC environment variables - self.motdgreet = None - self.motd = None - self.motdend = None self.identity = None self.users = [] self.channels = [] + + self.motdgreet = None + self.motd = None + self.motdend = None + + self.serv = None + self.welcome = None + self.hostinfo = None + self.servcreated = None + self.servinfo = None + self.serv005 = None self.supports = {} + self.throttledata = [] + self.throttled = False - self.lock = Lock() - self._linereceived = Condition(self.lock) - self.loglock = Lock() - self.sendlock = Lock() - self.outgoing = Queue.Queue() - self.outgoingthread = None + @property + def connected(self): + return self._connected - Thread.__init__(self) + @property + def registered(self): + return self._registered def logwrite(self, *lines): - with self.loglock: + with self._loglock: ts = timestamp() for line in lines: - print >>self.log, "%s %s"%(ts, line) + print >>self.log, "%s %s" % (ts, line) self.log.flush() def logopen(self, filename): - with self.loglock: + with self._loglock: ts = timestamp() newlog = open(filename, "a") if type(self.log) == file and not self.log.closed: - print >>self.log, "%s ### Log file closed" % (ts) if self.log not in (sys.stdout, sys.stderr): + print >>self.log, "%s ### Log file closed" % (ts) self.log.close() self.log = newlog print >>self.log, "%s ### Log file opened" % (ts) self.log.flush() - def event(self, method, modlist, exceptions=False, **params): - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in time.localtime()[0:6]]) + def _event(self, method, modlist, exceptions=False, **params): + # Used to call event handlers on all attached addons, when applicable. handled = [] unhandled = [] errors = [] for k, addon in enumerate(modlist): if modlist.index(addon) < k: + # Duplicate continue if method in dir(addon) and callable(getattr(addon, method)): try: @@ -218,7 +285,10 @@ class Connection(Thread): except: exc, excmsg, tb = sys.exc_info() errors.append((addon, exc, excmsg, tb)) - self.logwrite(*["!!! Exception in addon %(addon)s" % vars()]+["!!! %s"%line for line in traceback.format_exc().split("\n")]) + + # Print to log AND stderr + self.logwrite(*["!!! Exception in addon %(addon)s" % vars()] + [ + "!!! %s" % line for line in traceback.format_exc().split("\n")]) print >>sys.stderr, "Exception in addon %(addon)s" % vars() print >>sys.stderr, traceback.format_exc() if exceptions: # If set to true, we raise the exception. @@ -231,969 +301,1400 @@ class Connection(Thread): def addAddon(self, addon, trusted=False, **params): if addon in self.addons: - raise BaseException("Addon already added.") + raise BaseException, "Addon already added." with self.lock: - self.event("onAddonAdd", [addon], exceptions=True, **params) + self._event("onAddonAdd", [addon], exceptions=True, **params) self.addons.append(addon) + self.logwrite("*** Addon %s added." % repr(addon)) if trusted: self.trusted.append(addon) def insertAddon(self, index, addon, trusted=False, **params): if addon in self.addons: - raise BaseException("Addon already added.") + raise BaseException, "Addon already added." with self.lock: - self.event("onAddonAdd", [addon], exceptions=True, **params) + self._event("onAddonAdd", [addon], exceptions=True, **params) self.addons.insert(index, addon) + self.logwrite("*** Addon %s inserted into index %d." % + (repr(addon), index)) if trusted: self.trusted.append(addon) def rmAddon(self, addon, **params): with self.lock: self.addons.remove(addon) - self.event("onAddonRem", [addon], exceptions=True, **params) + self.logwrite("*** Addon %s removed." % repr(addon)) + self._event("onAddonRem", [addon], exceptions=True, **params) if addon in self.trusted: self.trusted.remove(addon) - def run(self): - privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=("Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice")) - maskmodeeventnames = dict(b=("Ban", "Unban"), e=( - "BanExcept", "UnbanExcept"), I=("Invite", "Uninvite")) - self.quitexpected = False - whoisstarted = False - nameslist = [] - wholist = [] - lists = {} - nameschan = None + def connect(self): + if self.isAlive(): + raise AlreadyConnected + with self.lock: + self._recvhandlerthread = Thread(target=self._recvhandler) + self._sendhandlerthread = Thread(target=self._sendhandler) + self._recvhandlerthread.start() + self._sendhandlerthread.start() + + def _connect(self): + with self.lock: + if self._connected: + raise AlreadyConnected server = self.server if self.ipv6 and ":" in server: - server = "[%s]"%server + server = "[%s]" % server port = self.port + + with self.lock: + self.logwrite( + "*** Attempting connection to %(server)s:%(port)s." % vars()) + self._event("onConnectAttempt", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], [])) try: + if self.ssl: + connection = socket.socket( + socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) + connection.settimeout(self.timeout) + self._connection = ssl.wrap_socket( + connection, cert_reqs=ssl.CERT_NONE) + else: + self._connection = socket.socket( + socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) + self._connection.settimeout(self.timeout) + self._connection.connect( + (self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port)) + except socket.error: + exc, excmsg, tb = sys.exc_info() with self.lock: - self.event("onSessionOpen", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) + self.logwrite( + "*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) + self._event("onConnectFail", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) + raise - self.logwrite("### Log session started") + with self.lock: + # Run onConnect on all addons to signal connection was established. + self._event("onConnect", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + self.logwrite( + "*** Connection to %(server)s:%(port)s established." % vars()) + self._connected = True + + def _procrecvline(self, line): + # If received PING, then just pong back transparently, bypassing + # _outgoingthread. + ping = re.findall("^PING :?(.*)$", line) + if len(ping): + with self.lock: + self._connection.send("PONG :%s\n" % ping[0]) + return - attempt = 1 - while True: # An entire connection lives within this while loop. When the connection fails, will try to reestablish, unless self.autoreconnect is set to False. - while True: # Enter retry loop - self.logwrite("*** Attempting connection to %(server)s:%(port)s." % vars()) + self.logwrite("<<< %s" % line) - with self.lock: - self.event("onConnectAttempt", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) + # Attempts to match against pattern ":src cmd target params :extinfo" + matches = re.findall(_ircrecvmatch, line) + + # We have a match! + if len(matches): + parsed = (origin, username, host, cmd, + target, params, extinfo) = matches[0] + unhandled = [] + + if re.match(_intmatch, cmd): + cmd = int(cmd) # Code is a numerical response + else: + cmd = cmd.upper() - try: - if self.ssl: - s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) - s.settimeout(self.timeout) - self.connection = ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE) + with self.lock: + if not self._registered: + if type(cmd) == int and cmd != 451 and target != "*": # Registration complete! + self.identity = self.user(target) + self.serv = origin + self._event("onRegistered", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + self._registered = True + + elif cmd == 433 and target == "*": # Server reports nick taken, so we need to try another. + self._trynick() + if not self._registered: # Registration is not yet complete + return + + if username and host: + nickname = origin + origin = self.user(origin) + if origin.nick != nickname: + # Origin nickname has changed + origin.user = nickname + if origin.username != username: + # Origin username has changed + origin.username = username + if origin.host != host: + # Origin host has changed + origin.host = host + + chanmatch = re.findall( + _targchanmatch % (re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target) + if chanmatch: + targetprefix, channame = chanmatch[0] + target = self.channel(channame) + if target.name != channame: + # Target channel name has changed + target.name = channame + elif re.match(_nickmatch, target) and cmd != "NICK": + targetprefix = "" + target = self.user(target) + else: + targetprefix = "" + + data = dict(origin=origin, cmd=cmd, target=target, + targetprefix=targetprefix, params=params, extinfo=extinfo) + + # Major codeblock here! Track IRC state. + # Send line to addons having onRecv method first + self._event("onRecv", self.addons, line=line, **data) + + # Support for further addon events is taken care of here. Each invocation of self._event will return (handled, unhandled, exceptions), + # where handled is the list of addons that have an event handler, and was executed without error, unhandled gives the list of addons + # not having the event handler, and exeptions giving the list of addons having an event handler, but an exception occurred. + # WARNING: When writing an addon, never, EVER attempt to aquire self.lock (IRC.lock from inside the method), or you will have a + # deadlock. + + if cmd == 1: + (handled, unhandled, exceptions) = self._event("onWelcome", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo) + self.welcome = extinfo # Welcome message + elif cmd == 2: + (handled, unhandled, exceptions) = self._event("onYourHost", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo) + self.hostinfo = extinfo # Your Host + elif cmd == 3: + (handled, unhandled, exceptions) = self._event("onServerCreated", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo) + self.servcreated = extinfo # Server Created + elif cmd == 4: + (handled, unhandled, exceptions) = self._event("onServInfo", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, servinfo=params) + self.servinfo = params # What is this code? + elif cmd == 5: # Server Supports + support = dict( + re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params)) + if support.has_key("CHANMODES"): + support["CHANMODES"] = support["CHANMODES"].split(",") + if support.has_key("PREFIX"): + matches = re.findall(_prefixmatch, support["PREFIX"]) + if matches: + support["PREFIX"] = matches[0] + else: + del support[ + "PREFIX"] # Might as well delete the info if it doesn't match expected pattern + (handled, unhandled, exceptions) = self._event("onSupports", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, supports=support) + self.supports.update(support) + if "serv005" in dir(self) and type(self.serv005) == list: + self.serv005.append(params) + else: + self.serv005 = [params] + elif cmd == 8: # Snomask + snomask = params.lstrip("+") + (handled, unhandled, exceptions) = self._event("onSnoMask", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, snomask=snomask) + self.identity.snomask = snomask + if "s" not in self.identity.modes: + self.snomask = "" + elif cmd == 221: # User Modes + modes = (params if params else extinfo).lstrip("+") + (handled, unhandled, exceptions) = self._event("onUserModes", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, snomask=modes) + self.identity.modes = modes + if "s" not in self.identity.modes: + self.snomask = "" + elif cmd == 251: # Net Stats + (handled, unhandled, exceptions) = self._event( + "onNetStats", self.addons, origin=origin, netstats=extinfo) + self.netstats = extinfo + elif cmd == 252: + opcount = int(params) + (handled, unhandled, exceptions) = self._event( + "onOpCount", self.addons, origin=origin, opcount=opcount) + self.opcount = opcount + elif cmd == 254: + chancount = int(params) + (handled, unhandled, exceptions) = self._event( + "onChanCount", self.addons, origin=origin, chancount=chancount) + self.chancount = chancount + + elif cmd == 305: # Returned from away status + (handled, unhandled, exceptions) = self._event( + "onReturn", self.addons, origin=origin, msg=extinfo) + self.identity.away = False + + elif cmd == 306: # Entered away status + (handled, unhandled, exceptions) = self._event( + "onAway", self.addons, origin=origin, msg=extinfo) + self.identity.away = True + + elif cmd == 311: # Start of WHOIS data + nickname, username, host, star = params.split() + user = self.user(nickname) + (handled, unhandled, exceptions) = self._event("onWhoisStart", self.addons, origin=origin, + user=user, nickname=nickname, username=username, host=host, realname=extinfo) + user.nick = nickname + user.username = username + user.host = host + + elif cmd == 301: # Away Message + user = self.user(params) + (handled, unhandled, exceptions) = self._event( + "onWhoisAway", self.addons, origin=origin, user=user, nickname=params, awaymsg=extinfo) + user.away = True + user.awaymsg = extinfo + + elif cmd == 303: # ISON Reply + users = [self.user(user) for user in extinfo.split(" ")] + (handled, unhandled, exceptions) = self._event( + "onIsonReply", self.addons, origin=origin, isonusers=users) + + elif cmd == 307: # Is a registered nick + (handled, unhandled, exceptions) = self._event("onWhoisRegisteredNick", + self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) + elif cmd == 378: # Connecting From + (handled, unhandled, exceptions) = self._event("onWhoisConnectingFrom", + self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) + elif cmd == 319: # Channels + (handled, unhandled, exceptions) = self._event("onWhoisChannels", self.addons, + origin=origin, user=self.user(params), nickname=params, chanlist=extinfo.split(" ")) + elif cmd == 310: # Availability + (handled, unhandled, exceptions) = self._event("onWhoisAvailability", + self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) + elif cmd == 312: # Server + nickname, server = params.split(" ") + user = self.user(nickname) + (handled, unhandled, exceptions) = self._event("onWhoisServer", self.addons, + origin=origin, user=user, nickname=nickname, server=server, servername=extinfo) + user.server = server + elif cmd == 313: # IRC Op + user = self.user(params) + (handled, unhandled, exceptions) = self._event( + "onWhoisOp", self.addons, origin=origin, user=user, nickname=params, msg=extinfo) + user.ircop = True + user.ircopmsg = extinfo + elif cmd == 317: # Idle and Signon times + nickname, idletime, signontime = params.split(" ") + user = self.user(nickname) + (handled, unhandled, exceptions) = self._event("onWhoisTimes", self.addons, origin=origin, + user=user, nickname=nickname, idletime=int(idletime), signontime=int(signontime), msg=extinfo) + user.idlesince = int(time.time()) - int(idletime) + user.signontime = int(signontime) + elif cmd == 671: # SSL + user = self.user(params) + (handled, unhandled, exceptions) = self._event( + "onWhoisSSL", self.addons, origin=origin, user=user, nickname=params, msg=extinfo) + user.ssl = True + elif cmd == 379: # User modes + (handled, unhandled, exceptions) = self._event("onWhoisModes", self.addons, + origin=origin, user=self.user(params), nickname=params, msg=extinfo) + elif cmd == 330: # Logged in as + nickname, loggedinas = params.split(" ") + user = self.user(nickname) + (handled, unhandled, exceptions) = self._event("onWhoisLoggedInAs", self.addons, + origin=origin, user=user, nickname=nickname, loggedinas=loggedinas, msg=extinfo) + user.loggedinas = loggedinas + elif cmd == 318: # End of WHOIS + (handled, unhandled, exceptions) = self._event("onWhoisEnd", self.addons, + origin=origin, user=self.user(params), nickname=params, msg=extinfo) + + elif cmd == 321: # Start LIST + (handled, unhandled, exceptions) = self._event( + "onListStart", self.addons, origin=origin, params=params, extinfo=extinfo) + elif cmd == 322: # LIST item + (chan, pop) = params.split(" ", 1) + (handled, unhandled, exceptions) = self._event("onListEntry", self.addons, + origin=origin, channel=self.channel(chan), population=int(pop), extinfo=extinfo) + elif cmd == 323: # End of LIST + (handled, unhandled, exceptions) = self._event( + "onListEnd", self.addons, origin=origin, endmsg=extinfo) + + elif cmd == 324: # Channel Modes + modeparams = params.split() + channame = modeparams.pop(0) + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + setmodes = modeparams.pop(0) + modedelta = [] + for mode in setmodes: + if mode == "+": + continue + elif mode in self.supports["CHANMODES"][2]: + param = modeparams.pop(0) + modedelta.append(("+%s" % mode, param)) + elif mode in self.supports["CHANMODES"][3]: + modedelta.append(("+%s" % mode, None)) + (handled, unhandled, exceptions) = self._event( + "onChannelModes", self.addons + channel.addons, channel=channel, modedelta=modedelta) + for ((modeset, mode), param) in modedelta: + if mode in self.supports["CHANMODES"][2]: + channel.modes[mode] = param + elif mode in self.supports["CHANMODES"][3]: + channel.modes[mode] = True + + elif cmd == 329: # Channel created + channame, created = params.split() + created = int(created) + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onChanCreated", self.addons + channel.addons, channel=channel, created=created) + channel.created = int(created) + + elif cmd == 332: # Channel Topic + channame = params + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onTopic", self.addons + channel.addons, origin=origin, channel=channel, topic=extinfo) + channel.topic = extinfo + + elif cmd == 333: # Channel Topic info + (channame, nick, dt) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onTopicInfo", self.addons + + channel.addons, origin=origin, channel=channel, topicsetby=nick, topictime=int(dt)) + channel.topicsetby = nick + channel.topictime = int(dt) + + elif cmd == 352: # WHO reply + (channame, username, host, serv, + nick, flags) = params.split() + try: + (hops, realname) = extinfo.split(" ", 1) + except ValueError: + hops = extinfo + realname = None + + chantypes = self.supports.get("CHANTYPES", "&#+!") + if re.match(_chanmatch % re.escape(chantypes), channame): + channel = self.channel(channame) + else: + channel = None + + user = self.user(nick) + + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onWhoEntry", self.addons + channel.addons, origin=origin, channel=channel, + user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname) + user.hops = hops + user.realname = realname + user.username = username + user.host = host + user.server = serv + user.away = "G" in flags + user.ircop = "*" in flags + if type(channel) == Channel: + if user not in channel.users: + channel.users.append(user) + if channel not in user.channels: + user.channels.append(channel) + for (mode, prefix) in zip(*self.supports["PREFIX"]): + if prefix in flags: + if mode in channel.modes.keys() and user not in channel.modes[mode]: + channel.modes[mode].append(user) + elif mode not in channel.modes.keys(): + channel.modes[mode] = [user] + + elif cmd == 315: # End of WHO reply + chantypes = self.supports.get("CHANTYPES", "&#+!") + if re.match(_chanmatch % re.escape(chantypes), params): + channel = self.channel(params) + (handled, unhandled, exceptions) = self._event( + "onWhoEnd", self.addons + channel.addons, origin=origin, param=params, endmsg=extinfo) + else: + (handled, unhandled, exceptions) = self._event( + "onWhoEnd", self.addons, origin=origin, param=params, endmsg=extinfo) + + elif cmd == 353: # NAMES reply + (flag, channame) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + + if self.supports.has_key("PREFIX"): + names = re.findall( + r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?" % + re.escape(self.supports["PREFIX"][1]), extinfo) + else: + names = re.findall( + r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo) + # Still put it into tuple form for + # compatibility in the next + # structure + (handled, unhandled, exceptions) = self._event("onNames", self.addons + channel.addons, + origin=origin, channel=channel, flag=flag, channame=channame, nameslist=names) + + for (symbs, nick, username, host) in names: + user = self.user(nick) + if user.nick != nick: + user.nick = nick + if username and user.username != username: + user.username = username + if host and user.host != host: + user.host = host + with channel.lock: + if channel not in user.channels: + user.channels.append(channel) + if user not in channel.users: + channel.users.append(user) + if self.supports.has_key("PREFIX"): + for symb in symbs: + mode = self.supports["PREFIX"][0][ + self.supports["PREFIX"][1].index(symb)] + if not channel.modes.has_key(mode): + channel.modes[mode] = [user] + elif user not in channel.modes[mode]: + channel.modes[mode].append(user) + + elif cmd == 366: # End of NAMES reply + channel = self.channel(params) + (handled, unhandled, exceptions) = self._event("onNamesEnd", self.addons + + channel.addons, origin=origin, channel=channel, channame=params, endmsg=extinfo) + + elif cmd == 372: # MOTD line + (handled, unhandled, exceptions) = self._event( + "onMOTDLine", self.addons, origin=origin, motdline=extinfo) + self.motd.append(extinfo) + elif cmd == 375: # Begin MOTD + (handled, unhandled, exceptions) = self._event( + "onMOTDStart", self.addons, origin=origin, motdgreet=extinfo) + self.motdgreet = extinfo + self.motd = [] + elif cmd == 376: + (handled, unhandled, exceptions) = self._event( + "onMOTDEnd", self.addons, origin=origin, motdend=extinfo) + self.motdend = extinfo # End of MOTD + + elif cmd == 386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal) + (channame, owner) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + user = self.user(owner) + if user.nick != owner: + user.nick = owner + if channel.modes.has_key("q"): + if user not in channel.modes["q"]: + channel.modes["q"].append(user) + else: + channel.modes["q"] = [user] + + elif cmd == 388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) + (channame, admin) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + if channel.name != channame: + channel.name = channame # Server seems to have changed the idea of the case of the channel name + user = self.user(admin) + if user.nick != admin: + user.nick = admin + if channel.modes.has_key("a"): + if user not in channel.modes["a"]: + channel.modes["a"].append(user) + else: + channel.modes["a"] = [user] + + elif cmd == "NICK": + newnick = extinfo if len(extinfo) else target + + addons = reduce( + lambda x, y: x + y, [chan.addons for chan in origin.channels], []) + self._event("onRecv", addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onNickChange", self.addons + addons, user=origin, newnick=newnick) + if origin == self.identity: + (handled, unhandled, exceptions) = self._event( + "onMeNickChange", self.addons + addons, newnick=newnick) + + for u in self.users: + if u.nick.lower() == newnick.lower(): + self.users.remove( + u) # Nick collision, safe to assume this orphaned user is offline, so we shall remove the old instance. + for channel in self.channels: + # If for some odd reason, the old user still + # appears common channels, then we will remove + # the user anyway. + if u in channel.users: + channel.users.remove(u) + origin.nick = newnick + + elif cmd == "JOIN": + channel = target if type( + target) == Channel else self.channel(extinfo) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onJoin", self.addons + channel.addons, user=origin, channel=channel) + + if origin == self.identity: # This means the bot is entering the room, + # and will reset all the channel data, on the assumption that such data may have changed. + # Also, the bot must request modes + with channel._joining: + if channel._joinrequested: + channel._joinreply = cmd + channel._joining.notify() + channel.topic = "" + channel.topicmod = "" + channel.modes = {} + channel.users = [] + self._event( + "onMeJoin", self.addons + channel.addons, channel=channel) + self.raw("MODE %s" % channel.name) + self.raw("WHO %s" % channel.name) + if "CHANMODES" in self.supports.keys(): + self.raw( + "MODE %s :%s" % (channel.name, self.supports["CHANMODES"][0])) + + if channel not in origin.channels: + origin.channels.append(channel) + if origin not in channel.users: + channel.users.append(origin) + + elif cmd == "KICK": + kicked = self.user(params) + if kicked.nick != params: + kicked.nick = params + + self._event("onRecv", target.addons, line=line, **data) + if origin == self.identity: + self._event( + "onMeKick", self.addons + target.addons, channel=target, kicked=kicked, kickmsg=extinfo) + if kicked == self.identity: + self._event("onMeKicked", self.addons + target.addons, + kicker=origin, channel=target, kickmsg=extinfo) + (handled, unhandled, exceptions) = self._event("onKick", self.addons + + target.addons, kicker=origin, channel=target, kicked=kicked, kickmsg=extinfo) + + if target in kicked.channels: + kicked.channels.remove(target) + if kicked in target.users: + target.users.remove(kicked) + if self.supports.has_key("PREFIX"): + for mode in self.supports["PREFIX"][0]: + if target.modes.has_key(mode) and kicked in target.modes[mode]: + target.modes[mode].remove(kicked) + + elif cmd == "PART": + try: + self._event("onRecv", target.addons, line=line, **data) + if origin == self.identity: + with target ._parting: + if target._partrequested: + target._partreply = cmd + target._parting.notify() + self._event( + "onMePart", self.addons + target.addons, channel=target, partmsg=extinfo) + (handled, unhandled, exceptions) = self._event( + "onPart", self.addons + target.addons, user=origin, channel=target, partmsg=extinfo) + + if target in origin.channels: + origin.channels.remove(target) + if origin in target.users: + target.users.remove(origin) + if self.supports.has_key("PREFIX"): + for mode in self.supports["PREFIX"][0]: + if target.modes.has_key(mode) and origin in target.modes[mode]: + target.modes[mode].remove(origin) + except: + print target + raise + elif cmd == "QUIT": + channels = list(origin.channels) + addons = reduce( + lambda x, y: x + y, [chan.addons for chan in origin.channels], []) + self._event("onRecv", addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onQuit", self.addons + addons, user=origin, quitmsg=extinfo) + for channel in origin.channels: + with channel.lock: + if origin in channel.users: + channel.users.remove(origin) + if self.supports.has_key("PREFIX"): + for mode in self.supports["PREFIX"][0]: + if channel.modes.has_key(mode) and origin in channel.modes[mode]: + channel.modes[mode].remove(origin) + origin.channels = [] + + elif cmd == "MODE": + if type(target) == Channel: + self._event("onRecv", target.addons, line=line, **data) + modedelta = [] + modeparams = params.split() + setmodes = modeparams.pop(0) + modeset = "+" + for mode in setmodes: + if mode in "+-": + modeset = mode else: - self.connection = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) - self.connection.connect((self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port)) - except socket.error: - exc, excmsg, tb = sys.exc_info() - self.event("onConnectFail", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) - self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) + if mode in self.supports["CHANMODES"][0] + self.supports["CHANMODES"][1]: + param = modeparams.pop(0) + modedelta.append( + ("%s%s" % (modeset, mode), param)) + if mode in _maskmodeeventnames.keys(): + if modeset == "+": + eventname = _maskmodeeventnames[ + mode][0] + if modeset == "-": + eventname = _maskmodeeventnames[ + mode][1] + matchesbot = glob.fnmatch.fnmatch( + "%s!%s@%s".lower() % (self.identity.nick, self.identity.username, self.identity.host), param.lower()) + self._event( + "on%s" % eventname, self.addons + target.addons, user=origin, channel=target, banmask=param) + if matchesbot: + self._event( + "onMe%s" % eventname, self.addons + target.addons, user=origin, channel=target, banmask=param) + elif mode in self.supports["CHANMODES"][2]: + if modeset == "+": + param = modeparams.pop(0) + modedelta.append( + ("%s%s" % (modeset, mode), param)) + else: + modedelta.append( + ("%s%s" % (modeset, mode), None)) + elif mode in self.supports["CHANMODES"][3]: + modedelta.append( + ("%s%s" % (modeset, mode), None)) + elif self.supports.has_key("PREFIX") and mode in self.supports["PREFIX"][0]: + modenick = modeparams.pop(0) + modeuser = self.user(modenick) + if mode in _privmodeeventnames.keys(): + if modeset == "+": + eventname = _privmodeeventnames[ + mode][0] + if modeset == "-": + eventname = _privmodeeventnames[ + mode][1] + self._event( + "on%s" % eventname, self.addons + target.addons, user=origin, channel=target, modeuser=modeuser) + if modeuser == self.identity: + self._event( + "onMe%s" % eventname, self.addons + target.addons, user=origin, channel=target) + modedelta.append( + ("%s%s" % (modeset, mode), modeuser)) + (handled, unhandled, exceptions) = self._event( + "onChanModeSet", self.addons + target.addons, user=origin, channel=target, modedelta=modedelta) + with target.lock: + for ((modeset, mode), param) in modedelta: + if mode in self.supports["CHANMODES"][0]: + if modeset == "+": + if target.modes.has_key(mode): + if param.lower() not in [mask.lower() for (mask, setby, settime) in target.modes[mode]]: + target.modes[mode].append( + (param, origin, int(time.time()))) + else: + target.modes[mode] = [ + (param, origin, int(time.time()))] + else: + if mode in target.modes.keys(): + if mode == "b": # Inspircd mode is case insentive when unsetting the mode + masks = [ + mask.lower() for (mask, setby, settime) in target.modes[mode]] + if param.lower() in masks: + index = masks.index( + param.lower()) + # print "Index: %d"%index + del target.modes[ + mode][index] + else: + masks = [ + mask for (mask, setby, settime) in target.modes[mode]] + if param in masks: + index = masks.index(param) + del target.modes[ + mode][index] + elif mode in self.supports["CHANMODES"][1]: + if modeset == "+": + target.modes[mode] = param + else: + target.modes[mode] = None + elif mode in self.supports["CHANMODES"][2]: + if modeset == "+": + target.modes[mode] = param + else: + target.modes[mode] = None + elif mode in self.supports["CHANMODES"][3]: + if modeset == "+": + target.modes[mode] = True + else: + target.modes[mode] = False + elif self.supports.has_key("PREFIX") and mode in self.supports["PREFIX"][0]: + if modeset == "+": + if target.modes.has_key(mode) and param not in target.modes[mode]: + target.modes[mode].append(param) + if not target.modes.has_key(mode): + target.modes[mode] = [param] + elif target.modes.has_key(mode) and param in target.modes[mode]: + target.modes[mode].remove(param) + elif type(target) == User: + modeparams = (params if params else extinfo).split() + setmodes = modeparams.pop(0) + modeset = "+" + for mode in setmodes: + if mode in "+-": + modeset = mode + continue + if modeset == "+": + if mode not in target.modes: + target.modes += mode + if mode == "s" and len(modeparams): + snomask = modeparams.pop(0) + for snomode in snomask: + if snomode in "+-": + snomodeset = snomode + continue + if snomodeset == "+" and snomode not in target.snomask: + target.snomask += snomode + if snomodeset == "-" and snomode in target.snomask: + target.snomask = target.snomask.replace( + snomode, "") + if modeset == "-": + if mode in target.modes: + target.modes = target.modes.replace( + mode, "") + if mode == "s": + target.snomask = "" + + elif cmd == "TOPIC": + self._event("onRecv", target.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onTopicSet", self.addons + target.addons, user=origin, channel=target, topic=extinfo) + + with target.lock: + target.topic = extinfo + target.topicsetby = origin + target.topictime = int(time.time()) + + elif cmd == "INVITE": + channel = self.channel(extinfo if extinfo else params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onInvite", self.addons + channel.addons, user=origin, channel=channel) + + elif cmd == "PRIVMSG": + if type(target) == Channel: + self._event("onRecv", target.addons, line=line, **data) + + # CTCP handling + ctcp = re.findall(_ctcpmatch, extinfo) + if ctcp: + (ctcptype, ext) = ctcp[0] + if ctcptype.upper() == "ACTION": + if type(target) == Channel: + (handled, unhandled, exceptions) = self._event( + "onChanAction", self.addons + target.addons, user=origin, channel=target, targetprefix=targetprefix, action=ext) + elif target == self.identity: + (handled, unhandled, exceptions) = self._event( + "onPrivAction", self.addons, user=origin, action=ext) else: - self.connected = True - self.connection.settimeout(self.timeout) + if type(target) == Channel: + (handled, unhandled, exceptions) = self._event("onChanCTCP", self.addons + target.addons, + user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext) + elif target == self.identity: + (handled, unhandled, exceptions) = self._event( + "onPrivCTCP", self.addons, user=origin, ctcptype=ctcptype, params=ext) + if ctcptype.upper() == "VERSION": + origin.ctcpreply("VERSION", self.ctcpversion()) + if ctcptype.upper() == "TIME": + tformat = time.ctime() + tz = time.tzname[0] + origin.ctcpreply( + "TIME", "%(tformat)s %(tz)s" % vars()) + if ctcptype.upper() == "PING": + origin.ctcpreply("PING", "%(ext)s" % vars()) + if ctcptype.upper() == "FINGER": + origin.ctcpreply("FINGER", "%(ext)s" % vars()) + else: + if type(target) == Channel: + (handled, unhandled, exceptions) = self._event( + "onChanMsg", self.addons + target.addons, user=origin, channel=target, targetprefix=targetprefix, msg=extinfo) + elif target == self.identity: + (handled, unhandled, exceptions) = self._event( + "onPrivMsg", self.addons, user=origin, msg=extinfo) + + elif cmd == "NOTICE": + if type(target) == Channel: + self._event("onRecv", target.addons, line=line, **data) + + # CTCP handling + ctcp = re.findall(_ctcpmatch, extinfo) + if ctcp and target == self.identity: + (ctcptype, ext) = ctcp[0] + (handled, unhandled, exceptions) = self._event( + "onCTCPReply", self.addons, origin=origin, ctcptype=ctcptype, params=ext) + else: + if type(target) == Channel: + (handled, unhandled, exceptions) = self._event( + "onChanNotice", self.addons + target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo) + elif target == self.identity: + (handled, unhandled, exceptions) = self._event( + "onPrivNotice", self.addons, origin=origin, msg=extinfo) + + elif cmd == 367: # Channel Ban list + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onBanListEntry", self.addons + channel.addons, + origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) + if "b" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: + channel.modes["b"].append( + (mask, setby, int(settime))) + else: + channel.modes["b"] = [(mask, setby, int(settime))] + elif cmd == 368: + channel = self.channel(params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onBanListEnd", self.addons + channel.addons, origin=origin, channel=channel, endmsg=extinfo) + + elif cmd == 346: # Channel Invite list + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onInviteListEntry", self.addons + + channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) + if "I" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]: + channel.modes["I"].append( + (mask, setby, int(settime))) + else: + channel.modes["I"] = [(mask, setby, int(settime))] + elif cmd == 347: + channel = self.channel(params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onInviteListEnd", self.addons + channel.addons, origin=origin, channel=channel, endmsg=extinfo) + + elif cmd == 348: # Channel Ban Exception list + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onBanExceptListEntry", self.addons + + channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) + if "e" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]: + channel.modes["e"].append( + (mask, setby, int(settime))) + else: + channel.modes["e"] = [(mask, setby, int(settime))] + elif cmd == 349: + channel = self.channel(params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onBanExceptListEnd", + self.addons + channel.addons, origin=origin, channel=channel, endmsg=extinfo) + + elif cmd == 910: # Channel Access List + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onAccessListEntry", self.addons + + channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) + if "w" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: + channel.modes["w"].append( + (mask, setby, int(settime))) + else: + channel.modes["w"] = [(mask, setby, int(settime))] + elif cmd == 911: + channel = self.channel(params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onAccessListEnd", self.addons + channel.addons, origin=origin, channel=channel, endmsg=extinfo) + + elif cmd == 941: # Spam Filter list + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onSpamfilterListEntry", self.addons + + channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) + if "g" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]: + channel.modes["g"].append( + (mask, setby, int(settime))) + else: + channel.modes["g"] = [(mask, setby, int(settime))] + elif cmd == 940: + channel = self.channel(params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onSpamfilterListEnd", + self.addons + channel.addons, origin=origin, channel=channel, endmsg=extinfo) + + elif cmd == 954: # Channel exemptchanops list + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onExemptChanOpsListEntry", self.addons + + channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) + if "X" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]: + channel.modes["X"].append( + (mask, setby, int(settime))) + else: + channel.modes["X"] = [(mask, setby, int(settime))] + elif cmd == 953: + channel = self.channel(params) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onExemptChanOpsListEnd", + self.addons + channel.addons, origin=origin, channel=channel, endmsg=extinfo) + + elif cmd == 728: # Channel quiet list + (channame, modechar, mask, setby, settime) = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event("onQuietListEntry", self.addons + channel.addons, + origin=origin, channel=channel, modechar=modechar, mask=mask, setby=setby, settime=int(settime)) + if "q" in channel.modes.keys(): + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["q"]]: + channel.modes["q"].append( + (mask, setby, int(settime))) + else: + channel.modes["q"] = [(mask, setby, int(settime))] + elif cmd == 729: + channame, modechar = params.split() + channel = self.channel(channame) + self._event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self._event( + "onQuietListEnd", self.addons + channel.addons, channel=channel, endmsg=extinfo) + + elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params + for param in params.split(): + if len(param) and param[0] in self.supports["CHANTYPES"]: + channel = self.channel(param) + self._event( + "onRecv", channel.addons, line=line, **data) + + elif type(cmd) == int: + (handled, unhandled, exceptions) = self._event("on%03d" % + cmd, self.addons, line=line, origin=origin, target=target, params=params, extinfo=extinfo) + else: + (handled, unhandled, exceptions) = self._event("on%s" % + cmd, self.addons, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo) - ### Setting up thread responsible for sending data back to IRC server. - self.outgoing._interrupted = False - self.outgoingthread = Outgoing(self) - self.outgoingthread.daemon = True - self.outgoingthread.start() + if cmd in (384, 403, 405, 471, 473, 474, 475, 476, 520, 477, 489, 495): # Channel Join denied + try: + channel = self.channel(params) + except InvalidName: + pass + else: + with channel._joining: + if channel._joinrequested: + channel._joinreply = (cmd, extinfo) + channel._joining.notify() - ### Run onConnect on all addons to signal connection was established. - self.event("onConnect", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) - self.logwrite("*** Connection to %(server)s:%(port)s established." % vars()) - break + elif cmd == 470: # Channel Join denied due to redirect + channelname, redirect = params.split() + try: + channel = self.channel(channelname) + except InvalidName: + pass + else: + with channel._joining: + if channel._joinrequested: + channel._joinreply = ( + cmd, "%s (%s)" % (extinfo, redirect)) + channel._joining.notify() + + # Handle events that were not handled. + self._event("onUnhandled", unhandled, line=line, origin=origin, + cmd=cmd, target=target, params=params, extinfo=extinfo) + + def _trynick(self): + (q, s) = divmod(self.trynick, len(self.nick)) + nick = self.nick[s] + if q > 0: + nick = "%s%d" % (nick, q) + self._send("NICK %s" % nick) + self.trynick += 1 + + def _recvhandler(self): + # Enforce that this function must only be run from within + # self._sendhandlerthread. + if currentThread() != self._recvhandlerthread: + raise RuntimeError, "This function is designed to run in its own thread." + server = self.server + if self.ipv6 and ":" in server: + server = "[%s]" % server + port = self.port - if self.quitexpected: - sys.exit() - if attempt < self.maxretries or self.maxretries == -1: - time.sleep(self.retrysleep) - if self.quitexpected: + try: + with self.lock: + self._event("onSessionOpen", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + + self.logwrite("### Log session started") + while True: # Autoreconnect loop + attempt = 1 + while True: # Autoretry loop + try: + self._connect() + break + except socket.error: + if self._quitexpected: + sys.exit() + if attempt < self.maxretries or self.maxretries < 0: + if self.retrysleep > 0: + time.sleep(self.retrysleep) + if self._quitexpected: + sys.exit() + attempt += 1 + else: + self.logwrite( + "*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars()) sys.exit() - attempt += 1 - else: - self.logwrite("*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars()) - sys.exit() - ### Connection succeeded + # Connection succeeded try: - ### Attempt initial registration. + with self._sendline: + self._sendline.notify() + + # Attempt initial registration. nick = self.nick[0] - trynick = 0 if self.passwd: - self.raw("PASS :%s" % self.passwd.split( - "\n")[0].rstrip()) - self.raw("NICK :%s" % nick.split("\n")[0].rstrip()) - self.raw("USER %s * * :%s" % (self.username.split("\n")[0].rstrip(), self.realname.split("\n")[0].rstrip())) + self._send("PASS %s" % self.passwd) + self._trynick() + self._send("USER %s * * :%s" % + (self.username.split("\n")[0].rstrip(), self.realname.split("\n")[0].rstrip())) - ### Initialize buffers + # Initialize buffers linebuf = [] readbuf = "" while True: # Main loop of IRC connection. while len(linebuf) == 0: # Need Moar Data - read = self.connection.recv(512) + read = self._connection.recv(512) - ### If read was empty, connection is terminated. + # If read was empty, connection is terminated. if read == "": sys.exit() - ### If read was successful, parse away! + # If read was successful, parse away! readbuf += read lastlf = readbuf.rfind("\n") if lastlf >= 0: - linebuf.extend(string.split(readbuf[0:lastlf], - "\n")) - readbuf = readbuf[lastlf+1:] + linebuf.extend( + string.split(readbuf[0:lastlf], "\n")) + readbuf = readbuf[lastlf + 1:] line = string.rstrip(linebuf.pop(0)) + self._procrecvline(line) - ### If received PING, then just pong back transparently. - ping = re.findall("^PING :?(.*)$", line) - if len(ping): - with self.lock: - self.connection.send("PONG :%s\n" % ping[0]) - continue - - self.logwrite("<<< %(line)s" % vars()) - - ### Attempts to match against pattern ":src cmd target params :extinfo" - matches = re.findall(r"^:(.+?)(?:!(.+?)@(.+?))?\s+(.+?)(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$", line) - - ### We have a match! - if len(matches): - parsed = (origin, username, host, cmd, target, params, extinfo) = matches[0] - unhandled = [] - - if re.match("^\\d+$", cmd): - cmd = int(cmd) # Code is a numerical response - else: - cmd = cmd.upper() - - with self._linereceived: - if not self.registered: - if type(cmd) == int and cmd != 451 and target != "*": # Registration complete! - self.registered = True - self.identity = self.user(target) - self.serv = origin - self.event("onRegistered", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) - - elif cmd == 433 and target == "*": # Server reports nick taken, so we need to try another. - trynick += 1 - (q, s) = divmod(trynick, len(self.nick)) - nick = self.nick[s] - if q > 0: - nick += str(q) - self.raw("NICK :%s" % nick.split("\n")[0].rstrip()) - if not self.registered: # Registration is not yet complete - continue - - if username and host: - nickname = origin - origin = self.user(origin) - if origin.nick != nickname: - ### Origin nickname has changed - origin.user = nickname - if origin.username != username: - ### Origin username has changed - origin.username = username - if origin.host != host: - ### Origin host has changed - origin.host = host - - chanmatch = re.findall(r"([%s]?)([%s]\S*)"%(re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target) - if chanmatch: - targetprefix, channame = chanmatch[0] - target = self.channel(channame) - if target.name != channame: - ### Target channel name has changed - target.name = channame - elif len(target) and target[0] != "$" and cmd != "NICK": - targetprefix = "" - target = self.user(target) - - self.data = data = dict(origin=origin, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo) - self._linereceived.notifyAll() - - ### Major codeblock here! Track IRC state. - ### Send line to addons first - self.event("onRecv", self.addons, line=line, - **data) - if cmd == 1: - (handled, unhandled, exceptions) = self.event("onWelcome", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo) - self.welcome = extinfo # Welcome message - elif cmd == 2: - (handled, unhandled, exceptions) = self.event("onYourHost", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo) - self.hostinfo = extinfo # Your Host - elif cmd == 3: - (handled, unhandled, exceptions) = self.event("onServerCreated", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo) - self.servcreated = extinfo # Server Created - elif cmd == 4: - (handled, unhandled, exceptions) = self.event("onServInfo", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, servinfo=params) - self.servinfo = params # What is this code? - elif cmd == 5: # Server Supports - support = dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params)) - if "CHANMODES" in support: - support["CHANMODES"] = support["CHANMODES"].split(",") - if "PREFIX" in support: - matches = re.findall("\\((.*)\\)(.*)", support["PREFIX"]) - if matches: - support["PREFIX"] = matches[0] - else: - del support["PREFIX"] # Might as well delete the info if it doesn't match expected pattern - (handled, unhandled, exceptions) = self.event("onSupports", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, supports=support) - self.supports.update(support) - if "serv005" in dir(self) and type(self.serv005) == list: - self.serv005.append(params) - else: - self.serv005 = [params] - elif cmd == 8: # Snomask - snomask = params.lstrip("+") - (handled, unhandled, exceptions) = self.event("onSnoMask", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, snomask=snomask) - self.identity.snomask = snomask - if "s" not in self.identity.modes: - self.snomask = "" - elif cmd == 221: # User Modes - modes = (params if params else extinfo).lstrip("+") - (handled, unhandled, exceptions) = self.event("onUserModes", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, snomask=modes) - self.identity.modes = modes - if "s" not in self.identity.modes: - self.snomask = "" - elif cmd == 251: # Net Stats - (handled, unhandled, exceptions) = self.event("onNetStats", self.addons, origin=origin, netstats=extinfo) - self.netstats = extinfo - elif cmd == 252: - opcount = int(params) - (handled, unhandled, exceptions) = self.event("onOpCount", self.addons, origin=origin, opcount=opcount) - self.opcount = opcount - elif cmd == 254: - chancount = int(params) - (handled, unhandled, exceptions) = self.event("onChanCount", self.addons, origin=origin, chancount=chancount) - self.chancount = chancount - - elif cmd == 305: # Returned from away status - (handled, unhandled, exceptions) = self.event("onReturn", self.addons, origin=origin, msg=extinfo) - self.identity.away = False - - elif cmd == 306: # Entered away status - (handled, unhandled, exceptions) = self.event("onAway", self.addons, origin=origin, msg=extinfo) - self.identity.away = True - - elif cmd == 311: # Start of WHOIS data - nickname, username, host, star = params.split() - user = self.user(nickname) - (handled, unhandled, exceptions) = self.event("onWhoisStart", self.addons, origin=origin, user=user, nickname=nickname, username=username, host=host, realname=extinfo) - user.nick = nickname - user.username = username - user.host = host - - elif cmd == 301: # Away Message - user = self.user(params) - (handled, unhandled, exceptions) = self.event("onWhoisAway", self.addons, origin=origin, user=user, nickname=params, awaymsg=extinfo) - user.away = True - user.awaymsg = extinfo - - elif cmd == 303: # ISON Reply - users = [self.user(user) for user in extinfo.split(" ")] - (handled, unhandled, exceptions) = self.event("onIsonReply", self.addons, origin=origin, isonusers=users) - - elif cmd == 307: # Is a registered nick - (handled, unhandled, exceptions) = self.event("onWhoisRegisteredNick", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) - elif cmd == 378: # Connecting From - (handled, unhandled, exceptions) = self.event("onWhoisConnectingFrom", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) - elif cmd == 319: # Channels - (handled, unhandled, exceptions) = self.event("onWhoisChannels", self.addons, origin=origin, user=self.user(params), nickname=params, chanlist=extinfo.split(" ")) - elif cmd == 310: # Availability - (handled, unhandled, exceptions) = self.event("onWhoisAvailability", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) - elif cmd == 312: # Server - nickname, server = params.split(" ") - user = self.user(nickname) - (handled, unhandled, exceptions) = self.event("onWhoisServer", self.addons, origin=origin, user=user, nickname=nickname, server=server, servername=extinfo) - user.server = server - elif cmd == 313: # IRC Op - user = self.user(params) - (handled, unhandled, exceptions) = self.event("onWhoisOp", self.addons, origin=origin, user=user, nickname=params, msg=extinfo) - user.ircop = True - user.ircopmsg = extinfo - elif cmd == 317: # Idle and Signon times - nickname, idletime, signontime = params.split(" ") - user = self.user(nickname) - (handled, unhandled, exceptions) = self.event("onWhoisTimes", self.addons, origin=origin, user=user, nickname=nickname, idletime=int(idletime), signontime=int(signontime), msg=extinfo) - user.idlesince = int(time.time())-int(idletime) - user.signontime = int(signontime) - elif cmd == 671: # SSL - user = self.user(params) - (handled, unhandled, exceptions) = self.event("onWhoisSSL", self.addons, origin=origin, user=user, nickname=params, msg=extinfo) - user.ssl = True - elif cmd == 379: # User modes - (handled, unhandled, exceptions) = self.event("onWhoisModes", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) - elif cmd == 330: # Logged in as - nickname, loggedinas = params.split(" ") - user = self.user(nickname) - (handled, unhandled, exceptions) = self.event("onWhoisLoggedInAs", self.addons, origin=origin, user=user, nickname=nickname, loggedinas=loggedinas, msg=extinfo) - user.loggedinas = loggedinas - elif cmd == 318: # End of WHOIS - (handled, unhandled, exceptions) = self.event("onWhoisEnd", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo) - - elif cmd == 321: # Start LIST - (handled, unhandled, exceptions) = self.event("onListStart", self.addons, origin=origin, params=params, extinfo=extinfo) - elif cmd == 322: # LIST item - (chan, pop) = params.split(" ", 1) - (handled, unhandled, exceptions) = self.event("onListEntry", self.addons, origin=origin, channel=self.channel(chan), population=int(pop), extinfo=extinfo) - elif cmd == 323: # End of LIST - (handled, unhandled, exceptions) = self.event("onListEnd", self.addons, origin=origin, endmsg=extinfo) - - elif cmd == 324: # Channel Modes - modeparams = params.split() - channame = modeparams.pop(0) - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - setmodes = modeparams.pop(0) - modedelta = [] - for mode in setmodes: - if mode == "+": - continue - elif mode in self.supports["CHANMODES"][2]: - param = modeparams.pop(0) - modedelta.append(("+%s"%mode, param)) - elif mode in self.supports["CHANMODES"][3]: - modedelta.append(("+%s"%mode, None)) - (handled, unhandled, exceptions) = self.event("onChannelModes", self.addons+channel.addons, channel=channel, modedelta=modedelta) - for ((modeset, mode), param) in modedelta: - if mode in self.supports["CHANMODES"][2]: - channel.modes[mode] = param - elif mode in self.supports["CHANMODES"][3]: - channel.modes[mode] = True - - elif cmd == 329: # Channel created - channame, created = params.split() - created = int(created) - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onChanCreated", self.addons+channel.addons, channel=channel, created=created) - channel.created = int(created) - - elif cmd == 332: # Channel Topic - channame = params - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onTopic", self.addons+channel.addons, origin=origin, channel=channel, topic=extinfo) - channel.topic = extinfo - - elif cmd == 333: # Channel Topic info - (channame, nick, dt) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onTopicInfo", self.addons+channel.addons, origin=origin, channel=channel, topicsetby=nick, topictime=int(dt)) - channel.topicsetby = nick - channel.topictime = int(dt) - - elif cmd == 352: # WHO reply - (channame, username, host, serv, nick, flags) = params.split() - try: - (hops, realname) = extinfo.split(" ", 1) - except ValueError: - hops = extinfo - realname = None - - if channame[0] in self.supports.get("CHANTYPES", "#"): - channel = self.channel(channame) - else: - channel = None - - user = self.user(nick) - - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onWhoEntry", self.addons+channel.addons, origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname) - user.hops = hops - user.realname = realname - user.username = username - user.host = host - user.server = serv - user.away = "G" in flags - user.ircop = "*" in flags - if type(channel) == Channel: - if user not in channel.users: - channel.users.append(user) - if channel not in user.channels: - user.channels.append(channel) - for (mode, prefix) in zip(*self.supports["PREFIX"]): - if prefix in flags: - if mode in channel.modes.keys() and user not in channel.modes[mode]: - channel.modes[mode].append(user) - elif mode not in channel.modes.keys(): - channel.modes[mode] = [user] - - elif cmd == 315: # End of WHO reply - (handled, unhandled, exceptions) = self.event("onWhoEnd", self.addons+channel.addons, origin=origin, param=params, endmsg=extinfo) - - elif cmd == 353: # NAMES reply - (flag, channame) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - - if "PREFIX" in self.supports: - names = re.findall(r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?"%re.escape(self.supports["PREFIX"][1]), extinfo) - else: - names = re.findall(r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo) # Still put it into tuple form for compatibility in the next structure - (handled, unhandled, exceptions) = self.event("onNames", self.addons+channel.addons, origin=origin, channel=channel, flag=flag, channame=channame, nameslist=names) - - for (symbs, nick, username, host) in names: - user = self.user(nick) - if user.nick != nick: - user.nick = nick - if username and user.username != username: - user.username = username - if host and user.host != host: - user.host = host - with channel.lock: - if channel not in user.channels: - user.channels.append(channel) - if user not in channel.users: - channel.users.append(user) - if "PREFIX" in self.supports: - for symb in symbs: - mode = self.supports["PREFIX"][0][self.supports["PREFIX"][1].index(symb)] - if mode not in channel.modes: - channel.modes[mode] = [user] - elif user not in channel.modes[mode]: - channel.modes[mode].append(user) - - elif cmd == 366: # End of NAMES reply - channel = self.channel(params) - (handled, unhandled, exceptions) = self.event("onNamesEnd", self.addons+channel.addons, origin=origin, channel=channel, channame=params, endmsg=extinfo) - - elif cmd == 372: # MOTD line - (handled, unhandled, exceptions) = self.event("onMOTDLine", self.addons, origin=origin, motdline=extinfo) - self.motd.append(extinfo) - elif cmd == 375: # Begin MOTD - (handled, unhandled, exceptions) = self.event("onMOTDStart", self.addons, origin=origin, motdgreet=extinfo) - self.motdgreet = extinfo - self.motd = [] - elif cmd == 376: - (handled, unhandled, exceptions) = self.event("onMOTDEnd", self.addons, origin=origin, motdend=extinfo) - self.motdend = extinfo # End of MOTD - - elif cmd == 386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal) - (channame, owner) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - user = self.user(owner) - if user.nick != owner: - user.nick = owner - if "q" in channel.modes: - if user not in channel.modes["q"]: - channel.modes["q"].append(user) - else: - channel.modes["q"] = [user] - - elif cmd == 388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) - (channame, admin) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - user = self.user(admin) - if user.nick != admin: - user.nick = admin - if "a" in channel.modes: - if user not in channel.modes["a"]: - channel.modes["a"].append(user) - else: - channel.modes["a"] = [user] - - elif cmd == "NICK": - newnick = extinfo if len(extinfo) else target - - addons = reduce(lambda x, y: x+y, [chan.addons for chan in origin.channels], []) - self.event("onRecv", addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onNickChange", self.addons+addons, user=origin, newnick=newnick) - if origin == self.identity: - (handled, unhandled, exceptions) = self.event("onMeNickChange", self.addons+addons, newnick=newnick) - - for u in self.users: - if u.nick.lower() == newnick.lower(): - self.users.remove(u) # Nick collision, safe to assume this orphaned user is offline, so we shall remove the old instance. - for channel in self.channels: - ### If for some odd reason, the old user still appears common channels, then we will remove the user anyway. - if u in channel.users: - channel.users.remove(u) - origin.nick = newnick - - elif cmd == "JOIN": - channel = target if type(target) == Channel else self.channel(extinfo) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onJoin", self.addons+channel.addons, user=origin, channel=channel) - - if origin == self.identity: # This means the bot is entering the room, - # and will reset all the channel data, on the assumption that such data may have changed. - # Also, the bot must request modes - with channel._joining: - if channel._joinrequested: - channel._joinreply = cmd - channel._joining.notify() - channel.topic = "" - channel.topicmod = "" - channel.modes = {} - channel.users = [] - self.event("onMeJoin", self.addons+channel.addons, channel=channel) - self.raw("MODE %s" % channel.name) - self.raw("WHO %s" % channel.name) - if "CHANMODES" in self.supports.keys(): - self.raw("MODE %s :%s" % (channel.name, self.supports["CHANMODES"][0])) - - if channel not in origin.channels: - origin.channels.append(channel) - if origin not in channel.users: - channel.users.append(origin) - - elif cmd == "KICK": - kicked = self.user(params) - if kicked.nick != params: - kicked.nick = params - - self.event("onRecv", target.addons, line=line, **data) - if origin == self.identity: - self.event("onMeKick", self.addons+target.addons, channel=target, kicked=kicked, kickmsg=extinfo) - if kicked == self.identity: - self.event("onMeKicked", self.addons+target.addons, kicker=origin, channel=target, kickmsg=extinfo) - (handled, unhandled, exceptions) = self.event("onKick", self.addons+target.addons, kicker=origin, channel=target, kicked=kicked, kickmsg=extinfo) - - if target in kicked.channels: - kicked.channels.remove(target) - if kicked in target.users: - target.users.remove(kicked) - if "PREFIX" in self.supports: - for mode in self.supports["PREFIX"][0]: - if mode in target.modes and kicked in target.modes[mode]: - target.modes[mode].remove(kicked) - - elif cmd == "PART": - self.event("onRecv", target.addons, line=line, **data) - if origin == self.identity: - with target ._parting: - if target._partrequested: - target._partreply = cmd - target._parting.notify() - self.event("onMePart", self.addons+target.addons, channel=target, partmsg=extinfo) - (handled, unhandled, exceptions) = self.event("onPart", self.addons+target.addons, user=origin, channel=target, partmsg=extinfo) - - if target in origin.channels: - origin.channels.remove(target) - if origin in target.users: - target.users.remove(origin) - if "PREFIX" in self.supports: - for mode in self.supports["PREFIX"][0]: - if mode in target.modes and origin in target.modes[mode]: - target.modes[mode].remove(origin) - - elif cmd == "QUIT": - channels = list(origin.channels) - addons = reduce(lambda x, y: x+y, [chan.addons for chan in origin.channels], []) - self.event("onRecv", addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onQuit", self.addons+addons, user=origin, quitmsg=extinfo) - for channel in origin.channels: - with channel.lock: - if origin in channel.users: - channel.users.remove(origin) - if "PREFIX" in self.supports: - for mode in self.supports["PREFIX"][0]: - if mode in channel.modes and origin in channel.modes[mode]: - channel.modes[mode].remove(origin) - origin.channels = [] - - elif cmd == "MODE": - if type(target) == Channel: - self.event("onRecv", target.addons, line=line, **data) - modedelta = [] - modeparams = params.split() - setmodes = modeparams.pop(0) - modeset = "+" - for mode in setmodes: - if mode in "+-": - modeset = mode - else: - if mode in self.supports["CHANMODES"][0]+self.supports["CHANMODES"][1]: - param = modeparams.pop(0) - modedelta.append(("%s%s"%(modeset, mode), param)) - if mode in maskmodeeventnames.keys(): - if modeset == "+": - eventname = maskmodeeventnames[mode][0] - if modeset == "-": - eventname = maskmodeeventnames[mode][1] - matchesbot = glob.fnmatch.fnmatch("%s!%s@%s".lower()%(self.identity.nick, self.identity.username, self.identity.host), param.lower()) - self.event("on%s"%eventname, self.addons+target.addons, user=origin, channel=target, banmask=param) - if matchesbot: - self.event("onMe%s"%eventname, self.addons+target.addons, user=origin, channel=target, banmask=param) - elif mode in self.supports["CHANMODES"][2]: - if modeset == "+": - param = modeparams.pop(0) - modedelta.append(("%s%s"%(modeset, mode), param)) - else: - modedelta.append(("%s%s"%(modeset, mode), None)) - elif mode in self.supports["CHANMODES"][3]: - modedelta.append(("%s%s"%(modeset, mode), None)) - elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]: - modenick = modeparams.pop(0) - modeuser = self.user(modenick) - if mode in privmodeeventnames.keys(): - if modeset == "+": - eventname = privmodeeventnames[mode][0] - if modeset == "-": - eventname = privmodeeventnames[mode][1] - self.event("on%s"%eventname, self.addons+target.addons, user=origin, channel=target, modeuser=modeuser) - if modeuser == self.identity: - self.event("onMe%s"%eventname, self.addons+target.addons, user=origin, channel=target) - modedelta.append(("%s%s"%(modeset, mode), modeuser)) - (handled, unhandled, exceptions) = self.event("onChanModeSet", self.addons+target.addons, user=origin, channel=target, modedelta=modedelta) - with target.lock: - for ((modeset, mode), param) in modedelta: - if mode in self.supports["CHANMODES"][0]: - if modeset == "+": - if mode in target.modes: - if param.lower() not in [mask.lower() for (mask, setby, settime) in target.modes[mode]]: - target.modes[mode].append((param, origin, int(time.time()))) - else: - target.modes[mode] = [(param, origin, int(time.time()))] - else: - if mode in target.modes.keys(): - if mode == "b": # Inspircd mode is case insentive when unsetting the mode - masks = [mask.lower() for (mask, setby, settime) in target.modes[mode]] - if param.lower() in masks: - index = masks.index(param.lower()) - #print "Index: %d"%index - del target.modes[mode][index] - else: - masks = [mask for (mask, setby, settime) in target.modes[mode]] - if param in masks: - index = masks.index(param) - del target.modes[mode][index] - elif mode in self.supports["CHANMODES"][1]: - if modeset == "+": - target.modes[mode] = param - else: - target.modes[mode] = None - elif mode in self.supports["CHANMODES"][2]: - if modeset == "+": - target.modes[mode] = param - else: - target.modes[mode] = None - elif mode in self.supports["CHANMODES"][3]: - if modeset == "+": - target.modes[mode] = True - else: - target.modes[mode] = False - elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]: - if modeset == "+": - if mode in target.modes and param not in target.modes[mode]: - target.modes[mode].append(param) - if mode not in target.modes: - target.modes[mode] = [param] - elif mode in target.modes and param in target.modes[mode]: - target.modes[mode].remove(param) - elif type(target) == User: - modeparams = (params if params else extinfo).split() - setmodes = modeparams.pop(0) - modeset = "+" - for mode in setmodes: - if mode in "+-": - modeset = mode - continue - if modeset == "+": - if mode not in target.modes: - target.modes += mode - if mode == "s" and len(modeparams): - snomask = modeparams.pop(0) - for snomode in snomask: - if snomode in "+-": - snomodeset = snomode - continue - if snomodeset == "+" and snomode not in target.snomask: - target.snomask += snomode - if snomodeset == "-" and snomode in target.snomask: - target.snomask = target.snomask.replace(snomode, "") - if modeset == "-": - if mode in target.modes: - target.modes = target.modes.replace(mode, "") - if mode == "s": - target.snomask = "" - - elif cmd == "TOPIC": - self.event("onRecv", target.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onTopicSet", self.addons+target.addons, user=origin, channel=target, topic=extinfo) - - with target.lock: - target.topic = extinfo - target.topicsetby = origin - target.topictime = int(time.time()) - - elif cmd == "INVITE": - channel = self.channel(extinfo if extinfo else params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onInvite", self.addons+channel.addons, user=origin, channel=channel) - - elif cmd == "PRIVMSG": - if type(target) == Channel: - self.event("onRecv", target.addons, line=line, **data) - - ### CTCP handling - ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo) - if ctcp: - (ctcptype, ext) = ctcp[0] - if ctcptype.upper() == "ACTION": - if type(target) == Channel: - (handled, unhandled, exceptions) = self.event("onChanAction", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, action=ext) - elif target == self.identity: - (handled, unhandled, exceptions) = self.event("onPrivAction", self.addons, user=origin, action=ext) - else: - if type(target) == Channel: - (handled, unhandled, exceptions) = self.event("onChanCTCP", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext) - elif target == self.identity: - (handled, unhandled, exceptions) = self.event("onPrivCTCP", self.addons, user=origin, ctcptype=ctcptype, params=ext) - if ctcptype.upper() == "VERSION": - origin.ctcpreply("VERSION", self.ctcpversion()) - if ctcptype.upper() == "TIME": - tformat = time.ctime() - tz = time.tzname[0] - origin.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars()) - if ctcptype.upper() == "PING": - origin.ctcpreply("PING", "%(ext)s" % vars()) - if ctcptype.upper() == "FINGER": - origin.ctcpreply("FINGER", "%(ext)s" % vars()) - else: - if type(target) == Channel: - (handled, unhandled, exceptions) = self.event("onChanMsg", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, msg=extinfo) - elif target == self.identity: - (handled, unhandled, exceptions) = self.event("onPrivMsg", self.addons, user=origin, msg=extinfo) - - elif cmd == "NOTICE": - if type(target) == Channel: - self.event("onRecv", target.addons, line=line, **data) - - ### CTCP handling - ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo) - if ctcp and target == self.identity: - (ctcptype, ext) = ctcp[0] - (handled, unhandled, exceptions) = self.event("onCTCPReply", self.addons, origin=origin, ctcptype=ctcptype, params=ext) - else: - if type(target) == Channel: - (handled, unhandled, exceptions) = self.event("onChanNotice", self.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo) - elif target == self.identity: - (handled, unhandled, exceptions) = self.event("onPrivNotice", self.addons, origin=origin, msg=extinfo) - - elif cmd == 367: # Channel Ban list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onBanListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) - if "b" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: - channel.modes["b"].append((mask, setby, int(settime))) - else: - channel.modes["b"] = [(mask, setby, int(settime))] - elif cmd == 368: - channel = self.channel(params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onBanListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo) - - elif cmd == 346: # Channel Invite list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onInviteListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) - if "I" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]: - channel.modes["I"].append((mask, setby, int(settime))) - else: - channel.modes["I"] = [(mask, setby, int(settime))] - elif cmd == 347: - channel = self.channel(params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onInviteListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo) - - elif cmd == 348: # Channel Ban Exception list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onBanExceptListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) - if "e" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]: - channel.modes["e"].append((mask, setby, int(settime))) - else: - channel.modes["e"] = [(mask, setby, int(settime))] - elif cmd == 349: - channel = self.channel(params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onBanExceptListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo) - - elif cmd == 910: # Channel Access List - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onAccessListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) - if "w" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: - channel.modes["w"].append((mask, setby, int(settime))) - else: - channel.modes["w"] = [(mask, setby, int(settime))] - elif cmd == 911: - channel = self.channel(params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onAccessListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo) - - elif cmd == 941: # Spam Filter list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onSpamfilterListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) - if "g" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]: - channel.modes["g"].append((mask, setby, int(settime))) - else: - channel.modes["g"] = [(mask, setby, int(settime))] - elif cmd == 940: - channel = self.channel(params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onSpamfilterListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo) - - elif cmd == 954: # Channel exemptchanops list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onExemptChanOpsListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)) - if "X" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]: - channel.modes["X"].append((mask, setby, int(settime))) - else: - channel.modes["X"] = [(mask, setby, int(settime))] - elif cmd == 953: - channel = self.channel(params) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onExemptChanOpsListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo) - - elif cmd == 728: # Channel quiet list - (channame, modechar, mask, setby, settime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onQuietListEntry", self.addons+channel.addons, origin=origin, channel=channel, modechar=modechar, mask=mask, setby=setby, settime=int(settime)) - if "q" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["q"]]: - channel.modes["q"].append((mask, setby, int(settime))) - else: - channel.modes["q"] = [(mask, setby, int(settime))] - elif cmd == 729: - channame, modechar = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.addons, line=line, **data) - (handled, unhandled, exceptions) = self.event("onQuietListEnd", self.addons+channel.addons, channel=channel, endmsg=extinfo) - - elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params - for param in params.split(): - if len(param) and param[0] in self.supports["CHANTYPES"]: - channel = self.channel(param) - self.event("onRecv", channel.addons, line=line, **data) - - elif type(cmd) == int: - (handled, unhandled, exceptions) = self.event("on%03d"%cmd, self.addons, line=line, origin=origin, target=target, params=params, extinfo=extinfo) - else: - (handled, unhandled, exceptions) = self.event("on%s"%cmd, self.addons, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo) - - if cmd in (384, 403, 405, 471, 473, 474, 475, 476, 520, 477, 489, 495): # Channel Join denied - try: - channel = self.channel(params) - except InvalidName: - pass - else: - with channel._joining: - if channel._joinrequested: - channel._joinreply = (cmd, extinfo) - channel._joining.notify() - - elif cmd == 470: # Channel Join denied due to redirect - channelname, redirect = params.split() - try: - channel = self.channel(channelname) - except InvalidName: - pass - else: - with channel._joining: - if channel._joinrequested: - channel._joinreply = (cmd, "%s (%s)"%(extinfo, redirect)) - channel._joining.notify() - - self.event("onUnhandled", unhandled, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo) - - else: # Line does NOT match ":origin cmd target params :extinfo" - self.event("onRecv", self.addons, line=line) except SystemExit: # Connection lost normally. pass + except socket.error: # Connection lost due to either ping timeout or connection reset by peer. Not a fatal error. exc, excmsg, tb = sys.exc_info() - self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) + with self.lock: + self.logwrite( + "*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) + self._event("onConnectFail", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) + except: # Unknown exception, treated as FATAL. Try to quit IRC and terminate thread with exception. - ### Quit with a (hopefully) useful quit message, or die trying. - self.quitexpected = True + # Quit with a (hopefully) useful quit message, or die + # trying. + self._quitexpected = True try: - self.quit("%s" % traceback.format_exc() - .rstrip().split("\n")[-1]) + self.quit( + "%s" % traceback.format_exc().rstrip().split("\n")[-1]) except: pass raise + finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred. + with self._sendline: + self._outgoing.clear() + self._sendline.notify() with self.lock: - (handled, unhandled, exceptions) = self.event("onDisconnect", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), expected=self.quitexpected) - self.connected = False - self.registered = False - self.identity = None - - ### Tell outgoing thread to quit. - self.outgoing.interrupt() + (handled, unhandled, exceptions) = self._event("onDisconnect", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), expected=self._quitexpected) + self._init() - ### Wait until the outgoing thread dies. - if self.outgoingthread and self.outgoingthread.isAlive(): - self.outgoingthread.join() - self.outgoingthread = None + # Notify _outgoingthread that the connection has been + # terminated. + with self._sendline: + self._sendline.notify() try: - self.connection.close() + self._connection.close() except: pass self.logwrite("*** Connection Terminated.") - if self.quitexpected or not self.autoreconnect: + if self._quitexpected or not self.autoreconnect: + self._quitexpected = False sys.exit() - ### If we make it to this point, then it is because connection was lost unexpectedly, and will attempt to reconnect if self.autoreconnect is True. - time.sleep(self.retrysleep) - except SystemExit: pass except: # Print exception to log file - self.logwrite(*["!!! FATAL Exception"]+["!!! %s"%line for line in traceback.format_exc().split("\n")]) - print >>sys.stderr, "FATAL Exception" % vars() + self.logwrite(*["!!! FATAL Exception"] + ["!!! %s" % + line for line in traceback.format_exc().split("\n")]) + print >>sys.stderr, "FATAL Exception" print >>sys.stderr, traceback.format_exc() sys.exit() finally: self.logwrite("### Log session ended") - (handled, unhandled, exceptions) = self.event("onSessionClose", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) - Thread.__init__(self) # Makes thread restartable + (handled, unhandled, exceptions) = self._event("onSessionClose", self.addons + + reduce(lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + + # Tell _sendhandler to quit + with self._sendline: + self._outgoing.append("quit") + self._sendline.notify() + + def _send(self, line, origin=None): + if "\r" in line or "\n" in line: + raise InvalidCharacter + cmd = line.split(" ")[0].upper() + + T = time.time() + if cmd == "PRIVMSG": + # Hard-coding a throttling mechanism for PRIVMSGs only here. Will later build support for custom throttlers. + # The throttle will be triggered when it attempts to send a sixth PRIVMSG in a four-second interval. + # When the throttle is active, PRIVMSGs will be sent in at least one-second intervals. + # The throttle is deactivated when three seconds elapse without + # sending a PRIVMSG. + while len(self.throttledata) and self.throttledata[0] < T - 4: + del self.throttledata[0] + if not self.throttled: + if len(self.throttledata) >= 5: + self.throttled = True + T = self.throttledata[-1] + 1 + else: + if len(self.throttledata) == 0 or self.throttledata[-1] < T - 2: + self.throttled = False + else: + T = max(T, self.throttledata[-1] + 1) + self.throttledata.append(T) + with self._sendline: + self._outgoing.append((T, line, origin)) + self._sendline.notify() + + def _procsendline(self, line, origin=None): + match = re.findall(_ircsendmatch, line) + if len(match) == 0: + return + (cmd, target, params, extinfo) = match[0] + cmd = cmd.upper() + with self.lock: + if cmd == "QUIT": + self._quitexpected = True + self._connection.send("%s\n" % line) + + # Modify line if it contains a password so that the password is not + # logged or sent to any potentially untrustworthy addons + if cmd == "PRIVMSG": + if target.upper() == "NICKSERV": + nscmd = re.findall( + r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) + if nscmd: + nscmd = nscmd[0] + if nscmd[0].upper() in ("IDENTIFY", "REGISTER"): + extinfo = "%s ********" % nscmd[0] + line = "%s %s :%s" % (cmd, target, extinfo) + elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): + extinfo = "%s %s ********" % nscmd[:2] + line = "%s %s :%s" % (cmd, target, extinfo) + elif nscmd[0].upper() == "SET": + if nscmd[1].upper() == "PASSWORD": + extinfo = "%s %s ********" % nscmd[:2] + line = "%s %s :%s" % (cmd, target, extinfo) + elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): + extinfo = "********" + line = "%s %s :%s" % (cmd, target, extinfo) + if target.upper() == "CHANSERV": + cscmd = re.findall( + r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) + if cscmd: + cscmd = cscmd[0] + if cscmd[0].upper() in ("IDENTIFY", "REGISTER"): + extinfo = "%s %s ********" % cscmd[:2] + line = "%s %s :%s" % (cmd, target, extinfo) + elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): + extinfo = "%s %s %s ********" % cscmd[:3] + line = "%s %s :%s" % (cmd, target, extinfo) + elif cscmd[0].upper() == "SET": + if cscmd[2].upper() == "PASSWORD": + extinfo = "%s %s %s ********" % cscmd[:3] + line = "%s %s :%s" % (cmd, target, extinfo) + elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): + extinfo = "********" + line = "%s %s :%s" % (cmd, target, extinfo) + + chanmatch = re.findall( + _targchanmatch % (re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target) + if chanmatch: + targetprefix, channame = chanmatch[0] + target = self.channel(channame) + if target.name != channame: + # Target channel name has changed + target.name = channame + elif re.match(_nickmatch, target) and cmd != "NICK": + targetprefix = "" + target = self.user(target) + + ctcp = re.findall(_ctcpmatch, extinfo) + if ctcp: + (ctcptype, ext) = ctcp[0] + if ctcptype.upper() == "ACTION": + if type(target) == Channel: + self._event( + "onSendChanAction", self.addons + + target.addons, + origin=origin, channel=target, targetprefix=targetprefix, action=ext) + elif type(target) == User: + self._event( + "onSendPrivAction", self.addons, origin=origin, user=target, action=ext) + else: + if type(target) == Channel: + self._event( + "onSendChanCTCP", self.addons + target.addons, origin=origin, + channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext) + elif type(target) == User: + self._event( + "onSendPrivCTCP", self.addons, origin=origin, user=target, ctcptype=ctcptype, params=ext) + else: + if type(target) == Channel: + self._event( + "onSendChanMsg", self.addons + target.addons, origin=origin, + channel=target, targetprefix=targetprefix, msg=extinfo) + elif type(target) == User: + self._event( + "onSendPrivMsg", self.addons, origin=origin, user=target, msg=extinfo) + + # elif target.upper()=="CHANSERV": + #msg=extinfo.split(" ") + # if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2: + # msg[2]="********" + #extinfo=" ".join(msg) + #line="%s %s :%s"%(cmd, target, extinfo) + elif cmd.upper() in ("NS", "NICKSERV"): + if target.upper() in ("IDENTIFY", "REGISTER"): + params = params.split(" ") + while "" in params: + params.remove("") + if len(params): + params[0] = "********" + params = " ".join(params) + line = "%s %s %s" % (cmd, target, params) + elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): + params = params.split(" ") + while "" in params: + params.remove("") + if len(params) > 1: + params[1] = "********" + params = " ".join(params) + line = "%s %s %s" % (cmd, target, params) + elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): + params = "" + target = "********" + line = "%s %s" % (cmd, target) + elif cmd.upper() == "OPER": + params = "********" + line = "%s %s %s" % (cmd, target, params) + elif cmd.upper() == "PASS": + extinfo = "********" + target = "" + line = "%s :%s" % (cmd, extinfo) + elif cmd.upper() == "IDENTIFY": + target = "********" + line = "%s %s" % (cmd, target) + self._event("onSend", self.addons, origin=origin, line=line, + cmd=cmd, target=target, params=params, extinfo=extinfo) + self.logwrite(">>> %s" % line) + + def _sendhandler(self): + # Enforce that this function must only be run from within + # self._sendhandlerthread. + if currentThread() != self._sendhandlerthread: + raise RuntimeError, "This function is designed to run in its own thread." + + try: + while True: + # Wait for one of the following: + # (1) An item placed into _outgoing + # (2) Connection is lost + # (3) self._recvhandlerthread is set to None + + with self._sendline: + if "quit" in self._outgoing: + sys.exit() + S = time.time() + if len(self._outgoing): + T, line, origin = min(self._outgoing) + if T > S: + self._sendline.wait(T - S) + continue + else: + self._outgoing.remove((T, line, origin)) + else: + self._sendline.wait() + continue + + try: + self._procsendline(line, origin=origin) + except socket.error: + exc, excmsg, tb = sys.exc_info() + with self.lock: + self.logwrite( + "*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) + self._event("onConnectFail", self.addons + reduce( + lambda x, y: x + y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) + with self._sendline: + self._outgoing.clear() + try: + self._connection.close() + except: + pass + + except SystemExit: + pass + + except: + tb = traceback.format_exc() + self._quitexpected = True + self.logwrite(*["!!! FATAL Exception"] + [ + "!!! %s" % line for line in tb.split("\n")]) + print >>sys.stderr, "FATAL Exception" + print >>sys.stderr, tb + with self._sendline: + try: + self._connection.send( + "QUIT :%s\n" % tb.rstrip().split("\n")[-1]) + self._connection.shutdown(socket.SHUT_WR) + except: + pass + finally: + with self._sendline: + self._outgoing.clear() # Clear out _outgoing. + + # For compatibility, when modules still expect irc.Connection to be a + # subclass of threading.Thread + def isAlive(self): + return type(self._recvhandlerthread) == Thread and self._recvhandlerthread.isAlive() and type(self._sendhandlerthread) == Thread and self._sendhandlerthread.isAlive() + + # For compatibility, when modules still expect irc.Connection to be a + # subclass of threading.Thread + def start(self): + return self.connect() def __repr__(self): server = self.server if self.ipv6 and ":" in server: - server = "[%s]"%server + server = "[%s]" % server port = self.port if self.identity: nick = self.identity.nick @@ -1212,61 +1713,60 @@ class Connection(Thread): else: protocol = "irc" return "<IRC Context: %(nick)s!%(user)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals() - #else: return "<IRC Context: irc%(ssl)s://%(server)s:%(port)s>" % locals() + # else: return "<IRC Context: irc%(ssl)s://%(server)s:%(port)s>" % + # locals() def oper(self, name, passwd, origin=None): - self.raw("OPER %s %s" % (re.findall("^([^\r\n\\s]*)", name)[0], - re.findall("^([^\r\n\\s]*)", passwd)[0]), origin=origin) + self.raw("OPER %s %s" % + (re.findall("^([^\r\n\\s]*)", name)[0], re.findall("^([^\r\n\\s]*)", passwd)[0]), origin=origin) def list(self, params="", origin=None): if len(re.findall("^([^\r\n\\s]*)", params)[0]): - self.raw("LIST %s" % (re.findall( - "^([^\r\n\\s]*)", params)[0]), origin=origin) + self.raw("LIST %s" % + (re.findall("^([^\r\n\\s]*)", params)[0]), origin=origin) else: self.raw("LIST", origin=origin) def getmotd(self, target="", origin=None): if len(re.findall("^([^\r\n\\s]*)", target)[0]): - self.raw("MOTD %s" % (re.findall( - "^([^\r\n\\s]*)", target)[0]), origin=origin) + self.raw("MOTD %s" % + (re.findall("^([^\r\n\\s]*)", target)[0]), origin=origin) else: self.raw("MOTD", origin=origin) def version(self, target="", origin=None): if len(re.findall("^([^\r\n\\s]*)", target)[0]): - self.raw("VERSION %s" % (re.findall( - "^([^\r\n\\s]*)", target)[0]), origin=origin) + self.raw("VERSION %s" % + (re.findall("^([^\r\n\\s]*)", target)[0]), origin=origin) else: self.raw("VERSION", origin=origin) def stats(self, query, target="", origin=None): if len(re.findall("^([^\r\n\\s]*)", target)[0]): - self.raw("STATS %s %s" % (query, re.findall( - "^([^\r\n\\s]*)", target)[0]), origin=origin) + self.raw("STATS %s %s" % + (query, re.findall("^([^\r\n\\s]*)", target)[0]), origin=origin) else: - self.raw("STATS %s"%query, origin=origin) + self.raw("STATS %s" % query, origin=origin) def quit(self, msg="", origin=None): - with self.lock: - if self.connected: - if len(re.findall("^([^\r\n]*)", msg)[0]): - self.raw("QUIT :%s" % re.findall("^([^\r\n]*)", - msg)[0], origin=origin) - else: - self.raw("QUIT", origin=origin) + if len(re.findall("^([^\r\n]*)", msg)[0]): + self._send("QUIT :%s" % + re.findall("^([^\r\n]*)", msg)[0], origin=origin) + else: + self._send("QUIT", origin=origin) def ctcpversion(self): reply = [] - ### Prepare reply for addon - reply.append("%(__name__)s %(__version__)s, %(__author__)s" % - vars(self)) + # Prepare reply for addon + reply.append( + "%(__name__)s %(__version__)s, %(__author__)s" % vars(self)) - ### Prepare reply for Python and OS versions + # Prepare reply for Python and OS versions pyver = sys.version.split("\n") - pyver[0] = "Python "+pyver[0] + pyver[0] = "Python " + pyver[0] reply.extend(pyver) reply.extend(platform.platform().split("\n")) - ### Prepare reply for extension addons + # Prepare reply for extension addons for addon in self.addons: try: r = "%(__name__)s %(__version__)s" % vars(addon) @@ -1278,46 +1778,47 @@ class Connection(Thread): return reduce(lambda x, y: "%s; %s" % (x, y), reply) def raw(self, line, origin=None): - if "\r" in line or "\n" in line: - raise InvalidCharacter - self.outgoing.put((line, origin)) + self._send(line, origin=origin) def user(self, nick): if self.supports.get("CASEMAPPING", "rfc1459") == "ascii": - users = [user for user in self.users if user.nick.lower( - ) == nick.lower()] + users = [ + user for user in self.users if user.nick.lower() == nick.lower()] else: - users = [user for user in self.users if user.nick.translate(_rfc1459casemapping) == nick.translate(_rfc1459casemapping)] + users = [user for user in self.users if user.nick.translate( + _rfc1459casemapping) == nick.translate(_rfc1459casemapping)] if len(users): return users[0] else: user = User(nick, self) self.users.append(user) - timestamp = reduce(lambda x, y: x+":"+y, [str( - t).rjust(2, "0") for t in time.localtime()[0:6]]) + timestamp = reduce(lambda x, y: x + ":" + y, [ + str(t).rjust(2, "0") for t in time.localtime()[0:6]]) return user def channel(self, name): if self.supports.get("CASEMAPPING", "rfc1459") == "ascii": - channels = [chan for chan in self.channels if chan.name.lower( - ) == name.lower()] + channels = [ + chan for chan in self.channels if chan.name.lower() == name.lower()] else: - channels = [chan for chan in self.channels if chan.name.translate(_rfc1459casemapping) == name.translate(_rfc1459casemapping)] + channels = [chan for chan in self.channels if chan.name.translate( + _rfc1459casemapping) == name.translate(_rfc1459casemapping)] if len(channels): return channels[0] else: - timestamp = reduce(lambda x, y: x+":"+y, [str( - t).rjust(2, "0") for t in time.localtime()[0:6]]) + timestamp = reduce(lambda x, y: x + ":" + y, [ + str(t).rjust(2, "0") for t in time.localtime()[0:6]]) chan = Channel(name, self) self.channels.append(chan) return chan class Channel(object): + def __init__(self, name, context): chantypes = context.supports.get("CHANTYPES", "&#+!") - if not re.match(r"^[%s][^%s\s]*$" % (re.escape(chantypes), re.escape("\x07,")), name): - raise InvalidName(repr(name)) + if not re.match(_chanmatch % re.escape(chantypes), name): + raise InvalidName, repr(name) self.name = name self.context = context self.addons = [] @@ -1340,37 +1841,37 @@ class Channel(object): if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]: raise InvalidPrefix for line in re.findall("([^\r\n]+)", msg): - self.context.raw("PRIVMSG %s%s :%s" % (target, - self.name, line), origin=origin) + self.context._send("PRIVMSG %s%s :%s" % + (target, self.name, line), origin=origin) def who(self, origin=None): - self.context.raw("WHO %s" % (self.name), origin=origin) + self.context._send("WHO %s" % (self.name), origin=origin) def names(self, origin=None): - self.context.raw("NAMES %s" % (self.name), origin=origin) + self.context._send("NAMES %s" % (self.name), origin=origin) def notice(self, msg, target="", origin=None): if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]: raise InvalidPrefix for line in re.findall("([^\r\n]+)", msg): - self.context.raw("NOTICE %s%s :%s" % (target, - self.name, line), origin=origin) + self.context._send("NOTICE %s%s :%s" % + (target, self.name, line), origin=origin) def settopic(self, msg, origin=None): - self.context.raw("TOPIC %s :%s" % (self.name, re.findall( - "^([^\r\n]*)", msg)[0]), origin=origin) + self.context._send("TOPIC %s :%s" % + (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin) def ctcp(self, act, msg="", origin=None): if len(re.findall("^([^\r\n]*)", msg)[0]): - self.msg("\01%s %s\01" % (act.upper(), re.findall( - "^([^\r\n]*)", msg)[0]), origin=origin) + self.msg("\01%s %s\01" % + (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin) else: self.msg("\01%s\01" % act.upper()) def ctcpreply(self, act, msg="", origin=None): if len(re.findall("^([^\r\n]*)", msg)[0]): - self.notice("\01%s %(msg)s\01" % (act.upper(), - re.findall("^([^\r\n]*)", msg)[0]), origin=origin) + self.notice("\01%s %(msg)s\01" % + (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin) else: self.notice("\01%s\01" % act.upper(), origin=origin) @@ -1380,7 +1881,7 @@ class Channel(object): def part(self, msg="", blocking=False, timeout=30, origin=None): with self.context.lock: if self.context.identity not in self.users: - ### Bot is not on the channel + # Bot is not on the channel raise NotOnChannel with self._parting: try: @@ -1388,19 +1889,20 @@ class Channel(object): raise ActionAlreadyRequested self._partrequested = True if len(re.findall("^([^\r\n]*)", msg)[0]): - self.context.raw("PART %s :%s" % (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin) + self.context._send( + "PART %s :%s" % (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin) else: - self.context.raw("PART %s" % self.name, origin=origin) + self.context._send("PART %s" % self.name, origin=origin) - ### Anticipated Numeric Replies: + # Anticipated Numeric Replies: - ### ERR_NEEDMOREPARAMS ERR_NOSUCHCHANNEL - ### ERR_NOTONCHANNEL + # ERR_NEEDMOREPARAMS ERR_NOSUCHCHANNEL + # ERR_NOTONCHANNEL if blocking: endtime = time.time() + timeout while True: - self._parting.wait(max(0, endtime-time.time())) + self._parting.wait(max(0, endtime - time.time())) t = time.time() if not self.context.connected: raise NotConnected @@ -1408,7 +1910,7 @@ class Channel(object): return elif type(self._partreply) == tuple and len(self._partreply) == 2: cmd, extinfo = self._partreply - raise exceptcodes[cmd](extinfo) + raise exceptcodes[cmd], extinfo if t > endtime: raise RequestTimedOut finally: @@ -1420,12 +1922,13 @@ class Channel(object): user) == User else re.findall("^([^\r\n\\s]*)", user)[0] if nickname == "": raise InvalidName - self.context.raw("INVITE %s %s" % (nickname, self.name), origin=origin) + self.context._send("INVITE %s %s" % + (nickname, self.name), origin=origin) def join(self, key="", blocking=False, timeout=30, origin=None): with self.context.lock: if self.context.identity in self.users: - ### Bot is already on the channel + # Bot is already on the channel raise AlreadyJoined with self._joining: try: @@ -1433,22 +1936,23 @@ class Channel(object): raise ActionAlreadyRequested self._joinrequested = True if len(re.findall("^([^\r\n\\s]*)", key)[0]): - self.context.raw("JOIN %s %s" % (self.name, re.findall("^([^\r\n\\s]*)", key)[0]), origin=origin) + self.context._send( + "JOIN %s %s" % (self.name, re.findall("^([^\r\n\\s]*)", key)[0]), origin=origin) else: - self.context.raw("JOIN %s" % self.name, origin=origin) + self.context._send("JOIN %s" % self.name, origin=origin) - ### Anticipated Numeric Replies: + # Anticipated Numeric Replies: - ### ERR_NEEDMOREPARAMS ERR_BANNEDFROMCHAN - ### ERR_INVITEONLYCHAN ERR_BADCHANNELKEY - ### ERR_CHANNELISFULL ERR_BADCHANMASK - ### ERR_NOSUCHCHANNEL ERR_TOOMANYCHANNELS - ### ERR_TOOMANYTARGETS ERR_UNAVAILRESOURCE + # ERR_NEEDMOREPARAMS ERR_BANNEDFROMCHAN + # ERR_INVITEONLYCHAN ERR_BADCHANNELKEY + # ERR_CHANNELISFULL ERR_BADCHANMASK + # ERR_NOSUCHCHANNEL ERR_TOOMANYCHANNELS + # ERR_TOOMANYTARGETS ERR_UNAVAILRESOURCE if blocking: endtime = time.time() + timeout while True: - self._joining.wait(max(0, endtime-time.time())) + self._joining.wait(max(0, endtime - time.time())) t = time.time() if not self.context.connected: raise NotConnected @@ -1456,7 +1960,7 @@ class Channel(object): return elif type(self._joinreply) == tuple and len(self._joinreply) == 2: cmd, extinfo = self._joinreply - raise exceptcodes[cmd](extinfo) + raise exceptcodes[cmd], extinfo if t > endtime: raise RequestTimedOut finally: @@ -1469,19 +1973,20 @@ class Channel(object): if nickname == "": raise InvalidName if len(re.findall("^([^\r\n]*)", msg)[0]): - self.context.raw("KICK %s %s :%s" % (self.name, nickname, - re.findall("^([^\r\n]*)", msg)[0]), origin=origin) + self.context._send("KICK %s %s :%s" % + (self.name, nickname, re.findall("^([^\r\n]*)", msg)[0]), origin=origin) else: - self.context.raw("KICK %s %s" % (self.name, - nickname), origin=origin) + self.context._send("KICK %s %s" % + (self.name, nickname), origin=origin) def __repr__(self): - return "<Channel: "+self.name+"@"+self.context.server+"/"+str(self.context.port)+">" + return "<Channel: " + self.name + "@" + self.context.server + "/" + str(self.context.port) + ">" class User(object): + def __init__(self, nick, context): - if not re.match(r"^[A-Za-z\^\`\\\|\_\{\}\[\]][A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]*$", nick): + if not re.match(_nickmatch, nick): raise InvalidName self.nick = nick self.username = "" @@ -1504,202 +2009,27 @@ class User(object): def msg(self, msg, origin=None): for line in re.findall("([^\r\n]+)", msg): - self.context.raw("PRIVMSG %s :%s" % (self.nick, - line), origin=origin) + self.context._send("PRIVMSG %s :%s" % + (self.nick, line), origin=origin) def notice(self, msg, origin=None): for line in re.findall("([^\r\n]+)", msg): - self.context.raw("NOTICE %s :%s" % (self.nick, - line), origin=origin) + self.context._send("NOTICE %s :%s" % + (self.nick, line), origin=origin) def ctcp(self, act, msg="", origin=None): if len(re.findall("^([^\r\n]*)", msg)[0]): - self.msg("\01%s %s\01" % (act.upper(), re.findall( - "^([^\r\n]*)", msg)[0]), origin=origin) + self.msg("\01%s %s\01" % + (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin) else: self.msg("\01%s\01" % act.upper()) def ctcpreply(self, act, msg="", origin=None): if len(re.findall("^([^\r\n]*)", msg)[0]): - self.notice("\01%s %s\01" % (act.upper(), - re.findall("^([^\r\n]*)", msg)[0]), origin=origin) + self.notice("\01%s %s\01" % + (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin) else: self.notice("\01%s\01" % act.upper(), origin=origin) def me(self, msg="", origin=None): self.ctcp("ACTION", msg, origin=origin) - - -class Outgoing(Thread): - def __init__(self, IRC, throttle=0.25, lines=10, t=5): - self.IRC = IRC - self.throttle = throttle - self.lines = lines - self.time = t - #self.queue=Queue() - Thread.__init__(self) - - def run(self): - try: - throttled = False - timestamps = [] - while True: - try: - q = self.IRC.outgoing.get() - except Queue.Interrupted: - break - if q == "quit" or not self.IRC.connected: - break - line, origin = q - match = re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I) - (cmd, target, params, extinfo) = match[0] - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - with self.IRC.lock: - try: - self.IRC.connection.send("%(line)s\n" % vars()) - except socket.error: - try: - self.IRC.connection.shutdown(0) - except: - pass - raise - - ### Modify line if it contains a password so that the password is not logged or sent to any potentially untrustworthy addons - #if re.match("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I): - if cmd.upper() == "PRIVMSG": - if target.upper() == "NICKSERV": - nscmd = re.findall(r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) - if nscmd: - nscmd = nscmd[0] - if nscmd[0].upper() in ("IDENTIFY", "REGISTER"): - extinfo = "%s ********"%nscmd[0] - line = "%s %s :%s"%(cmd, target, extinfo) - elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): - extinfo = "%s %s ********"%nscmd[:2] - line = "%s %s :%s"%(cmd, target, extinfo) - elif nscmd[0].upper() == "SET": - if nscmd[1].upper() == "PASSWORD": - extinfo = "%s %s ********"%nscmd[:2] - line = "%s %s :%s"%(cmd, target, extinfo) - elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - extinfo = "********" - line = "%s %s :%s"%(cmd, target, extinfo) - if target.upper() == "CHANSERV": - cscmd = re.findall(r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I) - if cscmd: - cscmd = cscmd[0] - if cscmd[0].upper() in ("IDENTIFY", "REGISTER"): - extinfo = "%s %s ********"%cscmd[:2] - line = "%s %s :%s"%(cmd, target, extinfo) - elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): - extinfo = "%s %s %s ********"%cscmd[:3] - line = "%s %s :%s"%(cmd, target, extinfo) - elif cscmd[0].upper() == "SET": - if cscmd[2].upper() == "PASSWORD": - extinfo = "%s %s %s ********"%cscmd[:3] - line = "%s %s :%s"%(cmd, target, extinfo) - elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - extinfo = "********" - line = "%s %s :%s"%(cmd, target, extinfo) - - chanmatch = re.findall("([%s]?)([%s].+)"%(re.escape(self.IRC.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.IRC.supports.get("CHANTYPES", "#"))), target) - if chanmatch: - targetprefix, channame = chanmatch[0] - target = self.IRC.channel(channame) - if target.name != channame: - ### Target channel name has changed - target.name = channame - elif len(target) and target[0] != "$" and cmd != "NICK": - targetprefix = "" - target = self.IRC.user(target) - - ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", - extinfo) - if ctcp: - (ctcptype, ext) = ctcp[0] - if ctcptype.upper() == "ACTION": - if type(target) == Channel: - self.IRC.event("onSendChanAction", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, action=ext) - elif type(target) == User: - self.IRC.event("onSendPrivAction", self.IRC.addons, origin=origin, user=target, action=ext) - else: - if type(target) == Channel: - self.IRC.event("onSendChanCTCP", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext) - elif type(target) == User: - self.IRC.event("onSendPrivCTCP", self.IRC.addons, origin=origin, user=target, ctcptype=ctcptype, params=ext) - else: - if type(target) == Channel: - self.IRC.event("onSendChanMsg", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo) - elif type(target) == User: - self.IRC.event("onSendPrivMsg", self.IRC.addons, origin=origin, user=target, msg=extinfo) - - #elif target.upper()=="CHANSERV": - #msg=extinfo.split(" ") - #if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2: - #msg[2]="********" - #extinfo=" ".join(msg) - #line="%s %s :%s"%(cmd, target, extinfo) - elif cmd.upper() == "NS": - if target.upper() in ("IDENTIFY", "REGISTER"): - params = params.split(" ") - while "" in params: - params.remove("") - if len(params): - params[0] = "********" - params = " ".join(params) - line = "%s %s %s"%(cmd, target, params) - elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"): - params = params.split(" ") - while "" in params: - params.remove("") - if len(params) > 1: - params[1] = "********" - params = " ".join(params) - line = "%s %s %s"%(cmd, target, params) - elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - params = "" - target = "********" - line = "%s %s"%(cmd, target) - elif cmd.upper() == "OPER": - params = "********" - line = "%s %s %s"%(cmd, target, params) - elif cmd.upper() == "PASS": - extinfo = "********" - target = "" - line = "%s :%s"%(cmd, extinfo) - elif cmd.upper() == "IDENTIFY": - target = "********" - line = "%s %s"%(cmd, target) - self.IRC.event("onSend", self.IRC.addons, origin=origin, line=line, cmd=cmd, target=target, params=params, extinfo=extinfo) - self.IRC.logwrite(">>> %(line)s" % vars()) - if cmd.upper() == "QUIT": - self.IRC.quitexpected = True - timestamps.append(time.time()) - while timestamps[0] < timestamps[-1]-self.time-0.1: - del timestamps[0] - if throttled: - if len(timestamps) < 2: - throttled = False - else: - if len(timestamps) >= self.lines: - throttled = True - if throttled: - time.sleep(max(timestamps[-1] + - self.throttle-time.time(), 0)) - except: - self.IRC.connection.send("QUIT :%s\n" % - traceback.format_exc().rstrip().split("\n")[-1]) - self.IRC.connection.close() - self.IRC.connection.shutdown(0) - - -class Pinger(Thread): - def __init__(self, connection, lock=None): - self.connection = connection - self.lock = lock - self.daemon = True - Thread.__init__(self) - - def run(self): - pass @@ -14,11 +14,20 @@ import ssl import urllib2 import irc -modemapping = dict( - Y="ircop", q="owner", a="admin", o="op", h="halfop", v="voice") +modemapping = dict(Y="ircop", q="owner", + a="admin", o="op", h="halfop", v="voice") + + +def LoggerReload(log): + newlog = Logger(logroot=log.logroot) + for IRC, label in log.labels.items(): + IRC.rmAddon(log) + IRC.addAddon(newlog, label=label) + return newlog class Logger(Thread): + def __init__(self, logroot): self.logroot = logroot path = [logroot] @@ -31,7 +40,7 @@ class Logger(Thread): while len(path) > 1: path[0] = os.path.join(*path[:2]) del path[1] - #print path + # print path os.mkdir(path[0]) self.logs = {} @@ -45,10 +54,10 @@ class Logger(Thread): def run(self): try: Y, M, D, h, m, s, w, d, dst = time.localtime() - nextrotate = int(time.mktime((Y, M, D+1, 0, 0, 0, 0, 0, -1))) + nextrotate = int(time.mktime((Y, M, D + 1, 0, 0, 0, 0, 0, -1))) while True: while nextrotate > time.time(): # May need to do this in a loop in case the following time.sleep command wakes up a second too early. - time.sleep(max(0.1, min((nextrotate-time.time(), 3600)))) + time.sleep(max(0.1, min((nextrotate - time.time(), 3600)))) with self.rotatelock: if all([not log or log.closed for log in self.logs.values()]): break @@ -60,31 +69,36 @@ class Logger(Thread): self.rotateLog(IRC) except: exc, excmsg, tb = sys.exc_info() - IRC.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()]+["!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) + IRC.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()] + [ + "!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) if IRC.identity: for channel in IRC.identity.channels: try: self.rotateLog(channel) except: exc, excmsg, tb = sys.exc_info() - IRC.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()]+["!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) + IRC.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()] + [ + "!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) for user in IRC.users: if user in self.logs.keys(): try: self.closeLog(user) except: exc, excmsg, tb = sys.exc_info() - IRC.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()]+["!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) - IRC.logopen(os.path.join(self.logroot, self.labels[IRC], "rawdata-%04d.%02d.%02d.log"%now[:3])) - nextrotate = int(time.mktime((Y, M, D+1, 0, 0, 0, 0, 0, -1))) + IRC.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()] + [ + "!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) + IRC.logopen( + os.path.join(self.logroot, self.labels[IRC], "rawdata-%04d.%02d.%02d.log" % now[:3])) + nextrotate = int(time.mktime((Y, M, D + 1, 0, 0, 0, 0, 0, -1))) finally: Thread.__init__(self) + self.daemon = True def onAddonAdd(self, IRC, label): if label in self.labels.values(): - raise BaseException("Label already exists") + raise BaseException, "Label already exists" if IRC in self.labels.keys(): - raise BaseException("Network already exists") + raise BaseException, "Network already exists" if not os.path.isdir(os.path.join(self.logroot, label)): os.mkdir(os.path.join(self.logroot, label)) self.labels[IRC] = label @@ -94,10 +108,10 @@ class Logger(Thread): for channel in IRC.identity.channels: self.openLog(channel) now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - IRC.logopen(os.path.join(self.logroot, self.labels[IRC], - "rawdata-%04d.%02d.%02d.log"%now[:3])) + timestamp = reduce(lambda x, y: x + ":" + y, [ + str(t).rjust(2, "0") for t in now[0:6]]) + IRC.logopen( + os.path.join(self.logroot, self.labels[IRC], "rawdata-%04d.%02d.%02d.log" % now[:3])) def onAddonRem(self, IRC): if IRC.connected: @@ -118,21 +132,25 @@ class Logger(Thread): if not self.isAlive(): self.start() now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) + timestamp = reduce(lambda x, y: x + ":" + y, [ + str(t).rjust(2, "0") for t in now[0:6]]) if type(window) == irc.Connection: - log = self.logs[window] = open(os.path.join(self.logroot, self.labels[window], "console-%04d.%02d.%02d.log"%now[:3]), "a") + log = self.logs[window] = open( + os.path.join(self.logroot, self.labels[window], "console-%04d.%02d.%02d.log" % now[:3]), "a") print >>log, "%s ### Log file opened" % (irc.timestamp()) elif type(window) == irc.Channel: label = self.labels[window.context] - log = self.logs[window] = open(os.path.join(self.logroot, label, "channel-%s-%04d.%02d.%02d.log"%((urllib2.quote(window.name.lower()).replace("/", "%2f"),)+now[:3])), "a") + log = self.logs[window] = open(os.path.join(self.logroot, label, "channel-%s-%04d.%02d.%02d.log" % ( + (urllib2.quote(window.name.lower()).replace("/", "%2f"),) + now[:3])), "a") print >>log, "%s ### Log file opened" % (irc.timestamp()) self.logs[window].flush() if window.context.identity in window.users: if window.topic: - print >>log, "%s <<< :%s 332 %s %s :%s" % (irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.topic) + print >>log, "%s <<< :%s 332 %s %s :%s" % ( + irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.topic) if window.topicsetby and window.topictime: - print >>log, "%s <<< :%s 333 %s %s %s %s" % (irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.topicsetby, window.topictime) + print >>log, "%s <<< :%s 333 %s %s %s %s" % ( + irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.topicsetby, window.topictime) if window.users: secret = "s" in window.modes.keys() and window.modes["s"] private = "p" in window.modes.keys() and window.modes["p"] @@ -141,18 +159,24 @@ class Logger(Thread): print >>log, "%s <<< :%s 353 %s %s %s :%s" % (irc.timestamp(), window.context.serv, window.context.identity.nick, - "@" if secret else ("*" if private else "="), + "@" if secret else ( + "*" if private else "="), window.name, - " ".join(["".join([symbols[k] if modes[k] in window.modes.keys() and user in window.modes[modes[k]] else "" for k in xrange(len(modes))])+user.nick for user in window.users])) + " ".join(["".join([symbols[k] if modes[k] in window.modes.keys() and user in window.modes[modes[k]] else "" for k in xrange(len(modes))]) + user.nick for user in window.users])) if window.modes: modes = window.modes.keys() - modestr = "".join([mode for mode in modes if mode not in window.context.supports["CHANMODES"][0]+window.context.supports["PREFIX"][0] and window.modes[mode]]) - params = " ".join([window.modes[mode] for mode in modes if mode in window.context.supports["CHANMODES"][1]+window.context.supports["CHANMODES"][2] and window.modes[mode]]) - print >>log, "%s <<< :%s 324 %s %s +%s %s" % (irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, modestr, params) + modestr = "".join([mode for mode in modes if mode not in window.context.supports[ + "CHANMODES"][0] + window.context.supports["PREFIX"][0] and window.modes[mode]]) + params = " ".join([window.modes[mode] for mode in modes if mode in window.context.supports[ + "CHANMODES"][1] + window.context.supports["CHANMODES"][2] and window.modes[mode]]) + print >>log, "%s <<< :%s 324 %s %s +%s %s" % ( + irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, modestr, params) if window.created: - print >>log, "%s <<< :%s 329 %s %s %s" % (irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.created) + print >>log, "%s <<< :%s 329 %s %s %s" % ( + irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.created) if type(window) == irc.User: - logname = os.path.join(self.logroot, self.labels[window.context], "query-%s-%04d.%02d.%02d.log"%((urllib2.quote(window.nick.lower()).replace("/", "%2f"),)+now[:3])) + logname = os.path.join(self.logroot, self.labels[window.context], "query-%s-%04d.%02d.%02d.log" % ( + (urllib2.quote(window.nick.lower()).replace("/", "%2f"),) + now[:3])) for (other, log) in self.logs.items(): if other == window: continue @@ -169,8 +193,8 @@ class Logger(Thread): def closeLog(self, window): if window in self.logs.keys() and type(self.logs[window]) == file and not self.logs[window].closed: - print >>self.logs[window], "%s ### Log file closed" % ( - irc.timestamp()) + print >>self.logs[ + window], "%s ### Log file closed" % (irc.timestamp()) self.logs[window].close() if window in self.logs.keys(): del self.logs[window] @@ -194,7 +218,7 @@ class Logger(Thread): ts, IRC.server, IRC.port) def onConnectFail(self, IRC, exc, excmsg, tb): - ### Called when a connection attempt fails. + # Called when a connection attempt fails. if IRC not in self.logs.keys() or (not self.logs[IRC]) or self.logs[IRC].closed: self.openLog(IRC) ts = irc.timestamp() @@ -205,7 +229,8 @@ class Logger(Thread): ts = irc.timestamp() for window in self.logs.keys(): if type(window) in (irc.Channel, irc.User) and window.context == IRC: - print >>self.logs[window], "%s *** Connection to %s:%s terminated." % (ts, IRC.server, IRC.port) + print >>self.logs[window], "%s *** Connection to %s:%s terminated." % ( + ts, IRC.server, IRC.port) self.logs[window].flush() self.closeLog(window) print >>self.logs[IRC], "%s *** Connection %s:%s terminated." % ( @@ -214,7 +239,7 @@ class Logger(Thread): self.closeLog(IRC) def onJoin(self, IRC, user, channel): - ### Called when somebody joins a channel, includes bot. + # Called when somebody joins a channel, includes bot. ts = irc.timestamp() if user == IRC.identity: self.openLog(channel) @@ -223,95 +248,111 @@ class Logger(Thread): self.logs[channel].flush() def onChanMsg(self, IRC, user, channel, targetprefix, msg): - ### Called when someone sends a PRIVMSG to channel. + # Called when someone sends a PRIVMSG to channel. ts = irc.timestamp() if type(user) == irc.User: - classes = " ".join([modemapping[mode] for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and user in channel.modes[mode]]) + classes = " ".join([modemapping[mode] + for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and user in channel.modes[mode]]) if classes: - print >>self.logs[channel], "%s %s <<< :%s!%s@%s PRIVMSG %s%s :%s" % (ts, classes, user.nick, user.username, user.host, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s %s <<< :%s!%s@%s PRIVMSG %s%s :%s" % ( + ts, classes, user.nick, user.username, user.host, targetprefix, channel.name, msg) else: - print >>self.logs[channel], "%s <<< :%s!%s@%s PRIVMSG %s%s :%s" % (ts, user.nick, user.username, user.host, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s <<< :%s!%s@%s PRIVMSG %s%s :%s" % ( + ts, user.nick, user.username, user.host, targetprefix, channel.name, msg) elif type(user) in (str, unicode): classes = "server" - print >>self.logs[channel], "%s %s <<< :%s PRIVMSG %s%s :%s" % (ts, - classes, user, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s %s <<< :%s PRIVMSG %s%s :%s" % ( + ts, classes, user, targetprefix, channel.name, msg) self.logs[channel].flush() def onChanAction(self, IRC, user, channel, targetprefix, action): - self.onChanMsg(IRC, user, channel, targetprefix, - "\x01ACTION %s\x01"%action) + self.onChanMsg( + IRC, user, channel, targetprefix, "\x01ACTION %s\x01" % action) def onChanNotice(self, IRC, origin, channel, targetprefix, msg): - ### Called when someone sends a NOTICE to channel. + # Called when someone sends a NOTICE to channel. ts = irc.timestamp() if type(origin) == irc.User: - classes = " ".join([modemapping[mode] for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and origin in channel.modes[mode]]) + classes = " ".join([modemapping[mode] + for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and origin in channel.modes[mode]]) if classes: - print >>self.logs[channel], "%s %s <<< :%s!%s@%s NOTICE %s%s :%s" % (ts, classes, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s %s <<< :%s!%s@%s NOTICE %s%s :%s" % ( + ts, classes, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg) else: - print >>self.logs[channel], "%s <<< :%s!%s@%s NOTICE %s%s :%s" % (ts, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s <<< :%s!%s@%s NOTICE %s%s :%s" % ( + ts, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg) elif type(origin) in (str, unicode): classes = "server" - print >>self.logs[channel], "%s %s <<< :%s NOTICE %s%s :%s" % (ts, - classes, origin, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s %s <<< :%s NOTICE %s%s :%s" % ( + ts, classes, origin, targetprefix, channel.name, msg) self.logs[channel].flush() def onPart(self, IRC, user, channel, partmsg): - ### Called when somebody parts the channel, includes bot. + # Called when somebody parts the channel, includes bot. ts = irc.timestamp() if partmsg: - print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s :%s" % (ts, user.nick, user.username, user.host, channel.name, partmsg) + print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s :%s" % ( + ts, user.nick, user.username, user.host, channel.name, partmsg) else: - print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s" % (ts, - user.nick, user.username, user.host, channel.name) + print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s" % ( + ts, user.nick, user.username, user.host, channel.name) self.logs[channel].flush() if user == IRC.identity: self.closeLog(channel) def onKick(self, IRC, kicker, channel, kicked, kickmsg): - ### Called when somebody is kicked from the channel, includes bot. + # Called when somebody is kicked from the channel, includes bot. ts = irc.timestamp() if kickmsg: - print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s :%s" % (ts, kicker.nick, kicker.username, kicker.host, channel.name, kicked.nick, kickmsg) + print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s :%s" % ( + ts, kicker.nick, kicker.username, kicker.host, channel.name, kicked.nick, kickmsg) else: - print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s" % (ts, user.nick, user.username, user.host, channel.name, kicked.nick) + print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s" % ( + ts, user.nick, user.username, user.host, channel.name, kicked.nick) self.logs[channel].flush() - if user == IRC.identity: + if kicked == IRC.identity: self.closeLog(channel) def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg): - ### Called when bot sends a PRIVMSG to channel. - ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent. + # Called when bot sends a PRIVMSG to channel. + # The variable origin refers to a class instance voluntarily + # identifying itself as that which requested data be sent. ts = irc.timestamp() - classes = " ".join([modemapping[mode] for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and IRC.identity in channel.modes[mode]]) + classes = " ".join([modemapping[mode] + for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and IRC.identity in channel.modes[mode]]) if classes: - print >>self.logs[channel], "%s %s >>> :%s!%s@%s PRIVMSG %s%s :%s" % (ts, classes, IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s %s >>> :%s!%s@%s PRIVMSG %s%s :%s" % ( + ts, classes, IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg) else: - print >>self.logs[channel], "%s >>> :%s!%s@%s PRIVMSG %s%s :%s" % (ts, IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg) + print >>self.logs[channel], "%s >>> :%s!%s@%s PRIVMSG %s%s :%s" % ( + ts, IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg) self.logs[channel].flush() def onSendChanAction(self, IRC, origin, channel, targetprefix, action): - ### origin is the source of the channel message - ### Called when bot sends an action (/me) to channel. - ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent. - self.onSendChanMsg(IRC, origin, channel, targetprefix, - "\x01ACTION %s\x01"%action) + # origin is the source of the channel message + # Called when bot sends an action (/me) to channel. + # The variable origin refers to a class instance voluntarily + # identifying itself as that which requested data be sent. + self.onSendChanMsg( + IRC, origin, channel, targetprefix, "\x01ACTION %s\x01" % action) def onPrivMsg(self, IRC, user, msg): - ### Called when someone sends a PRIVMSG to the bot. + # Called when someone sends a PRIVMSG to the bot. if user not in self.logs.keys(): self.openLog(user) ts = irc.timestamp() - print >>self.logs[user], "%s <<< :%s!%s@%s PRIVMSG %s :%s" % (ts, user.nick, user.username, user.host, IRC.identity.nick, msg) + print >>self.logs[user], "%s <<< :%s!%s@%s PRIVMSG %s :%s" % ( + ts, user.nick, user.username, user.host, IRC.identity.nick, msg) self.logs[user].flush() def onPrivNotice(self, IRC, origin, msg): - ### Called when someone sends a NOTICE to the bot. + # Called when someone sends a NOTICE to the bot. ts = irc.timestamp() if type(origin) == irc.User: if origin not in self.logs.keys(): self.openLog(origin) - print >>self.logs[origin], "%s <<< :%s!%s@%s NOTICE %s :%s" % (ts, origin.nick, origin.username, origin.host, IRC.identity.nick, msg) + print >>self.logs[origin], "%s <<< :%s!%s@%s NOTICE %s :%s" % ( + ts, origin.nick, origin.username, origin.host, IRC.identity.nick, msg) self.logs[origin].flush() else: print >>self.logs[IRC], "%s <<< :%s NOTICE %s :%s" % ( @@ -319,53 +360,57 @@ class Logger(Thread): self.logs[IRC].flush() def onPrivAction(self, IRC, user, action): - ### Called when someone sends an action (/me) to the bot. - self.onPrivMsg(IRC, user, "\x01ACTION %s\x01"%action) + # Called when someone sends an action (/me) to the bot. + self.onPrivMsg(IRC, user, "\x01ACTION %s\x01" % action) def onSendPrivMsg(self, IRC, origin, user, msg): - ### Called when bot sends a PRIVMSG to a user. - ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent. + # Called when bot sends a PRIVMSG to a user. + # The variable origin refers to a class instance voluntarily + # identifying itself as that which requested data be sent. if user not in self.logs.keys(): self.openLog(user) ts = irc.timestamp() - print >>self.logs[user], "%s >>> :%s!%s@%s PRIVMSG %s :%s" % (ts, IRC.identity.nick, IRC.identity.username, IRC.identity.host, user.nick, msg) + print >>self.logs[user], "%s >>> :%s!%s@%s PRIVMSG %s :%s" % ( + ts, IRC.identity.nick, IRC.identity.username, IRC.identity.host, user.nick, msg) self.logs[user].flush() def onSendPrivAction(self, IRC, origin, user, action): - ### Called when bot sends an action (/me) to a user. - ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent. - self.onSendPrivMsg(IRC, origin, user, "\x01ACTION %s\x01"%action) + # Called when bot sends an action (/me) to a user. + # The variable origin refers to a class instance voluntarily + # identifying itself as that which requested data be sent. + self.onSendPrivMsg(IRC, origin, user, "\x01ACTION %s\x01" % action) def onNickChange(self, IRC, user, newnick): - ### Called when somebody changes nickname. + # Called when somebody changes nickname. ts = irc.timestamp() line = "%s <<< :%s!%s@%s NICK %s" % ( ts, user.nick, user.username, user.host, newnick) - ### Print nick change in each channel the user is in. + # Print nick change in each channel the user is in. for channel in user.channels: print >>self.logs[channel], line self.logs[channel].flush() - ### And in the query if open. + # And in the query if open. if user in self.logs.keys(): print >>self.logs[user], line self.logs[user].flush() def onMeNickChange(self, IRC, newnick): - ### Called when the bot changes nickname. + # Called when the bot changes nickname. - ### Print nick change to all open queries, except for query with self (already done with onNickChange). + # Print nick change to all open queries, except for query with self + # (already done with onNickChange). ts = irc.timestamp() - line = "%s <<< :%s!%s@%s NICK %s" % (ts, IRC.identity.nick, - IRC.identity.username, IRC.identity.host, newnick) + line = "%s <<< :%s!%s@%s NICK %s" % ( + ts, IRC.identity.nick, IRC.identity.username, IRC.identity.host, newnick) for (window, log) in self.logs.items(): if type(window) == irc.User and window != IRC.identity: print >>log, line log.flush() def onQuit(self, IRC, user, quitmsg): - ### Called when somebody quits IRC. + # Called when somebody quits IRC. ts = irc.timestamp() if quitmsg: line = "%s <<< :%s!%s@%s QUIT :%s" % ( @@ -374,20 +419,20 @@ class Logger(Thread): line = "%s <<< :%s!%s@%s QUIT" % ( ts, user.nick, user.username, user.host) - ### Print quit in each channel the user was in. + # Print quit in each channel the user was in. for channel in user.channels: if channel in self.logs.keys() and not self.logs[channel].closed: print >>self.logs[channel], line self.logs[channel].flush() - ### And in the query if open. + # And in the query if open. if user in self.logs.keys(): print >>self.logs[user], line self.logs[user].flush() self.closeLog(user) def onNames(self, IRC, origin, channel, flag, channame, nameslist): - ### Called when a NAMES list is received. + # Called when a NAMES list is received. if channel in self.logs.keys() and not self.logs[channel].closed: log = self.logs[channel] else: @@ -398,7 +443,7 @@ class Logger(Thread): private = "p" in channel.modes.keys() and channel.modes["p"] modes, symbols = channel.context.supports["PREFIX"] print >>log, "%s <<< :%s 353 %s %s %s :%s" % (ts, origin, IRC.identity.nick, flag, channame, - " ".join(["%s%s!%s@%s"%(prefix, nick, username, host) if username and host else "%s%s"%(prefix, nick) for (prefix, nick, username, host) in nameslist])) + " ".join(["%s%s!%s@%s" % (prefix, nick, username, host) if username and host else "%s%s" % (prefix, nick) for (prefix, nick, username, host) in nameslist])) log.flush() def onNamesEnd(self, IRC, origin, channel, channame, endmsg): @@ -412,51 +457,53 @@ class Logger(Thread): log.flush() def onWhoisStart(self, IRC, origin, user, nickname, username, host, realname): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) - print >>self.logs[user], "%s <<< :%s 311 %s %s %s %s * :%s" % (irc.timestamp(), origin, IRC.identity.nick, nickname, username, host, realname) + print >>self.logs[user], "%s <<< :%s 311 %s %s %s %s * :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, username, host, realname) def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) print >>self.logs[user], "%s <<< :%s 307 %s %s :%s" % ( irc.timestamp(), origin, IRC.identity.nick, nickname, msg) def onWhoisAway(self, IRC, origin, user, nickname, awaymsg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) - print >>self.logs[user], "%s <<< :%s 301 %s %s :%s" % (irc.timestamp( - ), origin, IRC.identity.nick, nickname, awaymsg) + print >>self.logs[user], "%s <<< :%s 301 %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, awaymsg) def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) print >>self.logs[user], "%s <<< :%s 378 %s %s :%s" % ( irc.timestamp(), origin, IRC.identity.nick, nickname, msg) def onWhoisChannels(self, IRC, origin, user, nickname, chanlist): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) - print >>self.logs[user], "%s <<< :%s 319 %s %s :%s" % (irc.timestamp(), - origin, IRC.identity.nick, nickname, " ".join(chanlist)) + print >>self.logs[user], "%s <<< :%s 319 %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, " ".join(chanlist)) def onWhoisAvailability(self, IRC, origin, user, nickname, msg): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) print >>self.logs[user], "%s <<< :%s 310 %s %s :%s" % ( irc.timestamp(), origin, IRC.identity.nick, nickname, msg) def onWhoisServer(self, IRC, origin, user, nickname, server, servername): - ### Called when a WHOIS reply is received. + # Called when a WHOIS reply is received. if user not in self.logs.keys(): self.openLog(user) - print >>self.logs[user], "%s <<< :%s 312 %s %s %s :%s" % (irc.timestamp(), origin, IRC.identity.nick, nickname, server, servername) + print >>self.logs[user], "%s <<< :%s 312 %s %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, server, servername) def onWhoisOp(self, IRC, origin, user, nickname, msg): if user not in self.logs.keys(): @@ -467,7 +514,8 @@ class Logger(Thread): def onWhoisTimes(self, IRC, origin, user, nickname, idletime, signontime, msg): if user not in self.logs.keys(): self.openLog(user) - print >>self.logs[user], "%s <<< :%s 317 %s %s %d %d :%s" % (irc.timestamp(), origin, IRC.identity.nick, nickname, idletime, signontime, msg) + print >>self.logs[user], "%s <<< :%s 317 %s %s %d %d :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, idletime, signontime, msg) def onWhoisSSL(self, IRC, origin, user, nickname, msg): if user not in self.logs.keys(): @@ -484,7 +532,8 @@ class Logger(Thread): def onWhoisLoggedInAs(self, IRC, origin, user, nickname, loggedinas, msg): if user not in self.logs.keys(): self.openLog(user) - print >>self.logs[user], "%s <<< :%s 330 %s %s %s :%s" % (irc.timestamp(), origin, IRC.identity.nick, nickname, loggedinas, msg) + print >>self.logs[user], "%s <<< :%s 330 %s %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, loggedinas, msg) def onWhoisEnd(self, IRC, origin, user, nickname, msg): if user not in self.logs.keys(): @@ -494,19 +543,19 @@ class Logger(Thread): self.logs[user].flush() def onWhoEntry(self, IRC, **kwargs): - ### Called when a WHO list is received. + # Called when a WHO list is received. pass def onWhoEnd(self, IRC, **kwargs): - ### Called when a WHO list is received. + # Called when a WHO list is received. pass def onList(self, IRC, chanlistbegin, chanlist, endmsg): - ### Called when a channel list is received. + # Called when a channel list is received. pass def onTopic(self, IRC, origin, channel, topic): - ### Called when channel topic is received via 332 response. + # Called when channel topic is received via 332 response. ts = irc.timestamp() if channel in self.logs.keys() and not self.logs[channel].closed: log = self.logs[channel] @@ -517,26 +566,27 @@ class Logger(Thread): log.flush() def onTopicInfo(self, IRC, origin, channel, topicsetby, topictime): - ### Called when channel topic info is received via 333 response. + # Called when channel topic info is received via 333 response. ts = irc.timestamp() if channel in self.logs.keys() and not self.logs[channel].closed: log = self.logs[channel] else: log = self.logs[IRC] - print >>log, "%s <<< :%s 333 %s %s %s %d" % (ts, origin, - IRC.identity.nick, channel.name, topicsetby, topictime) + print >>log, "%s <<< :%s 333 %s %s %s %d" % ( + ts, origin, IRC.identity.nick, channel.name, topicsetby, topictime) log.flush() def onTopicSet(self, IRC, user, channel, topic): - ### Called when channel topic is changed. + # Called when channel topic is changed. ts = irc.timestamp() - print >>self.logs[channel], "%s <<< :%s!%s@%s TOPIC %s :%s" % (ts, - user.nick, user.username, user.host, channel.name, topic) + print >>self.logs[channel], "%s <<< :%s!%s@%s TOPIC %s :%s" % ( + ts, user.nick, user.username, user.host, channel.name, topic) self.logs[channel].flush() def onChanModeSet(self, IRC, user, channel, modedelta): - ### Called when channel modes are changed. - ### modedelta is a list of tuples of the format ("+x", parameter), ("+x", None) if no parameter is provided. + # Called when channel modes are changed. + # modedelta is a list of tuples of the format ("+x", parameter), ("+x", + # None) if no parameter is provided. ts = irc.timestamp() modestr = "" params = [] @@ -546,23 +596,26 @@ class Logger(Thread): modestr += sgn sign = sgn modestr += modechar - if param is not None: + if param != None: params.append(param.nick if type(param) == irc.User else param) if len(params): if type(user) == irc.User: - print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s %s" % (ts, user.nick, user.username, user.host, channel.name, modestr, " ".join(params)) + print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s %s" % ( + ts, user.nick, user.username, user.host, channel.name, modestr, " ".join(params)) else: - print >>self.logs[channel], "%s <<< :%s MODE %s %s %s" % (ts, user, channel.name, modestr, " ".join(params)) + print >>self.logs[channel], "%s <<< :%s MODE %s %s %s" % ( + ts, user, channel.name, modestr, " ".join(params)) else: if type(user) == irc.User: - print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s" % (ts, user.nick, user.username, user.host, channel.name, modestr) + print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s" % ( + ts, user.nick, user.username, user.host, channel.name, modestr) else: print >>self.logs[channel], "%s <<< :%s MODE %s %s" % ( ts, user, channel.name, modestr) self.logs[channel].flush() def onChannelModes(self, IRC, channel, modedelta): - ### Called when channel modes are received via 324 response. + # Called when channel modes are received via 324 response. ts = irc.timestamp() if channel in self.logs.keys() and not self.logs[channel].closed: log = self.logs[channel] @@ -576,17 +629,18 @@ class Logger(Thread): modestr += sgn sign = sgn modestr += modechar - if param is not None: + if param != None: params.append(param) if len(params): - print >>log, "%s <<< :%s 324 %s %s %s %s" % (ts, IRC.serv, IRC.identity.nick, channel.name, modestr, " ".join(params)) + print >>log, "%s <<< :%s 324 %s %s %s %s" % ( + ts, IRC.serv, IRC.identity.nick, channel.name, modestr, " ".join(params)) else: - print >>log, "%s <<< :%s 324 %s %s %s" % (ts, IRC.serv, - IRC.identity.nick, channel.name, modestr) + print >>log, "%s <<< :%s 324 %s %s %s" % ( + ts, IRC.serv, IRC.identity.nick, channel.name, modestr) log.flush() def onChanCreated(self, IRC, channel, created): - ### Called when a 329 response is received. + # Called when a 329 response is received. ts = irc.timestamp() if channel in self.logs.keys() and not self.logs[channel].closed: log = self.logs[channel] |