diff options
-rw-r--r-- | autoexec.py | 55 | ||||
-rw-r--r-- | bouncer.py | 513 | ||||
-rw-r--r-- | cannon.py | 56 | ||||
-rw-r--r-- | figlet.py | 33 | ||||
-rw-r--r-- | iqueue.py | 287 | ||||
-rw-r--r-- | irc.py | 1497 | ||||
-rw-r--r-- | logger.py | 864 | ||||
-rw-r--r-- | poker.py | 218 | ||||
-rw-r--r-- | sedbot.py | 105 | ||||
-rwxr-xr-x | startirc.py | 31 |
10 files changed, 2147 insertions, 1512 deletions
diff --git a/autoexec.py b/autoexec.py index a1fa52b..978501e 100644 --- a/autoexec.py +++ b/autoexec.py @@ -1,12 +1,13 @@ #!/usr/bin/python import re +import irc class Autoexec(object): def __init__(self): self.networks = {} - def onModuleAdd(self, IRC, label, onconnect=None, onregister=None, autojoin=None, usermodes=None, wallet=None, opername=None, opermodes=None, snomasks=None, operexec=None, operjoin=None): + def onAddonAdd(self, IRC, label, onconnect=None, onregister=None, autojoin=None, usermodes=None, wallet=None, opername=None, opermodes=None, snomasks=None, operexec=None, operjoin=None): labels = [v[0] for v in self.networks.values()] if label in labels: raise BaseException("Label already exists") @@ -14,7 +15,7 @@ class Autoexec(object): raise BaseException("Network already exists") self.networks[IRC] = (label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin) - def onModuleRem(self, IRC): + def onAddonRem(self, IRC): del self.networks[IRC] def onConnect(self, IRC): @@ -36,30 +37,24 @@ class Autoexec(object): if autojoin: IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self) - def onRecv(self, IRC, line, data): - if data is None: - return + def on381(self, IRC, line, origin, target, params, extinfo): (label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin) = self.networks[IRC] - (origin, ident, host, cmd, target, params, extinfo) = data - if cmd == "381" and opermodes: - if operexec: - for line in operexec: - IRC.raw(line, origin=self) - if opermodes: - IRC.raw("MODE %s %s"%(IRC.identity.nick, - opermodes), origin=self) - if snomasks: - IRC.raw("MODE %s +s %s"%( - IRC.identity.nick, snomasks), origin=self) - if operjoin: - IRC.raw("JOIN %s"%(",".join(operjoin)), origin=self) + if operexec: + for line in operexec: + IRC.raw(line, origin=self) + if opermodes: + IRC.raw("MODE %s %s"%(IRC.identity.nick, opermodes), origin=self) + if snomasks: + IRC.raw("MODE %s +s %s"%(IRC.identity.nick, snomasks), origin=self) + if operjoin: + IRC.raw("JOIN %s"%(",".join(operjoin)), origin=self) class NickServ(object): def __init__(self): self.networks = {} - def onModuleAdd(self, IRC, label, wallet=None, autojoin=None): + def onAddonAdd(self, IRC, label, wallet=None, autojoin=None): labels = [v[0] for v in self.networks.values()] #print labels if label in labels: @@ -68,16 +63,20 @@ class NickServ(object): raise BaseException("Network already exists") self.networks[IRC] = (label, wallet, autojoin) - def onModuleRem(self, IRC): + def onAddonRem(self, IRC): del self.networks[IRC] - def onRecv(self, IRC, line, data): - if data is None: - return - (origin, ident, host, cmd, target, params, extinfo) = data + def onPrivNotice(self, IRC, origin, msg): + label, wallet, autojoin = self.networks[IRC] + if type(origin) == irc.User and origin.nick.lower() == "nickserv": + if re.match("This nickname is registered( and protected)?", msg) and wallet and "%s/NickServ/%s"%(label, IRC.identity.nick.lower()) in wallet.keys(): + origin.msg("identify %s" % wallet["%s/NickServ/%s" % + (label, IRC.identity.nick.lower())]) + if re.match("You are now identified", msg): + if autojoin: + IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self) + + def on900(self, IRC, line, origin, target, params, extinfo): label, wallet, autojoin = self.networks[IRC] - if target == IRC.identity.nick and origin == "NickServ" and re.match("This nickname is registered( and protected)?.", extinfo) and wallet and "%s/NickServ/%s"%(label, target.lower()) in wallet.keys(): - IRC.user("NickServ").msg("identify %s" % wallet[ - "%s/NickServ/%s"%(label, target.lower())]) - if cmd == "900" and autojoin: + if autojoin: IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self) @@ -8,31 +8,35 @@ import sys import string import hashlib import traceback +import irc from threading import Thread, Lock import Queue class Bouncer (Thread): - def __init__(self, addr="", port=16667, ssl=False, certfile=None, keyfile=None, ignore=None, debug=False, log=sys.stderr, timeout=300): + 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): self.__name__ = "Bouncer for pyIRC" - self.__version__ = "1.0.0rc2" + self.__version__ = "1.1" self.__author__ = "Brian Sherson" - self.__date__ = "August 26, 2013" - #print "Initializing ListenThread..." + self.__date__ = "December 1, 2013" + self.addr = addr self.port = port self.servers = {} self.passwd = {} - self.socket = s = socket.socket() + self.socket = socket.socket( + socket.AF_INET6 if ipv6 else socket.AF_INET) self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) self.ssl = ssl + self.ipv6 = ipv6 self.certfile = certfile self.keyfile = keyfile - s.bind((self.addr, self.port)) + self.socket.bind((self.addr, self.port)) self.connections = [] self.ignore = ignore self.debug = debug self.timeout = timeout + self.autoaway = autoaway ### 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. @@ -71,102 +75,13 @@ class Bouncer (Thread): connection.settimeout(self.timeout) bouncer = BouncerConnection( self, connection, addr, debug=self.debug) - #bouncer.daemon=True - #self.connections.append(bouncer) - #bouncer.start() - #ccrecv.start() time.sleep(0.5) try: self.socket.close() except: pass - def onRecv(self, IRC, line, data): - if type(self.ignore) not in (list, tuple) or all([not re.match(pattern, line) for pattern in self.ignore]): - if data: - (origin, ident, host, cmd, target, params, extinfo) = data - #print data - if re.match("^\\d+$", cmd): - cmd = int(cmd) # Code is a numerical response - if cmd in (352, 315, 304) or (cmd == 461 and params.upper() == "WHO"): # WHO reply - if len(self.whoexpected[IRC]) and self.whoexpected[IRC][0] in self.connections: - self.whoexpected[IRC][0].send(line+"\n") - if cmd == 315 or (cmd == 461 and params.upper() == "WHO"): # End of WHO reply - origin = self.whoexpected[IRC][0] - del self.whoexpected[IRC][0] - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - if self.debug: - with IRC.loglock: - if issubclass(type(origin), Thread): - name = origin.name - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onRecv] Removing %(origin)s (%(name)s) from WHO expected list." % vars() - else: - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onRecv] Removing %(origin)s from 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.onRecv] WHO expected: %(obj)s (%(name)s)" % vars() - else: - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onRecv] WHO expected: %(obj)s" % vars() - IRC.log.flush() - elif cmd in (307, 311, 312, 313, 317, 318, 319, 330, 335, 336, 378, 379): # WHO reply - if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections: - self.whoisexpected[IRC][0].send(line+"\n") - if cmd == 318: # End of WHOIS reply - del self.whoisexpected[IRC][0] - elif cmd in (321, 322, 323): # LIST reply - if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections: - self.listexpected[IRC][0].send(line+"\n") - if cmd == 323: # End of LIST reply - del self.listexpected[IRC][0] - else: - for bouncer in self.connections: - #print bouncer.IRC - #print IRC - #print line - if bouncer.IRC == IRC: - bouncer.send(line+"\n") - - def onSend(self, IRC, line, data, origin): - if type(self.ignore) not in (list, tuple) or all([not re.match(pattern, line) for pattern in self.ignore]): - (cmd, target, params, extinfo) = data - if cmd.upper() in ("PRIVMSG", "NOTICE"): - for bouncerconnection in self.connections: - if bouncerconnection == origin: # Do NOT send the message back to the originating client. - continue - if bouncerconnection.IRC == IRC: # Send the message to the other clients connected to the bouncer. - ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", - extinfo) - if ctcp: - (ctcptype, ext) = ctcp[0] - if ctcptype == "ACTION": - bouncerconnection.send(":%s!%s@%s %s\n" % (bouncerconnection.IRC.identity.nick, bouncerconnection.IRC.identity.idnt, bouncerconnection.IRC.identity.host, line)) - ### Unless the message is a CTCP that is not ACTION. - else: - bouncerconnection.send(":%s!%s@%s %s\n" % (bouncerconnection.IRC.identity.nick, bouncerconnection.IRC.identity.idnt, bouncerconnection.IRC.identity.host, line)) - elif cmd.upper() == "WHO": - self.whoexpected[IRC].append(origin) - if self.debug: - with IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - 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() - elif cmd.upper() == "WHOIS": - self.whoisexpected[IRC].append(origin) - elif cmd.upper() == "LIST": - self.listexpected[IRC].append(origin) - - def onModuleAdd(self, IRC, label, passwd, hashtype="md5"): + def onAddonAdd(self, IRC, label, passwd, hashtype="md5"): if IRC in [connection for (connection, passwd, hashtype) in self.servers.values()]: return # Silently do nothing if label in self.servers.keys(): @@ -174,15 +89,11 @@ class Bouncer (Thread): self.servers[label] = (IRC, passwd, hashtype) self.whoexpected[IRC] = [] if self.debug: - with IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onModuleAdd] Clearing WHO expected list." % vars() - IRC.log.flush() + IRC.logwrite("dbg [Bouncer.onAddonAdd] Clearing WHO expected list." % vars()) self.whoisexpected[IRC] = [] self.listexpected[IRC] = [] - def onModuleRem(self, IRC): + def onAddonRem(self, IRC): for bouncerconnection in self.connections: if bouncerconnection.IRC == IRC: bouncerconnection.quit(quitmsg="Bouncer extension removed") @@ -197,32 +108,263 @@ class Bouncer (Thread): for bouncerconnection in self.connections: bouncerconnection.stop(quitmsg=quitmsg) - def onDisconnect(self, IRC): + def onDisconnect(self, IRC, expected=False): self.whoexpected[IRC] = [] if self.debug: - with IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - print >>IRC.log, "%(timestamp)s dbg [Bouncer.onDisconnect] Clearing WHO expected list." % vars() - IRC.log.flush() + 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") + 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. + 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)) + + def onSendChanAction(self, IRC, origin, channel, targetprefix, 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. + 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)) + + 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() + 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. + 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 + + def onWhoEnd(self, IRC, origin, param, endmsg): + ### 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] + + def onListStart(self, IRC, origin, params, extinfo): + ### 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 + + def onListEntry(self, IRC, origin, channel, population, extinfo): + ### 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 + + def onListEnd(self, IRC, origin, endmsg): + ### 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] + + def onWhoisStart(self, IRC, origin, user, nickname, username, host, realname): + ### 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 + + def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg): + ### 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 + + def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg): + ### 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 + + def onWhoisChannels(self, IRC, origin, user, nickname, chanlist): + ### 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 + + def onWhoisAvailability(self, IRC, origin, user, nickname, msg): + ### 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 + + def onWhoisServer(self, IRC, origin, user, nickname, server, servername): + ### 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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] + + def onUnhandled(self, IRC, line, origin, cmd, target, params, extinfo): + for bouncerconnection in self.connections: + if bouncerconnection.IRC == IRC: + bouncerconnection.send("%s\n"%line) + class BouncerConnection (Thread): def __init__(self, bouncer, connection, addr, debug=False): #print "Initializing ListenThread..." self.bouncer = bouncer self.connection = connection - self.host, self.port = self.addr = addr + if len(addr) == 4: + self.host, self.port = self.addr = addr[:2] + self.ipv6 = True + else: + self.host, self.port = self.addr = addr + self.ipv6 = False self.IRC = None self.pwd = None self.nick = None self.label = None - self.idnt = None + self.username = None self.realname = None self.addr = addr self.debug = debug @@ -237,16 +379,10 @@ class BouncerConnection (Thread): def send(self, data, flags=0): try: with self.lock: - self.connection.send(data, flags=flags) + self.connection.send(data) except socket.error: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - exc, excmsg, tb = sys.exc_info() - print >>self.IRC.log, "%(timestamp)s !!! [BouncerConnection.send] Exception in module %(module)s" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>self.IRC.log, "%(timestamp)s !!! [BouncerConnection.send] %(tbline)s" % vars() - self.IRC.log.flush() + 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")]) self.quit(quitmsg=excmsg.message) def __repr__(self): @@ -254,7 +390,7 @@ class BouncerConnection (Thread): port = self.IRC.port if self.IRC else "*" if self.IRC and self.IRC.identity: nick = self.IRC.identity.nick - ident = self.IRC.identity.idnt if self.IRC.identity.idnt else "*" + ident = self.IRC.identity.username if self.IRC.identity.username else "*" host = self.IRC.identity.host if self.IRC.identity.host else "*" else: nick = "*" @@ -282,7 +418,12 @@ class BouncerConnection (Thread): def run(self): ### Add connection to connection list. - listnumerics = dict(b=(367, 368, "channel ban list"), e=(348, 349, "Channel Exception List"), I=(346, 347, "Channel Invite Exception List"), w=(910, 911, "Channel Access List"), g=(941, 940, "chanel spamfilter list"), X=(954, 953, "channel exemptchanops list")) + listnumerics = dict(b=(367, 368, "channel ban list"), + e=(348, 349, "Channel Exception List"), + I=(346, 347, "Channel Invite Exception List"), + w=(910, 911, "Channel Access List"), + g=(941, 940, "chanel spamfilter list"), + X=(954, 953, "channel exemptchanops list")) passwd = None nick = None @@ -296,7 +437,7 @@ class BouncerConnection (Thread): while True: ### Read data (appending) into readbuf, then break lines and append lines to linebuf while len(linebuf) == 0: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) + timestamp = irc.timestamp() read = self.connection.recv(512) if read == "" and len(linebuf) == 0: # No more data to process. #self.quitmsg="Connection Closed" @@ -330,38 +471,31 @@ class BouncerConnection (Thread): self.quit("Access Denied") break - elif not self.idnt: # Bouncer expects a USER command to finish registration + elif not self.username: # Bouncer expects a USER command to finish registration if cmd.upper() == "USER": - self.idnt = target - #print self.idnt - if self.idnt in self.bouncer.servers.keys(): - self.IRC, passwdhash, hashtype = self.bouncer.servers[self.idnt] + self.username = target + #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 with self.IRC.lock: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%s *** [BouncerConnection] Incoming connection from %s to %s." % (timestamp, self.host, self.IRC) - self.IRC.log.flush() + 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: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%(timestamp)s dbg [BouncerConnection] Attempting to broadcast incoming connection %(self)s." % vars() - self.IRC.log.flush() + self.IRC.logwrite("dbg [BouncerConnection] Attempting to broadcast incoming connection %(self)s." % vars()) for bouncerconnection in self.bouncer.connections: + if bouncerconnection.IRC != self.IRC: + continue if self.debug: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%(timestamp)s dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars() - self.IRC.log.flush() + 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: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%(timestamp)s dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars() - self.IRC.log.flush() + 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. + 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): @@ -371,9 +505,9 @@ class BouncerConnection (Thread): ### 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.idnt, n) in labels: + while "*%s_%d"%(self.username, n) in labels: n += 1 - self.label = "*%s_%d"%(self.idnt, n) + self.label = "*%s_%d"%(self.username, n) ### Request Version info. #self.connection.send(":$bouncer PRIVMSG %s :\x01VERSION\x01\n" % (self.IRC.identity.nick)) @@ -384,8 +518,8 @@ class BouncerConnection (Thread): 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.servinfo)) - self.connection.send(":%s 004 %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.serv004)) + 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()] @@ -405,10 +539,16 @@ class BouncerConnection (Thread): self.connection.send(":%s 005 %s %s :are supported by this server\n" % (self.IRC.serv, self.IRC.identity.nick, support)) ### Send MOTD - 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)) - self.connection.send(":%s 376 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdend)) + 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)) @@ -416,7 +556,7 @@ class BouncerConnection (Thread): 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.idnt, self.IRC.identity.host)) + # 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)) @@ -435,7 +575,7 @@ class BouncerConnection (Thread): ### 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.idnt, self.IRC.identity.host, channel.name)) + 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)) @@ -469,33 +609,18 @@ class BouncerConnection (Thread): break elif cmd.upper() == "PING": - with self.lock: - self.connection.send(":%s PONG %s :%s\n" % (self.IRC.serv, self.IRC.serv, self.IRC.identity.nick)) - - elif cmd.upper() == "WHO" and target.lower() == "$bouncer": - with self.lock: - for bouncerconnection in self.bouncer.connections: - self.connection.send(":$bouncer 352 %s $bouncer %s %s $bouncer %s H@ :0 %s\n" % (self.IRC.identity.nick, bouncerconnection.idnt, bouncerconnection.host, bouncerconnection.label, bouncerconnection.IRC)) - self.connection.send(":$bouncer 315 %s $bouncer :End if /WHO list.\n" % (self.IRC.identity.nick)) + 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 target.lower() == "$bouncer": # Message to internal bouncer control channel - if ctcp and cmd.upper() == "NOTICE": - (ctcptype, ext) = ctcp[0] # Unpack CTCP info - if ctcptype == "VERSION": # Client is sending back version reply - reply = ":%s!%s@%s PRIVMSG $bouncer :Version reply: %s\n" % (self.label, self.idnt, self.addr[0], ext) - for bouncerconnection in self.bouncer.connections: - bouncerconnection.connection.send(reply) - elif ctcp: # If CTCP, only want to + 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. - with self.lock: - self.connection.send(":%s!%s@%s %s\n" % (self.IRC.identity.nick, self.IRC.identity.idnt, self.IRC.identity.host, line)) + 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: @@ -509,8 +634,10 @@ class BouncerConnection (Thread): 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: - self.connection.send(":%s 324 %s %s +%s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, modestr, params)) - self.connection.send(":%s 329 %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.created)) + 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) @@ -543,12 +670,8 @@ class BouncerConnection (Thread): exc, excmsg, tb = sys.exc_info() self.quitmsg = str(excmsg) if self.IRC: - with self.IRC.loglock: - exc, excmsg, tb = sys.exc_info() - print >>self.IRC.log, "%(timestamp)s !!! [BouncerConnection] Exception in module %(self)s" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>self.IRC.log, "%(timestamp)s !!! [BouncerConnection] %(tbline)s" % vars() - self.IRC.log.flush() + 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")]) finally: ### Juuuuuuust in case. with self.lock: @@ -559,36 +682,28 @@ class BouncerConnection (Thread): pass if self.IRC: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%s *** [BouncerConnection] Connection from %s terminated (%s)." % (timestamp, self.host, self.quitmsg) - self.IRC.log.flush() + 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.debug: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%(timestamp)s dbg [BouncerConnection] Attempting to broadcast terminated connection %(self)s." % vars() - self.IRC.log.flush() + self.IRC.logwrite("dbg [BouncerConnection] Attempting to broadcast terminated connection %(self)s." % vars()) for bouncerconnection in self.bouncer.connections: - if self.debug: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%(timestamp)s dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars() - self.IRC.log.flush() - 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)) + if bouncerconnection.IRC == self.IRC: if self.debug: - with self.IRC.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.IRC.log, "%(timestamp)s dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars() - self.IRC.log.flush() + 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)) + 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.idnt, self.host, self.quitmsg)) +# bouncerconnection.connection.send(":%s!%s@%s QUIT :%s\n" % (self.label, self.username, self.host, self.quitmsg)) # except: # pass @@ -8,33 +8,31 @@ class Cannon(object): def __init__(self): self.firecount = {} - def onRecv(self, IRC, line, data): - if data is None: - return - (origin, ident, host, cmd, target, params, extinfo) = data - if len(target) and target[0] == "#" and cmd == "PRIVMSG": - channel = IRC.channel(target) - matches = re.findall("^!fire\\s+(.*)$", extinfo) - if matches: - nickname = matches[0] - if any([nickname.lower() == user.nick.lower() for user in channel.users]): - user = IRC.user(nickname) - if user in self.firecount.keys(): - count = self.firecount[user]+1 - else: - count = 1 - self.firecount[user] = count - if 10 <= count%100 < 20: - ordinal = "th" - elif count%10 == 1: - ordinal = "st" - elif count%10 == 2: - ordinal = "nd" - elif count%10 == 3: - ordinal = "rd" - else: - ordinal = "th" - channel.me("fires %s out of a cannon for the %d%s time." % - (user.nick, count, ordinal)) + def onChanMsg(self, IRC, user, channel, targetprefix, msg): + matches = re.findall("^!fire\\s+(.*)$", msg) + if matches: + nickname = matches[0] + if any([nickname.lower() == usr.nick.lower() for usr in channel.users]): + vic = IRC.user(nickname) + if vic in self.firecount.keys(): + count = self.firecount[vic]+1 else: - channel.msg("%s: I cannot fire %s out of a cannon, as he or she is not here."%(origin, nickname)) + count = 1 + self.firecount[vic] = count + if 10 <= count%100 < 20: + ordinal = "th" + elif count%10 == 1: + ordinal = "st" + elif count%10 == 2: + ordinal = "nd" + elif count%10 == 3: + ordinal = "rd" + else: + ordinal = "th" + channel.me("fires %s out of a cannon for the %d%s time." % + (vic.nick, count, ordinal)) + else: + channel.msg("%s: I cannot fire %s out of a cannon, as he or she is not here."%(user.nick, nickname)) + + def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg): + self.onChanMsg(IRC, IRC.identity, channel, targetprefix, msg) @@ -4,22 +4,17 @@ import os class Figlet(object): - def onRecv(self, IRC, line, data): - if data is None: - return - (origin, ident, host, cmd, target, params, extinfo) = data - if len(target) and target[0] == "#" and cmd == "PRIVMSG": - channel = IRC.channel(target) - matches = re.findall("^!figlet\\s+(.*)$", extinfo) - if matches: - gif, fig = os.popen2("figlet") - gif.write(matches[0]) - gif.close() - while True: - line = fig.readline() - if line == "": - break - if re.match("^\\s+$", line.rstrip()): - continue - channel.msg(line.rstrip()) - fig.close() + def onChanMsg(self, IRC, user, channel, targetprefix, msg): + matches = re.findall("^!figlet\\s+(.*)$", msg) + if matches: + gif, fig = os.popen2("figlet") + gif.write(matches[0]) + gif.close() + while True: + line = fig.readline() + if line == "": + break + if re.match("^\\s+$", line.rstrip()): + continue + channel.msg(line.rstrip()) + fig.close() diff --git a/iqueue.py b/iqueue.py new file mode 100644 index 0000000..4ad2cab --- /dev/null +++ b/iqueue.py @@ -0,0 +1,287 @@ +'''A multi-producer, multi-consumer queue.''' + +try: + import threading +except ImportError: + import dummy_threading as threading +from collections import deque +from heapq import heappush, heappop +try: + from time import monotonic as time +except ImportError: + from time import time + +__all__ = ['Empty', 'Full', 'Queue', 'PriorityQueue', 'LifoQueue'] + + +class Empty(Exception): + 'Exception raised by Queue.get(block=0)/get_nowait().' + pass + + +class Full(Exception): + 'Exception raised by Queue.put(block=0)/put_nowait().' + pass + + +class Interrupted(Exception): + 'Exception raised by Queue.put and Queue.get when Queue is interrupted.' + pass + + +class Queue: + '''Create a queue object with a given maximum size. + + If maxsize is <= 0, the queue size is infinite. + ''' + + def __init__(self, maxsize=0): + self.maxsize = maxsize + self._init(maxsize) + + # mutex must be held whenever the queue is mutating. All methods + # that acquire mutex must release it before returning. mutex + # is shared between the three conditions, so acquiring and + # releasing the conditions also acquires and releases mutex. + self.mutex = threading.Lock() + + # Notify not_empty whenever an item is added to the queue; a + # thread waiting to get is notified then. + self.not_empty = threading.Condition(self.mutex) + + # Notify not_full whenever an item is removed from the queue; + # a thread waiting to put is notified then. + self.not_full = threading.Condition(self.mutex) + + # Notify all_tasks_done whenever the number of unfinished tasks + # drops to zero; thread waiting to join() is notified to resume + self.all_tasks_done = threading.Condition(self.mutex) + self.unfinished_tasks = 0 + self._interrupted = False + + def task_done(self): + '''Indicate that a formerly enqueued task is complete. + + Used by Queue consumer threads. For each get() used to fetch a task, + a subsequent call to task_done() tells the queue that the processing + on the task is complete. + + If a join() is currently blocking, it will resume when all items + have been processed (meaning that a task_done() call was received + for every item that had been put() into the queue). + + Raises a ValueError if called more times than there were items + placed in the queue. + ''' + with self.all_tasks_done: + unfinished = self.unfinished_tasks - 1 + if unfinished <= 0: + if unfinished < 0: + raise ValueError('task_done() called too many times') + self.all_tasks_done.notify_all() + self.unfinished_tasks = unfinished + + def join(self): + '''Blocks until all items in the Queue have been gotten and processed. + + The count of unfinished tasks goes up whenever an item is added to the + queue. The count goes down whenever a consumer thread calls task_done() + to indicate the item was retrieved and all work on it is complete. + + When the count of unfinished tasks drops to zero, join() unblocks. + ''' + with self.all_tasks_done: + while self.unfinished_tasks: + self.all_tasks_done.wait() + + def qsize(self): + '''Return the approximate size of the queue (not reliable!).''' + with self.mutex: + return self._qsize() + + def empty(self): + '''Return True if the queue is empty, False otherwise (not reliable!). + + This method is likely to be removed at some point. Use qsize() == 0 + as a direct substitute, but be aware that either approach risks a race + condition where a queue can grow before the result of empty() or + qsize() can be used. + + To create code that needs to wait for all queued tasks to be + completed, the preferred technique is to use the join() method. + ''' + with self.mutex: + return not self._qsize() + + def full(self): + '''Return True if the queue is full, False otherwise (not reliable!). + + This method is likely to be removed at some point. Use qsize() >= n + as a direct substitute, but be aware that either approach risks a race + condition where a queue can shrink before the result of full() or + qsize() can be used. + ''' + with self.mutex: + return 0 < self.maxsize <= self._qsize() + + def put(self, item, block=True, timeout=None): + '''Put an item into the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until a free slot is available. If 'timeout' is + a positive number, it blocks at most 'timeout' seconds and raises + the Full exception if no free slot was available within that time. + Otherwise ('block' is false), put an item on the queue if a free slot + is immediately available, else raise the Full exception ('timeout' + is ignored in that case). + ''' + with self.not_full: + ### Check if queue is interrupted + if self._interrupted: + raise Interrupted + if self.maxsize > 0: + if not block: + if self._qsize() >= self.maxsize: + raise Full + elif timeout is None: + while self._qsize() >= self.maxsize: + self.not_full.wait() + if self._interrupted: + raise Interrupted + elif timeout < 0: + raise ValueError("'timeout' must be a positive number") + else: + endtime = time() + timeout + while self._qsize() >= self.maxsize: + remaining = endtime - time() + if remaining <= 0.0: + raise Full + self.not_full.wait(remaining) + if self._interrupted: + raise Interrupted + self._put(item) + self.unfinished_tasks += 1 + self.not_empty.notify() + + def get(self, block=True, timeout=None): + '''Remove and return an item from the queue. + + If optional args 'block' is true and 'timeout' is None (the default), + block if necessary until an item is available. If 'timeout' is + a positive number, it blocks at most 'timeout' seconds and raises + the Empty exception if no item was available within that time. + Otherwise ('block' is false), return an item if one is immediately + available, else raise the Empty exception ('timeout' is ignored + in that case). + ''' + + with self.not_empty: + ### Check if queue is interrupted + if self._interrupted: + raise Interrupted + if not block: + if not self._qsize(): + raise Empty + elif timeout is None: + while not self._qsize(): + self.not_empty.wait() + if self._interrupted: + raise Interrupted + elif timeout < 0: + raise ValueError("'timeout' must be a positive number") + else: + endtime = time() + timeout + while not self._qsize(): + remaining = endtime - time() + if remaining <= 0.0: + raise Empty + self.not_empty.wait(remaining) + if self._interrupted: + raise Interrupted + item = self._get() + self.not_full.notify() + return item + + def interrupt(self): + '''Place Queue into interrupted state. Empties the queue and causes + all threads to raise the Interrupted exception when Queue.get and + Queue.put are called. + ''' + with self.not_empty: + self._interrupted = True + self.not_empty.notifyAll() + + with self.not_full: + while self._qsize(): + self._get() + self.not_full.notifyAll() + + def put_nowait(self, item): + '''Put an item into the queue without blocking. + + Only enqueue the item if a free slot is immediately available. + Otherwise raise the Full exception. + ''' + return self.put(item, block=False) + + def get_nowait(self): + '''Remove and return an item from the queue without blocking. + + Only get an item if one is immediately available. Otherwise + raise the Empty exception. + ''' + return self.get(block=False) + + # Override these methods to implement other queue organizations + # (e.g. stack or priority queue). + # These will only be called with appropriate locks held + + # Initialize the queue representation + def _init(self, maxsize): + self.queue = deque() + + def _qsize(self): + return len(self.queue) + + # Put a new item in the queue + def _put(self, item): + self.queue.append(item) + + # Get an item from the queue + def _get(self): + return self.queue.popleft() + + +class PriorityQueue(Queue): + '''Variant of Queue that retrieves open entries in priority order (lowest first). + + Entries are typically tuples of the form: (priority number, data). + ''' + + def _init(self, maxsize): + self.queue = [] + + def _qsize(self): + return len(self.queue) + + def _put(self, item): + heappush(self.queue, item) + + def _get(self): + return heappop(self.queue) + + +class LifoQueue(Queue): + '''Variant of Queue that retrieves most recently added entries first.''' + + def _init(self, maxsize): + self.queue = [] + + def _qsize(self): + return len(self.queue) + + def _put(self, item): + self.queue.append(item) + + def _get(self): + return self.queue.pop() @@ -8,16 +8,26 @@ import socket import os import platform import traceback -import Queue import ssl +import glob +import iqueue as Queue + + +def timestamp(): + t = time.time() + 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)) class Connection(Thread): - def __init__(self, server, nick="ircbot", ident="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): + 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.0.0rc2" + self.__version__ = "1.1" self.__author__ = "Brian Sherson" - self.__date__ = "August 26, 2013" + self.__date__ = "December 1, 2013" if port is None: self.port = 6667 if not ssl else 6697 @@ -30,7 +40,7 @@ class Connection(Thread): self.nick = nick self.realname = realname - self.idnt = ident + self.username = username self.passwd = passwd self.server = server self.ssl = ssl @@ -48,12 +58,13 @@ class Connection(Thread): self.quitexpected = False self.log = log - self.modules = [] + self.addons = [] self.trusted = [] ### Initialize IRC environment variables - self.motdgreet = "" - self.motd = [] + self.motdgreet = None + self.motd = None + self.motdend = None self.identity = None self.users = [] self.channels = [] @@ -66,83 +77,104 @@ class Connection(Thread): Thread.__init__(self) + def logwrite(self, *lines): + with self.loglock: + ts = timestamp() + for line in lines: + print >>self.log, "%s %s"%(ts, line) + self.log.flush() + + def logopen(self, filename): + 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): + 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): - #print method, modlist timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - for module in modlist: - #print module, dir(module) - #if modlist is not self.modules and module in self.modules: continue - if method in dir(module) and callable(getattr(module, method)): + handled = [] + unhandled = [] + errors = [] + for k, addon in enumerate(modlist): + if modlist.index(addon) < k: + continue + if method in dir(addon) and callable(getattr(addon, method)): try: - getattr(module, method)(self, **params) + getattr(addon, method)(self, **params) except: - with self.loglock: - exc, excmsg, tb = sys.exc_info() - print >>self.log, "%(timestamp)s !!! Exception in module %(module)s" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>self.log, "%(timestamp)s !!! %(tbline)s" % vars() - self.log.flush() + 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 >>sys.stderr, "Exception in addon %(addon)s" % vars() + print >>sys.stderr, traceback.format_exc() if exceptions: # If set to true, we raise the exception. raise + else: + handled.append(addon) + else: + unhandled.append(addon) + return (handled, unhandled, errors) - def addModule(self, module, trusted=False, **params): - if module in self.modules: - raise BaseException("Module already added.") + def addAddon(self, addon, trusted=False, **params): + if addon in self.addons: + raise BaseException("Addon already added.") with self.lock: - self.event("onModuleAdd", [module], exceptions=True, **params) - self.modules.append(module) + self.event("onAddonAdd", [addon], exceptions=True, **params) + self.addons.append(addon) if trusted: - self.trusted.append(module) + self.trusted.append(addon) - def insertModule(self, index, module, trusted=False, **params): - if module in self.modules: - raise BaseException("Module already added.") + def insertAddon(self, index, addon, trusted=False, **params): + if addon in self.addons: + raise BaseException("Addon already added.") with self.lock: - self.event("onModuleAdd", [module], exceptions=True, **params) - self.modules.insert(index, module) + self.event("onAddonAdd", [addon], exceptions=True, **params) + self.addons.insert(index, addon) if trusted: - self.trusted.append(module) + self.trusted.append(addon) - def rmModule(self, module, **params): + def rmAddon(self, addon, **params): with self.lock: - self.modules.remove(module) - self.event("onModuleRem", [module], exceptions=True, **params) - if module in self.trusted: - self.trusted.remove(module) + self.addons.remove(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 outgoingthread = None + whoisstarted = False + nameslist = [] + wholist = [] + lists = {} + nameschan = None server = self.server if self.ipv6 and ":" in server: server = "[%s]"%server port = self.port try: - modules = list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: - modules.append(module) with self.lock: - self.event("onLogOpen", modules) + self.event("onSessionOpen", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s ### Log session started" % vars() - self.log.flush() + self.logwrite("### Log session started") 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 - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Attempting connection to %(server)s:%(port)s." % vars() - self.log.flush() + self.logwrite("*** Attempting connection to %(server)s:%(port)s." % vars()) with self.lock: - self.event("onConnectAttempt", self.modules) + self.event("onConnectAttempt", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], [])) try: if self.ssl: @@ -153,31 +185,22 @@ class Connection(Thread): 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: - with self.loglock: - exc, excmsg, tb = sys.exc_info() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars() - self.log.flush() + 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()) else: self.connected = True self.connection.settimeout(self.timeout) ### Setting up thread responsible for sending data back to IRC server. + self.outgoing._interrupted = False outgoingthread = Outgoing(self) outgoingthread.daemon = True outgoingthread.start() - ### Run onConnect on all modules to signal connection was established. - modules = list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: - modules.append(module) - self.event("onConnect", modules) - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s established." % vars() - self.log.flush() + ### 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 if self.quitexpected: @@ -188,11 +211,8 @@ class Connection(Thread): sys.exit() attempt += 1 else: - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars() - self.log.flush() - break + self.logwrite("*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars()) + sys.exit() ### Connection succeeded try: @@ -202,7 +222,8 @@ class Connection(Thread): if self.passwd: self.raw("PASS :%(passwd)s" % vars(self)) self.raw("NICK :%(nick)s" % vars()) - self.raw("USER %(idnt)s * * :%(realname)s" % vars(self)) + self.raw("USER %(username)s * * :%(realname)s" % + vars(self)) ### Initialize buffers linebuf = [] @@ -233,17 +254,16 @@ class Connection(Thread): self.connection.send("PONG :%s\n" % ping[0]) continue - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >> self.log, "%(timestamp)s <<< %(line)s" % vars() - self.log.flush() + self.logwrite("<<< %(line)s" % vars()) ### Attempts to match against pattern ":src cmd target params :extinfo" - matches = re.findall("^:(.+?)(?:!(.+?)@(.+?))?\\s+(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line) + matches = re.findall(r"^:(.+?)(?:!(.+?)@(.+?))?\s+(.+?)(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$", line) ### We have a match! if len(matches): - parsed = (origin, ident, host, cmd, target, params, extinfo) = matches[0] + 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: @@ -255,13 +275,7 @@ class Connection(Thread): self.registered = True self.identity = self.user(target) self.serv = origin - - modules = list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: - modules.append(module) - self.event("onRegistered", modules) + 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 @@ -273,18 +287,49 @@ class Connection(Thread): 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("([%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) + + data = dict(origin=origin, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo) + ### Major codeblock here! Track IRC state. - ### Send line to modules first + ### Send line to addons first with self.lock: - self.event("onRecv", self.modules, line=line, data=parsed) + 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: - self.servinfo = extinfo # Server Created + (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: - self.serv004 = params # What is this code? + (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: @@ -295,173 +340,239 @@ class Connection(Thread): 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: # Channel Modes - self.identity.snomask = params.lstrip("+") + 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: # Channel Modes - self.identity.modes = (params if params else extinfo).lstrip("+") + 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: + elif cmd == 251: # Net Stats + (handled, unhandled, exceptions) = self.event("onNetStats", self.addons, origin=origin, netstats=extinfo) self.netstats = extinfo elif cmd == 252: - self.opcount = int(params) + opcount = int(params) + (handled, unhandled, exceptions) = self.event("onOpCount", self.addons, origin=origin, opcount=opcount) + self.opcount = opcount elif cmd == 254: - self.chancount = int(params) - elif cmd == 311: # WHOIS data - pass + 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 == 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 - self.chanlistbegin = (params, extinfo) - self.chanlist = {} + (handled, unhandled, exceptions) = self.event("onListStart", self.addons, origin=origin, params=params, extinfo=extinfo) elif cmd == 322: # LIST item (chan, pop) = params.split(" ", 1) - self.chanlist[chan] = (pop, extinfo) - elif cmd == 323: - self.chanlistend = extinfo # End of LIST + (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.modules, line=line, data=parsed) + 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.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name + 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.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name + 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.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name + 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 = dt - elif cmd == 346: # Invite - (channame, invite, nick, invtime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - if "I" in channel.modes: - if invite.lower() not in [m.lower() for (m, b, t) in channel.modes["I"]]: - channel.modes["I"].append((invite, nick, int(invtime))) - else: - channel.modes["I"] = [(invite, nick, int(invtime))] - elif cmd == 348: # Ban Exception - (channame, exception, nick, exctime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - if "e" in channel.modes: - if exception.lower() not in [m.lower() for (m, b, t) in channel.modes["e"]]: - channel.modes["e"].append((exception, nick, int(exctime))) - else: - channel.modes["e"] = [(exception, nick, int(exctime))] + channel.topictime = int(dt) + elif cmd == 352: # WHO reply - (channame, ident, host, serv, nick, flags) = params.split() + (channame, username, host, serv, nick, flags) = params.split() try: (hops, realname) = extinfo.split(" ", 1) except ValueError: hops = extinfo realname = None - channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name + + if channame[0] in self.supports.get("CHANTYPES", "#"): + channel = self.channel(channame) + else: + channel = None + user = self.user(nick) - if user.nick != nick: - user.nick = 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.idnt = ident + user.username = username user.host = host user.server = serv user.away = "G" in flags user.ircop = "*" in flags - 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] + 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 - (devnull, channame) = params.split() + (flag, channame) = params.split() channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name + self.event("onRecv", channel.addons, line=line, **data) + if "PREFIX" in self.supports: - names = re.findall("(["+re.escape(self.supports["PREFIX"][1])+"]*)(\\S+)", extinfo) + names = re.findall(r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?"%re.escape(self.supports["PREFIX"][1]), extinfo) else: - names = [("", name) for name in extinfo.split()] # Still put it into tuple form for compatibility in the next structure - for (symbs, nick) in names: + 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 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 == 367: # Channel Ban - (channame, ban, nick, bantime) = params.split() - channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - if "b" in channel.modes.keys(): - if ban.lower() not in [m.lower() for (m, b, t) in channel.modes["b"]]: - channel.modes["b"].append((ban, nick, int(bantime))) - else: - channel.modes["b"] = [(ban, nick, int(bantime))] - elif cmd == 372: - self.motd.append(extinfo) # MOTD item + 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 Admin (Unreal) - (channame, admin) = params.split() + + 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.modules, line=line, data=parsed) + 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) @@ -472,10 +583,11 @@ class Connection(Thread): 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.modules, line=line, data=parsed) + 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) @@ -486,220 +598,189 @@ class Connection(Thread): channel.modes["a"].append(user) else: channel.modes["a"] = [user] + elif cmd == "NICK": - user = self.user(origin) - modules = [] - for channel in user.channels: - for module in channel.modules: - if module not in modules: - modules.append(module) - self.event(modules, line, parsed) newnick = extinfo if len(extinfo) else target - #print user, newnick - ### Need to check if newnick is still listed + + 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: - #print u - #print [u.nick, newnick, u.nick.lower(), newnick.lower()] if u.nick.lower() == newnick.lower(): - with self.loglock: - print >>self.log, "%s *** Orphaned user %s!%s@%s was removed when %s!%s@%s changed his/her nick to %s."%(timestamp, u.nick, u.idnt, u.host, user.nick, user.idnt, user.host, newnick) - self.log.flush() - self.users.remove(u) + 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) - user.nick = newnick - elif cmd == "JOIN": - channame = target if len(target) else extinfo - user = self.user(origin) - if user.nick != origin: - user.nick = origin - if user.idnt != ident: - user.idnt = ident - if user.host != host: - user.host = host + origin.nick = newnick - channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name + 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 user == self.identity: # This means the bot is entering the room, + 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 channel.topic = "" channel.topicmod = "" channel.modes = {} channel.users = [] - self.raw("MODE %(channame)s" % vars()) - self.raw("WHO %(channame)s" % vars()) - self.raw("MODE %s :%s" % (channame, self.supports["CHANMODES"][0])) - if channel not in user.channels: - user.channels.append(channel) - if user not in channel.users: - channel.users.append(user) - elif cmd == "KICK": - kicker = self.user(origin) - if kicker.nick != origin: - kicker.nick = origin - if kicker.idnt != ident: - kicker.idnt = ident - if kicker.host != host: - kicker.host = host + 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 - channel = self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != target: - channel.name = target # Server seems to have changed the idea of the case of the channel name - if channel in kicked.channels: - kicked.channels.remove(channel) - if kicked in channel.users: - channel.users.remove(kicked) + 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 channel.modes and kicked in channel.modes[mode]: - channel.modes[mode].remove(kicked) - #if not len(chanobj.users): #Let's remove this channel - # del self.channels[target.lower()] - if all([kicked not in c.users for c in self.identity.channels]): - with self.loglock: - print >>self.log, "%s *** User %s!%s@%s was orphaned when being kicked from %s."%(timestamp, kicked.nick, kicked.idnt, kicked.host, channel.name) - self.log.flush() + if mode in target.modes and kicked in target.modes[mode]: + target.modes[mode].remove(kicked) + elif cmd == "PART": - user = self.user(origin) - if user.nick != origin: - user.nick = origin - if user.idnt != ident: - user.idnt = ident - if user.host != host: - user.host = host - - channel = self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != target: - channel.name = target # Server seems to have changed the idea of the case of the channel name - - if channel in user.channels: - user.channels.remove(channel) - if user in channel.users: - channel.users.remove(user) + self.event("onRecv", target.addons, line=line, **data) + if origin == self.identity: + 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 channel.modes and user in channel.modes[mode]: - channel.modes[mode].remove(user) - if all([user not in c.users for c in self.identity.channels]): - with self.loglock: - print >>self.log, "%s *** User %s!%s@%s was orphaned when parting %s."%(timestamp, user.nick, user.idnt, user.host, channel.name) - self.log.flush() - elif cmd == "QUIT": - user = self.user(origin) - if user.nick != origin: - user.nick = origin - if user.idnt != ident: - user.idnt = ident - if user.host != host: - user.host = host - channels = list(user.channels) - for channel in user.channels: - for module in channel.modules: - if module not in modules: - modules.append(module) - self.event(modules, line, parsed) - for channel in channels: - channel.lock.acquire(True) - for channel in user.channels: - if user in channel.users: - channel.users.remove(user) - if "PREFIX" in self.supports: - for mode in self.supports["PREFIX"][0]: - if mode in channel.modes and user in channel.modes[mode]: - channel.modes[mode].remove(user) - #if not len(chanobj.users): #Let's remove this channel - # del self.channels[chan] - # On second thought, no, since we may want to save certain info - user.channels = [] - for channel in channels: - channel.lock.release() - print >>self.log, "%s *** User %s!%s@%s was orphaned when quitting IRC."%(timestamp, user.nick, user.idnt, user.host) - elif cmd == "MODE": - if target[0] in self.supports["CHANTYPES"]: - user = self.user(origin) - if user.nick != origin: - user.nick = origin - if user.idnt != ident: - user.idnt = ident - if user.host != host: - user.host = host + if mode in target.modes and origin in target.modes[mode]: + target.modes[mode].remove(origin) - channel = self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) + 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 channel.name != target: - channel.name = target # Server seems to have changed the idea of the case of the channel name - - 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]: - param = modeparams.pop(0) + 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 == "+": - if mode in channel.modes: - if param.lower() not in [mask.lower() for (mask, setby, settime) in channel.modes[mode]]: - channel.modes[mode].append((param, origin, int(time.time()))) - else: - channel.modes[mode] = [(param, origin, int(time.time()))] - else: - if mode in channel.modes.keys(): - if mode == "b": # Inspircd mode is case insentive when unsetting the mode - masks = [mask.lower() for (mask, setby, settime) in channel.modes[mode]] - if param.lower() in masks: - index = masks.index(param.lower()) - #print "Index: %d"%index - del channel.modes[mode][index] - else: - masks = [mask for (mask, setby, settime) in channel.modes[mode]] - if param in masks: - index = masks.index(param) - del channel.modes[mode][index] - elif mode in self.supports["CHANMODES"][1]: + 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 == "+": - channel.modes[mode] = param + 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: - channel.modes[mode] = None - elif mode in self.supports["CHANMODES"][2]: - if modeset == "+": - param = modeparams.pop(0) - channel.modes[mode] = param - else: - channel.modes[mode] = None - elif mode in self.supports["CHANMODES"][3]: - if modeset == "+": - channel.modes[mode] = True - else: - channel.modes[mode] = False - elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]: - modenick = modeparams.pop(0) - modeuser = self.user(modenick) - if modeuser.nick != modenick: - modeuser.nick = modenick - if modeset == "+": - if mode in channel.modes and modeuser not in channel.modes[mode]: - channel.modes[mode].append(modeuser) - if mode not in channel.modes: - channel.modes[mode] = [modeuser] - elif mode in channel.modes and modeuser in channel.modes[mode]: - channel.modes[mode].remove(modeuser) - else: - user = self.user(target) + 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 = "+" @@ -708,126 +789,287 @@ class Connection(Thread): modeset = mode continue if modeset == "+": - if mode not in user.modes: - user.modes += mode + 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 user.snomask: - user.snomask += snomode - if snomodeset == "-" and snomode in user.snomask: - user.snomask = user.snomask.replace(snomode, "") + 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 user.modes: - user.modes = user.modes.replace(mode, "") + if mode in target.modes: + target.modes = target.modes.replace(mode, "") if mode == "s": - user.snomask = "" + target.snomask = "" + elif cmd == "TOPIC": - user = self.user(origin) - if user.nick != origin: - user.nick = origin - if user.idnt != ident: - user.idnt = ident - if user.host != host: - user.host = host - - channel = self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - - with channel.lock: - if channel.name != target: - channel.name = target # Server seems to have changed the idea of the case of the channel name - channel.topic = extinfo + 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 == "PRIVMSG": - user = self.user(origin) - if user.nick != origin: - user.nick = origin - if user.idnt != ident: - user.idnt = ident - if user.host != host: - user.host = host - - if target[0] in self.supports["CHANTYPES"]: - channel = self.channel(target) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != target: - channel.name = target # Server seems to have changed the idea of the case of the channel name - elif target[0] == "$": - pass # Server message -- Not implemented - else: - targetuser = self.user(target) - if targetuser.nick != target: - targetuser.nick = target # Server seems to have changed the idea of the case of the nickname + 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": - user.ctcpreply("VERSION", self.ctcpversion()) + origin.ctcpreply("VERSION", self.ctcpversion()) if ctcptype.upper() == "TIME": tformat = time.ctime() tz = time.tzname[0] - user.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars()) + origin.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars()) if ctcptype.upper() == "PING": - user.ctcpreply("PING", "%(ext)s" % vars()) + origin.ctcpreply("PING", "%(ext)s" % vars()) if ctcptype.upper() == "FINGER": - user.ctcpreply("FINGER", "%(ext)s" % vars()) + 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 + if 367 in lists.items(): + banlist = lists[367] + else: + banlist = lists[367] = [] + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + banlist.append((self.channel(channame), mask, setby, int(settime))) + 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)) + elif cmd == 368: + if 367 in lists.items(): + banlist = lists[367] + else: + banlist = lists[367] = [] + 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) + for (channame, mask, setby, settime) in banlist: + 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))] + lists[367] = [] + + elif cmd == 346: # Channel Invite list + if 346 in lists.items(): + invitelist = lists[346] + else: + invitelist = lists[346] = [] + (channame, mask, setby, settime) = params.split() + invitelist.append((self.channel(channame), mask, setby, int(settime))) + 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)) + elif cmd == 347: + if 346 in lists.items(): + invitelist = lists[346] + else: + invitelist = lists[346] = [] + 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) + for (channame, mask, setby, settime) in invitelist: + 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))] + lists[346] = [] + + elif cmd == 348: # Channel Ban Exception list + if 348 in lists.items(): + banexceptlist = lists[348] + else: + banexceptlist = lists[348] = [] + (channame, mask, setby, settime) = params.split() + banexceptlist.append((self.channel(channame), mask, setby, int(settime))) + 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)) + elif cmd == 349: + if 348 in lists.items(): + banexceptlist = lists[348] + else: + banexceptlist = lists[348] = [] + 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) + for (channame, mask, setby, settime) in banexceptlist: + 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))] + lists[348] = [] + elif cmd == 910: # Channel Access List + if 910 in lists.items(): + accesslist = lists[910] + else: + accesslist = lists[910] = [] (channame, mask, setby, settime) = params.split() + accesslist.append((self.channel(channame), mask, setby, int(settime))) channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - if "w" in channel.modes.keys(): - if mask not in [m for (m, b, t) in channel.modes["w"]]: - channel.modes["w"].append((mask, setby, int(settime))) + 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)) + elif cmd == 911: + if 911 in lists.items(): + accesslist = lists[910] else: - channel.modes["w"] = [(mask, setby, int(settime))] - elif cmd == 941: # Channel spamfilter List + accesslist = lists[910] = [] + 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) + for (channame, mask, setby, settime) in accesslist: + 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))] + lists[910] = [] + + elif cmd == 941: # Spam Filter list + if 941 in lists.items(): + spamfilterlist = lists[941] + else: + spamfilterlist = lists[941] = [] (channame, mask, setby, settime) = params.split() + spamfilterlist.append((self.channel(channame), mask, setby, int(settime))) channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - if "g" in channel.modes.keys(): - if mask not in [m for (m, b, t) in channel.modes["g"]]: - channel.modes["g"].append((mask, setby, int(settime))) + 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)) + elif cmd == 940: + if 941 in lists.items(): + spamfilterlist = lists[941] else: - channel.modes["g"] = [(mask, setby, int(settime))] - elif cmd == 954: # Channel spamfilter List + spamfilterlist = lists[941] = [] + 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) + for (channame, mask, setby, settime) in spamfilterlist: + 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))] + lists[941] = [] + + elif cmd == 954: # Channel exemptchanops list + if 954 in lists.items(): + exemptchanopslist = lists[954] + else: + exemptchanopslist = lists[954] = [] (channame, mask, setby, settime) = params.split() + exemptchanopslist.append((self.channel(channame), mask, setby, int(settime))) channel = self.channel(channame) - self.event("onRecv", channel.modules, line=line, data=parsed) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - if "X" in channel.modes.keys(): - if mask not in [m for (m, b, t) in channel.modes["X"]]: - channel.modes["X"].append((mask, setby, int(settime))) + 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)) + elif cmd == 953: + if 954 in lists.items(): + exemptchanopslist = lists[954] + else: + exemptchanopslist = lists[954] = [] + 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) + for (channame, mask, setby, settime) in exemptchanopslist: + 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))] + lists[954] = [] + + elif cmd == 728: # Channel quiet list + if 728 in lists.items(): + quietlist = lists[728] else: - channel.modes["X"] = [(mask, setby, int(settime))] + quietlist = lists[728] = [] + (channame, modechar, mask, setby, settime) = params.split() + quietlist.append((self.channel(channame), mask, setby, int(settime))) + 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)) + elif cmd == 729: + if 728 in lists.items(): + quietlist = lists[728] + else: + quietlist = lists[728] = [] + channel = self.channel(params) + self.event("onRecv", channel.addons, line=line, **data) + (handled, unhandled, exceptions) = self.event("onQuietListEnd", self.addons+channel.addons, channel=channel, endmsg=extinfo) + for (channame, mask, setby, settime) in quietlist: + 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))] + lists[728] = [] + 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.modules, line=line, data=parsed) + 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) + + self.event("onUnhandled", unhandled, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo) - else: # Line does NOT match ":src cmd target params :extinfo" - self.event("onRecv", self.modules, line=line, - data=None) + 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. - with self.loglock: - exc, excmsg, tb = sys.exc_info() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars() - self.log.flush() + exc, excmsg, tb = sys.exc_info() + self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) 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 try: self.quit("%s" % traceback.format_exc() .rstrip().split("\n")[-1]) @@ -836,34 +1078,25 @@ class Connection(Thread): raise finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred. with self.lock: - modules = list(self.modules) - for channel in self.channels: - for module in channel.modules: - if module not in modules: - modules.append(module) - self.event("onDisconnect", self.modules) + (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.put("quit") + self.outgoing.interrupt() ### Wait until the outgoing thread dies. - if outgoingthread: + if outgoingthread and outgoingthread.isAlive(): outgoingthread.join() outgoingthread = None - self.connected = False - self.registered = False - self.identity = None - try: self.connection.close() except: pass - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s *** Connection Terminated." % vars() - self.log.flush() + self.logwrite("*** Connection Terminated.") if self.quitexpected or not self.autoreconnect: sys.exit() @@ -875,23 +1108,14 @@ class Connection(Thread): pass except: # Print exception to log file - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s !!! Fatal Exception" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>self.log, "%(timestamp)s !!! %(tbline)s" % vars() - self.log.flush() + self.logwrite(*["!!! FATAL Exception"]+["!!! %s"%line for line in traceback.format_exc().split("\n")]) + print >>sys.stderr, "FATAL Exception" % vars() + print >>sys.stderr, traceback.format_exc() sys.exit() finally: - with self.loglock: - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust( - 2, "0") for t in time.localtime()[0:6]]) - print >>self.log, "%(timestamp)s ### Log session ended" % vars( - ) - self.log.flush() - + 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 def __repr__(self): @@ -901,11 +1125,11 @@ class Connection(Thread): port = self.port if self.identity: nick = self.identity.nick - ident = self.identity.idnt if self.identity.idnt else "*" + user = self.identity.username if self.identity.username else "*" host = self.identity.host if self.identity.host else "*" else: nick = "*" - ident = "*" + user = "*" host = "*" if self.ssl and self.ipv6: protocol = "ircs6" @@ -915,12 +1139,11 @@ class Connection(Thread): protocol = "irc6" else: protocol = "irc" - return "<IRC Context: %(nick)s!%(ident)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals() + 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() def quit(self, msg="", origin=None): with self.lock: - self.quitexpected = True if self.connected: if len(msg): self.raw("QUIT :%(msg)s" % vars(), origin=origin) @@ -929,7 +1152,7 @@ class Connection(Thread): def ctcpversion(self): reply = [] - ### Prepare reply for module + ### Prepare reply for addon reply.append("%(__name__)s %(__version__)s, %(__author__)s" % vars(self)) @@ -938,11 +1161,11 @@ class Connection(Thread): pyver[0] = "Python "+pyver[0] reply.extend(pyver) reply.extend(platform.platform().split("\n")) - ### Prepare reply for extension modules - for module in self.modules: + ### Prepare reply for extension addons + for addon in self.addons: try: - r = "%(__name__)s %(__version__)s" % vars(module) - if "__extinfo__" in vars(module): + r = "%(__name__)s %(__version__)s" % vars(addon) + if "__extinfo__" in vars(addon): r += ", %(__extinfo__)s" % vars() reply.append(r) except: @@ -950,7 +1173,11 @@ class Connection(Thread): return reduce(lambda x, y: "%s; %s" % (x, y), reply) def raw(self, line, origin=None): - self.outgoing.put((line, origin)) + try: + self.outgoing.put((line, origin)) + except: + print (line, origin) + raise def user(self, nick): users = [user for user in self.users if user.nick.lower( @@ -962,9 +1189,9 @@ class Connection(Thread): self.users.append(user) timestamp = reduce(lambda x, y: x+":"+y, [str( t).rjust(2, "0") for t in time.localtime()[0:6]]) - with self.loglock: - print >>self.log, "%s *** User %s created."%(timestamp, nick) - self.log.flush() + #with self.loglock: + # print >>self.log, "%s *** User %s created."%(timestamp, nick) + # self.log.flush() return user def channel(self, name): @@ -977,10 +1204,9 @@ class Connection(Thread): t).rjust(2, "0") for t in time.localtime()[0:6]]) chan = Channel(name, self) self.channels.append(chan) - with self.loglock: - print >>self.log, "%s *** Channel %s created."%( - timestamp, name) - self.log.flush() + #with self.loglock: + # print >>self.log, "%s *** Channel %s created."%(timestamp, name) + # self.log.flush() return chan @@ -988,7 +1214,7 @@ class Channel(object): def __init__(self, name, context): self.name = name self.context = context - self.modules = [] + self.addons = [] self.topic = "" self.topicsetby = "" self.topictime = () @@ -1055,7 +1281,7 @@ class Channel(object): class User(object): def __init__(self, nick, context): self.nick = nick - self.idnt = "" + self.username = "" self.host = "" self.channels = [] self.context = context @@ -1064,9 +1290,14 @@ class User(object): self.server = None self.hops = None self.ircop = False + self.ircopmsg = "" + self.idlesince = None + self.signontime = None + self.ssl = None + self.away = None def __repr__(self): - return "<User: %(nick)s!%(idnt)s@%(host)s>" % vars(self) + return "<User: %(nick)s!%(username)s@%(host)s>" % vars(self) def msg(self, msg, origin=None): nick = self.nick @@ -1092,20 +1323,6 @@ class User(object): self.ctcp("ACTION", msg, origin=origin) -class SendLines(Thread): - def __init__(self, connection, lines, origin=None): - self.connection = connection - self.lines = lines - self.origin = origin - Thread.__init__(self) - - def run(self): - for line in self.lines: - self.connection.raw(reply, origin=self.origin) - self.connection.log.flush() - time.sleep(2) - - class Outgoing(Thread): def __init__(self, IRC, throttle=0.25, lines=40, t=5): self.IRC = IRC @@ -1116,118 +1333,158 @@ class Outgoing(Thread): Thread.__init__(self) def run(self): - throttled = False - timestamps = [] - while True: - q = self.IRC.outgoing.get() - 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] - if cmd.upper() == "QUIT": - self.IRC.quitexpected = True - timestamp = reduce(lambda x, y: x+":"+y, [str( - t).rjust(2, "0") for t in time.localtime()[0:6]]) - with self.IRC.lock: + try: + throttled = False + timestamps = [] + while True: try: - self.IRC.connection.send("%(line)s\n" % vars()) - except socket.error: + 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.shutdown(0) - except: - pass - raise + 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 modules - #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": + ### 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() 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": + 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() 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) - #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) + 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 target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"): - params = "" + elif cmd.upper() == "PASS": + extinfo = "********" + target = "" + line = "%s :%s"%(cmd, extinfo) + elif cmd.upper() == "IDENTIFY": 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.modules, line=line, data=(cmd, target, params, extinfo), origin=origin) - with self.IRC.loglock: - print >>self.IRC.log, "%(timestamp)s >>> %(line)s" % vars() - self.IRC.log.flush() - 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)) + 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): @@ -12,80 +12,75 @@ import traceback import Queue import ssl import urllib2 +import irc +modemapping = dict( + Y="ircop", q="owner", a="admin", o="op", h="halfop", v="voice") -class LogRotate(Thread): - def __init__(self, logger): - self.logger = logger - Thread.__init__(self) - self.daemon = True - self.start() - - def run(self): - 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))) - #print time.time()-nextrotate - #print time.localtime(), time.localtime(nextrotate) - 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)))) - with self.logger.rotatelock: - if all([not log or log.closed for log in self.logger.consolelogs.values()+self.logger.channellogs.values()]): - ### If there are no logs to rotate, we are going to terminate this thread. Logger will spawn another LogRotate thread when needed. - self.logger.logrotate = None - break - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t) - .rjust(2, "0") for t in now[0:6]]) - for IRC in self.logger.labels.keys(): - if IRC.connected: - with IRC.lock: - try: - self.logger.rotateConsoleLog(IRC) - except: - with IRC.loglock: - exc, excmsg, tb = sys.exc_info() - print >>IRC.log, "%(timestamp)s !!! [LogRotate] Exception in module %(module)s" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>IRC.log, "%(timestamp)s !!! [LogRotate] %(tbline)s" % vars() - IRC.log.flush() - if IRC.identity: - for channel in IRC.identity.channels: - try: - self.logger.rotateChannelLog(channel) - except: - with IRC.loglock: - exc, excmsg, tb = sys.exc_info() - print >>IRC.log, "%(timestamp)s !!! [LogRotate] Exception in module %(module)s" % vars() - for tbline in traceback.format_exc().split("\n"): - print >>IRC.log, "%(timestamp)s !!! [LogRotate] %(tbline)s" % vars() - IRC.log.flush() - nextrotate += 3600*24 - -class Logger(object): +class Logger(Thread): def __init__(self, logroot): self.logroot = logroot path = [logroot] - #print path + while not os.path.isdir(path[0]): split = os.path.split(path[0]) path.insert(1, split[1]) path[0] = split[0] - #print path + while len(path) > 1: path[0] = os.path.join(*path[:2]) del path[1] #print path os.mkdir(path[0]) - #return - self.consolelogs = {} - self.channellogs = {} + + self.logs = {} self.labels = {} self.rotatelock = Lock() - self.logrotate = None - def onModuleAdd(self, IRC, label): + Thread.__init__(self) + self.daemon = True + self.start() + + 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))) + 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)))) + with self.rotatelock: + if all([not log or log.closed for log in self.logs.values()]): + break + Y, M, D, h, m, s, w, d, dst = now = time.localtime() + for IRC in self.labels.keys(): + if IRC.connected: + with IRC.lock: + try: + 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")]) + 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")]) + 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))) + finally: + Thread.__init__(self) + + def onAddonAdd(self, IRC, label): if label in self.labels.values(): raise BaseException("Label already exists") if IRC in self.labels.keys(): @@ -94,289 +89,514 @@ class Logger(object): os.mkdir(os.path.join(self.logroot, label)) self.labels[IRC] = label if IRC.connected: - self.openConsoleLog(IRC) + self.openLog(IRC) if IRC.identity: for channel in IRC.identity.channels: - self.openChannelLog(channel) + 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])) - def onModuleRem(self, IRC): + def onAddonRem(self, IRC): if IRC.connected: - for channel in self.channellogs.keys(): + for channel in self.logs.keys(): if channel in IRC.channels: - if not self.channellogs[channel].closed: - self.closeChannelLog(channel) - del self.channellogs[channel] - if not self.consolelogs[IRC].closed: - self.closeConsoleLog(IRC) + if not self.logs[channel].closed: + self.closeLog(channel) + for user in self.logs.keys(): + if user in IRC.users: + if not self.logs[user].closed: + self.closeLog(user) + if not self.logs[IRC].closed: + self.closeLog(IRC) del self.labels[IRC] - del self.consolelogs[IRC] - def openConsoleLog(self, IRC): - with self.rotatelock: - if not self.logrotate or not self.logrotate.isAlive(): - self.logrotate = LogRotate(self) - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - self.consolelogs[IRC] = open(os.path.join(self.logroot, self.labels[ - IRC], "console-%04d.%02d.%02d.log"%now[:3]), "a") - print >>self.consolelogs[IRC], "%s %s ### Log session started" % ( - timestamp, time.tzname[now[-1]]) - self.consolelogs[IRC].flush() - - def closeConsoleLog(self, IRC): - if IRC in self.consolelogs.keys() and type(self.consolelogs[IRC]) == file and not self.consolelogs[IRC].closed: - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t) - .rjust(2, "0") for t in now[0:6]]) - print >>self.consolelogs[IRC], "%s %s ### Log session ended" % ( - timestamp, time.tzname[now[-1]]) - self.consolelogs[IRC].close() - - def rotateConsoleLog(self, IRC): - self.closeConsoleLog(IRC) - self.openConsoleLog(IRC) - - def openChannelLog(self, channel): + def openLog(self, window): with self.rotatelock: - if not self.logrotate or not self.logrotate.isAlive(): - self.logrotate = LogRotate(self) - self.logrotate.daemon = True - self.logrotate.start() + 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]]) - label = self.labels[channel.context] - self.channellogs[channel] = open(os.path.join(self.logroot, label, "channel-%s-%04d.%02d.%02d.log"%((urllib2.quote(channel.name.lower()).replace("/", "%2f"),)+now[:3])), "a") - print >>self.channellogs[channel], "%s %s ### Log session started" % ( - timestamp, time.tzname[now[-1]]) - self.channellogs[channel].flush() - if channel.context.identity in channel.users: - if channel.topic: - print >>self.channellogs[channel], "%s %s <<< :%s 332 %s %s :%s" % (timestamp, time.tzname[now[-1]], channel.context.serv, channel.context.identity.nick, channel.name, channel.topic) - if channel.topicsetby and channel.topictime: - print >>self.channellogs[channel], "%s %s <<< :%s 333 %s %s %s %s" % (timestamp, time.tzname[now[-1]], channel.context.serv, channel.context.identity.nick, channel.name, channel.topicsetby, channel.topictime) - if channel.users: - secret = "s" in channel.modes.keys() and channel.modes["s"] - private = "p" in channel.modes.keys() and channel.modes["p"] - namesusers = [] - modes, symbols = channel.context.supports["PREFIX"] - print >>self.channellogs[channel], "%s %s <<< :%s 353 %s %s %s :%s" % (timestamp, time.tzname[now[-1]], - channel.context.serv, - channel.context.identity.nick, - "@" if secret else ("*" if private else "="), - channel.name, - " ".join(["".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])) - if channel.modes: - modes = channel.modes.keys() - modestr = "".join([mode for mode in modes if mode not in channel.context.supports["CHANMODES"][0]+channel.context.supports["PREFIX"][0] and channel.modes[mode]]) - params = " ".join([channel.modes[mode] for mode in modes if mode in channel.context.supports["CHANMODES"][1]+channel.context.supports["CHANMODES"][2] and channel.modes[mode]]) - print >>self.channellogs[channel], "%s %s <<< :%s 324 %s %s +%s %s" % (timestamp, time.tzname[now[-1]], channel.context.server, channel.context.identity.nick, channel.name, modestr, params) - if channel.created: - print >>self.channellogs[channel], "%s %s <<< :%s 329 %s %s %s" % (timestamp, time.tzname[now[-1]], channel.context.serv, channel.context.identity.nick, channel.name, channel.created) - self.channellogs[channel].flush() - - def closeChannelLog(self, channel): - if channel in self.channellogs.keys() and type(self.channellogs[channel]) == file and not self.channellogs[channel].closed: - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t) - .rjust(2, "0") for t in now[0:6]]) - print >>self.channellogs[channel], "%s %s ### Log session ended" % (timestamp, time.tzname[now[-1]]) - self.channellogs[channel].close() - - def rotateChannelLog(self, channel): - self.closeChannelLog(channel) - self.openChannelLog(channel) - - def onRecv(self, IRC, line, data): - modemapping = dict(Y="ircop", q="owner", a="admin", o="op", - h="halfop", v="voice") - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - if data is None: - print >>self.consolelogs[IRC], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.consolelogs[IRC].flush() - return - (origin, ident, host, cmd, target, params, extinfo) = data - if re.match("^\\d+$", cmd): - cmd = int(cmd) - if cmd in (324, 329): - modeparams = params.split() - channame = modeparams[0] - channel = IRC.channel(channame) - if channel in self.channellogs.keys() and not self.channellogs[channel].closed: - log = self.channellogs[channel] + 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") + 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") + 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) + 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) + if window.users: + secret = "s" in window.modes.keys() and window.modes["s"] + private = "p" in window.modes.keys() and window.modes["p"] + namesusers = [] + modes, symbols = window.context.supports["PREFIX"] + print >>log, "%s <<< :%s 353 %s %s %s :%s" % (irc.timestamp(), + window.context.serv, + window.context.identity.nick, + "@" 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])) + 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) + if 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])) + for (other, log) in self.logs.items(): + if other == window: + continue + if log.name == logname: + print >>log, "%s ### Log file closed" % (irc.timestamp()) + del self.logs[other] + self.logs[window] = log + if window not in self.logs.keys(): + log = self.logs[window] = open(logname, "a") else: - log = self.consolelogs[IRC] - print >>log, "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - log.flush() - elif cmd == 332: - channel = IRC.channel(params) - if channel in self.channellogs.keys() and not self.channellogs[channel].closed: - log = self.channellogs[channel] + log = self.logs[window] + print >>log, "%s ### Log file opened" % (irc.timestamp()) + log.flush() + + 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()) + self.logs[window].close() + if window in self.logs.keys(): + del self.logs[window] + + def rotateLog(self, window): + self.closeLog(window) + self.openLog(window) + + def onConnectAttempt(self, IRC): + if IRC not in self.logs.keys() or (not self.logs[IRC]) or self.logs[IRC].closed: + self.openLog(IRC) + ts = irc.timestamp() + print >>self.logs[IRC], "%s *** Attempting connection to %s:%s." % ( + ts, IRC.server, IRC.port) + + def onConnect(self, IRC): + if IRC not in self.logs.keys() or (not self.logs[IRC]) or self.logs[IRC].closed: + self.openLog(IRC) + ts = irc.timestamp() + print >>self.logs[IRC], "%s *** Connection to %s:%s established." % ( + ts, IRC.server, IRC.port) + + def onConnectFail(self, IRC, exc, excmsg, tb): + ### 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() + print >>self.logs[IRC], "%s *** Connection to %s:%s failed: %s." % ( + ts, IRC.server, IRC.port, excmsg) + + def onDisconnect(self, IRC, expected=False): + 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) + self.logs[window].flush() + self.closeLog(window) + print >>self.logs[IRC], "%s *** Connection %s:%s terminated." % ( + ts, IRC.server, IRC.port) + self.logs[IRC].flush() + self.closeLog(IRC) + + def onJoin(self, IRC, user, channel): + ### Called when somebody joins a channel, includes bot. + ts = irc.timestamp() + if user == IRC.identity: + self.openLog(channel) + print >>self.logs[channel], "%s <<< :%s!%s@%s JOIN %s" % ( + ts, user.nick, user.username, user.host, channel.name) + self.logs[channel].flush() + + def onChanMsg(self, IRC, user, channel, targetprefix, msg): + ### 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]]) + 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) else: - log = self.consolelogs[IRC] - print >>log, "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - log.flush() - elif cmd == 333: - (channame, nick, dt) = params.split() - channel = IRC.channel(channame) - if not self.channellogs[channel].closed: - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - elif cmd == 353: - (flag, channame) = params.split() - channel = IRC.channel(channame) - if not self.channellogs[channel].closed: - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - elif cmd == "JOIN": - user = IRC.user(origin) - channel = IRC.channel(target if len(target) else extinfo) - if user == IRC.identity: - self.openChannelLog(channel) - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - elif cmd == "PRIVMSG": - if target and target[0] in IRC.supports["CHANTYPES"]: - channel = IRC.channel(target) - if ident and host: - user = IRC.user(origin) - classes = " ".join([modemapping[mode] for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and user in channel.modes[mode]]) - else: - classes = "server" - if classes: - print >>self.channellogs[channel], "%s %s %s <<< %s" % (timestamp, time.tzname[now[-1]], classes, line) - else: - print >>self.channellogs[channel], "%s %s <<< %s" % (timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - elif cmd == "NOTICE": - if target and (target[0] in IRC.supports["CHANTYPES"] or (len(target) > 1 and target[0] in IRC.supports["PREFIX"][1] and target[1] in IRC.supports["CHANTYPES"])): - if target[0] in IRC.supports["PREFIX"][1]: - channel = IRC.channel(target[1:]) - else: - channel = IRC.channel(target) - if ident and host: - user = IRC.user(origin) - classes = " ".join([modemapping[mode] for mode in IRC.supports["PREFIX"][0] if mode in channel.modes.keys() and user in channel.modes[mode]]) - else: - classes = "server" - if classes: - print >>self.channellogs[channel], "%s %s %s <<< %s" % (timestamp, time.tzname[now[-1]], classes, line) - else: - print >>self.channellogs[channel], "%s %s <<< %s" % (timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - elif target.lower() == IRC.identity.nick.lower() and not ident and not host: - print >>self.consolelogs[IRC], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.consolelogs[IRC].flush() - elif cmd == "TOPIC": - channel = IRC.channel(target) - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - elif cmd == "PART": - user = IRC.user(origin) - channel = IRC.channel(target) - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - if user == IRC.identity: - self.closeChannelLog(channel) - elif cmd == "KICK": - kicked = IRC.user(params) - channel = IRC.channel(target) - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() - if kicked == IRC.identity: - self.closeChannelLog(channel) - elif cmd == "MODE": - if target and target[0] in IRC.supports["CHANTYPES"]: - channel = IRC.channel(target) - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() + 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) + self.logs[channel].flush() + + def onChanAction(self, IRC, user, channel, targetprefix, 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. + 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]]) + 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) else: - print >>self.consolelogs[IRC], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.consolelogs[IRC].flush() - elif cmd in ("NICK", "QUIT"): - user = IRC.user(origin) - for channel in user.channels: - print >>self.channellogs[channel], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.channellogs[channel].flush() + 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) + self.logs[channel].flush() + + def onPart(self, IRC, user, channel, partmsg): + ### 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) else: - print >>self.consolelogs[IRC], "%s %s <<< %s" % ( - timestamp, time.tzname[now[-1]], line) - self.consolelogs[IRC].flush() + 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 onConnectAttempt(self, IRC): - if IRC not in self.consolelogs.keys() or (not self.consolelogs[IRC]) or self.consolelogs[IRC].closed: - self.openConsoleLog(IRC) - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - print >>self.consolelogs[IRC], "%s %s *** Attempting connection to %s:%s." % (timestamp, time.tzname[now[-1]], IRC.server, IRC.port) + def onKick(self, IRC, kicker, channel, kicked, kickmsg): + ### 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) + else: + 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: + self.closeLog(channel) - def onConnect(self, IRC): - if IRC not in self.consolelogs.keys() or (not self.consolelogs[IRC]) or self.consolelogs[IRC].closed: - self.openConsoleLog(IRC) - #self.openConsoleLog(IRC) - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - print >>self.consolelogs[IRC], "%s %s *** Connection to %s:%s established." % (timestamp, time.tzname[now[-1]], IRC.server, IRC.port) + 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. + 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]]) + 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) + 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) + self.logs[channel].flush() - def onDisconnect(self, IRC): - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - for channel in IRC.identity.channels: - print >>self.channellogs[channel], "%s %s *** Connection to %s:%s terminated." % (timestamp, time.tzname[now[-1]], IRC.server, IRC.port) - self.channellogs[channel].flush() - self.closeChannelLog(channel) - print >>self.consolelogs[IRC], "%s %s *** Connection %s:%s terminated." % (timestamp, time.tzname[now[-1]], IRC.server, IRC.port) - self.consolelogs[IRC].flush() - self.closeConsoleLog(IRC) - - def onSend(self, IRC, line, data, origin): - modemapping = dict(Y="ircop", q="owner", a="admin", o="op", - h="halfop", v="voice") - now = time.localtime() - timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2, - "0") for t in now[0:6]]) - (cmd, target, params, extinfo) = data - if IRC.registered and cmd == "PRIVMSG" and "CHANTYPES" in IRC.supports.keys() and len(target) and target[0] in IRC.supports["CHANTYPES"]: - channel = IRC.channel(target) - if channel in IRC.identity.channels: - 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.channellogs[channel], "%s %s %s >>> :%s!%s@%s %s" % (timestamp, time.tzname[now[-1]], classes, IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, line) - else: - print >>self.channellogs[channel], "%s %s >>> :%s!%s@%s %s" % (timestamp, time.tzname[now[-1]], IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, line) - self.channellogs[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) + + def onPrivMsg(self, IRC, user, msg): + ### 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) + self.logs[user].flush() + + def onPrivNotice(self, IRC, origin, msg): + ### 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) + self.logs[origin].flush() + else: + print >>self.logs[IRC], "%s <<< :%s NOTICE %s :%s" % ( + ts, origin, IRC.identity.nick, msg) + 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) + + 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. + 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) + 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) + + def onNickChange(self, IRC, user, newnick): + ### 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. + for channel in user.channels: + print >>self.logs[channel], line + self.logs[channel].flush() + + ### 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. + + ### 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) + 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. + ts = irc.timestamp() + if quitmsg: + line = "%s <<< :%s!%s@%s QUIT :%s" % ( + ts, user.nick, user.username, user.host, quitmsg) + else: + line = "%s <<< :%s!%s@%s QUIT" % ( + ts, user.nick, user.username, user.host) + + ### 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. + 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. + if channel in self.logs.keys() and not self.logs[channel].closed: + log = self.logs[channel] + else: + log = self.logs[IRC] + ts = irc.timestamp() + + secret = "s" in channel.modes.keys() and channel.modes["s"] + 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])) + log.flush() + + def onNamesEnd(self, IRC, origin, channel, channame, endmsg): + if channel in self.logs.keys() and not self.logs[channel].closed: + log = self.logs[channel] + else: + log = self.logs[IRC] + ts = irc.timestamp() + print >>log, "%s <<< :%s 366 %s %s :%s" % ( + ts, origin, IRC.identity.nick, channame, endmsg) + log.flush() + + def onWhoisStart(self, IRC, origin, user, nickname, username, host, realname): + ### 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) + + def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg): + ### 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. + 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) + + def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg): + ### 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. + 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)) + + def onWhoisAvailability(self, IRC, origin, user, nickname, msg): + ### 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. + 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) + + def onWhoisOp(self, IRC, origin, user, nickname, msg): + if user not in self.logs.keys(): + self.openLog(user) + print >>self.logs[user], "%s <<< :%s 313 %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, msg) + + 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) + + def onWhoisSSL(self, IRC, origin, user, nickname, msg): + if user not in self.logs.keys(): + self.openLog(user) + print >>self.logs[user], "%s <<< :%s 671 %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, msg) + + def onWhoisModes(self, IRC, origin, user, nickname, msg): + if user not in self.logs.keys(): + self.openLog(user) + print >>self.logs[user], "%s <<< :%s 339 %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, msg) + + 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) + + def onWhoisEnd(self, IRC, origin, user, nickname, msg): + if user not in self.logs.keys(): + self.openLog(user) + print >>self.logs[user], "%s <<< :%s 318 %s %s :%s" % ( + irc.timestamp(), origin, IRC.identity.nick, nickname, msg) + self.logs[user].flush() + + def onWhoEntry(self, IRC, **kwargs): + ### Called when a WHO list is received. + pass + + def onWhoEnd(self, IRC, **kwargs): + ### Called when a WHO list is received. + pass + + def onList(self, IRC, chanlistbegin, chanlist, endmsg): + ### Called when a channel list is received. + pass + + def onTopic(self, IRC, origin, channel, topic): + ### 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] + else: + log = self.logs[IRC] + print >>log, "%s <<< :%s 332 %s %s :%s" % ( + ts, origin, IRC.identity.nick, channel.name, topic) + log.flush() + + def onTopicInfo(self, IRC, origin, channel, topicsetby, topictime): + ### 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) + log.flush() + + def onTopicSet(self, IRC, user, channel, topic): + ### 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) + 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. + ts = irc.timestamp() + modestr = "" + params = [] + sign = "" + for (sgn, modechar), param in modedelta: + if sgn != sign: + modestr += sgn + sign = sgn + modestr += modechar + if param is not 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)) else: - print >>self.consolelogs[IRC], "%s %s >>> :%s!%s@%s %s" % (timestamp, time.tzname[now[-1]], IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, line) - self.consolelogs[IRC].flush() - if IRC.registered and len(target) and (target[0] in IRC.supports["CHANTYPES"] or (len(target) > 1 and target[0] in IRC.supports["PREFIX"][1] and target[1] in IRC.supports["CHANTYPES"])) and cmd == "NOTICE": - channel = IRC.channel(target[1:] if target[0] - in IRC.supports["PREFIX"][1] else target) - if channel in IRC.identity.channels: - 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.channellogs[channel], "%s %s %s >>> :%s!%s@%s %s" % (timestamp, time.tzname[now[-1]], classes, IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, line) - else: - print >>self.channellogs[channel], "%s %s >>> :%s!%s@%s %s" % (timestamp, time.tzname[now[-1]], IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, line) - self.channellogs[channel].flush() + 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) else: - print >>self.consolelogs[IRC], "%s %s >>> :%s!%s@%s %s" % (timestamp, time.tzname[now[-1]], IRC.identity.nick, IRC.identity.idnt, IRC.identity.host, line) - self.consolelogs[IRC].flush() + 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. + ts = irc.timestamp() + if channel in self.logs.keys() and not self.logs[channel].closed: + log = self.logs[channel] + else: + log = self.logs[IRC] + modestr = "" + params = [] + sign = "" + for (sgn, modechar), param in modedelta: + if sgn != sign: + modestr += sgn + sign = sgn + modestr += modechar + if param is not 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)) + else: + 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. + 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 329 %s %s %d" % ( + ts, IRC.serv, IRC.identity.nick, channel.name, created) + log.flush() + + def onUnhandled(self, IRC, line, origin, cmd, target, params, extinfo): + ts = irc.timestamp() + print >>self.logs[IRC], "%s <<< %s" % (ts, line) + self.logs[IRC].flush() diff --git a/poker.py b/poker.py deleted file mode 100644 index 9255548..0000000 --- a/poker.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/python -import re -import os -import random -import string -import itertools - -spade = '\xe2\x99\xa0' -heart = '\xe2\x99\xa5' -diamond = '\xe2\x99\xa6' -club = '\xe2\x99\xa3' -faces = ["A"]+range(2, 11)+list("JQKA") -suits = [(spade, 1), (club, 1), (heart, 4), (diamond, 4)] -handsmapping = ["High card", "One pair", "Two pair", "Three of a kind", "Straight", "Flush", "Full house", "Four of a kind", "Straight flush", "Royal flush"] - - -class Game(object): - def __init__(self): - self.deck = deck = list(itertools.product(xrange(1, 14), xrange(4))) - random.shuffle(deck) - random.shuffle(deck) - random.shuffle(deck) - random.shuffle(deck) - random.shuffle(deck) - self.status = 0 - self.players = [] - self.hands = {} - self.waiting = None - - -class Poker(object): - def __init__(self): - self.games = {} - - def onRecv(self, IRC, line, data): - if data is None: - return - (origin, ident, host, cmd, target, params, extinfo) = data - #print data - if len(target) and target[0] == "#" and cmd == "PRIVMSG": - channel = IRC.channel(target) - user = IRC.user(origin) - matches = re.findall("^!poker (\\S+)(?:\\s+(.*))?$", extinfo) - if matches: - cmd, param = matches[0] - if cmd == "newgame": - if all([m not in channel.modes.keys() or user not in channel.modes[m] for m in "qao"]): - channel.msg("%s: You are not operator."%origin) - elif channel in self.games.keys(): - channel.msg("%s: There is already a game going on in this channel."%origin) - else: - self.games[channel] = Game() - channel.msg("A new poker game has started. Type \x02!poker sit\x02 to join.") - elif cmd == "sit": - if channel not in self.games.keys(): - channel.msg("%s: There is no game going on in this channel."%origin) - elif self.games[channel].status != 0: - channel.msg("%s: Cannot join the game at this time." % - origin) - elif user in self.games[channel].players: - channel.msg("%s: You are already in the game."%origin) - elif len(self.games[channel].players) >= 8: - channel.msg("%s: This game is full."%origin) - else: - self.games[channel].players.append(user) - channel.msg("%s: Welcome to the game."%origin) - elif cmd == "deal": - if all([m not in channel.modes.keys() or user not in channel.modes[m] for m in "qao"]): - channel.msg("%s: You are not operator."%origin) - elif channel not in self.games.keys(): - channel.msg("%s: There is no game going on in this channel."%origin) - elif len(self.games[channel].players) == 0: - channel.msg("%s: Nobody has sat yet."%origin) - elif self.games[channel].status > 0: - channel.msg("%s: The cards have already been dealt." % - origin) - else: - channel.me("deals poker hands to %s"%(string.join([user.nick for user in self.games[channel].players], ", "))) - P = len(self.games[channel].players) - for user in self.games[channel].players: - hand = list(self.games[channel].deck[0:5*P:P]) - del self.games[channel].deck[0:5*P:P] - self.games[channel].hands[user] = hand - user.notice("Your poker hand is: %s"%(string.join(["\x03%d,0\x02%s%s\x0f"%(suits[s][1], faces[f], suits[s][0]) for (f, s) in hand], ", "))) - self.games[channel].status = 1 - self.games[channel].waiting = self.games[ - channel].players[0] - channel.msg("The cards have been dealt.") - channel.msg("%s: Do you wish to draw any cards? Type \x02!poker draw n1,n2,...\x02, where n1,n2,... is a list of cards \x02by index\x02 you wish to draw (0 for first card, 1 for second, etc...). Empty list means you wish to keep all cards."%self.games[channel].waiting.nick) - elif cmd == "draw": - if channel not in self.games.keys(): - channel.msg("%s: There is no game going on in this channel."%origin) - elif user not in self.games[channel].players: - channel.msg("%s: You are not in this game."%origin) - elif self.games[channel].status != 1: - channel.msg("%s: We are not exchanging cards yet." % - origin) - elif self.games[channel].waiting != user: - channel.msg("%s: It is not your turn to draw cards yet."%origin) - else: - if param and any([card not in "01234" for card in param.split(",")]): - channel.msg("%s: I could not understand your request."%origin) - else: - if param == "": - channel.msg("%s is keeping all cards."%origin) - discards = [] - else: - discards = [] - #print "Param",param - for cardid in param.split(","): - card = self.games[channel].hands[user][int(cardid)] - #print "Discarding ",card - if card not in discards: - discards.append(card) - for card in discards: - self.games[channel].hands[user].remove(card) - channel.msg("%s is exchanging %d card%s."%(origin, len(discards), "s" if len(discards) > 1 else "")) - self.games[channel].hands[user].extend(self.games[channel].deck[:len(discards)]) - del self.games[channel].deck[:len(discards)] - self.games[channel].deck.extend(discards) - user.notice("Your new poker hand is: %s"%(string.join(["\x03%d,0\x02%s%s\x0f"%(suits[s][1], faces[f], suits[s][0]) for (f, s) in self.games[channel].hands[user]], ", "))) - k = self.games[channel].players.index(user) - if k < len(self.games[channel].players)-1: - self.games[channel].waiting = self.games[channel].players[k+1] - channel.msg("%s: Do you wish to draw any cards? Type \x02!poker draw n1,n2,...\x02, where n1,n2,... is a list of cards \x02by index\x02 you wish to draw (0 for first card, 1 for second, etc...). Empty list means you wish to keep all cards."%self.games[channel].waiting.nick) - else: - self.games[channel].waiting = None - channel.msg("Exchanges done! Waiting for dealer to type \x02!poker show\x02.") - self.games[channel].status = 2 - elif cmd == "show": - if all([m not in channel.modes.keys() or user not in channel.modes[m] for m in "qao"]): - channel.msg("%s: Access denied."%origin) - elif channel not in self.games.keys(): - channel.msg("%s: There is no game going on in this channel."%origin) - elif self.games[channel].status != 2: - channel.msg("%s: We are not ready to show cards." % - origin) - else: - results = [] - for user in self.games[channel].players: - hand = self.games[channel].hands[user] - t = evalhand(hand) - channel.msg("%s\xe2\x80\x99s poker hand is: %s. A \x02%s\x02."%(user.nick, string.join(["\x03%d,0\x02%s%s\x0f"%(suits[s][1], faces[f], suits[s][0]) for (f, s) in hand], ", "), handsmapping[t[0]])) - results.append((t, user)) - results.sort(reverse=True) - top = results[0][0] - winners = [user.nick for (t, user) - in results if t == top] - if len(winners) > 2: - channel.msg("The winners are %s, and %s. A %d-way tie. Well played, gentlemen!"%(string.join(winners[:-1], ", "), winners[-1], len(winners))) - elif len(winners) == 2: - channel.msg("The winners are %s and %s. A tie. Well played, gentlemen!"%tuple(winners)) - else: - channel.msg("The winner is %s. Well played, gentlemen!"%winners[0]) - del self.games[channel] - #matches=re.findall("^!shuffle(?:\\s+([\\d]+))?$",extinfo) - #if matches: - #if matches[0]: shuffles=int(matches[0]) - #else: shuffles=1 - #if shuffles>1000: - #channel.msg("Get real, %s!"%origin) - #return - #for s in xrange(shuffles): random.shuffle(deck) - #channel.me("shuffles the deck %d time%s."%(shuffles, "s" if shuffles>1 else "")) - - -def evalhand(hand): - facevalues = [face for (face, suit) in hand] - facevalues.sort(reverse=True) - suits = [suit for (face, suit) in hand] - - duplicities = [(facevalues.count(k), k) for k in xrange(1, 14)] - duplicities.sort(reverse=True) - counts = [count for (count, k) in duplicities] - faces = [k for (count, k) in duplicities] - ### Check for flush - if suits == [0]*5: - flush = True - elif suits == [1]*5: - flush = True - elif suits == [2]*5: - flush = True - elif suits == [3]*5: - flush = True - else: - flush = False - - ### Check for straight - if (max(counts) == 1 and max(facevalues)-min(facevalues) == 4) or counts == [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]: - straight = True - else: - straight = False - - if flush and not straight: - return (5,)+tuple(faces) - elif straight and not flush: - return (4,)+tuple(faces) - elif flush and counts == [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1]: - return (9,)+tuple(faces) - elif flush and straight: - return (8,)+tuple(faces) - - if 3 in counts and 2 in counts: - return (6,)+tuple(faces) - - if 4 in counts: - return (7,)+tuple(faces) - - if 3 in counts: - return (3,)+tuple(faces) - - if counts.count(2) == 2: - return (2,)+tuple(faces) - - if 2 in counts: - return (1,)+tuple(faces) - - return (0,)+tuple(faces) @@ -7,72 +7,53 @@ import time class SED(object): def __init__(self, expiry=1800): self.__name__ = "SED Bot" - self.__version__ = "0.0.1" + self.__version__ = "0.0.2" self.expiry = expiry self.history = [] self.pattern = r"^!?s([,/#])((?:.|\\\1)*)\1((?:.|\\\1)*)\1([ig]*)$" - def onRecv(self, IRC, line, data): - if data is None: - return - self.replace(IRC, *data) + def onChanMsg(self, IRC, user, channel, targetprefix, msg): + matches = re.findall(self.pattern, msg) + if matches: + separator, find, replace, flags = matches[0] + find = re.sub("\\\\([,/#\\\\])", "\\1", find) + replace = re.sub("\\\\(,/#\\\\)", "\\1", replace) + match = False + for t, IRC2, user2, channel2, msg2, isaction in self.history.__reversed__(): + if channel != channel2: + continue + try: + if re.findall(find, msg2): + sub = re.sub(find, replace, msg2, flags=re.I if "i" in flags else 0) + match = True + else: + continue + except: + channel.msg("%s: Invalid syntax" % user.nick, origin=self) + raise + if isaction: + channel.msg("What %s really meant was: *%s %s" % (user2.nick, user2.nick, sub), origin=self) + else: + channel.msg("What %s really meant to say was: %s" % + (user2.nick, sub), origin=self) + break + if not match: + channel.msg("%s: I tried. I really tried! But I could not find the pattern: %s" % (user.nick, find), origin=self) + else: + self.history.append((time.time(), IRC, user, channel, msg, False)) + while len(self.history) and self.history[0][0] < time.time()-1800: + del self.history[0] - def onSend(self, IRC, line, data, origin): - if origin == self: - return - #print data - (cmd, target, params, extinfo) = data - if IRC.identity: - self.replace(IRC, IRC.identity.nick, - IRC.identity.idnt, IRC.identity.host, *data) + def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg): + if origin != self: # Ignore messages sent from THIS addon. + self.onChanMsg(IRC, IRC.identity, channel, targetprefix, msg) - def replace(self, IRC, origin, ident, host, cmd, target, params, extinfo): - ### Clear out old data that has expired. - while len(self.history) and self.history[0][0] < time.time()-self.expiry: + def onChanAction(self, IRC, user, channel, targetprefix, action): + self.history.append((time.time( + ), IRC, user, channel, targetprefix, action, True)) + while len(self.history) and self.history[0][0] < time.time()-1800: del self.history[0] - if len(target) and target[0] == "#" and cmd == "PRIVMSG": - target = IRC.channel(target) - matches = re.findall(self.pattern, extinfo) - if matches: - separator, find, replace, flags = matches[0] - #print matches - find = re.sub("\\\\([,/#\\\\])", "\\1", find) - replace = re.sub("\\\\(,/#\\\\)", "\\1", replace) - #print find, replace - match = False - #print self.history - #print find - #print replace - for t, IRC2, (origin2, ident2, host2, cmd2, target2, params2, extinfo2) in self.history.__reversed__(): - #print target, target2, origin2, extinfo2 - if target != target2: - continue - action = re.findall("^\x01ACTION\\s+(.*)\x01$", extinfo2) - #print action - if action: - try: - if re.findall(find, action[0]): - sub = re.sub(find, replace, action[0], flags=re.I if "i" in flags else 0) - target.msg("What %s really meant was: *%s %s" % (origin2, origin2, sub), origin=self) - match = True - break - except: - target.msg("%s: Invalid syntax" % (origin), - origin=self) - raise - else: - try: - if re.findall(find, extinfo2): - sub = re.sub(find, replace, extinfo2, flags=re.I if "i" in flags else 0) - target.msg("What %s really meant to say was: %s" % (origin2, sub), origin=self) - match = True - break - except: - target.msg("%s: Invalid syntax" % (origin), - origin=self) - raise - if not match: - target.msg("%s: I tried. I really tried! But I could not find the pattern: %s" % (origin, find), origin=self) - else: - self.history.append((time.time(), IRC, (origin, ident, - host, cmd, target, params, extinfo))) + + def onSendChanAction(self, IRC, origin, channel, targetprefix, action): + if origin != self: # Ignore messages sent from THIS addon. + self.onChanAction(IRC, IRC.identity, channel, targetprefix, action) diff --git a/startirc.py b/startirc.py index 554c6b3..c3a6cec 100755 --- a/startirc.py +++ b/startirc.py @@ -20,21 +20,21 @@ networks = {} def quit(quitmsg="Goodbye!"): global networks - modules = [] + addons = [] for IRC in networks.values(): if IRC.isAlive(): IRC.quit(quitmsg) while any([IRC.isAlive() for IRC in networks.values()]): time.sleep(0.25) for IRC in networks.values(): - for module in list(IRC.modules): - IRC.rmModule(module) - if module not in modules: - modules.append(module) - for module in modules: - if "stop" in dir(module) and callable(module.stop) and "isAlive" in dir(module) and callable(module.isAlive) and module.isAlive(): + for addon in list(IRC.addons): + IRC.rmAddon(addon) + if addon not in addons: + addons.append(addon) + for addon in addons: + if "stop" in dir(addon) and callable(addon.stop) and "isAlive" in dir(addon) and callable(addon.isAlive) and addon.isAlive(): try: - module.stop() + addon.stop() except: pass print "Goodbye!" @@ -53,21 +53,22 @@ signal.signal(signal.SIGTERM, sigterm) logroot = os.path.join(os.environ["HOME"], "IRC") -insomnialog = open(os.path.join(logroot, "insomnia.log"), "a") InsomniaIRC = networks["InsomniaIRC"] = irc.Connection( - server="perseus.insomniairc.net", ipv6=False, ssl=True, log=insomnialog) + server="perseus.insomniairc.net", ipv6=False, ssl=True, log=open("/dev/null", "w")) ax = autoexec.Autoexec() log = logger.Logger(logroot) + +### Be sure to generate your own cert.pem and key.pem files! BNC = bouncer.Bouncer( - "", 16698, ssl=True, certfile="cert.pem", keyfile="key.pem") + "", 16698, ssl=True, certfile="cert.pem", keyfile="key.pem", autoaway="I'm off to see the wizard!") for (label, IRC) in networks.items(): - IRC.addModule(log, label=label) + IRC.addAddon(log, label=label) ### The password is 'hunter2' - IRC.addModule(BNC, label=label, passwd="6b97ed68d14eb3f1aa959ce5d49c7dc612e1eb1dafd73b1e705847483fd6a6c809f2ceb4e8df6ff9984c6298ff0285cace6614bf8daa9f0070101b6c89899e22", hashtype="sha512") + IRC.addAddon(BNC, label=label, passwd="6b97ed68d14eb3f1aa959ce5d49c7dc612e1eb1dafd73b1e705847483fd6a6c809f2ceb4e8df6ff9984c6298ff0285cace6614bf8daa9f0070101b6c89899e22", hashtype="sha512") -InsomniaIRC.addModule(ax, label="InsomniaIRC", autojoin=["#chat"]) +InsomniaIRC.addAddon(ax, label="InsomniaIRC", autojoin=["#chat"]) for (label, IRC) in networks.items(): - IRC.start() + IRC.start()
\ No newline at end of file |