diff options
-rw-r--r-- | autoexec.py | 8 | ||||
-rw-r--r-- | bouncer.py | 1435 | ||||
-rw-r--r-- | cannon.py | 15 | ||||
-rw-r--r-- | figlet.py | 1 | ||||
-rw-r--r-- | irc.conf | 48 | ||||
-rw-r--r-- | irc.py | 3198 | ||||
-rw-r--r-- | logger.py | 353 | ||||
-rw-r--r-- | modjson.py | 835 | ||||
-rw-r--r-- | quote.py | 2 | ||||
-rwxr-xr-x | startirc.py | 6 |
10 files changed, 3836 insertions, 2065 deletions
diff --git a/autoexec.py b/autoexec.py index a699099..501ec2c 100644 --- a/autoexec.py +++ b/autoexec.py @@ -26,11 +26,12 @@ class Autoexec(object): if context in self.networks.keys(): raise BaseException, "Network already exists" self.networks[context] = irc.Config( - label=label, onconnect=onconnect, onregister=onregister, autojoin=irc.ChanList( + self, label=label, onconnect=onconnect, onregister=onregister, autojoin=irc.ChanList( autojoin, context=context), usermodes=usermodes, nsautojoin=irc.ChanList(nsautojoin, context=context), nsmatch=nsmatch, wallet=wallet, opername=opername, opermodes=opermodes, snomasks=snomasks, operexec=operexec, operjoin=irc.ChanList(operjoin, context=context), autorejoin=autorejoin) self._rejoinchannels[context] = None + return self.networks[context] def onDisconnect(self, context, expected): conf = self.networks[context] @@ -38,6 +39,11 @@ class Autoexec(object): self._rejoinchannels[context] = irc.ChanList( context.identity.channels, context=context) # Store a *copy* of the list of channels + def onQuit(self, context, user, quitmsg): + if user == context.identity and not context._quitexpected: + # Bot received a QUIT message for itself, and was not expected. + self.onDisconnect(context, False) + def onAddonRem(self, context): del self.networks[context], self._rejoinchannels[context] @@ -10,19 +10,27 @@ import hashlib import traceback import irc import getpass -from threading import Thread, Lock +from threading import Thread +from threading import RLock as Lock import Queue import chardet # TODO: Rewrite this *entire* module and make more efficient. +_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")) + def BouncerReload(BNC): if BNC.isAlive(): BNC.stop() - if BNC.__version__ == "1.2": + if BNC.__version__ == "1.3": newBNC = Bouncer( - addr=BNC.addr, port=BNC.port, ssl=BNC.ssl, ipv6=BNC.ipv6, + addr=BNC.addr, port=BNC.port, secure=BNC.ssl, ipv6=BNC.ipv6, certfile=BNC.certfile, keyfile=BNC.keyfile, timeout=BNC.timeout, autoaway=BNC.autoaway) for label, (context, passwd, hashtype) in BNC.servers.items(): context.rmAddon(BNC) @@ -37,36 +45,30 @@ def BouncerReload(BNC): class Bouncer (Thread): + __name__ = "Bouncer for pyIRC" + __version__ = "2.0" + __author__ = "Brian Sherson" + __date__ = "February 21, 2014" - def __init__(self, addr="", port=16667, ssl=False, ipv6=False, certfile=None, keyfile=None, ignore=None, debug=False, timeout=300, autoaway=None): - self.__name__ = "Bouncer for pyIRC" - self.__version__ = "1.3" - self.__author__ = "Brian Sherson" - self.__date__ = "February 9, 2014" - self.__options__ = dict( - addr=addr, port=port, ssl=ssl, ipv6=ipv6, certfile=certfile, - keyfile=keyfile, ignore=ignore, debug=debug, timeout=timeout, autoaway=autoaway) - + def __init__(self, addr="", port=16667, secure=False, ipv6=False, certfile=None, keyfile=None, ignore=None, debug=False, timeout=300, autoaway=None, servname="bouncer.site"): self.addr = addr self.port = port self.conf = {} self.passwd = {} - self.socket = socket.socket( - socket.AF_INET6 if ipv6 else socket.AF_INET) - self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket = None self.ssl = ssl self.ipv6 = ipv6 self.certfile = certfile self.keyfile = keyfile - self.socket.bind((self.addr, self.port)) - self.connections = [] + self.clients = [] self.ignore = ignore self.debug = debug self.timeout = timeout self.autoaway = autoaway + self.servname = servname self._stopexpected = False - # Keep track of what extensions/connections are requesting WHO, WHOIS, and LIST, because we don't want to spam every bouncer connection with the server's replies. + # Keep track of what extensions/clients are requesting WHO, WHOIS, and LIST, because we don't want to spam every bouncer connection with the server's replies. # In the future, MAY implement this idea in the irc module. self._whoexpected = {} self._whoisexpected = {} @@ -78,15 +80,20 @@ class Bouncer (Thread): self.start() def __repr__(self): - return "<Bouncer listening on port %(addr)s:%(port)s>" % vars(self) + h = hash(self) + return "<Bouncer listening on {self.addr}:{self.port} at 0x{h:x}0>".format(**vars()) def run(self): + self.socket = socket.socket( + socket.AF_INET6 if self.ipv6 else socket.AF_INET) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + self.socket.bind((self.addr, self.port)) self.socket.listen(5) #print ((self,"Now listening on port "+str(self.port))) while True: try: (connection, addr) = self.socket.accept() - if self.ssl: + if self.secure: connection = ssl.wrap_socket( connection, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23) #print ((self,"New client connecting from %s:%s"%addr)) @@ -108,6 +115,9 @@ class Bouncer (Thread): self.socket.close() except: pass + self.socket = None + Thread.__init__(self) + self.daemon = True def onAddonAdd(self, context, label, passwd=None, hashtype="sha512", ignore=None, autoaway=None, translations=None, hidden=None): for (context2, conf2) in self.conf.items(): @@ -123,7 +133,7 @@ class Bouncer (Thread): print "Passwords do not match!" passwd = hashlib.new(hashtype, passwd).hexdigest() conf = irc.Config( - label=label, passwd=passwd, hashtype=hashtype, ignore=ignore, autoaway=autoaway, + self, label=label, passwd=passwd, hashtype=hashtype, ignore=ignore, autoaway=autoaway, translations={} if translations == None else translations, hidden=irc.ChanList(hidden, context=context)) self.conf[context] = conf self._whoexpected[context] = [] @@ -132,139 +142,114 @@ class Bouncer (Thread): "dbg [Bouncer.onAddonAdd] Clearing WHO expected list." % vars()) self._whoisexpected[context] = [] self._listexpected[context] = [] + return conf def onAddonRem(self, context): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - bouncerconnection.quit(quitmsg="Bouncer extension removed") + for client in self.clients: + if client.context == context: + client.quit(quitmsg="Bouncer extension removed") del self.conf[context] del self._whoexpected[context], self._whoisexpected[ context], self._listexpected[context] - def stop(self): + def stop(self, disconnectall=False): self._stopexpected = True self.socket.shutdown(0) + if disconnectall: + self.disconnectall() def disconnectall(self, quitmsg="Disconnecting all sessions"): - for bouncerconnection in self.connections: - bouncerconnection.quit(quitmsg=quitmsg) + for client in self.clients: + client.quit(quitmsg=quitmsg) def onDisconnect(self, context, expected=False): self._whoexpected[context] = [] self._whoisexpected[context] = [] self._listexpected[context] = [] - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - #bouncerconnection.quit(quitmsg="context connection lost") - if context.identity: - for channel in context.identity.channels: - bouncerconnection.send(":%s!%s@%s PART %s :Bouncer Connection Lost\n" % ( - context.identity.nick, context.identity.username, context.identity.host, channel.name)) - bouncerconnection.send(":%s!%s@%s QUIT :Bouncer Connection Lost\n" % ( - context.identity.nick, context.identity.username, context.identity.host)) - bouncerconnection.send( - ":*Bouncer* NOTICE %s :Connection to %s:%s has been lost.\n" % - (bouncerconnection.nick, context.server, context.port)) + if context.identity: + for channel in context.identity.channels: + self.broadcast(context, origin=context.identity, cmd="PART", target=channel, extinfo="Bouncer Connection Lost", clients=[ + client for client in self.clients if channel not in client.hidden]) + self.broadcast(context, origin=context.identity, + cmd="QUIT", extinfo="Bouncer Connection Lost") + self.broadcast( + context, origin=self.servname, cmd="NOTICE", target=context.identity, + extinfo=":Connection to %s:%s has been lost." % (context.server, context.port)) def onQuit(self, context, user, quitmsg): # For some odd reason, certain networks (*cough*Freenode*cough*) will send a quit message for the user, causing context.identity.channels to be cleared # before onDisconnect can be executed. This is the remedy. - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - if quitmsg: - bouncerconnection.send(":%s!%s@%s QUIT :%s\n" % ( - user.nick, user.username, user.host, quitmsg)) - else: - bouncerconnection.send( - ":%s!%s@%s QUIT\n" % (user.nick, user.username, user.host)) - if user == context.identity: - for channel in context.identity.channels: - bouncerconnection.send(":%s!%s@%s PART %s :Bouncer Connection Lost\n" % ( - context.identity.nick, context.identity.username, context.identity.host, channel.name)) + if user == context.identity: + for channel in context.identity.channels: + self.broadcast(context, origin=user, cmd="PART", target=channel, extinfo="Bouncer Connection Lost", clients=[ + client for client in self.clients if channel not in client.hidden]) + self.broadcast(context, origin=user, cmd="QUIT", extinfo=quitmsg, clients=[ + client for client in self.clients if any([user in channel for channel in context.channels if channel not in client.hidden])]) def onConnectAttempt(self, context): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - bouncerconnection.send( - ":*Bouncer* NOTICE %s :Attempting connection to %s:%s.\n" % - (bouncerconnection.nick, context.server, context.port)) + self.broadcast( + context, origin=self.servname, cmd="NOTICE", target=context.identity, + extinfo="Attempting connection to %s:%s." % (context.server, context.port)) def onConnect(self, context): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - bouncerconnection.send( - ":*Bouncer* NOTICE %s :Connection to %s:%s established.\n" % - (bouncerconnection.nick, context.server, context.port)) + self.broadcast( + context, origin=self.servname, cmd="NOTICE", target=context.identity, + extinfo="Connection to %s:%s established." % (context.server, context.port)) def onMeNickChange(self, context, newnick): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - bouncerconnection.send(":%s!%s@%s NICK %s\n" % - (context.identity.nick, context.identity.username, context.identity.host, newnick)) - bouncerconnection.nick = newnick + for client in self.clients: + if client.context == context: + client.send( + origin=context.identity, cmd="NICK", target=newnick) + client.nick = newnick def onNickChange(self, context, user, newnick): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - bouncerconnection.send(":%s!%s@%s NICK %s\n" % - (user.nick, user.username, user.host, newnick)) - bouncerconnection.nick = newnick + self.broadcast(context, origin=user, cmd="NICK", target=newnick, clients=[ + client for client in self.clients if any([user in channel for channel in context.channels if channel not in client.hidden])]) def onRegistered(self, context): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - if bouncerconnection.nick != context.identity.nick: - bouncerconnection.send(":%s!%s@%s NICK %s\n" % ( - bouncerconnection.nick, bouncerconnection.username, bouncerconnection.host, context.identity.nick)) - bouncerconnection.nick = context.identity.nick + for client in self.clients: + if client.context == context: + if client.nick != context.identity.nick: + client.send(origin="%s!%s@%s" % + (client.nick, client.username, client.host), cmd="NICK", target=context.identity.nick) + client.nick = context.identity.nick def onConnectFail(self, context, exc, excmsg, tb): - for bouncerconnection in self.connections: - if bouncerconnection.context == context: - bouncerconnection.send( - ":*Bouncer* NOTICE %s :Connection to %s:%s failed: %s.\n" % - (bouncerconnection.nick, context.server, context.port, excmsg)) + for client in self.clients: + if client.context == context: + client.send( + origin=self.servname, cmd="NOTICE", target=client.nick, + extinfo="Connection to %s:%s failed: %s." % (context.server, context.port, excmsg)) def onSendChanMsg(self, context, 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. - conf = self.conf[context] - if channel in conf.translations.keys(): - channame = conf.translations[channel] - else: - channame = channel.name - for bouncerconnection in self.connections: - if context == bouncerconnection.context and origin != bouncerconnection and channel not in bouncerconnection.hidden: - bouncerconnection.send(":%s!%s@%s PRIVMSG %s%s :%s\n" % ( - context.identity.nick, context.identity.username, context.identity.host, targetprefix, channame, msg)) + self.broadcast( + context, origin=context.identity, cmd="PRIVMSG", targetprefix=targetprefix, + target=channel, extinfo=msg, clients=[client for client in self.clients if client != origin]) def onSendChanAction(self, context, origin, channel, targetprefix, action): self.onSendChanMsg( - context, origin, channel, targetprefix, "\x01ACTION %s\x01" % action) + context, origin, channel, targetprefix, u"\x01ACTION {action}\x01".format(**vars())) def onSendChanNotice(self, context, 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. - conf = self.conf[context] - if channel in conf.translations.keys(): - channame = conf.translations[channel] - else: - channame = channel.name - for bouncerconnection in self.connections: - if context == bouncerconnection.context and origin != bouncerconnection: - bouncerconnection.send(":%s!%s@%s NOTICE %s%s :%s\n" % - (context.identity.nick, context.identity.username, context.identity.host, targetprefix, channame, msg)) + self.broadcast( + context, origin=context.identity, cmd="NOTICE", targetprefix=targetprefix, + target=channel, extinfo=msg, clients=[client for client in self.clients if client != origin]) - def onSend(self, context, origin, line, cmd, target, params, extinfo): + def onSend(self, context, origin, line, cmd, target, targetprefix, params, extinfo): if cmd.upper() == "WHO": self._whoexpected[context].append(origin) if self.debug: if issubclass(type(origin), Thread): name = origin.name context.logwrite( - "dbg [Bouncer.onSend] Adding %(origin)s (%(name)s) to WHO expected list." % vars()) + "dbg [Bouncer.onSend] Adding {origin} ({name}) to WHO expected list.".format(**vars())) else: context.logwrite( "dbg [Bouncer.onSend] Adding %(origin)s to WHO expected list." % vars()) @@ -278,26 +263,19 @@ class Bouncer (Thread): def onWhoEntry(self, context, origin, channel, user, channame, username, host, serv, nick, flags, hops, realname): # Called when a WHO list is received. conf = self.conf[context] - if context.channel(channame) in conf.translations.keys(): - channame = conf.translations[context.channel(channame)] - if len(self._whoexpected[context]) and self._whoexpected[context][0] in self.connections: - bncconnection = self._whoexpected[context][0] - bncconnection.send(":%s 352 %s %s %s %s %s %s %s :%s %s\n" % - (origin, context.identity.nick, channame, username, host, serv, nick, flags, hops, realname)) + if len(self._whoexpected[context]) and self._whoexpected[context][0] in self.clients: + client = self._whoexpected[context][0] + client.send(origin=origin, cmd=352, target=context.identity, params=u"{channame} {username} {host} {serv} {nick} {flags}".format( + **vars()), extinfo=u"{hops} {realname}".format(**vars())) + # client.send(":%s 352 %s %s %s %s %s %s %s :%s %s\n"%(origin, context.identity.nick, channame, username, host, serv, nick, flags, hops, realname)) def onWhoEnd(self, context, origin, param, endmsg): # Called when a WHO list is received. - try: - conf = self.conf[context] - chantypes = context.supports.get("CHANTYPES", "&#+!") - if re.match(irc._chanmatch % re.escape(chantypes), param) and context[param] in conf.translations.keys(): - param = conf.translations[context.channel(param)] - except: - pass - if len(self._whoexpected[context]) and self._whoexpected[context][0] in self.connections: - bncconnection = self._whoexpected[context][0] - bncconnection.send(":%s 315 %s %s :%s\n" % - (origin, context.identity.nick, param, endmsg)) + if len(self._whoexpected[context]) and self._whoexpected[context][0] in self.clients: + client = self._whoexpected[context][0] + client.send( + origin=origin, cmd=315, target=context.identity, params=param, extinfo=endmsg) + #client.send(":%s 315 %s %s :%s\n"%(origin, context.identity.nick, param, endmsg)) if self.debug: if issubclass(type(self._whoexpected[context][0]), Thread): name = self._whoexpected[context][0].name @@ -315,10 +293,11 @@ class Bouncer (Thread): def onListStart(self, context, origin, params, extinfo): # Called when a WHO list is received. - if len(self._listexpected[context]) and self._listexpected[context][0] in self.connections: - bncconnection = self._listexpected[context][0] - bncconnection.send(":%s 321 %s %s :%s\n" % - (origin, context.identity.nick, params, extinfo)) + if len(self._listexpected[context]) and self._listexpected[context][0] in self.clients: + client = self._listexpected[context][0] + client.send(origin=origin, cmd=321, + target=context.identity, params=params, extinfo=extinfo) + #client.send(":%s 321 %s %s :%s\n"%(origin, context.identity.nick, params, extinfo)) def onListEntry(self, context, origin, channel, population, extinfo): # Called when a WHO list is received. @@ -327,161 +306,138 @@ class Bouncer (Thread): channame = conf.translations[channel] else: channame = channel.name - if len(self._listexpected[context]) and self._listexpected[context][0] in self.connections: - bncconnection = self._listexpected[context][0] - bncconnection.send(":%s 322 %s %s %d :%s\n" % - (origin, context.identity.nick, channame, population, extinfo)) + if len(self._listexpected[context]) and self._listexpected[context][0] in self.clients: + client = self._listexpected[context][0] + client.send(origin=origin, cmd=322, target=context.identity, + params=u"{channame} {population}".format(**vars()), extinfo=extinfo) + # client.send(":%s 322 %s %s %d :%s\n"%(origin, context.identity.nick, channame, population, extinfo)) def onListEnd(self, context, origin, endmsg): # Called when a WHO list is received. - if len(self._listexpected[context]) and self._listexpected[context][0] in self.connections: - bncconnection = self._listexpected[context][0] - bncconnection.send(":%s 323 %s :%s\n" % - (origin, context.identity.nick, endmsg)) + if len(self._listexpected[context]) and self._listexpected[context][0] in self.clients: + client = self._listexpected[context][0] + client.send( + origin=origin, cmd=323, target=context.identity, extinfo=endmsg) + # client.send(":%s 323 %s :%s\n"%(origin, context.identity.nick, endmsg)) del self._listexpected[context][0] def onWhoisStart(self, context, origin, user, nickname, username, host, realname): # Called when a WHOIS reply is received. - if len(self._whoisexpected[context]): - if self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 311 %s %s %s %s * :%s\n" % - (origin, context.identity.nick, nickname, username, host, realname)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=311, target=context.identity, + params=u"{nickname} {username} {host} *".format(**vars()), extinfo=realname) + # client.send(":%s 311 %s %s %s %s * :%s\n" % (origin, context.identity.nick, nickname, username, host, realname)) def onWhoisRegisteredNick(self, context, origin, user, nickname, msg): # Called when a WHOIS reply is received. - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 307 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send( + origin=origin, cmd=307, target=context.identity, params=nickname, extinfo=msg) + # client.send(":%s 307 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg)) def onWhoisConnectingFrom(self, context, origin, user, nickname, msg): # Called when a WHOIS reply is received. - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 378 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=378, + target=context.identity, params=nickname, extinfo=msg) + # client.send(":%s 378 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg)) def onWhoisChannels(self, context, origin, user, nickname, chanlist): # Called when a WHOIS reply is received. # TODO: Translations implementation - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 319 %s %s :%s\n" % - (origin, context.identity.nick, nickname, " ".join(chanlist))) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=319, target=context.identity, + params=nickname, extinfo=" ".join(chanlist)) + # client.send(":%s 319 %s %s :%s\n" % (origin, context.identity.nick, nickname, " ".join(chanlist))) def onWhoisAvailability(self, context, origin, user, nickname, msg): # Called when a WHOIS reply is received. - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 310 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send( + origin=origin, cmd=310, target=context.identity, params=nickname, extinfo=msg) + # client.send(":%s 310 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg)) def onWhoisServer(self, context, origin, user, nickname, server, servername): # Called when a WHOIS reply is received. - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 312 %s %s %s :%s\n" % - (origin, context.identity.nick, nickname, server, servername)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=312, target=context.identity, + params=u"{nickname} {server}".format(**vars()), extinfo=servername) + # client.send(":%s 312 %s %s %s :%s\n" % (origin, context.identity.nick, nickname, server, servername)) def onWhoisOp(self, context, origin, user, nickname, msg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 313 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send( + origin=origin, cmd=313, target=context.identity, params=nickname, extinfo=msg) + # client.send(":%s 313 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg)) def onWhoisAway(self, context, origin, user, nickname, awaymsg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 301 %s %s :%s\n" % - (origin, context.identity.nick, nickname, awaymsg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=301, target=context.identity, + params=u"{nickname} {idletime} {signontime}".format(**vars()), extinfo=awaymsg) + # client.send(":%s 301 %s %s :%s\n" % (origin, context.identity.nick, nickname, awaymsg)) def onWhoisTimes(self, context, origin, user, nickname, idletime, signontime, msg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 317 %s %s %d %d :%s\n" % - (origin, context.identity.nick, nickname, idletime, signontime, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=317, target=context.identity, + params=u"{nickname} {idletime} {signontime}".format(**vars()), extinfo=msg) + # client.send(":%s 317 %s %s %d %d :%s\n" % (origin, context.identity.nick, nickname, idletime, signontime, msg)) def onWhoisSSL(self, context, origin, user, nickname, msg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 671 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=671, + target=context.identity, params=nickname, extinfo=msg) + # client.send(":%s 671 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg)) def onWhoisModes(self, context, origin, user, nickname, msg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 339 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send( + origin=origin, cmd=339, target=context.identity, params=nickname, extinfo=msg) + # ":%s 339 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg)) def onWhoisLoggedInAs(self, context, origin, user, nickname, loggedinas, msg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 330 %s %s %s :%s\n" % - (origin, context.identity.nick, nickname, loggedinas, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=330, target=context.identity, + params=" ".join((nickname, loggedinas)), extinfo=msg) + # ":%s 330 %s %s %s :%s\n" % (origin, context.identity.nick, nickname, loggedinas, msg)) def onWhoisEnd(self, context, origin, user, nickname, msg): - if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.connections: - bncconnection = self._whoisexpected[context][0] - bncconnection.send(":%s 318 %s %s :%s\n" % - (origin, context.identity.nick, nickname, msg)) + if len(self._whoisexpected[context]) and self._whoisexpected[context][0] in self.clients: + client = self._whoisexpected[context][0] + client.send(origin=origin, cmd=318, + target=context.identity, params=nickname, extinfo=msg) + # ":%s 318 %s %s :%s\n" % (origin, context.identity.nick, nickname, msg) del self._whoisexpected[context][0] def onJoin(self, context, user, channel): - conf = self.conf[context] - if channel in conf.translations.keys(): - channame = conf.translations[channel] - else: - channame = channel.name - line = ":%s!%s@%s JOIN %s" % ( - user.nick, user.username, user.host, channame) - for bouncerconnection in self.connections: - if bouncerconnection.context == context and channel not in bouncerconnection.hidden: - bouncerconnection.send("%s\n" % line) + self.broadcast(context, origin=user, cmd="JOIN", target=channel, clients=[ + client for client in self.clients if channel not in client.hidden]) def onUnhandled(self, context, line, origin, cmd, target, params, extinfo, targetprefix): conf = self.conf[context] + self.broadcast( + context, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo, + targetprefix=targetprefix, clients=[client for client in self.clients if target not in client.hidden]) - if type(origin) == irc.User: - origin = "%s!%s@%s" % (origin.nick, origin.username, origin.host) - - if target in conf.translations.keys(): - target = conf.translations[target] - - chantarg = None - - if type(target) == irc.User: - target = target.nick - elif type(target) == irc.Channel: - chantarg = target - target = target.name - - if params: # Channels which appear in params - oldparams = params - params = [] - for param in oldparams.split(): - chantypes = context.supports.get("CHANTYPES", "&#+!") - if re.match(irc._chanmatch % re.escape(chantypes), param) and context[param] in conf.translations.keys(): - params.append(conf.translations[context[param]]) - else: - params.append(param) - - if target: - if type(cmd) == int: - cmd = "%03d" % cmd - if params and extinfo: - line = ":%s %s %s %s :%s" % ( - origin, cmd, target, " ".join(params), extinfo) - elif params: - line = ":%s %s %s %s" % (origin, cmd, target, " ".join(params)) - elif extinfo: - line = ":%s %s %s :%s" % (origin, cmd, target, extinfo) - else: - line = ":%s %s %s" % (origin, cmd, target) - - for bouncerconnection in self.connections: - if bouncerconnection.context == context and chantarg not in bouncerconnection.hidden: - bouncerconnection.send("%s\n" % line) + def broadcast(self, context, origin=None, cmd=None, target=None, params=None, extinfo=None, targetprefix=None, clients=None): + if clients == None: + clients = self.clients + for client in clients: + with client.lock: + if client.context == context and not client.quitting: + client.send( + origin, cmd, target, params, extinfo, targetprefix) class BouncerConnection (Thread): @@ -503,49 +459,94 @@ class BouncerConnection (Thread): self.quitmsg = "Connection Closed" self.quitting = False self.hidden = irc.ChanList() + self.translations = {} + self.namesx = False + self.uhnames = False Thread.__init__(self) self.daemon = True self.start() - def send(self, data, flags=0): - try: - with self.lock: + def sendstr(self, data, flags=0): + with self.lock: + try: self.connection.send(data.encode("utf8")) - except socket.error: - exc, excmsg, tb = sys.exc_info() - print >>self.context.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) + except socket.error: + exc, excmsg, tb = sys.exc_info() + print >>self.context.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) + + # Format and send a string to the client + def send(self, origin=None, cmd=None, target=None, params=None, extinfo=None, targetprefix=None, flags=0): + if type(target) == irc.Channel: + if targetprefix == None: + targetprefix = "" + if target in self.translations.keys(): + target = targetprefix + self.translations[target] + else: + target = targetprefix + target.name + elif type(target) == irc.User: + target = target.nick - def __repr__(self): - server = self.context.server if self.context else "*" - port = self.context.port if self.context else "*" - if self.context and self.context.identity: - nick = self.context.identity.nick - ident = self.context.identity.username if self.context.identity.username else "*" - host = self.context.identity.host if self.context.identity.host else "*" - else: - nick = "*" - ident = "*" - host = "*" - if self.context.ssl and self.context.ipv6: - protocol = "ircs6" - elif self.context.ssl: - protocol = "ircs" - elif self.context.ipv6: - protocol = "irc6" + if type(cmd) == int: + cmd = "%03d" % cmd + + translated = [] + if params: + for param in params.split(" "): + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + if re.match(irc._chanmatch % re.escape(chantypes), param) and self.context[param] in self.translations.keys(): + translated.append(self.translations[self.context[param]]) + else: + translated.append(param) + params = " ".join(translated) + + if params: + line = u"{cmd} {target} {params}".format(**vars()) + elif target: + line = u"{cmd} {target}".format(**vars()) else: - protocol = "irc" - addr = self.host - return "<Bouncer connection from %(addr)s to %(nick)s!%(ident)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals() + line = cmd + + if extinfo != None: + line = u"{line} :{extinfo}".format(**vars()) + + if type(origin) == irc.User: + line = u":{origin:full} {line}".format(**vars()) + elif origin: + line = u":{origin} {line}".format(**vars()) + self.sendstr(u"{line}\n".format(**vars())) + + #server=self.context.server if self.context else "*" + #port=self.context.port if self.context else "*" + # if self.context and self.context.identity: + # nick=self.context.identity.nick + #ident=self.context.identity.username if self.context.identity.username else "*" + #host=self.context.identity.host if self.context.identity.host else "*" + # else: + # nick="*" + # ident="*" + # host="*" + # if self.context.ssl and self.context.ipv6: + # protocol="ircs6" + # elif self.context.ssl: + # protocol="ircs" + # elif self.context.ipv6: + # protocol="irc6" + # else: + # protocol="irc" + # addr=self.host + def __repr__(self): + return "<Bouncer connection from {self.host} to {self.context.identity} on {self.context:uri}>".format(**vars()) def quit(self, quitmsg="Disconnected"): - if not self.quitting: - self.quitmsg = quitmsg - with self.lock: + with self.lock: + if not self.quitting: + self.quitmsg = quitmsg try: - self.connection.send("ERROR :Closing link: (%s@%s) [%s]\n" % ( + self.send(cmd="ERROR", extinfo="Closing link: (%s@%s) [%s]\n" % ( self.context.identity.nick if self.context else "*", self.host, quitmsg)) except: pass @@ -556,6 +557,185 @@ class BouncerConnection (Thread): pass self.quitting = True + def showchannel(self, channel): + with self.context.lock, self.lock: + if channel in self.hidden: + self.hidden.remove(channel) + if self.context.identity in channel.users: + self.send( + origin=self.context.identity, cmd="JOIN", target=channel) + self.sendchanneltopic(channel) + self.sendchannelnames(channel) + + def sendchanneltopic(self, channel): + with self.context.lock, self.lock: + if channel.topic and channel.topictime: + self.send(origin=self.bouncer.servname, cmd=332, + target=self.context.identity, params=channel.name, extinfo=channel.topic) + # u":{self.context.serv} 332 {self.context.identity.nick} {self.name} :{self.topic}".format(**vars()) + self.send( + origin=self.bouncer.servname, cmd=333, target=self.context.identity, + params="{channel.name} {channel.topicsetby} {channel.topictime}".format(**vars())) + # u":{self.context.serv} 333 {self.context.identity.nick} {self.name} {self.topicsetby.nick} {self.topictime}".format(**vars()) + else: + self.send(origin=self.bouncer.servname, cmd=331, + target=self.context.identity, params=channel.name, extinfo="No topic is set") + # u":{self.context.serv} 331 {self.context.identity.nick} + # {self.name} :No topic is set".format(**vars())] + + def sendchannelnames(self, channel): + with self.context.lock, self.lock: + secret = "s" in channel.modes.keys() and channel.modes["s"] + private = "p" in channel.modes.keys() and channel.modes["p"] + flag = "@" if secret else ("*" if private else "=") + + modes, symbols = supports = self.context.supports.get( + "PREFIX", irc._defaultprefix) + users = list(channel.users) + users.sort(key=lambda user: ([user not in channel.modes.get(mode, []) + for mode, char in zip(*supports)], user.nick.lower())) + if self.uhnames: + template = u"{prefixes}{user:full}" + else: + template = u"{prefixes}{user}" + + nameslist = [] + for user in users: + prefixes = u"".join( + [prefix if mode in channel.modes.keys() and user in channel.modes[mode] else "" for prefix, mode in zip(symbols, modes)]) + if not self.namesx: + prefixes = prefixes[:1] + nameslist.append(template.format(**vars())) + names = " ".join(nameslist) + + lines = [] + while len(names) > 196: + index = names.rfind(" ", 0, 196) + slice = names[:index] + self.send( + origin=self.bouncer.servname, cmd=353, target=self.context.identity, + params="{flag} {channel.name}".format(**vars()), extinfo=slice) + #u":{channel.context.serv} 353 {channel.context.identity.nick} {flag} {channel.name} :{slice}".format(**vars()) + names = names[index + 1:] + if len(names): + self.send( + origin=self.bouncer.servname, cmd=353, target=self.context.identity, + params="{flag} {channel.name}".format(**vars()), extinfo=names) + #u":{channel.context.serv} 353 {channel.context.identity.nick} {flag} {channel.name} :{names}".format(**vars()) + + self.send( + origin=self.bouncer.servname, cmd=366, target=self.context.identity, + params=channel.name, extinfo="End of /NAMES list.") + # u":{channel.context.serv} 366 {channel.context.identity.nick} {channel.name} :End of /NAMES list.".format(**vars()) + + def sendchannelmodes(self, channel, modechars=None): + with self.context.lock, self.lock: + if modechars: + for mode in modechars: + if mode not in _listnumerics.keys(): + continue + i, e, l = _listnumerics[mode] + if mode in channel.modes.keys(): + for (mask, setby, settime) in channel.modes[mode]: + self.send( + origin=self.bouncer.servname, cmd=i, target=self.context.identity, + params=u"{channel.name} {mask} {setby} {settime}".format(**vars())) + self.send(origin=self.bouncer.servname, cmd=e, + target=self.context.identity, params=u"{channel.name} {l}".format(**vars())) + else: + items = channel.modes.items() + chanmodes = self.context.supports.get( + "CHANMODES", irc._defaultchanmodes) + prefix = self.context.supports.get( + "PREFIX", irc._defaultprefix) + modes = "".join( + [mode for (mode, val) in items if mode not in chanmodes[0] + prefix[0] and val]) + params = " ".join( + [val for (mode, val) in items if mode in chanmodes[1] + chanmodes[2] and val]) + if modes and params: + self.send( + origin=self.bouncer.servname, cmd=324, target=self.context.identity, + params="{channel.name} +{modes} {params}".format(**vars())) + # u":{channel.context.identity.server} 324 {channel.context.identity.nick} {channel.name} +{modes} {params}".format(**vars()) + elif modes: + self.send( + origin=self.bouncer.servname, cmd=324, target=self.context.identity, + params="{channel.name} +{modes}".format(**vars())) + # u":{channel.context.identity.server} 324 {channel.context.identity.nick} {channel.name} +{modes}".format(**vars()) + + def sendsupports(self): + with self.context.lock, self.lock: + 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.context.supports.items()] + if "UHNAMES" not in supports: + supports.append("UHNAMES") + if "NAMESX" not in supports: + supports.append("NAMESX") + supports.sort() + supports = " ".join(supports) + lines = [] + while len(supports) > 196: + index = supports.rfind(" ", 0, 196) + slice = supports[:index] + self.send( + origin=self.bouncer.servname, cmd=5, target=self.context.identity, + params=slice, extinfo="are supported by this server") + # u":{self.context.serv} 005 {self.context.identity.nick} {slice} :are supported by this server".format(**vars()) + supports = supports[index + 1:] + if supports: + self.send( + origin=self.bouncer.servname, cmd=5, target=self.context.identity, + params=supports, extinfo="are supported by this server") + # u":{self.context.serv} 005 {self.context.identity.nick} {supports} :are supported by this server".format(**vars()) + + def sendgreeting(self): + with self.context.lock, self.lock: + if self.context.welcome: + self.send(origin=self.bouncer.servname, cmd=1, + target=self.context.identity, extinfo=self.context.welcome) + # u":{self.context.serv} 001 {self.context.identity.nick} :{self.context.welcome}".format(**vars()) + if self.context.hostinfo: + self.send(origin=self.bouncer.servname, cmd=2, + target=self.context.identity, extinfo=self.context.hostinfo) + # u":{self.context.serv} 002 {self.context.identity.nick} :{self.context.hostinfo}".format(**vars()) + if self.context.servcreated: + self.send(origin=self.bouncer.servname, cmd=3, + target=self.context.identity, extinfo=self.context.servcreated) + # u":{self.context.serv} 003 {self.context.identity.nick} :{self.context.servcreated}".format(**vars()) + if self.context.servinfo: + self.send(origin=self.bouncer.servname, cmd=4, + target=self.context.identity, params=self.context.servinfo) + # u":{self.context.serv} 004 {self.context.identity.nick} {self.context.servinfo}".format(**vars()) + + def sendmotd(self): + with self.context.lock, self.lock: + if self.context.motdgreet and self.context.motd and self.context.motdend: + self.send(origin=self.bouncer.servname, cmd=375, + target=self.context.identity, extinfo=self.context.motdgreet) + # u":{server} 375 {self.identity.nick} :{self.motdgreet}".format(**vars()) + for motdline in self.context.motd: + self.send(origin=self.bouncer.servname, cmd=372, + target=self.context.identity, extinfo=motdline) + # u":{server} 372 {self.identity.nick} :{motdline}".format(**vars()) + self.send(origin=self.bouncer.servname, cmd=376, + target=self.context.identity, extinfo=self.context.motdend) + # u":{server} 376 {self.identity.nick} :{self.motdend}".format(**vars()) + else: + self.send(origin=self.bouncer.servname, cmd=422, + target=self.context.identity, extinfo="MOTD File is missing") + # u":{server} 422 {self.identity.nick} :MOTD File is missing".format(**vars()) + + def sendusermodes(self): + with self.context.lock, self.lock: + self.send( + origin=self.bouncer.servname, cmd=221, target=self.context.identity, + params="+{self.context.identity.modes}".format(**vars())) + if "s" in self.context.identity.modes: + self.send( + origin=self.bouncer.servname, cmd=8, target=self.context.identity, + params="+{self.context.identity.snomask}".format(**vars()), extinfo="Server notice mask") + def run(self): # Name loopup should happen here instead ipv4match = re.findall( @@ -575,13 +755,6 @@ class BouncerConnection (Thread): # 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")) - passwd = None nick = None user = None @@ -596,7 +769,14 @@ class BouncerConnection (Thread): # append lines to linebuf while len(linebuf) == 0: timestamp = irc.timestamp() - read = self.connection.recv(512) + try: + read = self.connection.recv(512) + except socket.error, msg: + self.quit(msg) + sys.exit() + except ssl.SSLError, msg: + self.quit(msg) + sys.exit() if read == "" and len(linebuf) == 0: # No more data to process. #self.quitmsg="Connection Closed" sys.exit() @@ -649,17 +829,10 @@ class BouncerConnection (Thread): contextfound = True break if not contextfound: - with self.context.lock: - self.quit("Access Denied") - self.context.logwrite( - "*** [BouncerConnection] Incoming connection from %s to %s denied: Invalid password." % (self.host, self.context)) - for bouncerconnection in self.bouncer.connections: - if bouncerconnection.context != self.context: - continue - if not bouncerconnection.quitting: - bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s dened: Invalid password.\n" % ( - bouncerconnection.context.identity.nick, self.host, self.context)) - break + self.quit("Access Denied") + print >>sys.stderr, "*** [BouncerConnection] Incoming connection from %s denied: Context not found." % ( + self.host) + break passmatch = hashlib.new( conf.hashtype, passwd).hexdigest() == conf.passwd with self.context.lock: @@ -667,396 +840,130 @@ class BouncerConnection (Thread): self.quit("Access Denied") self.context.logwrite( "*** [BouncerConnection] Incoming connection from %s to %s denied: Invalid password." % (self.host, self.context)) - for bouncerconnection in self.bouncer.connections: - if bouncerconnection.context != self.context: - continue - if not bouncerconnection.quitting: - bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s dened: Invalid password.\n" % ( - bouncerconnection.context.identity.nick, self.host, self.context)) + self.bouncer.broadcast( + self.context, origin=self.bouncer.servname, cmd="NOTICE", target=client.context.identity, + extinfo="Incoming connection from %s to %s denied: Invalid password." % (self.host, self.context)) + # for client in self.bouncer.clients: + # if client.context!=self.context: + # continue + # if not client.quitting: + #client.send(origin=self.bouncer.servname, cmd="NOTICE", target=client.context.identity, extinfo="Incoming connection from %s to %s dened: Invalid password.\n" % (self.host, self.context)) break self.context.logwrite( - "*** [BouncerConnection] Incoming connection from %s to %s." % (self.host, self.context)) + "*** [BouncerConnection] Incoming connection from %s to %s established." % (self.host, self.context)) with self.bouncer.lock: + self.translations = dict( + self.bouncer.conf[self.context].translations) # Announce connection to all other bouncer - # connections. - for bouncerconnection in self.bouncer.connections: - if bouncerconnection.context != self.context: - continue - if not bouncerconnection.quitting: - bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s\n" % ( - bouncerconnection.context.identity.nick, self.host, self.context)) - if len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.context == self.context]) == 0 and self.context.registered and type(self.context.identity) == irc.User and self.context.identity.away: - # Bouncer connection should automatically - # return from away status. + # clients. + self.bouncer.broadcast( + self.context, origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="Incoming connection from %s to %s established." % (self.host, self.context)) + # for client in self.bouncer.clients: + # if client.context!=self.context: + # continue + # if not client.quitting: + #client.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s\n" % (client.context.identity.nick, self.host, self.context)) + if len([client for client in self.bouncer.clients if client.context == self.context]) == 0 and self.context.registered and type(self.context.identity) == irc.User and self.context.identity.away: + # Bouncer connection should + # automatically return from away + # status. self.context.raw("AWAY") self.hidden = irc.ChanList( self.bouncer.conf[self.context].hidden, context=self.context) - self.bouncer.connections.append(self) + self.bouncer.clients.append(self) if self.context.registered: # Send Greeting. with self.lock: - if self.context.welcome: - self.connection.send( - (u":%s 001 %s :%s\n" % (self.context.serv, self.context.identity.nick, self.context.welcome)).encode("utf8")) - if self.context.hostinfo: - self.connection.send( - (u":%s 002 %s :%s\n" % (self.context.serv, self.context.identity.nick, self.context.hostinfo)).encode("utf8")) - if self.context.servcreated: - self.connection.send( - (u":%s 003 %s :%s\n" % (self.context.serv, self.context.identity.nick, self.context.servcreated)).encode("utf8")) - if self.context.servinfo: - self.connection.send( - (u":%s 004 %s %s\n" % (self.context.serv, self.context.identity.nick, self.context.servinfo)).encode("utf8")) - - # Send 005 response. - if self.context.supports: - supports = ["CHANMODES=%s" % (",".join(value)) if name == "CHANMODES" else "PREFIX=(%s)%s" % value if name == "PREFIX" else "%s=%s" % ( - name, value) if value else name for name, value in self.context.supports.items()] - supports.sort() - supportsreply = [] - supportsstr = " ".join(supports) - index = 0 - while True: - if len(supportsstr) - index > 196: - nextindex = supportsstr.rfind( - " ", index, index + 196) - supportsreply.append( - supportsstr[index:nextindex]) - index = nextindex + 1 - else: - supportsreply.append( - supportsstr[index:]) - break - for support in supportsreply: - self.connection.send((u":%s 005 %s %s :are supported by this server\n" % ( - self.context.serv, self.context.identity.nick, support)).encode("utf8")) - - # Send MOTD - if self.context.motdgreet and self.context.motd and self.context.motdend: - self.connection.send( - (u":%s 375 %s :%s\n" % (self.context.serv, self.context.identity.nick, self.context.motdgreet)).encode("utf8")) - for motdline in self.context.motd: - self.connection.send( - (u":%s 372 %s :%s\n" % (self.context.serv, self.context.identity.nick, motdline)).encode("utf8")) - try: - self.connection.send( - (u":%s 376 %s :%s\n" % (self.context.serv, self.context.identity.nick, self.context.motdend)).encode("utf8")) - except AttributeError: - self.connection.send( - (u":%s 376 %s\n" % (self.context.serv, self.context.identity.nick)).encode("utf8")) - else: - self.connection.send((u":%s 422 %s :MOTD File is missing\n" % ( - self.context.serv, self.context.identity.nick)).encode("utf8")) - - # Send user modes and snomasks. - self.connection.send( - (u":%s 221 %s +%s\n" % (self.context.serv, self.context.identity.nick, self.context.identity.modes)).encode("utf8")) - - if "s" in self.context.identity.modes and self.context.identity.snomask: - self.connection.send((u":%s 008 %s +%s :Server notice mask\n" % ( - self.context.serv, self.context.identity.nick, self.context.identity.snomask)).encode("utf8")) + self.sendgreeting() + self.sendsupports() + self.sendmotd() + self.sendusermodes() # Join user to channels. for channel in self.context.identity.channels: - if channel in self.hidden: - continue - - if channel in conf.translations.keys(): - channame = conf.translations[ - channel] - else: - channame = channel.name - # JOIN command - self.connection.send( - (u":%s!%s@%s JOIN :%s\n" % (self.context.identity.nick, self.context.identity.username, self.context.identity.host, channame)).encode("utf8")) - - # Topic - self.connection.send( - (u":%s 332 %s %s :%s\n" % (self.context.serv, self.context.identity.nick, channame, channel.topic)).encode("utf8")) - self.connection.send((u":%s 333 %s %s %s %s\n" % (self.context.serv, self.context.identity.nick, channame, channel.topicsetby.nick if type( - channel.topicsetby) == irc.User else channel.topicsetby, channel.topictime)).encode("utf8")) - - # Determine if +s or +p modes are set - # in channel - secret = "s" in channel.modes.keys() and channel.modes[ - "s"] - private = "p" in channel.modes.keys( - ) and channel.modes["p"] - - # Construct NAMES for channel. - namesusers = [] - modes, symbols = self.context.supports[ - "PREFIX"] - self.connection.send((u":%s 353 %s %s %s :%s\n" % ( - self.context.serv, - self.context.identity.nick, - "@" if secret else ( - "*" if private else "="), - channame, - u" ".join([u"".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])) - ).encode("utf8")) - self.connection.send((u":%s 366 %s %s :End of /NAMES list.\n" % ( - self.context.serv, self.context.identity.nick, channame)).encode("utf8")) + if channel not in self.hidden: + self.showchannel(channel) else: self.send( - u":*Bouncer* NOTICE %s :Not connected to server. Type /bncconnect to attempt connection.\n" % self.nick) - self.send( - u":%s 001 %s :Welcome to the Bouncer context Network %s!%s@%s\n" % - ("*Bouncer*", self.nick, self.nick, self.username, self.host)) + origin=self.bouncer.servname, cmd="NOTICE", target=self.nick, + extinfo="Not connected to server. Type /bncconnect to attempt connection.") + #self.send(u":%s 001 %s :Welcome to the Bouncer context Network %s!%s@%s\n" % ("*Bouncer*", self.nick, self.nick, self.username, self.host)) else: # Client did not send USER command when expected self.quit("Access Denied") print "*** [BouncerConnection] Incoming connection from %s failed: Expected USER." % (self.host) break - elif cmd.upper() == "QUIT": - self.quit(extinfo) - break - - elif cmd.upper() == "SHOW": - if target: - for chan in target.split(","): - chantypes = self.context.supports.get( - "CHANTYPES", "&#+!") - if re.match(irc._chanmatch % re.escape(chantypes), target): - translationfound = False - for channel, channame in conf.translations.items(): - if translation.lower() == chan.lower(): - translationfound = True - if not translationfound: - channel = self.context[chan] - channame = channel.name - - if channel in self.hidden: - with self.lock: - self.hidden.remove(channel) - - if channel in self.context.identity.channels: - # JOIN command - self.connection.send( - (u":%s!%s@%s JOIN :%s\n" % (self.context.identity.nick, self.context.identity.username, self.context.identity.host, channame)).encode("utf8")) - - # Topic - self.connection.send( - (u":%s 332 %s %s :%s\n" % (self.context.serv, self.context.identity.nick, channame, channel.topic)).encode("utf8")) - self.connection.send((u":%s 333 %s %s %s %s\n" % (self.context.serv, self.context.identity.nick, channame, channel.topicsetby.nick if type( - channel.topicsetby) == irc.User else channel.topicsetby, channel.topictime)).encode("utf8")) - - # Determine if +s or +p modes are - # set in channel - secret = "s" in channel.modes.keys() and channel.modes[ - "s"] - private = "p" in channel.modes.keys( - ) and channel.modes["p"] - - # Construct NAMES for channel. - namesusers = [] - modes, symbols = self.context.supports[ - "PREFIX"] - self.connection.send((u":%s 353 %s %s %s :%s\n" % ( - self.context.serv, - self.context.identity.nick, - "@" if secret else ( - "*" if private else "="), - channame, - u" ".join([u"".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])) - ).encode("utf8")) - self.connection.send((u":%s 366 %s %s :End of /NAMES list.\n" % ( - self.context.serv, self.context.identity.nick, channame)).encode("utf8")) - else: - self.connection.send((u":%s 442 %s %s :You are not on that channel.\n" % ( - self.context.serv, self.context.identity.nick, channame)).encode("utf8")) - else: - self.connection.send((u":%s 403 %s %s :Invalid channel name.\n" % ( - self.context.serv, self.context.identity.nick, chan)).encode("utf8")) - else: - self.connection.send((u":%s 461 %s SHOW :Not enough parameters.\n" % ( - self.context.serv, self.context.identity.nick)).encode("utf8")) - self.connection.send((u":%s 304 %s :SYNTAX SHOW <channel>{,<channel>}\n" % ( - self.context.serv, self.context.identity.nick)).encode("utf8")) - elif cmd.upper() == "HIDE": - if target: - for chan in target.split(","): - chantypes = self.context.supports.get( - "CHANTYPES", "&#+!") - if re.match(irc._chanmatch % re.escape(chantypes), target): - translationfound = False - for channel, channame in conf.translations.items(): - if translation.lower() == chan.lower(): - translationfound = True - if not translationfound: - channel = self.context[chan] - channame = channel.name - - if channel not in self.hidden: - with self.lock: - self.hidden.append(channel) - - if channel in self.context.identity.channels: - # PART command - self.connection.send((u":%s!%s@%s PART %s :Hiding channel\n" % ( - self.context.identity.nick, self.context.identity.username, self.context.identity.host, channame)).encode("utf8")) - else: - self.connection.send((u":%s 442 %s %s :You are not on that channel.\n" % ( - self.context.serv, self.context.identity.nick, channame)).encode("utf8")) - else: - self.connection.send((u":%s 403 %s %s :Invalid channel name.\n" % ( - self.context.serv, self.context.identity.nick, chan)).encode("utf8")) - else: - self.connection.send((u":%s 461 %s HIDE :Not enough parameters.\n" % ( - self.context.serv, self.context.identity.nick)).encode("utf8")) - self.connection.send((u":%s 304 %s :SYNTAX HIDE <channel>{,<channel>}\n" % ( - self.context.serv, self.context.identity.nick)).encode("utf8")) - - elif cmd.upper() == "PING": - self.send(":%s PONG %s :%s\n" % - (self.context.serv, self.context.serv, self.context.identity.nick if type(self.context.identity) == irc.User else "***")) - - elif cmd.upper() == "BNCCONNECT": - with self.context.lock: - if self.context.isAlive() and self.context.connected: - self.send( - ":*Bouncer* NOTICE %s :Bouncer is already connected.\n" % self.nick) - self.context.start() - - elif cmd.upper() == "BNCQUIT": - with self.context.lock: - if self.context.isAlive() and self.context.connected and self.context.registered: - quitmsg = " ".join( - [word for word in [target, params, extinfo] if word]) - self.context.quit(quitmsg) - else: - self.send( - ":*Bouncer* NOTICE %s :Bouncer is already disconnected.\n" % self.nick) - else: - if target: - targetlist = [] + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + if cmd.upper() not in ("SETTRANSLATE", "RMTRANSLATE"): + translated = [] for targ in target.split(","): - translationfound = False - for (channel, translation) in conf.translations.items(): - if translation.lower() == targ.lower(): - # print channel - targetlist.append(channel.name) - translationfound = True - break - if not translationfound: - targetlist.append(targ) - target = ",".join(targetlist) - - oldparams = params - params = [] - for param in oldparams.split(): - translationfound = False - for (channel, translation) in conf.translations.items(): - # print target, (channel, translation) - if translation.lower() == param.lower(): - # print channel - params.append(channel.name) - translationfound = True - break - if not translationfound: - params.append(param) - params = " ".join(params) - - #print (cmd, target, params, extinfo) - - if params and extinfo: - line = "%s %s %s :%s" % ( - cmd, target, params, extinfo) - elif params: - line = "%s %s %s" % (cmd, target, params) - elif extinfo: - line = "%s %s :%s" % (cmd, target, extinfo) - else: - line = "%s %s" % (cmd, target) - - with self.context.lock: - # print "Locked" - # print self.context.connected, - # self.context.registered, cmd.upper() - if not self.context.connected: - self.send( - ":*Bouncer* NOTICE %s :Not connected to server. Type /bncconnect to attempt connection.\n" % self.nick) - break - - elif not self.context.registered: - self.send( - ":*Bouncer* NOTICE %s :Not registered.\n" % self.nick) - break - - elif cmd.upper() in ("PRIVMSG", "NOTICE"): - # Check if CTCP - ctcp = re.findall( - "^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo) - - if ctcp: # If CTCP, only want to - (ctcptype, ext) = ctcp[0] # Unpack CTCP info - - if ctcptype == "LAGCHECK": # Client is doing a lag check. No need to send to context network, just reply back. - self.send(":%s!%s@%s %s\n" % ( - self.context.identity.nick, self.context.identity.username, self.context.identity.host, line)) - else: - self.context.raw(line, origin=self) - else: - self.context.raw(line, origin=self) - - elif cmd.upper() == "MODE": # Will want to determine is requesting modes, or attempting to modify modes. - # if target and "CHANTYPES" in - # self.context.supports.keys() and target[0] in - # self.context.supports["CHANTYPES"]: - chantypes = self.context.supports.get( - "CHANTYPES", "&#+!") - if re.match(irc._chanmatch % re.escape(chantypes), target): - channel = self.context[target] - if channel in conf.translations.keys(): - channame = conf.translations[channel] - else: - channame = channel.name - - if params == "": - modes = channel.modes.keys() - modestr = "".join([mode for mode in modes if mode not in self.context.supports[ - "CHANMODES"][0] + self.context.supports["PREFIX"][0] and channel.modes[mode]]) - params = " ".join([channel.modes[mode] for mode in modes if mode in self.context.supports[ - "CHANMODES"][1] + self.context.supports["CHANMODES"][2] and channel.modes[mode]]) - with self.lock: - if len(modestr): - self.connection.send( - (u":%s 324 %s %s +%s %s\n" % (self.context.serv, self.context.identity.nick, channame, modestr, params)).encode("utf8")) - if channel.created: - self.connection.send( - (u":%s 329 %s %s %s\n" % (self.context.serv, self.context.identity.nick, channame, channel.created)).encode("utf8")) - elif re.match("^\\+?[%s]+$" % self.context.supports["CHANMODES"][0], params) and extinfo == "": - # print "ddd Mode List Request", params - redundant = [] - for mode in params.lstrip("+"): - if mode in redundant or mode not in listnumerics.keys(): - continue - i, e, l = listnumerics[mode] - with self.lock: - if mode in channel.modes.keys(): - for (mask, setby, settime) in channel.modes[mode]: - self.connection.send( - (u":%s %d %s %s %s %s %s\n" % (self.context.serv, i, channel.context.identity.nick, channame, mask, setby, settime)).encode("utf8")) - self.connection.send( - (u":%s %d %s %s :End of %s\n" % (self.context.serv, e, channel.context.identity.nick, channame, l)).encode("utf8")) - redundant.append(mode) - else: - self.context.raw(line, origin=self) - elif params == "" and target.lower() == self.context.identity.nick.lower(): - with self.lock: - self.connection.send( - (u":%s 221 %s +%s\n" % (self.context.serv, self.context.identity.nick, self.context.identity.modes)).encode("utf8")) - if "s" in self.context.identity.modes and self.context.identity.snomask: - self.connection.send((u":%s 008 %s +%s :Server notice mask\n" % ( - self.context.serv, self.context.identity.nick, self.context.identity.snomask)).encode("utf8")) - else: - self.context.raw(line, origin=self) + translatefound = False + if re.match(irc._chanmatch % re.escape(chantypes), targ): + for channel, translate in self.translations.items(): + if targ.lower() == translate.lower(): + translated.append(channel.name) + translatefound = True + break + if not translatefound: + translated.append(targ) + target = ",".join(translated) + + translated = [] + for param in params.split(" "): + translatefound = False + if re.match(irc._chanmatch % re.escape(chantypes), param): + for channel, translate in self.translations.items(): + if param.lower() == translate.lower(): + translated.append(channel.name) + translatefound = True + break + if not translatefound: + translated.append(param) + params = " ".join(translated) + + if params: + line = u"{cmd} {target} {params}".format(**vars()) + elif target: + line = u"{cmd} {target}".format(**vars()) else: - self.context.raw(line, origin=self) + line = cmd + + if extinfo: + line = u"{line} :{extinfo}".format(**vars()) + + cmdmethod = "cmd%s" % cmd.upper() + if hasattr(self, cmdmethod): + method = getattr(self, cmdmethod) + try: + method(line, target, params, extinfo) + except SystemExit: + sys.exit() + except: + if self.context: + exc, excmsg, tb = sys.exc_info() + self.context.logwrite(*[u"!!! [BouncerConnection] Exception in thread %(self)s" % vars()] + [ + u"!!! [BouncerConnection] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")]) + print >>sys.stderr, "Exception in thread %(self)s" % vars( + ) + print >>sys.stderr, traceback.format_exc() + elif not self.context.connected: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.nick, + extinfo="Not connected to server. Type /bncconnect to attempt connection.") + continue + + elif not self.context.registered: + self.send(origin=self.bouncer.servname, cmd="NOTICE", + target=self.nick, extinfo="Not registered.") + continue + else: + self.context.raw(line, origin=self) except SystemExit: pass # No need to pass error message if break resulted from sys.exit() except: @@ -1066,8 +973,8 @@ class BouncerConnection (Thread): exc, excmsg, tb = sys.exc_info() self.context.logwrite(*["!!! [BouncerConnection] Exception in thread %(self)s" % vars()] + [ "!!! [BouncerConnection] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")]) - print >>sys.stderr, "Exception in thread %(self)s" % vars() - print >>sys.stderr, traceback.format_exc() + print >>sys.stderr, "Exception in thread %(self)s" % vars() + print >>sys.stderr, traceback.format_exc() finally: # Juuuuuuust in case. with self.lock: @@ -1081,30 +988,204 @@ class BouncerConnection (Thread): self.context.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.context.connected and self.context.identity and len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.context == self.context]) == 0 and self.context.registered and type(self.context.identity) == irc.User and not self.context.identity.away and self.bouncer.autoaway: + with self.bouncer.lock: + if self in self.bouncer.clients: + self.bouncer.clients.remove(self) + if self.context.connected and self.context.identity and len([client for client in self.bouncer.clients if client.context == self.context]) == 0 and self.context.registered and type(self.context.identity) == irc.User and not self.context.identity.away and self.bouncer.autoaway: # Bouncer automatically sets away status. self.context.raw("AWAY :%s" % self.bouncer.autoaway) - if self.debug: - self.context.logwrite( - "dbg [BouncerConnection] Attempting to broadcast terminated connection %(self)s." % vars()) - for bouncerconnection in self.bouncer.connections: - if bouncerconnection.context == self.context: - if self.debug: - self.context.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.context.identity.nick, self.host, self.context, self.quitmsg)) - if self.debug: - self.context.logwrite( - "dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars()) - -# Announce QUIT to other bouncer connections. -# for bouncerconnection in self.bouncer.connections: -# try: -# bouncerconnection.connection.send(":%s!%s@%s QUIT :%s\n" % (self.label, self.username, self.host, self.quitmsg)) -# except: -# pass + self.bouncer.broadcast( + self.context, origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="Connection from %s to %s terminated (%s)\n" % (self.host, self.context, self.quitmsg)) + # ":*Bouncer* NOTICE %s :Connection from %s to %s terminated (%s)\n" % (client.context.identity.nick, self.host, self.context, self.quitmsg)) + + def cmdQUIT(self, line, target, params, extinfo): + self.quit(extinfo) + sys.exit() + + def cmdPROTOCTL(self, line, target, params, extinfo): + protoparams = [target.upper()] + params.upper().split() + if "NAMESX" in protoparams: + self.namesx = True + if "UHNAMES" in protoparams: + self.uhnames = True + + def cmdPING(self, line, target, params, extinfo): + with self.context.lock: + if True or (self.context.identity and type(self.context.identity) == irc.User): + self.send(origin=self.bouncer.servname, + cmd="PONG", target=params, extinfo=target) + # u":{self.context.identity.server} PONG {params} + # :{target}\n".format(**vars()).encode("utf8")) + else: + self.send(origin=self.bouncer.servname, + cmd="PONG", params=params, extinfo=target) + self.send( + u":{self.context.server} PONG {params} :{target}\n".format(**vars()).encode("utf8")) + + def cmdPRIVMSG(self, line, target, params, extinfo): + # Check if CTCP + ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo) + + if ctcp: + (ctcptype, ext) = ctcp[0] # Unpack CTCP info + + if ctcptype == "LAGCHECK": # Client is doing a lag check. No need to send to context network, just reply back. + self.send( + u":{self.context.identity:full} {line}\n".format(**vars()).encode("utf8")) + else: + self.context.raw(line, origin=self) + else: + self.context.raw(line, origin=self) + + def cmdMODE(self, line, target, params, extinfo): # Will want to determine is requesting modes, or attempting to modify modes. + # if target and "CHANTYPES" in self.context.supports.keys() and + # target[0] in self.context.supports["CHANTYPES"]: + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + chanmodes = self.context.supports.get( + "CHANMODES", irc._defaultchanmodes) + prefix = self.context.supports.get("PREFIX", irc._defaultprefix) + if re.match(irc._chanmatch % re.escape(chantypes), target): + channel = self.context[target] + + if params == "": + # We are requesting the modes for the channel + if self.context.identity in channel.users: + # We are in the channel, and we know the channel modes + self.sendchannelmodes(channel) + else: + # We are NOT in the channel, so we will forward the request + # to the server. + self.context.raw( + u"MODE {channel.name}".format(**vars()), origin=self) + + elif re.match("^\\+?[%s]+$" % chanmodes[0], params) and extinfo == "": + # We are requesting one or more mode lists. + modechars = "" + for mode in params.lstrip("+"): + if mode not in modechars: + modechars += mode + if self.context.identity in channel.users: + self.sendchannelmodes(channel, modechars) + else: + self.context.raw( + u"MODE {channel.name} {params}".format(**vars()), origin=self) + else: + self.context.raw(line, origin=self) + elif params == "" and target.lower() == self.context.identity.nick.lower(): + self.sendusermodes() + else: + self.context.raw( + u"MODE {target} {params}".format(**vars()), origin=self) + + def cmdNAMES(self, line, target, params, extinfo): + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + chanmodes = self.context.supports.get( + "CHANMODES", irc._defaultchanmodes) + prefix = self.context.supports.get("PREFIX", irc._defaultprefix) + fallback = [] + with self.lock: + for channame in target.split(): + if re.match(irc._chanmatch % re.escape(chantypes), channame): + channel = self.context[channame] + with self.lock: + if self.context.identity in channel: + self.sendchannelnames(channel) + else: + fallback.append(channame) + else: + fallback.append(channame) + if fallback: + self.context.raw("NAMES %s" % + (",".join(fallback)), origin=self) + + def cmdSHOW(self, line, target, params, extinfo): + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + with self.lock: + for channame in target.split(): + if re.match(irc._chanmatch % re.escape(chantypes), channame): + channel = self.context[channame] + if channel in self.hidden: + if self.context.identity in channel: + self.showchannel(channel) + else: + self.hidden.remove(channel) + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="{channel.name} removed from hidden list, but not joined.".format(**vars())) + else: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="{channel.name} not in hidden list.".format(**vars())) + else: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="{channame}: invalid channel name.".format(**vars())) + + def cmdHIDE(self, line, target, params, extinfo): + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + with self.lock: + for channame in target.split(): + if re.match(irc._chanmatch % re.escape(chantypes), channame): + channel = self.context[channame] + if channel not in self.hidden: + if self.context.identity in channel: + self.send( + origin=self.context.identity, cmd="PART", target=channel, extinfo="Hiding channel") + else: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="{channel.name} added to the hidden list, but not joined.".format(**vars())) + self.hidden.append(channel) + else: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="{channel.name} already in hidden list.".format(**vars())) + else: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="{channame}: invalid channel name.".format(**vars())) + + def cmdSETTRANSLATE(self, line, target, params, extinfo): + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + with self.lock: + if re.match(irc._chanmatch % re.escape(chantypes), target) and re.match(irc._chanmatch % re.escape(chantypes), target): + channel = self.context[target] + if self.context.supports.get("CASEMAPPING", "rfc1459") == "ascii": + translations_lower = [translation.translate(irc._rfc1459casemapping) + for translation in self.translations.values()] + params_lower = params.translate(irc._rfc1459casemapping) + else: + translations_lower = [translation.lower() + for translation in self.translations.values()] + params_lower = params.lower() + if params_lower in translations_lower: + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="Cannot set translation for {channel.name} to {param}.".format(**vars())) + else: + self.send(origin=self.context.identity, cmd="PART", + target=channel, extinfo="Translating...") + self.translations[channel] = params + self.showchannel(channel) + + def cmdRMTRANSLATE(self, line, target, params, extinfo): + chantypes = self.context.supports.get( + "CHANTYPES", irc._defaultchantypes) + with self.lock: + if re.match(irc._chanmatch % re.escape(chantypes), target): + channel = self.context[target] + if channel not in self.translations.keys(): + self.send( + origin=self.bouncer.servname, cmd="NOTICE", target=self.context.identity, + extinfo="Cannot remove translation for {channel.name}.".format(**vars())) + else: + self.send(origin=self.context.identity, cmd="PART", + target=channel, extinfo="Translating...") + del self.translations[channel] + self.showchannel(channel) @@ -5,6 +5,7 @@ import os class Cannon(object): + def __init__(self): self.firecount = {} @@ -15,24 +16,26 @@ class Cannon(object): 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 + count = self.firecount[vic] + 1 else: count = 1 self.firecount[vic] = count - if 10 <= count%100 < 20: + if 10 <= count % 100 < 20: ordinal = "th" - elif count%10 == 1: + elif count % 10 == 1: ordinal = "st" - elif count%10 == 2: + elif count % 10 == 2: ordinal = "nd" - elif count%10 == 3: + 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)) + 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,6 +4,7 @@ import os class Figlet(object): + def onChanMsg(self, IRC, user, channel, targetprefix, msg): matches = re.findall("^!figlet\\s+(.*)$", msg) if matches: diff --git a/irc.conf b/irc.conf new file mode 100644 index 0000000..498ce86 --- /dev/null +++ b/irc.conf @@ -0,0 +1,48 @@ +{ + "addons": { + "logger": { + "class": "logger.Logger", + "logroot": "/home/caretaker82/IRC" + }, + "bouncer": { + "class": "bouncer.Bouncer", + "port": 16698, + "certfile": "cert.pem", + "keyfile": "key.pem", + "autoaway": "I'm off to see the wizard!" + }, + "autoexec": { + "class": "autoexec.Autoexec" + } + }, + "networks": { + "InsomniaIRC": { + "class": "irc.Connection", + "server": "irc.insomniairc.net", + "nick": "pyIRC", + "secure": true, + "addons": [ + { + "addon": <addons.logger>, + "label": "InsomniaIRC" + }, + { + "addon": <addons.bouncer>, + "label": "InsomniaIRC", + "passwd": "6b97ed68d14eb3f1aa959ce5d49c7dc612e1eb1dafd73b1e705847483fd6a6c809f2ceb4e8df6ff9984c6298ff0285cace6614bf8daa9f0070101b6c89899e22", + "translations": {}, + "hidden": [] + }, + { + "addon": <addons.autoexec>, + "label": "InsomniaIRC", + "autojoin": [ + "#chat" + ], + "nsautojoin": [], + "operjoin": [] + } + ] + } + } +}
\ No newline at end of file @@ -1,5 +1,6 @@ #!/usr/bin/python -from threading import Thread, Condition, Lock, currentThread +from threading import Thread, Condition, currentThread +from threading import RLock as Lock import re import time import sys @@ -10,10 +11,29 @@ import platform import traceback import ssl import glob -from collections import deque -import iqueue as Queue +from collections import deque, OrderedDict import chardet import codecs +import new +import inspect +import warnings +import random + + +def autodecode(s): + try: + return s.decode("utf8") + except UnicodeDecodeError: + # Attempt to figure encoding + detected = chardet.detect(s) + try: + return s.decode(detected['encoding']) + except UnicodeDecodeError: + return s.decode("utf8", "replace") + + +class AddonWarning(Warning): + pass class InvalidName(BaseException): @@ -134,6 +154,9 @@ _realnamematch = r"^[^\n]*$" _ircmatch = r"^(?::(.+?)(?:!(.+?)@(.+?))?\s+)?([A-Za-z0-9]+?)\s*(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$" _ctcpmatch = "^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$" _prefixmatch = r"\((.*)\)(.*)" +_defaultchanmodes = u"b,k,l,imnpst".split(",") +_defaultprefix = ("ov", "@+") +_defaultchantypes = "&#+!" _privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=( "Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice")) @@ -154,28 +177,21 @@ def timestamp(): class Connection(object): + __name__ = "pyIRC" + __version__ = "2.0" + __author__ = "Brian Sherson" + __date__ = "February 21, 2014" - 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, quietpingpong=True, pinginterval=60): - self.__name__ = "pyIRC" - self.__version__ = "1.3" - self.__author__ = "Brian Sherson" - self.__date__ = "February 8, 2014" - - if port == None: - self.port = 6667 if not ssl else 6697 - else: + def __init__(self, server, nick="ircbot", username="python", realname="Python IRC Library", passwd=None, port=None, ipvers=(socket.AF_INET6, socket.AF_INET), secure=False, autoreconnect=True, timeout=300, retrysleep=5, maxretries=15, protoctl=("UHNAMES", "NAMESX"), quietpingpong=True, pinginterval=60, addons=None, autostart=False): + if port is None or (type(port) == int and 0 < port < 65536): self.port = port + else: + raise ValueError, "Invalid value for 'port'" - if type(nick) in (str, unicode): - if re.match(_nickmatch, nick): - self.nick = [nick] - else: - raise InvalidCharacter - elif type(nick) in (list, tuple): - if all([re.match(_nickmatch, n) for n in nick]): - self.nick = nick - else: - raise InvalidCharacter + if re.match(_nickmatch, nick) if (type(nick) in (str, unicode)) else all([re.match(_nickmatch, n) for n in nick]) if (type(nick) in (list, tuple)) else False: + self.nick = nick + else: + raise ValueError, "Invalid value for 'nick'" if re.match(_realnamematch, realname): self.realname = realname @@ -193,27 +209,51 @@ class Connection(object): raise InvalidCharacter self.server = server - self.ssl = ssl - self.ipv6 = ipv6 + self.secure = secure + self.ipvers = ipvers if type(ipvers) == tuple else (ipvers,) - self.autoreconnect = autoreconnect - self.maxretries = maxretries - self.timeout = timeout - self.retrysleep = retrysleep - self.quietpingpong = quietpingpong - self.pinginterval = pinginterval + self.protoctl = protoctl - self._quitexpected = False - self.log = log + if type(autoreconnect) == bool: + self.autoreconnect = autoreconnect + else: + raise ValueError, "Invalid value for 'autoreconnect'" - self.addons = [] - self.trusted = [] # To be implemented later + if type(maxretries) in (int, long): + self.maxretries = maxretries + else: + raise ValueError, "Invalid value for 'maxretries'" + + if type(timeout) in (int, long): + self.timeout = timeout + else: + raise ValueError, "Invalid value for 'timeout'" + + if type(retrysleep) in (int, long): + self.retrysleep = retrysleep + else: + raise ValueError, "Invalid value for 'retrysleep'" + + if type(quietpingpong) == bool: + self.quietpingpong = quietpingpong + else: + raise ValueError, "Invalid value for 'quietpingpong'" + + if type(pinginterval) in (int, long): + self.pinginterval = pinginterval + else: + raise ValueError, "Invalid value for 'pinginterval'" + + self._quitexpected = False + self.log = sys.stdout self.lock = Lock() self._loglock = Lock() self._outlock = Lock() self._sendline = Condition(self._outlock) + self._connecting = Condition(self.lock) + self._disconnecting = Condition(self.lock) self._outgoing = deque() self._sendhandlerthread = None @@ -222,9 +262,22 @@ class Connection(object): # Initialize IRC environment variables self.users = UserList(context=self) self.channels = ChanList(context=self) + self.addons = [] + + self.trusted = [] # To be implemented later self._init() + if type(addons) == list: + for addon in addons: + if type(addon) == dict: + self.addAddon(**addon) + else: + self.addAddon(addon) + if autostart: + self.connect() def _init(self): + self.ipver = None + self.addr = None self._connected = False self._registered = False self._connection = None @@ -258,14 +311,18 @@ class Connection(object): with self._loglock: ts = timestamp() for line in lines: - print >>self.log, "%s %s" % (ts, line) + try: + print >>self.log, u"%s %s" % (ts, line) + except: + print line + raise self.log.flush() def logopen(self, filename, encoding="utf8"): with self._loglock: ts = timestamp() newlog = codecs.open(filename, "a", encoding=encoding) - if type(self.log) == file and not self.log.closed: + if isinstance(self.log, codecs.StreamReaderWriter) and not self.log.closed: if self.log not in (sys.stdout, sys.stderr): print >>self.log, "%s ### Log file closed" % (ts) self.log.close() @@ -273,154 +330,332 @@ class Connection(object): print >>self.log, "%s ### Log file opened" % (ts) self.log.flush() - def _event(self, method, modlist, exceptions=False, data=None, **params): - # Used to call event handlers on all attached addons, when applicable. + # Used to call event handlers on all attached addons, when applicable. + def _event(self, addons, events, line=None, data=None, exceptions=False): handled = [] unhandled = [] errors = [] - for k, addon in enumerate(modlist): - if modlist.index(addon) < k: + for k, addon in enumerate(addons): + if addons.index(addon) < k: # Duplicate continue - if method in dir(addon) and callable(getattr(addon, method)): - f = getattr(addon, method) - args = params - elif "onOther" in dir(addon) and callable(addon.onOther) and data: - f = addon.onOther - args = data - elif "onUnhandled" in dir(addon) and callable(addon.onUnhandled) and data: - # Backwards compatability for addons that still use - # onUnhandled. Use onOther in future development. - f = addon.onUnhandled - args = data + + if type(addon) == Config: + addon = addon.addon + + fellback = False # Switch this to True when a fallback is used so that we only call onOther once. + + # Iterate through all events. + for (method, args, fallback) in events: + if method in dir(addon) and callable(getattr(addon, method)): + f = getattr(addon, method) + elif fallback and not fellback: + if "onOther" in dir(addon) and callable(addon.onOther) and data: + f = addon.onOther + args = dict(line=line, **data) + fellback = True + elif "onUnhandled" in dir(addon) and callable(addon.onUnhandled) and data: + # Backwards compatability for addons that still use + # onUnhandled. Use onOther in future development. + f = addon.onUnhandled + args = dict(line=line, **data) + fellback = True + else: + unhandled.append(addon) + continue + else: + unhandled.append(addon) + continue + + if type(f) == new.instancemethod: + argspec = inspect.getargspec(f.im_func) + else: + argspec = inspect.getargspec(f) + if argspec.keywords == None: + args = { + arg: val for arg, val in args.items() if arg in argspec.args} + try: + f(self, **args) + except: + # print f, args + exc, excmsg, tb = sys.exc_info() + errors.append((addon, exc, excmsg, tb)) + + # Print to log AND stderr + tblines = [u"!!! Exception in addon %(addon)s" % vars()] + tblines.append(u"!!! Function: %s" % f) + tblines.append(u"!!! Arguments: %s" % args) + for line in traceback.format_exc().split("\n"): + tblines.append(u"!!! %s" % autodecode(line)) + self.logwrite(*tblines) + print >>sys.stderr, "Exception in addon %(addon)s" % vars() + print >>sys.stderr, u"Function: %s" % f + print >>sys.stderr, u"Arguments: %s" % args + print >>sys.stderr, traceback.format_exc() + if exceptions: # If set to true, we raise the exception. + raise + else: + handled.append(addon) + return (handled, unhandled, errors) + + # TODO: Build method validation into the next two addons, Complain when a method is not callable or does not take in the expected arguments. + # Inspects the methods of addon to make sure + def validateAddon(self, addon): + supported = self.eventsupports() + keys = supported.keys() + for fname in dir(addon): + if fname in keys: + supportedargs = supported[fname] + elif re.match(r"^on(?:Send)?[A-Z]+$", fname): + supportedargs = ( + "line", "origin", "target", "targetprefix", "params", "extinfo") + elif re.match(r"^on\d{3}$", fname): + supportedargs = ( + "line", "origin", "target", "params", "extinfo") else: - unhandled.append(addon) continue - try: - f(self, **args) - except: - exc, excmsg, tb = sys.exc_info() - errors.append((addon, exc, excmsg, tb)) - - # Print to log AND stderr - self.logwrite(*["!!! Exception in addon %(addon)s" % vars()] + [ - "!!! %s" % line for line in traceback.format_exc().split("\n")]) - print >>sys.stderr, "Exception in addon %(addon)s" % vars() - print >>sys.stderr, traceback.format_exc() - if exceptions: # If set to true, we raise the exception. - raise + func = getattr(addon, fname) + argspec = inspect.getargspec(func) + if type(func) == new.instancemethod: + funcargs = argspec.args[1:] + if argspec.defaults: + requiredargs = funcargs[:-len(argspec.defaults)] else: - handled.append(addon) - return (handled, unhandled, errors) - - # TODO: Build method validation into the next two addons, Complain when a - # method is not callable or does not take in the expected arguments. + requiredargs = funcargs + contextarg = funcargs[0] + unsupported = [ + arg for arg in requiredargs[1:] if arg not in supportedargs] + if len(unsupported): + warnings.warn( + "Function '%s' requires unsupported arguments: %s" % + (func.__name__, ", ".join(unsupported)), AddonWarning) + self.logwrite( + "!!! AddonWarning: Function '%s' requires unsupported arguments: %s" % + (func.__name__, ", ".join(unsupported))) + if argspec.keywords == None: + unsupported = [ + arg for arg in supportedargs if arg not in funcargs[1:]] + if len(unsupported): + warnings.warn( + "Function '%s' does not accept supported arguments: %s" % + (func.__name__, ", ".join(unsupported)), AddonWarning) + self.logwrite( + "!!! AddonWarning: Function '%s' does not accept supported arguments: %s" % + (func.__name__, ", ".join(unsupported))) def addAddon(self, addon, trusted=False, **params): - if addon in self.addons: - raise BaseException, "Addon already added." + self.validateAddon(addon) + for a in self.addons: + if (type(a) == Config and a.addon is addon) or a is addon: + raise BaseException, "Addon already added." with self.lock: - self._event("onAddonAdd", [addon], exceptions=True, **params) - self.addons.append(addon) + if params: + defconf = Config(addon, **params) + else: + defconf = addon + if hasattr(addon, "onAddonAdd") and callable(addon.onAddonAdd): + conf = addon.onAddonAdd(self, **params) + if conf is not None: + self.addons.append(conf) + else: + self.addons.append(defconf) + else: + self.addons.append(defconf) self.logwrite("*** Addon %s added." % repr(addon)) if trusted: self.trusted.append(addon) def insertAddon(self, index, addon, trusted=False, **params): - if addon in self.addons: - raise BaseException, "Addon already added." + self.validateAddon(addon) + for a in self.addons: + if (type(a) == Config and a.addon is addon) or a is addon: + raise BaseException, "Addon already added." with self.lock: - self._event("onAddonAdd", [addon], exceptions=True, **params) - self.addons.insert(index, addon) + if params: + defconf = Config(addon, **params) + else: + defconf = addon + if hasattr(addon, "onAddonAdd") and callable(addon.onAddonAdd): + conf = addon.onAddonAdd(self, **params) + if conf is not None: + self.addons.insert(index, conf) + else: + self.addons.insert(index, defconf) + else: + self.addons.insert(index, defconf) self.logwrite("*** Addon %s inserted into index %d." % (repr(addon), index)) if trusted: self.trusted.append(addon) - def rmAddon(self, addon, **params): + def rmAddon(self, addon): with self.lock: self.addons.remove(addon) self.logwrite("*** Addon %s removed." % repr(addon)) - self._event("onAddonRem", [addon], exceptions=True, **params) if addon in self.trusted: self.trusted.remove(addon) + if hasattr(addon, "onAddonRem") and callable(addon.onAddonAdd): + addon.onAddonRem(self) - def connect(self, server=None, port=None, ssl=None, ipv6=None): - if self.isAlive(): - raise AlreadyConnected - with self._sendline: - self._outgoing.clear() - with self.lock: - self._recvhandlerthread = Thread( - target=self._recvhandler, name="Receive Handler", kwargs=dict(server=None, port=None, ssl=None, ipv6=None)) + def connect(self, server=None, port=None, secure=None, ipvers=None, forcereconnect=False, blocking=False): + if ipvers != None: + ipvers = ipvers if type(ipvers) == tuple else (ipvers,) + else: + ipvers = self.ipvers + + server = server if server else self.server + port = port if port else self.port + secure = secure if secure != None else self.secure + + with self._connecting: + if self.isAlive(): + if forcereconnect: + self.quit("Changing server...", blocking=True) + else: + raise AlreadyConnected + with self._sendline: + self._outgoing.clear() + self._recvhandlerthread = Thread(target=self._recvhandler, name="Receive Handler", kwargs=dict( + server=server, port=port, secure=secure, ipvers=ipvers)) self._sendhandlerthread = Thread( target=self._sendhandler, name="Send Handler") self._recvhandlerthread.start() self._sendhandlerthread.start() + if blocking: + self._connecting.wait() + if not self.connected: + raise NotConnected - def _connect(self): + def _connect(self, addr, ipver, secure, hostname=None): with self.lock: if self._connected: raise AlreadyConnected - server = self.server - if self.ipv6 and ":" in server: - server = "[%s]" % server - port = self.port - with self.lock: + if hostname: + if ipver == socket.AF_INET6: + addrstr = "{hostname} ([{addr[0]}]:{addr[1]})".format( + **vars()) + else: + addrstr = "{hostname} ({addr[0]}:{addr[1]})".format( + **vars()) + else: + if ipver == socket.AF_INET6: + addrstr = "[{addr[0]}]:{addr[1]}".format(**vars()) + else: + addrstr = "{addr[0]}:{addr[1]}".format(**vars()) self.logwrite( - "*** Attempting connection to %(server)s:%(port)s." % vars()) - self._event("onConnectAttempt", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + "*** Attempting connection to {addrstr}.".format(**vars())) + self._event(self.getalladdons(), [ + ("onConnectAttempt", dict(), False)]) + try: - if self.ssl: - connection = socket.socket( - socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) + connection = socket.socket(ipver, socket.SOCK_STREAM) + if secure: connection.settimeout(self.timeout) self._connection = ssl.wrap_socket( connection, cert_reqs=ssl.CERT_NONE) else: - self._connection = socket.socket( - socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM) + self._connection = connection self._connection.settimeout(self.timeout) - self._connection.connect( - (self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port)) + self._connection.connect(addr) except socket.error: exc, excmsg, tb = sys.exc_info() + self.logwrite( + "*** Connection to {addrstr} failed: {excmsg}.".format(**vars())) with self.lock: - self.logwrite( - "*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) - self._event("onConnectFail", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) + self._event(self.getalladdons(), [ + ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)]) raise - - with self.lock: + else: # 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], [])) + with self.lock: + self._event( + self.getalladdons(), [("onConnect", dict(), False)]) self.logwrite( - "*** Connection to %(server)s:%(port)s established." % vars()) + "*** Connection to {addrstr} established.".format(**vars())) + self.addr = addr self._connected = True + with self._connecting: + self._connecting.notifyAll() + + def _tryaddrs(self, server, addrs, ipver, secure): + for addr in addrs: + try: + if server == addr[0]: + self._connect(addr=addr, secure=secure, ipver=ipver) + else: + self._connect( + hostname=server, addr=addr, secure=secure, ipver=ipver) + except socket.error, msg: + if self._quitexpected: + sys.exit() + if msg.errno == 101: # Network is unreachable, will pass the exception on. + raise + if self.retrysleep > 0: + time.sleep(self.retrysleep) + if self._quitexpected: + sys.exit() + else: + return True + return False + + def _tryipver(self, server, port, ipver, secure): + if ipver == socket.AF_INET6: + self.logwrite( + "*** Attempting to resolve {server} to an IPv6 address...".format(**vars())) + else: + self.logwrite( + "*** Attempting to resolve {server}...".format(**vars())) + + try: + addrs = socket.getaddrinfo( + server, port if port is not None else 6697 if self.secure else 6667, ipver) + except socket.gaierror, msg: + self.logwrite("*** Resolution failed: {msg}.".format(**vars())) + raise + + # Weed out duplicates + addrs = list( + set([sockaddr for family, socktype, proto, canonname, sockaddr in addrs if family == ipver])) + + n = len(addrs) + if n == 1: + addr = addrs[0] + self.logwrite( + "*** Name {server} resolves to {addr[0]}.".format(**vars())) + else: + self.logwrite( + "*** Name {server} resolves to {n} addresses, choosing one at random until success.".format(**vars())) + random.shuffle(addrs) + + return self._tryaddrs(server, addrs, ipver, secure) + + def _tryipvers(self, server, port, ipvers, secure): + for ipver in ipvers: + try: + ret = self._tryipver(server, port, ipver, secure) + except socket.gaierror, msg: + if msg.errno == -2: # Name or service not known. Again, just try next ipver. + continue + else: + raise + except socket.error, msg: + if msg.errno == 101: # Don't err out, just try next ipver. + continue + else: + raise + else: + if ret: + self.ipver = ipver + return True + return False def _procrecvline(self, line): - # If received PING, then just pong back transparently, bypassing _outgoingthread. - #ping=re.findall("^PING :?(.*)$", line) - # if len(ping): - # if not self.quietpingpong: - #self.logwrite("<<< %s" % line) - # with self.lock: - #self._connection.send("PONG :%s\n" % ping[0]) - # if not self.quietpingpong: - #self.logwrite(">>> %s" % "PONG :%s" % ping[0]) - # return - - # Attempts to match against pattern ":src cmd target params :extinfo" matches = re.findall(_ircmatch, line) # We have a match! if len(matches): - parsed = (origin, username, host, cmd, - target, params, extinfo) = matches[0] + (origin, username, host, cmd, target, params, extinfo) = matches[0] unhandled = [] if re.match(_intmatch, cmd): @@ -435,12 +670,15 @@ class Connection(object): self._send(u"PONG :%s" % extinfo) with self.lock: + data = dict(origin=origin, cmd=cmd, target=target, + targetprefix=None, params=params, extinfo=extinfo) + if not self._registered: if type(cmd) == int and cmd != 451 and target != "*": # Registration complete! self.identity = self.user(target, init=True) self.serv = origin - self._event("onRegistered", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + self._event(self.getalladdons(), [ + ("onRegistered", dict(), False)], line, data) self._registered = True elif cmd == 433 and target == "*": # Server reports nick taken, so we need to try another. @@ -452,7 +690,7 @@ class Connection(object): nickname = origin origin = self.user(origin) if origin.nick != nickname: - # Origin nickname has changed + # Origin nickname case has changed origin.user = nickname if origin.username != username: # Origin username has changed @@ -461,988 +699,145 @@ class Connection(object): # Origin host has changed origin.host = host + # Check to see if target matches a channel (optionally with + # prefix) + prefix = self.supports.get("PREFIX", _defaultprefix) + chantypes = self.supports.get("CHANTYPES", _defaultchantypes) chanmatch = re.findall( - _targchanmatch % (re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target) + _targchanmatch % (re.escape(prefix[1]), re.escape(chantypes)), target) + if chanmatch: targetprefix, channame = chanmatch[0] target = self.channel(channame) if target.name != channame: - # Target channel name has changed + # Target channel name has changed case target.name = channame + + # Check to see if target matches a valid nickname. Do NOT + # convert target to User instance if cmd is NICK. elif re.match(_nickmatch, target) and cmd != "NICK": targetprefix = "" target = self.user(target) + + # Otherwise, target is just left as a string else: targetprefix = "" - data = dict(line=line, origin=origin, cmd=cmd, target=target, + data = dict(origin=origin, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo) - # Major codeblock here! Track IRC state. - # Send line to addons having onRecv method first - if cmd not in ("PING", "PONG") or not self.quietpingpong: - self._event("onRecv", self.addons, **data) - - # Support for further addon events is taken care of here. Each invocation of self._event will return (handled, unhandled, exceptions), - # where handled is the list of addons that have an event handler, and was executed without error, unhandled gives the list of addons - # not having the event handler, and exeptions giving the list of addons having an event handler, but an exception occurred. - # WARNING: When writing an addon, never, EVER attempt to aquire self.lock (IRC.lock from inside the method), or you will have a - # deadlock. - - if cmd == 1: - self._event("onWelcome", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo, data=data) - self.welcome = extinfo # Welcome message - elif cmd == 2: - self._event("onYourHost", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo, data=data) - self.hostinfo = extinfo # Your Host - elif cmd == 3: - self._event("onServerCreated", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo, data=data) - self.servcreated = extinfo # Server Created - elif cmd == 4: - self._event("onServInfo", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, servinfo=params, data=data) - self.servinfo = params # What is this code? - elif cmd == 5: # Server Supports - support = dict( - re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params)) - if support.has_key("CHANMODES"): - support["CHANMODES"] = support["CHANMODES"].split(",") - if support.has_key("PREFIX"): - matches = re.findall(_prefixmatch, support["PREFIX"]) - if matches: - support["PREFIX"] = matches[0] - else: - del support[ - "PREFIX"] # Might as well delete the info if it doesn't match expected pattern - self._event("onSupports", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, supports=support, data=data) - self.supports.update(support) - if "serv005" in dir(self) and type(self.serv005) == list: - self.serv005.append(params) - else: - self.serv005 = [params] - elif cmd == 8: # Snomask - snomask = params.lstrip("+") - self._event("onSnoMask", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, snomask=snomask, data=data) - self.identity.snomask = snomask - if "s" not in self.identity.modes: - self.snomask = "" - elif cmd == 221: # User Modes - modes = (params if params else extinfo).lstrip("+") - self._event("onUserModes", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), origin=origin, snomask=modes, data=data) - self.identity.modes = modes - if "s" not in self.identity.modes: - self.snomask = "" - elif cmd == 251: # Net Stats - self._event( - "onNetStats", self.addons, origin=origin, netstats=extinfo, data=data) - self.netstats = extinfo - elif cmd == 252: - opcount = int(params) - self._event( - "onOpCount", self.addons, origin=origin, opcount=opcount, data=data) - self.opcount = opcount - elif cmd == 254: - chancount = int(params) - self._event( - "onChanCount", self.addons, origin=origin, chancount=chancount, data=data) - self.chancount = chancount - - elif cmd == 305: # Returned from away status - self._event( - "onReturn", self.addons, origin=origin, msg=extinfo, data=data) - self.identity.away = False - - elif cmd == 306: # Entered away status - self._event( - "onAway", self.addons, origin=origin, msg=extinfo, data=data) - self.identity.away = True - - elif cmd == 311: # Start of WHOIS data - nickname, username, host, star = params.split() - user = self.user(nickname) - self._event( - "onWhoisStart", self.addons, origin=origin, user=user, - nickname=nickname, username=username, host=host, realname=extinfo, data=data) - user.nick = nickname - user.username = username - user.host = host - - elif cmd == 301: # Away Message - user = self.user(params) - self._event("onWhoisAway", self.addons, origin=origin, - user=user, nickname=params, awaymsg=extinfo, data=data) - user.away = True - user.awaymsg = extinfo - - elif cmd == 303: # ISON Reply - users = [self.user(user) for user in extinfo.split(" ")] - self._event( - "onIsonReply", self.addons, origin=origin, isonusers=users, data=data) - - elif cmd == 307: # Is a registered nick - self._event( - "onWhoisRegisteredNick", self.addons, origin=origin, - user=self.user(params), nickname=params, msg=extinfo, data=data) - elif cmd == 378: # Connecting From - self._event( - "onWhoisConnectingFrom", self.addons, origin=origin, - user=self.user(params), nickname=params, msg=extinfo, data=data) - elif cmd == 319: # Channels - self._event("onWhoisChannels", self.addons, origin=origin, user=self.user( - params), nickname=params, chanlist=extinfo.split(" "), data=data) - elif cmd == 310: # Availability - self._event( - "onWhoisAvailability", self.addons, origin=origin, - user=self.user(params), nickname=params, msg=extinfo, data=data) - elif cmd == 312: # Server - nickname, server = params.split(" ") - user = self.user(nickname) - self._event( - "onWhoisServer", self.addons, origin=origin, user=user, - nickname=nickname, server=server, servername=extinfo, data=data) - user.server = server - elif cmd == 313: # IRC Op - user = self.user(params) - self._event("onWhoisOp", self.addons, origin=origin, - user=user, nickname=params, msg=extinfo, data=data) - user.ircop = True - user.ircopmsg = extinfo - elif cmd == 317: # Idle and Signon times - nickname, idletime, signontime = params.split(" ") - user = self.user(nickname) - self._event( - "onWhoisTimes", self.addons, origin=origin, user=user, nickname=nickname, - idletime=int(idletime), signontime=int(signontime), msg=extinfo, data=data) - user.idlesince = int(time.time()) - int(idletime) - user.signontime = int(signontime) - elif cmd == 671: # SSL - user = self.user(params) - self._event("onWhoisSSL", self.addons, origin=origin, - user=user, nickname=params, msg=extinfo, data=data) - user.ssl = True - elif cmd == 379: # User modes - self._event("onWhoisModes", self.addons, origin=origin, user=self.user( - params), nickname=params, msg=extinfo, data=data) - elif cmd == 330: # Logged in as - nickname, loggedinas = params.split(" ") - user = self.user(nickname) - self._event( - "onWhoisLoggedInAs", self.addons, origin=origin, user=user, - nickname=nickname, loggedinas=loggedinas, msg=extinfo, data=data) - user.loggedinas = loggedinas - elif cmd == 318: # End of WHOIS - try: - user = self.user(params) - except InvalidName: - user = params - self._event("onWhoisEnd", self.addons, origin=origin, - user=user, nickname=params, msg=extinfo, data=data) - - elif cmd == 321: # Start LIST - self._event( - "onListStart", self.addons, origin=origin, params=params, extinfo=extinfo, data=data) - elif cmd == 322: # LIST item - (chan, pop) = params.split(" ", 1) - self._event("onListEntry", self.addons, origin=origin, channel=self.channel( - chan), population=int(pop), extinfo=extinfo, data=data) - elif cmd == 323: # End of LIST - self._event( - "onListEnd", self.addons, origin=origin, endmsg=extinfo, data=data) - - elif cmd == 324: # Channel Modes - modeparams = params.split() - channame = modeparams.pop(0) - channel = self.channel(channame) - self._event("onRecv", channel.addons, **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)) - self._event("onChannelModes", self.addons + channel.addons, - channel=channel, modedelta=modedelta, data=data) - for ((modeset, mode), param) in modedelta: - if mode in self.supports["CHANMODES"][2]: - channel.modes[mode] = param - elif mode in self.supports["CHANMODES"][3]: - channel.modes[mode] = True - - elif cmd == 329: # Channel created - channame, created = params.split() - created = int(created) - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event("onChanCreated", self.addons + channel.addons, - channel=channel, created=created, data=data) - channel.created = int(created) - - elif cmd == 332: # Channel Topic - channame = params - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event("onTopic", self.addons + channel.addons, - origin=origin, channel=channel, topic=extinfo, data=data) - channel.topic = extinfo - - elif cmd == 333: # Channel Topic info - (channame, nick, dt) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onTopicInfo", self.addons + channel.addons, origin=origin, - channel=channel, topicsetby=nick, topictime=int(dt), data=data) - channel.topicsetby = nick - channel.topictime = int(dt) - - elif cmd == 352: # WHO reply - (channame, username, host, serv, - nick, flags) = params.split() - try: - (hops, realname) = extinfo.split(" ", 1) - except ValueError: - hops = extinfo - realname = None - - chantypes = self.supports.get("CHANTYPES", "&#+!") - if re.match(_chanmatch % re.escape(chantypes), channame): - channel = self.channel(channame) - else: - channel = None + # Parse - user = self.user(nick) + # Takes the given data and runs it through a parse method to determine what addon methods should be called later, and prepares the arguments + # to be passed to each of these methods. + # This part does not update the IRC state. + parsename = ( + "parse%03d" if type(cmd) == int else "parse%s") % cmd - if type(channel) == Channel: - self._event("onRecv", channel.addons, **data) - 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, data=data) - else: - self._event( - "onWhoEntry", self.addons, origin=origin, channel=channel, user=user, channame=channame, - username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname, data=data) - user.hops = hops - user.realname = realname - user.username = username - user.host = host - user.server = serv - user.away = "G" in flags - user.ircop = "*" in flags - if type(channel) == Channel: - if user not in channel.users: - channel.users.append(user) - if channel not in user.channels: - user.channels.append(channel) - for (mode, prefix) in zip(*self.supports["PREFIX"]): - if prefix in flags: - if mode in channel.modes.keys() and user not in channel.modes[mode]: - channel.modes[mode].append(user) - elif mode not in channel.modes.keys(): - channel.modes[mode] = [user] - - elif cmd == 315: # End of WHO reply - chantypes = self.supports.get("CHANTYPES", "&#+!") - if re.match(_chanmatch % re.escape(chantypes), params): - channel = self.channel(params) - self._event("onWhoEnd", self.addons + channel.addons, - origin=origin, param=params, endmsg=extinfo, data=data) - else: - self._event( - "onWhoEnd", self.addons, origin=origin, param=params, endmsg=extinfo, data=data) - - elif cmd == 353: # NAMES reply - (flag, channame) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - - if self.supports.has_key("PREFIX"): - names = re.findall( - r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?" % - re.escape(self.supports["PREFIX"][1]), extinfo) - else: - names = re.findall( - r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo) - # Still put it into tuple form for - # compatibility in the next - # structure - self._event( - "onNames", self.addons + channel.addons, origin=origin, - channel=channel, flag=flag, channame=channame, nameslist=names, data=data) - - for (symbs, nick, username, host) in names: - user = self.user(nick) - if user.nick != nick: - user.nick = nick - if username and user.username != username: - user.username = username - if host and user.host != host: - user.host = host - with channel.lock: - if channel not in user.channels: - user.channels.append(channel) - if user not in channel.users: - channel.users.append(user) - if self.supports.has_key("PREFIX"): - for symb in symbs: - mode = self.supports["PREFIX"][0][ - self.supports["PREFIX"][1].index(symb)] - if not channel.modes.has_key(mode): - channel.modes[mode] = [user] - elif user not in channel.modes[mode]: - channel.modes[mode].append(user) - - elif cmd == 366: # End of NAMES reply - channel = self.channel(params) - self._event( - "onNamesEnd", self.addons + channel.addons, origin=origin, - channel=channel, channame=params, endmsg=extinfo, data=data) - - elif cmd == 372: # MOTD line - self._event( - "onMOTDLine", self.addons, origin=origin, motdline=extinfo, data=data) - self.motd.append(extinfo) - elif cmd == 375: # Begin MOTD - self._event( - "onMOTDStart", self.addons, origin=origin, motdgreet=extinfo, data=data) - self.motdgreet = extinfo - self.motd = [] - elif cmd == 376: - self._event( - "onMOTDEnd", self.addons, origin=origin, motdend=extinfo, data=data) - self.motdend = extinfo # End of MOTD - - elif cmd == 386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal) - (channame, owner) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - user = self.user(owner) - if user.nick != owner: - user.nick = owner - if channel.modes.has_key("q"): - if user not in channel.modes["q"]: - channel.modes["q"].append(user) - else: - channel.modes["q"] = [user] - - elif cmd == 388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) - (channame, admin) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - if channel.name != channame: - channel.name = channame # Server seems to have changed the idea of the case of the channel name - user = self.user(admin) - if user.nick != admin: - user.nick = admin - if channel.modes.has_key("a"): - if user not in channel.modes["a"]: - channel.modes["a"].append(user) - else: - channel.modes["a"] = [user] - - elif cmd == "NICK": - newnick = extinfo if len(extinfo) else target - - addons = reduce( - lambda x, y: x + y, [chan.addons for chan in origin.channels], []) - self._event("onRecv", addons, **data) - self._event( - "onNickChange", self.addons + addons, user=origin, newnick=newnick, data=data) - if origin == self.identity: - self._event( - "onMeNickChange", self.addons + addons, newnick=newnick) - - for u in self.users: - if u.nick.lower() == newnick.lower(): - self.users.remove( - u) # Nick collision, safe to assume this orphaned user is offline, so we shall remove the old instance. - for channel in self.channels: - # If for some odd reason, the old user still - # appears common channels, then we will remove - # the user anyway. - if u in channel.users: - channel.users.remove(u) - origin.nick = newnick - - elif cmd == "JOIN": - channel = target if type( - target) == Channel else self.channel(extinfo) - self._event("onRecv", channel.addons, **data) - self._event( - "onJoin", self.addons + channel.addons, user=origin, channel=channel, data=data) - - if origin == self.identity: # This means the bot is entering the room, - # and will reset all the channel data, on the assumption that such data may have changed. - # Also, the bot must request modes - with channel._joining: - if channel._joinrequested: - channel._joinreply = cmd - channel._joining.notify() - channel._init() - self._event( - "onMeJoin", self.addons + channel.addons, channel=channel) - self._send(u"MODE %s" % channel.name) - self._send(u"WHO %s" % channel.name) - if "CHANMODES" in self.supports.keys(): - self._send( - u"MODE %s :%s" % (channel.name, self.supports["CHANMODES"][0])) - - if channel not in origin.channels: - origin.channels.append(channel) - if origin not in channel.users: - channel.users.append(origin) - - elif cmd == "KICK": - kicked = self.user(params) - if kicked.nick != params: - kicked.nick = params - - self._event("onRecv", target.addons, **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) - self._event( - "onKick", self.addons + target.addons, kicker=origin, - channel=target, kicked=kicked, kickmsg=extinfo, data=data) - - if target in kicked.channels: - kicked.channels.remove(target) - if kicked in target.users: - target.users.remove(kicked) - if self.supports.has_key("PREFIX"): - for mode in self.supports["PREFIX"][0]: - if target.modes.has_key(mode) and kicked in target.modes[mode]: - target.modes[mode].remove(kicked) - - elif cmd == "PART": + # This is the case that there is a parse method specific to the + # given cmd. + if hasattr(self, parsename) and callable(getattr(self, parsename)): + parsemethod = getattr(self, parsename) try: - self._event("onRecv", target.addons, **data) - if origin == self.identity: - with target._parting: - if target._partrequested: - target._partreply = cmd - target._parting.notify() - self._event( - "onMePart", self.addons + target.addons, channel=target, partmsg=extinfo) - self._event("onPart", self.addons + target.addons, - user=origin, channel=target, partmsg=extinfo, data=data) - - if target in origin.channels: - origin.channels.remove(target) - if origin in target.users: - target.users.remove(origin) - if self.supports.has_key("PREFIX"): - for mode in self.supports["PREFIX"][0]: - if target.modes.has_key(mode) and origin in target.modes[mode]: - target.modes[mode].remove(origin) + addons, events = parsemethod( + origin, target, targetprefix, params, extinfo) except: - print target - raise - elif cmd == "QUIT": - channels = list(origin.channels) - addons = reduce( - lambda x, y: x + y, [chan.addons for chan in origin.channels], []) - self._event("onRecv", addons, **data) - self._event( - "onQuit", self.addons + addons, user=origin, quitmsg=extinfo, data=data) - for channel in origin.channels: - with channel.lock: - if origin in channel.users: - channel.users.remove(origin) - if self.supports.has_key("PREFIX"): - for mode in self.supports["PREFIX"][0]: - if channel.modes.has_key(mode) and origin in channel.modes[mode]: - channel.modes[mode].remove(origin) - origin.channels = [] - - elif cmd == "MODE": - if type(target) == Channel: - self._event("onRecv", target.addons, **data) - modedelta = [] - modeparams = params.split() - setmodes = modeparams.pop(0) - modeset = "+" - for mode in setmodes: - if mode in "+-": - modeset = mode - else: - if mode in self.supports["CHANMODES"][0] + self.supports["CHANMODES"][1]: - param = modeparams.pop(0) - modedelta.append( - ("%s%s" % (modeset, mode), param)) - if mode in _maskmodeeventnames.keys(): - if modeset == "+": - eventname = _maskmodeeventnames[ - mode][0] - if mode == "k": - target.key = param - if modeset == "-": - eventname = _maskmodeeventnames[ - mode][1] - if mode == "k": - target.key = None - matchesbot = glob.fnmatch.fnmatch( - "%s!%s@%s".lower() % (self.identity.nick, self.identity.username, self.identity.host), param.lower()) - self._event( - "on%s" % eventname, self.addons + target.addons, user=origin, channel=target, banmask=param) - if matchesbot: - self._event( - "onMe%s" % eventname, self.addons + target.addons, user=origin, channel=target, banmask=param) - elif mode in self.supports["CHANMODES"][2]: - if modeset == "+": - param = modeparams.pop(0) - modedelta.append( - ("%s%s" % (modeset, mode), param)) - else: - modedelta.append( - ("%s%s" % (modeset, mode), None)) - elif mode in self.supports["CHANMODES"][3]: - modedelta.append( - ("%s%s" % (modeset, mode), None)) - elif self.supports.has_key("PREFIX") and mode in self.supports["PREFIX"][0]: - modenick = modeparams.pop(0) - modeuser = self.user(modenick) - if mode in _privmodeeventnames.keys(): - if modeset == "+": - eventname = _privmodeeventnames[ - mode][0] - if modeset == "-": - eventname = _privmodeeventnames[ - mode][1] - self._event( - "on%s" % eventname, self.addons + target.addons, user=origin, channel=target, modeuser=modeuser) - if modeuser == self.identity: - self._event( - "onMe%s" % eventname, self.addons + target.addons, user=origin, channel=target) - modedelta.append( - ("%s%s" % (modeset, mode), modeuser)) - self._event( - "onChanModeSet", self.addons + target.addons, - user=origin, channel=target, modedelta=modedelta, data=data) - with target.lock: - for ((modeset, mode), param) in modedelta: - if mode in self.supports["CHANMODES"][0]: - if modeset == "+": - if target.modes.has_key(mode): - if param.lower() not in [mask.lower() for (mask, setby, settime) in target.modes[mode]]: - target.modes[mode].append( - (param, origin, int(time.time()))) - else: - target.modes[mode] = [ - (param, origin, int(time.time()))] - else: - if mode in target.modes.keys(): - if mode == "b": # Inspircd mode is case insentive when unsetting the mode - masks = [ - mask.lower() for (mask, setby, settime) in target.modes[mode]] - if param.lower() in masks: - index = masks.index( - param.lower()) - # print "Index: %d"%index - del target.modes[ - mode][index] - else: - masks = [ - mask for (mask, setby, settime) in target.modes[mode]] - if param in masks: - index = masks.index(param) - del target.modes[ - mode][index] - elif mode in self.supports["CHANMODES"][1]: - if modeset == "+": - target.modes[mode] = param - else: - target.modes[mode] = None - elif mode in self.supports["CHANMODES"][2]: - if modeset == "+": - target.modes[mode] = param - else: - target.modes[mode] = None - elif mode in self.supports["CHANMODES"][3]: - if modeset == "+": - target.modes[mode] = True - else: - target.modes[mode] = False - elif self.supports.has_key("PREFIX") and mode in self.supports["PREFIX"][0]: - if modeset == "+": - if target.modes.has_key(mode) and param not in target.modes[mode]: - target.modes[mode].append(param) - if not target.modes.has_key(mode): - target.modes[mode] = [param] - elif target.modes.has_key(mode) and param in target.modes[mode]: - target.modes[mode].remove(param) - elif target == self.identity: - modeparams = (params if params else extinfo).split() - setmodes = modeparams.pop(0) - modedelta = [] - modeset = "+" - for mode in setmodes: - if mode in "+-": - modeset = mode - continue - if modeset == "+": - if mode == "s": - if len(modeparams): - snomask = modeparams.pop(0) - snomaskdelta = [] - snomodeset = "+" - for snomode in snomask: - if snomode in "+-": - snomodeset = snomode - continue - snomaskdelta.append( - "%s%s" % (snomodeset, snomode)) - modedelta.append(("+s", snomaskdelta)) - else: - modedelta.append(("+s", [])) - else: - modedelta.append(("+%s" % mode, None)) - if modeset == "-": - modedelta.append(("-%s" % mode, None)) - self._event( - "onUserModeSet", self.addons, origin=origin, modedelta=modedelta, data=data) - for ((modeset, mode), param) in modedelta: - if modeset == "+": - if mode not in target.modes: - target.modes += mode - if mode == "s": - for snomodeset, snomode in param: - if snomodeset == "+" and snomode not in target.snomask: - target.snomask += snomode - if snomodeset == "-" and snomode in target.snomask: - target.snomask = target.snomask.replace( - snomode, "") - if modeset == "-": - if mode in target.modes: - target.modes = target.modes.replace( - mode, "") - if mode == "s": - target.snomask = "" - - elif cmd == "TOPIC": - self._event("onRecv", target.addons, **data) - self._event("onTopicSet", self.addons + target.addons, - user=origin, channel=target, topic=extinfo, data=data) - - with target.lock: - target.topic = extinfo - target.topicsetby = origin - target.topictime = int(time.time()) - - elif cmd == "INVITE": - channel = self.channel(extinfo if extinfo else params) - self._event("onRecv", channel.addons, **data) - self._event( - "onInvite", self.addons + channel.addons, user=origin, channel=channel, data=data) - - elif cmd == "PRIVMSG": - if type(target) == Channel: - self._event("onRecv", target.addons, **data) - - # CTCP handling - ctcp = re.findall(_ctcpmatch, extinfo) - if ctcp: - (ctcptype, ext) = ctcp[0] - if ctcptype.upper() == "ACTION": - if type(target) == Channel: - self._event( - "onChanAction", self.addons + target.addons, user=origin, - channel=target, targetprefix=targetprefix, action=ext, data=data) - elif target == self.identity: - self._event( - "onPrivAction", self.addons, user=origin, action=ext, data=data) - else: - if type(target) == Channel: - self._event( - "onChanCTCP", self.addons + target.addons, user=origin, channel=target, - targetprefix=targetprefix, ctcptype=ctcptype, params=ext, data=data) - elif target == self.identity: - self._event( - "onPrivCTCP", self.addons, user=origin, ctcptype=ctcptype, params=ext, data=data) - if ctcptype.upper() == "VERSION": - origin.ctcpreply("VERSION", self.ctcpversion()) - if ctcptype.upper() == "TIME": - tformat = time.ctime() - tz = time.tzname[0] - origin.ctcpreply( - "TIME", "%(tformat)s %(tz)s" % vars()) - if ctcptype.upper() == "PING": - origin.ctcpreply("PING", "%(ext)s" % vars()) - if ctcptype.upper() == "FINGER": - origin.ctcpreply("FINGER", "%(ext)s" % vars()) - else: - if type(target) == Channel: - self._event( - "onChanMsg", self.addons + target.addons, user=origin, - channel=target, targetprefix=targetprefix, msg=extinfo, data=data) - elif target == self.identity: - self._event( - "onPrivMsg", self.addons, user=origin, msg=extinfo, data=data) - - elif cmd == "NOTICE": - if type(target) == Channel: - self._event("onRecv", target.addons, **data) - - # CTCP handling - ctcp = re.findall(_ctcpmatch, extinfo) - if ctcp and target == self.identity: - (ctcptype, ext) = ctcp[0] - self._event( - "onCTCPReply", self.addons, origin=origin, ctcptype=ctcptype, params=ext, data=data) - else: - if type(target) == Channel: - self._event( - "onChanNotice", self.addons + target.addons, origin=origin, - channel=target, targetprefix=targetprefix, msg=extinfo, data=data) - elif target == self.identity: - self._event( - "onPrivNotice", self.addons, origin=origin, msg=extinfo, data=data) - - elif cmd == 367: # Channel Ban list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onBanListEntry", self.addons + channel.addons, origin=origin, - channel=channel, mask=mask, setby=setby, settime=int(settime), data=data) - if "b" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: - channel.modes["b"].append( - (mask, setby, int(settime))) - else: - channel.modes["b"] = [(mask, setby, int(settime))] - elif cmd == 368: - channel = self.channel(params) - self._event("onRecv", channel.addons, **data) - self._event("onBanListEnd", self.addons + channel.addons, - origin=origin, channel=channel, endmsg=extinfo, data=data) - - elif cmd == 346: # Channel Invite list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onInviteListEntry", self.addons + channel.addons, origin=origin, - channel=channel, mask=mask, setby=setby, settime=int(settime), data=data) - if "I" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]: - channel.modes["I"].append( - (mask, setby, int(settime))) - else: - channel.modes["I"] = [(mask, setby, int(settime))] - elif cmd == 347: - channel = self.channel(params) - self._event("onRecv", channel.addons, **data) - self._event( - "onInviteListEnd", self.addons + channel.addons, - origin=origin, channel=channel, endmsg=extinfo, data=data) - - elif cmd == 348: # Channel Ban Exception list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onBanExceptListEntry", self.addons + channel.addons, origin=origin, - channel=channel, mask=mask, setby=setby, settime=int(settime), data=data) - if "e" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]: - channel.modes["e"].append( - (mask, setby, int(settime))) - else: - channel.modes["e"] = [(mask, setby, int(settime))] - elif cmd == 349: - channel = self.channel(params) - self._event("onRecv", channel.addons, **data) - self._event( - "onBanExceptListEnd", self.addons + channel.addons, - origin=origin, channel=channel, endmsg=extinfo, data=data) - - elif cmd == 910: # Channel Access List - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onAccessListEntry", self.addons + channel.addons, origin=origin, - channel=channel, mask=mask, setby=setby, settime=int(settime), data=data) - if "w" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: - channel.modes["w"].append( - (mask, setby, int(settime))) - else: - channel.modes["w"] = [(mask, setby, int(settime))] - elif cmd == 911: - channel = self.channel(params) - self._event("onRecv", channel.addons, **data) - self._event( - "onAccessListEnd", self.addons + channel.addons, - origin=origin, channel=channel, endmsg=extinfo, data=data) - - elif cmd == 941: # Spam Filter list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onSpamfilterListEntry", self.addons + channel.addons, origin=origin, - channel=channel, mask=mask, setby=setby, settime=int(settime), data=data) - if "g" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]: - channel.modes["g"].append( - (mask, setby, int(settime))) - else: - channel.modes["g"] = [(mask, setby, int(settime))] - elif cmd == 940: - channel = self.channel(params) - self._event("onRecv", channel.addons, **data) - self._event( - "onSpamfilterListEnd", self.addons + channel.addons, - origin=origin, channel=channel, endmsg=extinfo, data=data) - - elif cmd == 954: # Channel exemptchanops list - (channame, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onExemptChanOpsListEntry", self.addons + channel.addons, origin=origin, - channel=channel, mask=mask, setby=setby, settime=int(settime), data=data) - if "X" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]: - channel.modes["X"].append( - (mask, setby, int(settime))) - else: - channel.modes["X"] = [(mask, setby, int(settime))] - elif cmd == 953: - channel = self.channel(params) - self._event("onRecv", channel.addons, **data) - self._event( - "onExemptChanOpsListEnd", self.addons + channel.addons, - origin=origin, channel=channel, endmsg=extinfo, data=data) - - elif cmd == 728: # Channel quiet list - (channame, modechar, mask, setby, settime) = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onQuietListEntry", self.addons + channel.addons, origin=origin, channel=channel, - modechar=modechar, mask=mask, setby=setby, settime=int(settime), data=data) - if "q" in channel.modes.keys(): - if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["q"]]: - channel.modes["q"].append( - (mask, setby, int(settime))) - else: - channel.modes["q"] = [(mask, setby, int(settime))] - elif cmd == 729: - channame, modechar = params.split() - channel = self.channel(channame) - self._event("onRecv", channel.addons, **data) - self._event( - "onQuietListEnd", self.addons + channel.addons, channel=channel, endmsg=extinfo, data=data) - - elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params - for param in params.split(): - if len(param) and param[0] in self.supports["CHANTYPES"]: - channel = self.channel(param) - self._event("onRecv", channel.addons, **data) - - elif type(cmd) == int: - self._event( - "on%03d" % cmd, self.addons, line=line, origin=origin, - target=target, params=params, extinfo=extinfo, data=data) - elif not (cmd in ("PING", "PONG") and self.quietpingpong): - self._event( - "on%s" % cmd, self.addons, line=line, origin=origin, - cmd=cmd, target=target, params=params, extinfo=extinfo, data=data) - - if cmd in (384, 403, 405, 471, 473, 474, 475, 476, 520, 477, 489, 495): # Channel Join denied - try: - channel = self.channel(params) - except InvalidName: - pass + exc, excmsg, tb = sys.exc_info() + + # Print to log AND stderr + tblines = [ + u"!!! There was an error in parsing the following line:", u"!!! %s" % line] + for tbline in traceback.format_exc().split("\n"): + tblines.append(u"!!! %s" % autodecode(tbline)) + self.logwrite(*tblines) + print >>sys.stderr, u"There was an error in parsing the following line:" + print >>sys.stderr, u"%s" % line + print >>sys.stderr, traceback.format_exc() + return + else: + addons = self.addons + if type(cmd) == int: + events = [ + ("on%03d" % cmd, dict(line=line, origin=origin, target=target, params=params, extinfo=extinfo), True)] else: - with channel._joining: - if channel._joinrequested: - channel._joinreply = (cmd, extinfo) - channel._joining.notify() + events = [ + ("on%s" % cmd.upper(), dict(line=line, origin=origin, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), True)] - elif cmd == 470: # Channel Join denied due to redirect - channelname, redirect = params.split() - try: - channel = self.channel(channelname) - except InvalidName: - pass - else: - with channel._joining: - if channel._joinrequested: - channel._joinreply = ( - cmd, "%s (%s)" % (extinfo, redirect)) - channel._joining.notify() + # Supress pings and pongs if self.quietpingpong is set to True + if cmd in ("PING", "PONG") and self.quietpingpong: + return - # Handle events that were not handled. - # if not (cmd in ("PING", "PONG") and self.quietpingpong): - # self._event("onUnhandled", unhandled, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo) + # Send parsed data to addons having onRecv method first + self._event( + addons + [self], [("onRecv", dict(line=line, **data), False)], line, data) - def _trynick(self): - (q, s) = divmod(self.trynick, len(self.nick)) - nick = self.nick[s] - if q > 0: - nick = "%s%d" % (nick, q) - self._send(u"NICK %s" % nick) - self.trynick += 1 + # Support for further addon events is taken care of here. We also treat the irc.Connection instance itself as an addon for the purpose of + # tracking the IRC state, and should be invoked *last*. + self._event(addons + [self], events, line, data) - def _recvhandler(self, server=None, port=None, ssl=None, ipv6=None): - pingreq = None - # Enforce that this function must only be run from within - # self._sendhandlerthread. - if currentThread() != self._recvhandlerthread: + def _recvhandler(self, server, port, ipvers, secure): + if currentThread() != self._recvhandlerthread: # Enforce that this function must only be run from within self._sendhandlerthread. raise RuntimeError, "This function is designed to run in its own thread." - server = self.server - if self.ipv6 and ":" in server: - server = "[%s]" % server - port = self.port try: with self.lock: - self._event("onSessionOpen", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + self._event(self.getalladdons(), [ + ("onSessionOpen", dict(), False)]) + + self.logwrite("### Session started") - self.logwrite("### Log session started") - while True: # Autoreconnect loop + ipvers = ipvers if type(ipvers) == tuple else (ipvers,) + + # Autoreconnect loop + while True: attempt = 1 - while True: # Autoretry loop - try: - self._connect() + + # Autoretry loop + while True: + servisip = False + for ipver in ipvers: # Check to see if address is a valid ip address instead of host name + try: + socket.inet_pton(ipver, server) + except socket.error: + continue # Not a valid ip address under this ipver. + # Is a valid ip address under this ipver. + if ipver == socket.AF_INET6: + self._tryaddrs( + server, [(server, port, 0, 0)], ipver, secure) + else: + ret = self._tryaddrs( + server, [(server, port)], ipver, secure) + servisip = True + break + # Otherwise, we assume server is a hostname + if not servisip: + ret = self._tryipvers(server, port, ipvers, secure) + if ret: + self.server = server + self.port = port + self.ipvers = ipvers + self.secure = secure break - except socket.error: + if self._quitexpected: + sys.exit() + if self.retrysleep > 0: + time.sleep(self.retrysleep) + if self._quitexpected: + sys.exit() + if attempt < self.maxretries or self.maxretries < 0: if self._quitexpected: sys.exit() - if attempt < self.maxretries or self.maxretries < 0: - if self.retrysleep > 0: - time.sleep(self.retrysleep) - if self._quitexpected: - sys.exit() - attempt += 1 - else: - self.logwrite( - "*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars()) - sys.exit() + attempt += 1 + else: + self.logwrite( + "*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars()) + with self._connecting: + self._connecting.notifyAll() + sys.exit() # Connection succeeded try: + pingreq = None with self._sendline: self._sendline.notify() @@ -1481,13 +876,8 @@ class Connection(object): string.split(readbuf[0:lastlf], "\n")) readbuf = readbuf[lastlf + 1:] - line = string.rstrip(linebuf.pop(0)) - try: - line = line.decode("utf8") - except UnicodeDecodeError: - # Attempt to figure encoding - charset = chardet.detect(line)['encoding'] - line = line.decode(charset) + line = linebuf.pop(0).rstrip("\r") + line = autodecode(line) self._procrecvline(line) except SystemExit: # Connection lost normally. @@ -1498,8 +888,8 @@ class Connection(object): with self.lock: self.logwrite( "*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) - self._event("onConnectFail", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) + self._event(self.getalladdons(), [ + ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)]) except: # Unknown exception, treated as FATAL. Try to quit IRC and terminate thread with exception. # Quit with a (hopefully) useful quit message, or die @@ -1513,20 +903,16 @@ class Connection(object): raise finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred. - with self._sendline: + with self._sendline: # Notify _outgoingthread that the connection has been terminated. self._outgoing.clear() self._sendline.notify() - with self.lock: - self._event("onDisconnect", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), expected=self._quitexpected) + with self._disconnecting: + self._disconnecting.notifyAll() + self._event(self.getalladdons(), [ + ("onDisconnect", dict(expected=self._quitexpected), False)]) self._init() - # Notify _outgoingthread that the connection has been - # terminated. - with self._sendline: - self._sendline.notify() - try: self._connection.close() except: @@ -1549,18 +935,1164 @@ class Connection(object): sys.exit() finally: - self.logwrite("### Log session ended") - self._event("onSessionClose", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], [])) + self.logwrite("### Session ended") + self._event(self.getalladdons(), [ + ("onSessionClose", dict(), False)]) # Tell _sendhandler to quit with self._sendline: self._outgoing.append("quit") self._sendline.notify() + # Gets a list of *all* addons, including channel-specific addons. + def getalladdons(self): + return self.addons + reduce(lambda x, y: x + y, [chan.addons for chan in self.channels], []) + + # The following methods matching parse* are used to determine what addon methods will be called, and prepares the arguments to be passed. + # These methods can also be used to determine event support by invoking + # them with no parameters. This allows for addition of event supports. + # Each is expected to return a tuple (addons, [(method, args, fallback), ...]). + # 'addons' refers to the list of addons whose methods should be called. + # [(method, args, fallback), ...] is a list of methods and parameters to be called, as well as a flag to determine when a fallback is permitted. + # 'method' refers to the name of the method to be invoked in the addons + # 'args' is a dict of arguments that should be passed as parameters to event. + # 'fallback' is a flag to determine when a fallback to 'onOther' is permitted. + # Each of these functions should allow passing None to all arguments, in + # which case, should report back *all* supported methods. + def parse001(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + return (self.getalladdons(), [("onWelcome", dict(origin=origin, msg=extinfo), True)]) + + def parse002(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + return (self.getalladdons(), [("onYourHost", dict(origin=origin, msg=extinfo), True)]) + + def parse003(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + return (self.getalladdons(), [("onServerCreated", dict(origin=origin, msg=extinfo), True)]) + + def parse004(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + return (self.getalladdons(), [("onServInfo", dict(origin=origin, servinfo=params), True)]) + + def parse005(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Server Supports + if origin == None: + return (None, [("onSupports", dict(origin=None, supports=None, msg=None), True)]) + support = dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params)) + if support.has_key("CHANMODES"): + support["CHANMODES"] = support["CHANMODES"].split(",") + if support.has_key("PREFIX"): + matches = re.findall(_prefixmatch, support["PREFIX"]) + if matches: + support["PREFIX"] = matches[0] + else: + del support["PREFIX"] + # Might as well delete the info if it doesn't match + # expected pattern + return (self.getalladdons(), [("onSupports", dict(origin=origin, supports=support, msg=extinfo), True)]) + + def parse008(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Snomask + if origin == None: + return (None, [("onSnoMask", dict(origin=None, snomask=None), True)]) + snomask = params.lstrip("+") + return (self.getalladdons(), [("onSnoMask", dict(origin=origin, snomask=snomask), True)]) + + def parse221(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # User Modes + if origin == None: + return (self.getalladdons(), [("onUserModes", dict(origin=None, modes=None), True)]) + modes = (params if params else extinfo).lstrip("+") + return (self.getalladdons(), [("onUserModes", dict(origin=origin, modes=modes), True)]) + + def parse251(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Net Stats + return (self.addons, [("onNetStats", dict(origin=origin, netstats=extinfo), True)]) + + def parse252(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Operator count + if origin == None: + return (None, [("onOpCount", dict(origin=None, opcount=None), True)]) + opcount = int(params) + return (self.addons, [("onOpCount", dict(origin=origin, opcount=opcount), True)]) + + def parse254(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Count + if origin == None: + return (self.addons, [("onChanCount", dict(origin=None, chancount=None), True)]) + chancount = int(params) + return (self.addons, [("onChanCount", dict(origin=origin, chancount=chancount), True)]) + + def parse305(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Returned from away status + return (self.getalladdons(), [("onReturn", dict(origin=origin, msg=extinfo), True)]) + + def parse306(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Entered away status + return (self.getalladdons(), [("onAway", dict(origin=origin, msg=extinfo), True)]) + + def parse311(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Start of WHOIS data + if origin == None: + return (None, [("onWhoisStart", dict(origin=None, user=None, nickname=None, username=None, host=None, realname=None), True)]) + nickname, username, host, star = params.split() + user = self.user(nickname) + return (self.addons, [("onWhoisStart", dict(origin=origin, user=user, nickname=nickname, username=username, host=host, realname=extinfo), True)]) + + def parse301(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Away Message + if origin == None: + return (None, [("onWhoisAway", dict(origin=None, user=None, nickname=None, awaymsg=None), True)]) + user = self.user(params) + return (self.addons, [("onWhoisAway", dict(origin=origin, user=user, nickname=params, awaymsg=extinfo), True)]) + + def parse303(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # ISON Reply + if origin == None: + return (None, [("onIsonReply", dict(origin=None, isonusers=None), True)]) + users = [self.user(user) for user in extinfo.split(" ")] + return (self.addons, [("onIsonReply", dict(origin=origin, isonusers=users), True)]) + + def parse307(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Is a registered nick + if origin == None: + return (None, [("onWhoisRegisteredNick", dict(origin=None, user=None, nickname=None, msg=None), True)]) + return (self.addons, [("onWhoisRegisteredNick", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)]) + + def parse378(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Connecting From + if origin == None: + return (None, [("onWhoisConnectingFrom", dict(origin=None, user=None, nickname=None, msg=None), True)]) + return (self.addons, [("onWhoisConnectingFrom", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)]) + + def parse319(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channels + if origin == None: + return (None, [("onWhoisChannels", dict(origin=None, user=None, nickname=None, chanlist=None), True)]) + return (self.addons, [("onWhoisChannels", dict(origin=origin, user=self.user(params), nickname=params, chanlist=extinfo.split(" ")), True)]) + + def parse310(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Availability + if origin == None: + return (None, [("onWhoisAvailability", dict(origin=None, user=None, nickname=None, msg=None), True)]) + return (self.addons, [("onWhoisAvailability", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)]) + + def parse312(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Server + if origin == None: + return (None, [("onWhoisServer", dict(origin=None, user=None, nickname=None, server=None, servername=None), True)]) + nickname, server = params.split(" ") + user = self.user(nickname) + return (self.addons, [("onWhoisServer", dict(origin=origin, user=user, nickname=nickname, server=server, servername=extinfo), True)]) + + def parse313(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # IRC Op + if origin == None: + return (None, [("onWhoisOp", dict(origin=None, user=None, nickname=None, msg=None), True)]) + user = self.user(params) + return (self.addons, [("onWhoisOp", dict(origin=origin, user=user, nickname=params, msg=extinfo), True)]) + + def parse317(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Idle and Signon times + if origin == None: + return (None, [("onWhoisTimes", dict(origin=None, user=None, nickname=None, idletime=None, signontime=None, msg=None), True)]) + nickname, idletime, signontime = params.split(" ") + user = self.user(nickname) + return (self.addons, [("onWhoisTimes", dict(origin=origin, user=user, nickname=nickname, idletime=int(idletime), signontime=int(signontime), msg=extinfo), True)]) + + def parse671(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # SSL + if origin == None: + return (None, [("onWhoisSSL", dict(origin=None, user=None, nickname=None, msg=None), True)]) + user = self.user(params) + return (self.addons, [("onWhoisSSL", dict(origin=origin, user=user, nickname=params, msg=extinfo), True)]) + + def parse379(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # User modes + if origin == None: + return (None, [("onWhoisModes", dict(origin=None, user=None, nickname=None, msg=None), True)]) + return (self.addons, [("onWhoisModes", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)]) + + def parse330(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Logged in as + if origin == None: + return (None, [("onWhoisLoggedInAs", dict(origin=None, user=None, nickname=None, loggedinas=None, msg=None), True)]) + nickname, loggedinas = params.split(" ") + user = self.user(nickname) + return (self.addons, [("onWhoisLoggedInAs", dict(origin=origin, user=user, nickname=nickname, loggedinas=loggedinas, msg=extinfo), True)]) + + def parse318(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of WHOIS + if origin == None: + return (None, [("onWhoisEnd", dict(origin=None, user=None, nickname=None, msg=None), True)]) + try: + user = self.user(params) + except InvalidName: + user = params + return (self.addons, [("onWhoisEnd", dict(origin=origin, user=user, nickname=params, msg=extinfo), True)]) + + def parse321(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Start LIST + return (None, [("onListStart", dict(origin=origin, params=params, extinfo=extinfo), True)]) + + def parse322(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # LIST item + if origin == None: + return (None, [("onListEntry", dict(origin=None, channel=None, population=None, extinfo=None), True)]) + (chan, pop) = params.split(" ", 1) + try: + return (self.addons, [("onListEntry", dict(origin=origin, channel=self.channel(chan), population=int(pop), extinfo=extinfo), True)]) + except: + return (self.addons, [("onListEntry", dict(origin=origin, channel=chan, population=int(pop), extinfo=extinfo), True)]) + + def parse323(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of LIST + return (None, [("onListEnd", dict(origin=None, endmsg=None), True)]) + + def parse324(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Modes + if origin == None: + return (None, [("onChannelModes", dict(origin=None, channel=None, modedelta=None), True)]) + modeparams = params.split() + channame = modeparams.pop(0) + channel = self.channel(channame) + + chanmodes = self.supports.get("CHANMODES", _defaultchanmodes) + setmodes = modeparams.pop(0) + modedelta = [] + for mode in setmodes: + if mode == "+": + continue + elif mode in [2]: + param = modeparams.pop(0) + modedelta.append(("+%s" % mode, param)) + elif mode in chanmodes[3]: + modedelta.append(("+%s" % mode, None)) + return (self.addons + channel.addons, [("onChannelModes", dict(origin=origin, channel=channel, modedelta=modedelta), True)]) + + def parse329(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel created + if origin == None: + return (None, [("onChanCreated", dict(origin=None, channel=None, created=None), True)]) + channame, created = params.split() + created = int(created) + channel = self.channel(channame) + return (self.addons + channel.addons, [("onChanCreated", dict(origin=origin, channel=channel, created=created), True)]) + + def parse332(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Topic + if origin == None: + return (None, [("onTopic", dict(origin=None, channel=None, topic=None), True)]) + channame = params + channel = self.channel(channame) + return (self.addons + channel.addons, [("onTopic", dict(origin=origin, channel=channel, topic=extinfo), True)]) + + def parse333(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Topic info + if origin == None: + return (None, [("onTopicInfo", dict(origin=None, channel=None, topicsetby=None, topictime=None), True)]) + (channame, nick, dt) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onTopicInfo", dict(origin=origin, channel=channel, topicsetby=nick, topictime=int(dt)), True)]) + + def parse352(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # WHO reply + if origin == None: + return (None, [("onWhoEntry", dict(origin=None, channel=None, user=None, channame=None, username=None, host=None, serv=None, nick=None, flags=None, hops=None, realname=None), True)]) + (channame, username, host, serv, nick, flags) = params.split() + try: + (hops, realname) = extinfo.split(" ", 1) + except ValueError: + hops = extinfo + realname = None + + chantypes = self.supports.get("CHANTYPES", _defaultchantypes) + if re.match(_chanmatch % re.escape(chantypes), channame): + channel = self.channel(channame) + else: + channel = None + + user = self.user(nick) + + if type(channel) == Channel: + return (self.addons + channel.addons, [("onWhoEntry", dict(origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname), True)]) + else: + return (self.addons, [("onWhoEntry", dict(origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname), True)]) + + def parse315(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of WHO reply + if origin == None: + return (None, [("onWhoEnd", dict(origin=None, param=None, endmsg=None), True)]) + chantypes = self.supports.get("CHANTYPES", _defaultchantypes) + if re.match(_chanmatch % re.escape(chantypes), params): + channel = self.channel(params) + return (self.addons + channel.addons, [("onWhoEnd", dict(origin=origin, param=params, endmsg=extinfo), True)]) + else: + return (self.addons, [("onWhoEnd", dict(origin=origin, param=params, endmsg=extinfo), True)]) + + def parse353(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # NAMES reply + if origin == None: + return (None, [("onNames", dict(origin=None, channel=None, flag=None, channame=None, nameslist=None), True)]) + (flag, channame) = params.split() + channel = self.channel(channame) + + if self.supports.has_key("PREFIX"): + names = re.findall(r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?" % + re.escape(self.supports["PREFIX"][1]), extinfo) + else: + names = re.findall(r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo) + # Still put it into tuple form for compatibility + # in the next structure + return (self.addons + channel.addons, [("onNames", dict(origin=origin, channel=channel, flag=flag, channame=channame, nameslist=names), True)]) + + def parse366(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of NAMES reply + if origin == None: + return (None, [("onNamesEnd", dict(origin=None, channel=None, channame=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onNamesEnd", dict(origin=origin, channel=channel, channame=params, endmsg=extinfo), True)]) + + def parse372(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # MOTD line + return (self.addons, [("onMOTDLine", dict(origin=origin, motdline=extinfo), True)]) + self.motd.append(extinfo) + + def parse375(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Begin MOTD + return (self.addons, [("onMOTDStart", dict(origin=origin, motdgreet=extinfo), True)]) + + def parse376(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + return (self.addons, [("onMOTDEnd", dict(origin=origin, motdend=extinfo), True)]) + + def parseNICK(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + + if origin == None: + return (None, [ + ("onNickChange", dict(user=None, newnick=None), True), + ("onMeNickChange", dict(newnick=None), False) + ]) + + newnick = extinfo if len(extinfo) else target + + addons = reduce( + lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], []) + + if origin == self.identity: + return (self.addons + addons, [ + ("onNickChange", dict(user=origin, newnick=newnick), True), + ("onMeNickChange", dict(newnick=newnick), False) + ]) + return (self.addons + addons, [("onNickChange", dict(user=origin, newnick=newnick), True)]) + + def parseJOIN(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + + if origin == None: + return (None, [ + ("onMeJoin", dict(channel=None), False), + ("onJoin", dict(user=None, channel=None), True) + ]) + + if type(target) == Channel: + channel = target + else: + channel = self.channel(extinfo) + channel.name = extinfo + + if origin == self.identity: + return (self.addons + channel.addons, [ + ("onMeJoin", dict(channel=channel), False), + ("onJoin", dict(user=origin, channel=channel), True), + ]) + + return (self.addons + channel.addons, [("onJoin", dict(user=origin, channel=channel), True)]) + + def parseKICK(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + if origin == None: + return (None, [ + ("onMeKick", dict(channel=None, kicked=None, kickmsg=None), True), + ("onMeKicked", dict( + kicker=None, channel=None, kickmsg=None), True), + ("onKick", dict(kicker=None, channel=None, kicked=None, kickmsg=None), True) + ]) + events = [] + if origin == self.identity: + events.append( + ("onMeKick", dict(channel=target, kicked=kicked, kickmsg=extinfo), False)) + + kicked = self.user(params) + if kicked.nick != params: + kicked.nick = params + + if kicked == self.identity: + events.append( + ("onMeKicked", dict(kicker=origin, channel=target, kickmsg=extinfo), False)) + + events.append( + ("onKick", dict(kicker=origin, channel=target, kicked=kicked, kickmsg=extinfo), True)) + return (self.addons + target.addons, events) + + if target in kicked.channels: + kicked.channels.remove(target) + if kicked in target.users: + target.users.remove(kicked) + if self.supports.has_key("PREFIX"): + for mode in self.supports["PREFIX"][0]: + if target.modes.has_key(mode) and kicked in target.modes[mode]: + target.modes[mode].remove(kicked) + + def parsePART(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + if origin == None: + return (None, [ + ("onMePart", dict(channel=None, partmsg=None), True), + ("onPart", dict(user=None, channel=None, partmsg=None), True) + ]) + if origin == self.identity: + return (self.addons + target.addons, [ + ("onMePart", dict(channel=target, partmsg=extinfo), False), + ("onPart", dict(user=origin, channel=target, partmsg=extinfo), True) + ]) + else: + return (self.addons + target.addons, [("onPart", dict(user=origin, channel=target, partmsg=extinfo), True)]) + + def parseQUIT(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + if origin == None: + return (None, [("onQuit", dict(user=None, quitmsg=None), True)]) + + # Include addons for channels that both user and bot are in + # simultaneously. + addons = reduce( + lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], []) + return (self.addons + addons, [("onQuit", dict(user=origin, quitmsg=extinfo), True)]) + + def parseMODE(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + if origin == None: + events = [ + ("onChanModeSet", dict( + user=None, channel=None, modedelta=None), True), + ("onUserModeSet", dict(origin=None, modedelta=None), True) + ] + for (mode, (setname, unsetname)) in _maskmodeeventnames.items(): + events.append( + ("on%s" % setname, dict(user=None, channel=None, banmask=None), False)) + events.append( + ("onMe%s" % setname, dict(user=None, channel=None, banmask=None), False)) + events.append( + ("on%s" % unsetname, dict(user=None, channel=None, banmask=None), False)) + events.append( + ("onMe%s" % unsetname, dict(user=None, channel=None, banmask=None), False)) + for (mode, (setname, unsetname)) in _privmodeeventnames.items(): + events.append( + ("on%s" % setname, dict(user=None, channel=None, modeuser=None), False)) + events.append( + ("onMe%s" % setname, dict(user=None, channel=None), False)) + events.append( + ("on%s" % unsetname, dict(user=None, channel=None, banmask=None), False)) + events.append( + ("onMe%s" % unsetname, dict(user=None, channel=None), False)) + return (None, events) + if type(target) == Channel: + events = [] + modedelta = [] + modeparams = params.split() + setmodes = modeparams.pop(0) + modeset = "+" + chanmodes = self.supports.get("CHANMODES", _defaultchanmodes) + prefix = self.supports.get("PREFIX", _defaultprefix) + for mode in setmodes: + if mode in "+-": + modeset = mode + else: + if mode in chanmodes[0] + chanmodes[1]: + param = modeparams.pop(0) + modedelta.append(("%s%s" % (modeset, mode), param)) + if mode in _maskmodeeventnames.keys(): + if modeset == "+": + eventname = _maskmodeeventnames[mode][0] + if mode == "k": + target.key = param + if modeset == "-": + eventname = _maskmodeeventnames[mode][1] + if mode == "k": + target.key = None + matchesbot = glob.fnmatch.fnmatch( + "%s!%s@%s".lower() % (self.identity.nick, self.identity.username, self.identity.host), param.lower()) + events.append( + ("on%s" % eventname, dict(user=origin, channel=target, banmask=param), False)) + if matchesbot: + events.append( + ("onMe%s" % eventname, dict(user=origin, channel=target, banmask=param), False)) + elif mode in 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 chanmodes[3]: + modedelta.append(("%s%s" % (modeset, mode), None)) + elif mode in prefix[0]: + modenick = modeparams.pop(0) + modeuser = self.user(modenick) + if mode in _privmodeeventnames.keys(): + if modeset == "+": + eventname = _privmodeeventnames[mode][0] + if modeset == "-": + eventname = _privmodeeventnames[mode][1] + events.append( + ("on%s" % eventname, dict(user=origin, channel=target, modeuser=modeuser), False)) + if modeuser == self.identity: + events.append( + ("onMe%s" % eventname, dict(user=origin, channel=target), False)) + modedelta.append(("%s%s" % (modeset, mode), modeuser)) + events.append( + ("onChanModeSet", dict(user=origin, channel=target, modedelta=modedelta), True)) + return (self.addons + target.addons, events) + elif target == self.identity: + modeparams = (params if params else extinfo).split() + setmodes = modeparams.pop(0) + modedelta = [] + modeset = "+" + for mode in setmodes: + if mode in "+-": + modeset = mode + continue + if modeset == "+": + if mode == "s": + if len(modeparams): + snomask = modeparams.pop(0) + snomaskdelta = [] + snomodeset = "+" + for snomode in snomask: + if snomode in "+-": + snomodeset = snomode + continue + snomaskdelta.append( + "%s%s" % (snomodeset, snomode)) + modedelta.append(("+s", snomaskdelta)) + else: + modedelta.append(("+s", [])) + else: + modedelta.append(("+%s" % mode, None)) + if modeset == "-": + modedelta.append(("-%s" % mode, None)) + return (self.addons, [("onUserModeSet", dict(origin=origin, modedelta=modedelta), True)]) + + def parseTOPIC(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + if origin == None: + return (None, [("onTopicSet", dict(user=None, channel=None, topic=None), True)]) + return (self.addons + target.addons, [("onTopicSet", dict(user=origin, channel=target, topic=extinfo), True)]) + + def parseINVITE(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + return ([], []) + if origin == None: + return (None, [("onInvite", dict(user=None, channel=None), True)]) + channel = self.channel(extinfo if extinfo else params) + return (self.addons + channel.addons, [("onInvite", dict(user=origin, channel=channel), True)]) + + def parsePRIVMSG(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + ctcp = re.findall(_ctcpmatch, extinfo) + if ctcp: + (ctcptype, ext) = ctcp[0] + if type(target) == User: + if ctcptype.upper() == "ACTION": + return (self.addons, [("onSendPrivAction", dict(origin=origin, user=target, action=ext), True)]) + return (self.addons, [("onSendCTCP", dict(origin=origin, user=target, ctcptype=ctcptype, params=ext), True)]) + elif type(target) == Channel: + if ctcptype.upper() == "ACTION": + return (self.addons, [("onSendChanAction", dict(origin=origin, channel=target, targetprefix=targetprefix, action=ext), True)]) + return (self.addons, [("onSendChanCTCP", dict(origin=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext), True)]) + else: + if type(target) == User: + return (self.addons, [("onSendPrivMsg", dict(origin=origin, user=target, msg=extinfo), True)]) + elif type(target) == Channel: + return (self.addons + target.addons, [("onSendChanMsg", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo), True)]) + if origin == None: + return (None, [ + ("onPrivMsg", dict(user=None, msg=None), True), + ("onChanMsg", dict(user=None, channel=None, targetprefix=None, msg=None), True), + ("onCTCP", dict(user=None, ctcptype=None, params=None), True), + ("onChanCTCP", dict(user=None, channel=None, + targetprefix=None, ctcptype=None, params=None), True), + ("onPrivAction", dict(user=None, action=None), True), + ("onChanAction", dict( + user=None, channel=None, targetprefix=None, action=None), True), + ("onSendPrivMsg", dict( + origin=None, user=None, msg=None), True), + ("onSendChanMsg", dict( + origin=None, channel=None, targetprefix=None, msg=None), True), + ("onSendCTCP", dict(origin=None, user=None, ctcptype=None, params=None), True), + ("onSendPrivAction", dict( + origin=None, user=None, action=None), True), + ("onSendChanAction", dict( + origin=None, channel=None, targetprefix=None, action=None), True), + ("onSendChanCTCP", dict(origin=None, channel=None, + targetprefix=None, ctcptype=None, params=None), True), + ]) + ctcp = re.findall(_ctcpmatch, extinfo) + if ctcp: + (ctcptype, ext) = ctcp[0] + if target == self.identity: + if ctcptype.upper() == "ACTION": + return (self.addons, [("onPrivAction", dict(user=origin, action=ext), True)]) + return (self.addons, [("onCTCP", dict(user=origin, ctcptype=ctcptype, params=ext), True)]) + if type(target) == Channel: + if ctcptype.upper() == "ACTION": + return (self.addons, [("onChanAction", dict(user=origin, channel=target, targetprefix=targetprefix, action=ext), True)]) + return (self.addons, [("onChanCTCP", dict(user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext), True)]) + else: + if type(target) == Channel: + return (self.addons + target.addons, [("onChanMsg", dict(user=origin, channel=target, targetprefix=targetprefix, msg=extinfo), True)]) + elif target == self.identity: + return (self.addons, [("onPrivMsg", dict(user=origin, msg=extinfo), True)]) + + def parseNOTICE(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False): + if outgoing: + ctcp = re.findall(_ctcpmatch, extinfo) + if ctcp: + (ctcptype, ext) = ctcp[0] + return (self.addons, [("onSendCTCPReply", dict(origin=origin, ctcptype=ctcptype, params=ext), True)]) + else: + if type(target) == Channel: + return (self.addons + target.addons, [("onSendChanNotice", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo), True)]) + elif type(target) == User: + return (self.addons, [("onSendPrivNotice", dict(origin=origin, user=target, msg=extinfo), True)]) + if origin == None: + return (None, [ + ("onPrivNotice", dict(origin=None, msg=None), True), + ("onChanNotice", dict( + origin=None, channel=None, targetprefix=None, msg=None), True), + ("onCTCPReply", dict( + origin=None, ctcptype=None, params=None), True), + ("onSendPrivNotice", dict(origin=None, msg=None), True), + ("onSendChanNotice", dict( + origin=None, channel=None, targetprefix=None, msg=None), True), + ("onSendCTCPReply", dict( + origin=None, ctcptype=None, params=None), True), + ]) + ctcp = re.findall(_ctcpmatch, extinfo) + # print ctcp + if ctcp and target == self.identity: + (ctcptype, ext) = ctcp[0] + return (self.addons, [("onCTCPReply", dict(origin=origin, ctcptype=ctcptype, params=ext), True)]) + else: + if type(target) == Channel: + return (self.addons + target.addons, [("onChanNotice", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo), True)]) + elif target == self.identity: + # print "onPrivNotice" + return (self.addons, [("onPrivNotice", dict(origin=origin, msg=extinfo), True)]) + + def parse367(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Ban list + if origin == None: + return (None, [("onBanListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)]) + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onBanListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse368(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onBanListEnd", dict(origin=None, channel=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onBanListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)]) + + def parse346(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Invite list + if origin == None: + return (None, [("onInviteListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)]) + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onInviteListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse347(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onInviteListEnd", dict(origin=None, channel=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onInviteListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)]) + + def parse348(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Ban Exception list + if origin == None: + return (None, [("onBanExceptListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)]) + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onBanExceptListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse349(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onBanExceptListEnd", dict(origin=None, channel=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onBanExceptListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)]) + + def parse910(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Access List + if origin == None: + return (None, [("onAccessListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)]) + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onAccessListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse911(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onAccessListEnd", dict(origin=None, channel=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onAccessListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)]) + + def parse941(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Spam Filter list + if origin == None: + return (None, [("onSpamfilterListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)]) + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onSpamfilterListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse940(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onSpamfilterListEnd", dict(origin=None, channel=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onSpamfilterListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)]) + + def parse954(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel exemptchanops list + if origin == None: + return (None, [("onExemptChanOpsListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)]) + (channame, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onExemptChanOpsListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse953(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onExemptChanOpsListEnd", dict(origin=None, channel=None, endmsg=None), True)]) + channel = self.channel(params) + return (self.addons + channel.addons, [("onExemptChanOpsListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)]) + + def parse728(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel quiet list + if origin == None: + return (None, [("onQuietListEntry", dict(origin=None, channel=None, modechar=None, mask=None, setby=None, settime=None), True)]) + (channame, modechar, mask, setby, settime) = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onQuietListEntry", dict(origin=origin, channel=channel, modechar=modechar, mask=mask, setby=setby, settime=int(settime)), True)]) + + def parse729(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): + if origin == None: + return (None, [("onQuietListEnd", dict(channel=None, endmsg=None), True)]) + channame, modechar = params.split() + channel = self.channel(channame) + return (self.addons + channel.addons, [("onQuietListEnd", dict(channel=channel, endmsg=extinfo), True)]) + + def eventsupports(self): + supports = {} + for item in dir(self): + if re.match(r"parse(\d{3}|[A-Z]+)", item): + parsemethod = getattr(self, item) + addons, events = parsemethod() + for (event, args, fallback) in events: + supports[event] = tuple(args.keys()) + supports.update({"onConnect": (), + "onRegistered": (), + "onConnectAttempt": (), + "onConnectFail": ("exc", "excmsg", "tb"), + "onSessionOpen": (), + "onSessionClose": (), + "onDisconnect": ("expected",), + "onOther": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"), + "onUnhandled": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"), + "onRecv": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"), + "onSend": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"), + }) + return supports + + # Here are the builtin event handlers. + def onWelcome(self, context, origin, msg): + self.welcome = msg # Welcome message + + def onYourHost(self, context, origin, msg): + self.hostinfo = msg # Your Host + + def onServerCreated(self, context, origin, msg): + self.servcreated = msg # Server Created + + def onServInfo(self, context, origin, servinfo): + self.servinfo = servinfo # What is this code? + + def onSupports(self, context, origin, supports, msg): # Server Supports + protos = u" ".join( + [proto for proto in self.protoctl if proto in supports.keys()]) + if protos: + self._send(u"PROTOCTL {protos}".format(**vars())) + self.supports.update(supports) + + def onSnoMask(self, context, origin, snomask): # Snomask + self.identity.snomask = snomask + if "s" not in self.identity.modes: + self.identity.snomask = "" + + def onUserModes(self, context, origin, modes): # User Modes + self.identity.modes = modes + if "s" not in self.identity.modes: + self.identity.snomask = "" + + def onNetStats(self, context, origin, netstats): # Net Stats + self.netstats = netstats + + def onOpCount(self, context, origin, opcount): + self.opcount = opcount + + def onChanCount(self, context, origin, chancount): + self.chancount = chancount + + def onReturn(self, identity, origin, msg): # Returned from away status + self.identity.away = False + self.identity.awaymsg = None + + def onAway(self, identity, origin, msg): # Entered away status + self.identity.away = True + self.identity.awaymsg = msg + + def onWhoisStart(self, context, origin, user, nickname, username, host, realname): # Start of WHOIS data + user.nick = nickname + user.username = username + user.host = host + + def onWhoisAway(self, context, origin, user, nickname, awaymsg): # Away Message + user.away = True + user.awaymsg = awaymsg + + def onWhoisServer(self, context, origin, user, nickname, server, servername): # Server + user.server = server + + def onWhoisOp(self, context, origin, user, nickname, msg): # IRC Op + user.ircop = True + user.ircopmsg = msg + + def onWhoisTimes(self, context, origin, user, nickname, idletime, signontime, msg): # Idle and Signon times + user.idlesince = int(time.time()) - idletime + user.signontime = signontime + + def onWhoisSSL(self, context, origin, user, nickname, msg): # SSL + user.secure = True + + def onWhoisLoggedInAs(self, context, origin, user, nickname, loggedinas, msg): # Logged in as + user.loggedinas = loggedinas + + def onChannelModes(self, context, origin, channel, modedelta): # Channel Modes + chanmodes = self.supports.get("CHANMODES", _defaultchanmodes) + for ((modeset, mode), param) in modedelta: + if mode in chanmodes[2]: + channel.modes[mode] = param + elif mode in chanmodes[3]: + channel.modes[mode] = True + + def onChanCreated(self, context, origin, channel, created): # Channel created + channel.created = created + + def onTopic(self, context, origin, channel, topic): # Channel Topic + channel.topic = topic + + def onTopicInfo(self, context, origin, channel, topicsetby, topictime): # Channel Topic info + channel.topicsetby = topicsetby + channel.topictime = topictime + + def onWhoEntry(self, context, origin, channel, user, channame, username, host, serv, nick, flags, hops, realname): # WHO reply + user.hops = hops + user.realname = realname + user.username = username + user.host = host + user.server = serv + user.away = "G" in flags + user.ircop = "*" in flags + if type(channel) == Channel: + if user not in channel.users: + channel.users.append(user) + if channel not in user.channels: + user.channels.append(channel) + for (mode, prefix) in zip(*self.supports.get("PREFIX", _defaultprefix)): + 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] + + def onNames(self, context, origin, channel, flag, channame, nameslist): # NAMES reply + for (symbs, nick, username, host) in nameslist: + user = self.user(nick) + if user.nick != nick: + user.nick = nick + if username and user.username != username: + user.username = username + if host and user.host != host: + user.host = host + with channel.lock: + if channel not in user.channels: + user.channels.append(channel) + if user not in channel.users: + channel.users.append(user) + prefix = self.supports.get("PREFIX", _defaultprefix) + for symb in symbs: + mode = prefix[0][prefix[1].index(symb)] + if not channel.modes.has_key(mode): + channel.modes[mode] = [user] + elif user not in channel.modes[mode]: + channel.modes[mode].append(user) + + def onMOTDLine(self, context, origin, motdline): # MOTD line + self.motd.append(motdline) + + def onMOTDStart(self, context, origin, motdgreet): # Begin MOTD + self.motdgreet = motdgreet + self.motd = [] + + def onMOTDEnd(self, context, origin, motdend): + self.motdend = motdend # End of MOTD + + # elif cmd==386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal) + #(channame,owner)=params.split() + # channel=self.channel(channame) + #self._event("onRecv", channel.addons, **data) + # if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name + # user=self.user(owner) + #if user.nick!=owner: user.nick=owner + # if channel.modes.has_key("q"): + #if user not in channel.modes["q"]: channel.modes["q"].append(user) + # else: channel.modes["q"]=[user] + + # elif cmd==388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal) + #(channame,admin)=params.split() + # channel=self.channel(channame) + #self._event("onRecv", channel.addons, **data) + # if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name + # user=self.user(admin) + #if user.nick!=admin: user.nick=admin + # if channel.modes.has_key("a"): + #if user not in channel.modes["a"]: channel.modes["a"].append(user) + # else: channel.modes["a"]=[user] + + def onNickChange(self, context, user, newnick): + for other in self.users: + if self.supports.get("CASEMAPPING", "rfc1459") == "ascii": + collision = other.nick.lower() == newnick.lower() + else: + collision = other.nick.translate( + _rfc1459casemapping) == newnick.translate(_rfc1459casemapping) + if collision: + self.users.remove( + other) # 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 other in channel.users: + channel.users.remove(other) + user.nick = newnick + + def onJoin(self, context, user, channel): + if channel not in user.channels: + user.channels.append(channel) + if user not in channel.users: + channel.users.append(user) + + def onMeJoin(self, context, channel): + channel._init() + with channel._joining: + if channel._joinrequested: + channel._joinreply = "JOIN" + channel._joining.notify() + self._send(u"MODE %s" % channel.name) + self._send(u"WHO %s" % channel.name) + self._send(u"MODE %s :%s" % + (channel.name, self.supports.get("CHANMODES", _defaultchanmodes)[0])) + + def onKick(self, context, kicker, channel, kicked, kickmsg): + if channel in kicked.channels: + kicked.channels.remove(channel) + if kicked in channel.users: + channel.users.remove(kicked) + prefix = self.supports.get("PREFIX", _defaultprefix) + for mode in prefix[0]: + if mode in channel.modes.keys() and kicked in channel.modes[mode]: + channel.modes[mode].remove(kicked) + + def onPart(self, context, user, channel, partmsg): + if channel in user.channels: + user.channels.remove(channel) + if user in channel.users: + channel.users.remove(user) + prefix = self.supports.get("PREFIX", _defaultprefix) + for mode in prefix[0]: + if mode in channel.modes.keys() and user in channel.modes[mode]: + channel.modes[mode].remove(user) + + def onMePart(self, context, channel, partmsg): + with channel._parting: + if channel._partrequested: + channel._partreply = "PART" + channel._parting.notify() + + def onMeKicked(self, context, kicker, channel, kickmsg): + with channel._parting: + if channel._partrequested: + channel._partreply = "KICK" + channel._parting.notify() + + def onQuit(self, context, user, quitmsg): + channels = list(user.channels) + for channel in channels: + with channel.lock: + if user in channel.users: + channel.users.remove(user) + prefix = self.supports.get("PREFIX", _defaultprefix) + for mode in prefix[0]: + if mode in channel.modes.keys() and user in channel.modes[mode]: + channel.modes[mode].remove(user) + user._init() + + def onChanModeSet(self, context, user, channel, modedelta): + chanmodes = self.supports.get("CHANMODES", _defaultchanmodes) + prefix = self.supports.get("PREFIX", _defaultprefix) + with channel.lock: + for ((modeset, mode), param) in modedelta: + if mode in chanmodes[0] + prefix[0]: + if mode not in channel.modes.keys(): + channel.modes[mode] = [] + if mode in chanmodes[0]: + if modeset == "+": + if param.lower() not in [mask.lower() for (mask, setby, settime) in channel.modes[mode]]: + channel.modes[mode].append( + (param, user, int(time.time()))) + else: + 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()) + 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 chanmodes[1]: + if modeset == "+": + channel.modes[mode] = param + else: + channel.modes[mode] = None + elif mode in chanmodes[2]: + if modeset == "+": + channel.modes[mode] = param + else: + channel.modes[mode] = None + elif mode in chanmodes[3]: + if modeset == "+": + channel.modes[mode] = True + else: + channel.modes[mode] = False + elif mode in prefix[0]: + if modeset == "+": + if param not in channel.modes[mode]: + channel.modes[mode].append(param) + elif param in channel.modes[mode]: + channel.modes[mode].remove(param) + + def onUserModeSet(self, context, origin, modedelta): + for ((modeset, mode), param) in modedelta: + if modeset == "+": + if mode not in self.identity.modes: + self.identity.modes += mode + if mode == "s": + for snomodeset, snomode in param: + if snomodeset == "+" and snomode not in self.identity.snomask: + self.identity.snomask += snomode + if snomodeset == "-" and snomode in self.identity.snomask: + self.identity.snomask = self.identity.snomask.replace( + snomode, "") + if modeset == "-": + if mode in self.identity.modes: + self.identity.modes = self.identity.modes.replace(mode, "") + if mode == "s": + self.identity.snomask = "" + + def onTopicSet(self, context, user, channel, topic): + with channel.lock: + channel.topic = topic + channel.topicsetby = user + channel.topictime = int(time.time()) + + def onCTCP(self, context, user, ctcptype, params): + if ctcptype.upper() == "VERSION": + user.ctcpreply("VERSION", self.ctcpversion()) + elif ctcptype.upper() == "TIME": + tformat = time.ctime() + tz = time.tzname[0] + user.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars()) + elif ctcptype.upper() == "PING": + user.ctcpreply("PING", params) + elif ctcptype.upper() == "FINGER": + user.ctcpreply("FINGER", params) + + def onChanCTCP(self, context, user, channel, targetprefix, ctcptype, params): + self.onCTCP(context, user, ctcptype, params) + + def onBanListEntry(self, context, origin, channel, mask, setby, settime): # Channel Ban list + if "b" not in channel.modes.keys(): + channel.modes["b"] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]: + channel.modes["b"].append((mask, setby, int(settime))) + + def onInviteListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list + if "I" not in channel.modes.keys(): + channel.modes["I"] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]: + channel.modes["I"].append((mask, setby, int(settime))) + + def onBanExceptListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list + if "e" not in channel.modes.keys(): + channel.modes["e"] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]: + channel.modes["e"].append((mask, setby, int(settime))) + + def onAccessListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list + if "w" not in channel.modes.keys(): + channel.modes["w"] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["w"]]: + channel.modes["w"].append((mask, setby, int(settime))) + + def onSpamfilterListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list + if "g" not in channel.modes.keys(): + channel.modes["g"] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]: + channel.modes["g"].append((mask, setby, int(settime))) + + def onExemptChanOpsListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list + if "X" not in channel.modes.keys(): + channel.modes["X"] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]: + channel.modes["X"].append((mask, setby, int(settime))) + + def onQuietListEntry(self, context, origin, channel, modechar, mask, setby, settime): # Channel quiet list (Freenode) + if modechar not in channel.modes.keys(): + channel.modes[modechar] = [] + if mask.lower() not in [m.lower() for (m, s, t) in channel.modes[modechar]]: + channel.modes[modechar].append((mask, setby, int(settime))) + + def onOther(self, context, line, origin, cmd, target, targetprefix, params, extinfo): + if cmd in (384, 403, 405, 471, 473, 474, 475, 476, 520, 477, 489, 495): # Channel Join denied + try: + channel = self.channel(params) + except InvalidName: + pass + else: + with channel._joining: + if channel._joinrequested: + channel._joinreply = (cmd, extinfo) + channel._joining.notify() + + elif cmd == 470: # Channel Join denied due to redirect + channelname, redirect = params.split() + try: + channel = self.channel(channelname) + except InvalidName: + pass + else: + with channel._joining: + if channel._joinrequested: + channel._joinreply = ( + cmd, "%s (%s)" % (extinfo, redirect)) + channel._joining.notify() + + # elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params + # for param in params.split(): + # if len(param) and param[0] in self.supports["CHANTYPES"]: + # channel=self.channel(param) + #self._event("onRecv", channel.addons, **data) + + def _trynick(self): + (q, s) = divmod(self.trynick, len(self.nick) + if type(self.nick) in (list, tuple) else 1) + nick = self.nick[s] if type(self.nick) in (list, tuple) else self.nick + if q > 0: + nick = "%s%d" % (nick, q) + self._send(u"NICK %s" % nick) + self.trynick += 1 + def _send(self, line, origin=None, T=None): + with self.lock: + if not self.connected: + raise NotConnected if "\r" in line or "\n" in line: raise InvalidCharacter + if type(line) == str: + line = autodecode(line) cmd = line.split(" ")[0].upper() if T == None: @@ -1645,47 +2177,24 @@ class Connection(object): extinfo = "********" line = "%s %s :%s" % (cmd, target, extinfo) - chanmatch = re.findall( - _targchanmatch % (re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target) - if chanmatch: - targetprefix, channame = chanmatch[0] - target = self.channel(channame) - if target.name != channame: - # Target channel name has changed - target.name = channame - elif re.match(_nickmatch, target) and cmd != "NICK": - targetprefix = "" - target = self.user(target) - - ctcp = re.findall(_ctcpmatch, extinfo) - if ctcp: - (ctcptype, ext) = ctcp[0] - if ctcptype.upper() == "ACTION": - if type(target) == Channel: - self._event( - "onSendChanAction", self.addons + - target.addons, - origin=origin, channel=target, targetprefix=targetprefix, action=ext) - elif type(target) == User: - self._event( - "onSendPrivAction", self.addons, origin=origin, user=target, action=ext) - else: - if type(target) == Channel: - self._event( - "onSendChanCTCP", self.addons + target.addons, origin=origin, - channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext) - elif type(target) == User: - self._event( - "onSendPrivCTCP", self.addons, origin=origin, user=target, ctcptype=ctcptype, params=ext) - else: - if type(target) == Channel: - self._event( - "onSendChanMsg", self.addons + target.addons, origin=origin, - channel=target, targetprefix=targetprefix, msg=extinfo) - elif type(target) == User: - self._event( - "onSendPrivMsg", self.addons, origin=origin, user=target, msg=extinfo) - + #ctcp=re.findall(_ctcpmatch, extinfo) + # if ctcp: + #(ctcptype,ext)=ctcp[0] + # if ctcptype.upper()=="ACTION": + # if type(target)==Channel: + #self._event("onSendChanAction", self.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, action=ext) + # elif type(target)==User: + #self._event("onSendPrivAction", self.addons, origin=origin, user=target, action=ext) + # else: + # if type(target)==Channel: + #self._event("onSendChanCTCP", self.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext) + # elif type(target)==User: + #self._event("onSendPrivCTCP", self.addons, origin=origin, user=target, ctcptype=ctcptype, params=ext) + # else: + # if type(target)==Channel: + #self._event("onSendChanMsg", self.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo) + # elif type(target)==User: + #self._event("onSendPrivMsg", self.addons, origin=origin, user=target, msg=extinfo) # elif target.upper()=="CHANSERV": #msg=extinfo.split(" ") # if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2: @@ -1723,9 +2232,67 @@ class Connection(object): elif cmd.upper() == "IDENTIFY": target = "********" line = "%s %s" % (cmd, target) + + prefix = self.supports.get("PREFIX", _defaultprefix) + chantypes = self.supports.get("CHANTYPES", _defaultchantypes) + chanmatch = re.findall(_targchanmatch % + (re.escape(prefix[1]), re.escape(chantypes)), target) + + # Check to see if target matches a channel (optionally with prefix) + if chanmatch: + targetprefix, channame = chanmatch[0] + target = self.channel(channame) + if target.name != channame: + # Target channel name has changed + target.name = channame + # Check to see if target matches a valid nickname. Do NOT convert + # target to User instance if cmd is NICK. + elif re.match(_nickmatch, target) and cmd != "NICK": + targetprefix = "" + target = self.user(target) + + # Otherwise, target is just left as a string + else: + targetprefix = "" + + parsename = ("parse%03d" if type(cmd) == int else "parse%s") % cmd + if hasattr(self, parsename): + parsemethod = getattr(self, parsename) + if callable(parsemethod): + try: + addons, events = parsemethod( + origin, target, targetprefix, params, extinfo, outgoing=True) + except: + exc, excmsg, tb = sys.exc_info() + + # Print to log AND stderr + tblines = [ + u"!!! There was an error in parsing the following line:", u"!!! %s" % line] + for tbline in traceback.format_exc().split("\n"): + tblines.append(u"!!! %s" % autodecode(tbline)) + self.logwrite(*tblines) + print >>sys.stderr, u"There was an error in parsing the following line:" + print >>sys.stderr, u"%s" % line + print >>sys.stderr, traceback.format_exc() + return + else: + addons = self.addons + if type(cmd) == unicode: + events = [( + "onSend%s" % cmd.upper(), dict(line=line, origin=origin if origin else self, + target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), True)] + else: + events = [] + if addons == None: + addons = [] + + if cmd not in ("PING", "PONG") or not self.quietpingpong: # Supress pings and pongs if self.quietpingpong is set to True + self._event( + addons + [self], [("onSend", dict(origin=origin if origin else self, line=line, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), False)], line) + self._event(addons + [self], events, line) + if not (cmd in ("PING", "PONG") and self.quietpingpong): - self._event("onSend", self.addons, origin=origin, line=line, - cmd=cmd, target=target, params=params, extinfo=extinfo) + #self._event(self.addons, [("onSend" , dict(origin=origin, line=line, cmd=cmd, target=target, params=params, extinfo=extinfo), False)]) self.logwrite(">>> %s" % line) self._connection.send("%s\n" % origline.encode('utf8')) @@ -1766,9 +2333,9 @@ class Connection(object): exc, excmsg, tb = sys.exc_info() with self.lock: self.logwrite( - "*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars()) - self._event("onConnectFail", self.addons + reduce( - lambda x, y: x + y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb) + u"*** Connection to {self:uri} failed: {excmsg}.".format(**vars())) + self._event(self.getalladdons(), [ + ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)]) with self._sendline: self._outgoing.clear() try: @@ -1782,9 +2349,11 @@ class Connection(object): except: tb = traceback.format_exc() self._quitexpected = True - self.logwrite(*["!!! FATAL Exception"] + [ - "!!! %s" % line for line in tb.split("\n")]) - print >>sys.stderr, "FATAL Exception" + tblines = [u"!!! FATAL Exception"] + for line in traceback.format_exc().split("\n"): + tblines.append(u"!!! %s" % autodecode(line)) + self.logwrite(*tblines) + print >>sys.stderr, "FATAL Exception in {self}".format(**vars()) print >>sys.stderr, tb with self._sendline: try: @@ -1809,41 +2378,39 @@ class Connection(object): def __repr__(self): server = self.server - if self.ipv6 and ":" in server: + if self.ipver == socket.AF_INET6 and ":" in server: server = "[%s]" % server - port = self.port if self.identity: - nick = self.identity.nick - user = self.identity.username if self.identity.username else "*" - host = self.identity.host if self.identity.host else "*" - else: - nick = "*" - user = "*" - host = "*" - if self.ssl and self.ipv6: - protocol = "ircs6" - elif self.ssl: - protocol = "ircs" - elif self.ipv6: - protocol = "irc6" + return "<IRC Context: {self.identity:full} on {self:uri}>".format(**locals()) else: - protocol = "irc" - return "<IRC Context: %(nick)s!%(user)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals() - # else: return "<IRC Context: irc%(ssl)s://%(server)s:%(port)s>" % - # locals() + return "<IRC Context: *!*@* on {self:uri}>".format(**locals()) + + def __format__(self, fmt): + port = self.port if self.port is not None else 6697 if self.secure else 6667 + if fmt == "uri": + ssl = "s" if self.secure else "" + proto = "6" if self.ipver == socket.AF_INET6 else "" + if self.ipver == socket.AF_INET6 and ":" in self.server: + return "irc{ssl}{proto}://[{self.server}]:{port}".format(**locals()) + else: + return "irc{ssl}{proto}://{self.server}:{port}".format(**locals()) def oper(self, name, passwd, origin=None): - self._send(u"OPER %s %s" % - (re.findall("^([^\r\n\\s]*)", name)[0], re.findall("^([^\r\n\\s]*)", passwd)[0]), origin=origin) + if re.match(".*[\n\r\\s]", name) or re.match(".*[\n\r\\s]", passwd): + raise InvalidCharacter + self._send(u"OPER {name} {passwd}".format(**vars()), origin=origin) def list(self, params="", origin=None): - if len(re.findall("^([^\r\n\\s]*)", params)[0]): - self._send(u"LIST %s" % - (re.findall("^([^\r\n\\s]*)", params)[0]), origin=origin) + if re.match(".*[\n\r\\s]", params): + raise InvalidCharacter + if params: + self._send(u"LIST {params}".format(**vars()), origin=origin) else: self._send(u"LIST", origin=origin) def getmotd(self, target="", origin=None): + if re.match(".*[\n\r\\s]", name) or re.match(".*[\n\r\\s]", passwd): + raise InvalidCharacter if len(re.findall("^([^\r\n\\s]*)", target)[0]): self._send(u"MOTD %s" % (re.findall("^([^\r\n\\s]*)", target)[0]), origin=origin) @@ -1864,39 +2431,58 @@ class Connection(object): else: self._send(u"STATS %s" % query, origin=origin) - def quit(self, msg="", origin=None): - if len(re.findall("^([^\r\n]*)", msg)[0]): - self._send(u"QUIT :%s" % - re.findall("^([^\r\n]*)", msg)[0], origin=origin) + # Quit IRC session gracefully + def quit(self, msg="", origin=None, blocking=False): + if "\r" in msg or "\n" in msg: + raise InvalidCharacter + if msg: + self._send(u"QUIT :%s" % msg, origin=origin) else: self._send(u"QUIT", origin=origin) + if blocking: + with self._disconnecting: + while self.connected: + self._disconnecting.wait() + self._recvhandlerthread.join() + self._sendhandlerthread.join() + + # Force disconnect -- Not even sending QUIT to server. + def disconnect(self): + with self.lock: + self._quitexpected = True + self._connection.shutdown(2) def ctcpversion(self): reply = [] - # Prepare reply for addon + # Prepare reply for this module reply.append( - "%(__name__)s %(__version__)s, %(__author__)s" % vars(self)) + u"{self.__name__} {self.__version__}, {self.__author__}".format(**vars())) # Prepare reply for Python and OS versions pyver = sys.version.split("\n") pyver[0] = "Python " + pyver[0] reply.extend(pyver) reply.extend(platform.platform().split("\n")) - # Prepare reply for extension addons + + # Prepare reply for each addons for addon in self.addons: try: - r = "%(__name__)s %(__version__)s" % vars(addon) - if "__extinfo__" in vars(addon): - r += ", %(__extinfo__)s" % vars() - reply.append(r) + if hasattr(addon, "__extinfo__"): + reply.append( + u"{addon.__name__} {addon.__version__}, {addon.__extinfo__}".format(**vars())) + else: + reply.append( + u"{addon.__name__} {addon.__version__}".format(**vars())) except: pass - return reduce(lambda x, y: "%s; %s" % (x, y), reply) + return u"; ".join(reply) def raw(self, line, origin=None): self._send(line, origin=origin) def user(self, nick, init=False): + if type(nick) == str: + nick = autodecode(nick) if self.supports.get("CASEMAPPING", "rfc1459") == "ascii": users = [ user for user in self.users if user.nick.lower() == nick.lower()] @@ -1915,6 +2501,8 @@ class Connection(object): return user def channel(self, name, init=False): + if type(name) == str: + name = autodecode(name) if self.supports.get("CASEMAPPING", "rfc1459") == "ascii": channels = [ chan for chan in self.channels if chan.name.lower() == name.lower()] @@ -1933,7 +2521,7 @@ class Connection(object): return chan def __getitem__(self, item): - chantypes = self.supports.get("CHANTYPES", "&#+!") + chantypes = self.supports.get("CHANTYPES", _defaultchantypes) if re.match(_chanmatch % re.escape(chantypes), item): return self.channel(item) elif re.match(_usermatch, item): @@ -1941,34 +2529,94 @@ class Connection(object): else: raise TypeError, "String argument does not match valid channel name or nick name." + def fmtsupports(self): + 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.supports.items()] + supports.sort() + supports = " ".join(supports) + lines = [] + while len(supports) > 196: + index = supports.rfind(" ", 0, 196) + slice = supports[:index] + lines.append( + u":{self.serv} 005 {self.identity.nick} {slice} :are supported by this server".format(**vars())) + supports = supports[index + 1:] + if supports: + lines.append( + u":{self.serv} 005 {self.identity.nick} {supports} :are supported by this server".format(**vars())) + return lines + + def fmtgreeting(self): + # Prepare greeting (Responses 001 through 004) + lines = [] + if self.welcome: + lines.append( + u":{self.serv} 001 {self.identity.nick} :{self.welcome}".format(**vars())) + if self.hostinfo: + lines.append( + u":{self.serv} 002 {self.identity.nick} :{self.hostinfo}".format(**vars())) + if self.servcreated: + lines.append( + u":{self.serv} 003 {self.identity.nick} :{self.servcreated}".format(**vars())) + if self.servinfo: + lines.append( + u":{self.serv} 004 {self.identity.nick} {self.servinfo}".format(**vars())) + return lines + + def fmtusermodes(self): + # Prepars 221 response + return u":{self.serv} 221 {self.identity.nick} +{self.identity.modes}".format(**vars()) + + def fmtsnomasks(self): + # Prepare 008 response + return u":{self.serv} 008 {self.identity.nick} +{self.identity.snomask} :Server notice mask".format(**vars()) + + def fmtmotd(self): + if self.motdgreet and self.motd and self.motdend: + lines = [] + lines.append( + u":{self.serv} 375 {self.identity.nick} :{self.motdgreet}".format(**vars())) + for motdline in self.motd: + lines.append( + u":{self.serv} 372 {self.identity.nick} :{motdline}".format(**vars())) + lines.append( + u":{self.serv} 376 {self.identity.nick} :{self.motdend}".format(**vars())) + return lines + else: + return [u":{self.serv} 422 {self.identity.nick} :MOTD File is missing".format(**vars())] + class Channel(object): def __init__(self, name, context, key=None): - chantypes = context.supports.get("CHANTYPES", "&#+!") + chantypes = context.supports.get("CHANTYPES", _defaultchantypes) if not re.match(_chanmatch % re.escape(chantypes), name): raise InvalidName, repr(name) self.name = name self.context = context self.key = key + self.lock = Lock() self._init() + self._joining = Condition(self.lock) + self._parting = Condition(self.lock) + self._joinrequested = False + self._joinreply = None + self._partrequested = False + self._partreply = None def _init(self): + for user in self.context.users: + if self in user.channels: + user.channels.remove(self) self.addons = [] self.topic = "" self.topicsetby = "" - self.topictime = () + self.topictime = None self.topicmod = "" self.modes = {} self.users = UserList(context=self.context) self.created = None - self.lock = Lock() - self._joinrequested = False - self._joinreply = None - self._joining = Condition(self.lock) - self._partrequested = False - self._partreply = None - self._parting = Condition(self.lock) def msg(self, msg, target="", origin=None): if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]: @@ -1977,12 +2625,92 @@ class Channel(object): self.context._send(u"PRIVMSG %s%s :%s" % (target, self.name, line), origin=origin) - def who(self, origin=None): + def who(self, origin=None, blocking=False): + # Send WHO request to server self.context._send(u"WHO %s" % (self.name), origin=origin) + def fmtwho(self): + # Create WHO reply from current data. TODO + pass + def names(self, origin=None): self.context._send(u"NAMES %s" % (self.name), origin=origin) + def fmtnames(self, sort=None, uhnames=False, namesx=False): + # Create NAMES reply from current data. + secret = "s" in self.modes.keys() and self.modes["s"] + private = "p" in self.modes.keys() and self.modes["p"] + flag = "@" if secret else ("*" if private else "=") + + modes, symbols = self.context.supports.get("PREFIX", ("ohv", "@%+")) + users = list(self.users) + if sort == "mode": + users.sort(key=lambda user: ([user not in self.modes.get(mode, []) + for mode, char in zip(*self.context.supports.get("PREFIX", ("ohv", "@%+")))], user.nick.lower())) + elif sort == "nick": + users.sort(key=lambda user: user.nick.lower()) + if uhnames: + template = u"{prefixes}{user:full}" + else: + template = u"{prefixes}{user}" + + nameslist = [] + for user in users: + prefixes = u"".join( + [prefix if mode in self.modes.keys() and user in self.modes[mode] else "" for prefix, mode in zip(symbols, modes)]) + if not namesx: + prefixes = prefixes[:1] + nameslist.append(template.format(**vars())) + names = " ".join(nameslist) + + lines = [] + while len(names) > 196: + index = names.rfind(" ", 0, 196) + slice = names[:index] + lines.append( + u":{self.context.identity.server} 353 {self.context.identity.nick} {flag} {self.name} :{slice}".format(**vars())) + names = names[index + 1:] + if len(names): + lines.append( + u":{self.context.identity.server} 353 {self.context.identity.nick} {flag} {self.name} :{names}".format(**vars())) + + lines.append( + u":{self.context.identity.server} 366 {self.context.identity.nick} {self.name} :End of /NAMES list.".format(**vars())) + return lines + + def fmttopic(self): + # Prepares 332 and 333 responses + if self.topic and self.topictime: + response332 = u":{self.context.identity.server} 332 {self.context.identity.nick} {self.name} :{self.topic}".format( + **vars()) + if type(self.topicsetby) == User: + response333 = u":{self.context.identity.server} 333 {self.context.identity.nick} {self.name} {self.topicsetby.nick} {self.topictime}".format( + **vars()) + else: + response333 = u":{self.context.identity.server} 333 {self.context.identity.nick} {self.name} {self.topicsetby} {self.topictime}".format( + **vars()) + return [response332, response333] + else: + return [u":{self.context.identity.server} 331 {self.context.identity.nick} {self.name} :No topic is set".format(**vars())] + + def fmtchancreated(self): + # Prepares 329 responses + return u":{self.context.identity.server} 329 {self.context.identity.nick} {self.name} {self.created}".format(**vars()) + + def fmtmodes(self): + items = self.modes.items() + chanmodes = self.context.supports.get("CHANMODES", _defaultchanmodes) + modes = "".join( + [mode for (mode, val) in items if mode not in chanmodes[0] + self.context.supports["PREFIX"][0] and val]) + params = " ".join( + [val for (mode, val) in items if mode in chanmodes[1] + chanmodes[2] and val]) + if modes and params: + return u":{self.context.identity.server} 324 {self.context.identity.nick} {self.name} +{modes} {params}".format(**vars()) + elif modes: + return u":{self.context.identity.server} 324 {self.context.identity.nick} {self.name} +{modes}".format(**vars()) + else: + return None + def notice(self, msg, target="", origin=None): if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]: raise InvalidPrefix @@ -2039,7 +2767,7 @@ class Channel(object): t = time.time() if not self.context.connected: raise NotConnected - elif self._partreply == "PART": + elif self._partreply in ("PART", "KICK"): return elif type(self._partreply) == tuple and len(self._partreply) == 2: cmd, extinfo = self._partreply @@ -2063,6 +2791,8 @@ class Channel(object): if self.context.identity in self.users: # Bot is already on the channel raise AlreadyJoined + if not self.context.connected: + raise NotConnected with self._joining: try: if self._joinrequested: @@ -2113,11 +2843,17 @@ class Channel(object): (self.name, nickname), origin=origin) def __repr__(self): - return (u"<Channel: %s@%s/%d>" % (self.name, self.context.server, self.context.port)).encode("utf8") + return u"<Channel: {self.name} on {self.context:uri}>".format(**vars()) def __contains__(self, item): return item in self.users + def __format__(self, fmt): + return self.name + + def json(self): + return self.name + class User(object): @@ -2140,12 +2876,18 @@ class User(object): self.ircopmsg = "" self.idlesince = None self.signontime = None - self.ssl = None + self.secure = None self.away = None def __repr__(self): return (u"<User: %(nick)s!%(username)s@%(host)s>" % vars(self)).encode("utf8") + def __format__(self, fmt): + if fmt == "full": + return u"{self.nick}!{self.username}@{self.host}".format(**locals()) + else: + return self.nick + def msg(self, msg, origin=None): for line in re.findall("([^\r\n]+)", msg): self.context._send(u"PRIVMSG %s :%s" % @@ -2173,12 +2915,55 @@ class User(object): def me(self, msg="", origin=None): self.ctcp("ACTION", msg, origin=origin) + def json(self): + return self.nick + class Config(object): - def __init__(self, **kwargs): + def __init__(self, addon, **kwargs): + self.addon = addon self.__dict__.update(kwargs) + def json(self): + if "onAddonAdd" in dir(self.addon) and type(self.addon.onAddonAdd) == new.instancemethod: + conf = OrderedDict(addon=self.addon) + try: + arginspect = inspect.getargspec(self.addon.onAddonAdd) + except: + raise TypeError( + repr(self.addon.onAddonAdd) + " is not JSON serializable") + + if arginspect.defaults: + requiredargs = arginspect.args[ + 2:len(arginspect.args) - len(arginspect.defaults)] + argswithdefaults = arginspect.args[ + len(arginspect.args) - len(arginspect.defaults):] + defaultvalues = arginspect.defaults + else: + requiredargs = arginspect.args[2:] + argswithdefaults = [] + defaultvalues = [] + + for key in requiredargs: + try: + conf[key] = getattr(self, key) + except AttributeError: + print key + raise TypeError( + repr(self) + " is not JSON serializable (Cannot recover required argument '%s')" % key) + + for key, default in zip(argswithdefaults, defaultvalues): + try: + value = getattr(self, key) + if value != default: + conf[key] = getattr(self, key) + except AttributeError: + pass + return conf + else: + return self.addon + class ChanList(list): @@ -2336,3 +3121,20 @@ class UserList(list): def __str__(self): return ",".join([user.nick for user in self]) + + +class Server(object): + + def __init__(self, name, context): + self.name = name + self.context = context + self.lock = Lock() + self._init() + + def _init(self): + self.stats = {} + self.users = UserList(context=self.context) + self.created = None + self.motdgreet = None + self.motd = [] + self.motdend = None @@ -21,9 +21,12 @@ modemapping = dict(Y="ircop", q="owner", def LoggerReload(log): newlog = Logger(logroot=log.logroot) - for IRC, label in log.labels.items(): - IRC.rmAddon(log) - IRC.addAddon(newlog, label=label) + with newlog.rotatelock, log.rotatelock: + newlog.logs = log.logs + log.logs = {} + for context, label in log.labels.items(): + context.rmAddon(log) + context.addAddon(newlog, label=label) return newlog @@ -63,75 +66,80 @@ class Logger(Thread): 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: + for context in self.labels.keys(): + if context.connected: + with context.lock: try: - self.rotateLog(IRC) + self.rotateLog(context) 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: + context.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()] + [ + "!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) + if context.identity: + for channel in context.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: + context.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()] + [ + "!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) + for user in context.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])) + context.logwrite(*["!!! [Logger] Exception in module %(module)s" % vars()] + [ + "!!! [Logger] %s" % tbline for tbline in traceback.format_exc().split("\n")]) + context.logopen( + os.path.join(self.logroot, self.labels[context], "rawdata-%04d.%02d.%02d.log" % now[:3])) nextrotate = int(time.mktime((Y, M, D + 1, 0, 0, 0, 0, 0, -1))) finally: Thread.__init__(self) self.daemon = True - def onAddonAdd(self, IRC, label): + def onAddonAdd(self, context, label): if label in self.labels.values(): raise BaseException, "Label already exists" - if IRC in self.labels.keys(): + if context in self.labels.keys(): raise BaseException, "Network already exists" if not os.path.isdir(os.path.join(self.logroot, label)): os.mkdir(os.path.join(self.logroot, label)) - self.labels[IRC] = label - if IRC.connected: - self.openLog(IRC) - if IRC.identity: - for channel in IRC.identity.channels: + self.labels[context] = label + if context.connected: + self.openLog(context) + if context.identity: + for channel in context.identity.channels: self.openLog(channel) now = time.localtime() timestamp = reduce(lambda x, y: x + ":" + y, [ str(t).rjust(2, "0") for t in now[0:6]]) - IRC.logopen( - os.path.join(self.logroot, self.labels[IRC], "rawdata-%04d.%02d.%02d.log" % now[:3])) + context.logopen( + os.path.join(self.logroot, self.labels[context], "rawdata-%04d.%02d.%02d.log" % now[:3])) - def onAddonRem(self, IRC): - if IRC.connected: + def onAddonRem(self, context): + if context.connected: for channel in self.logs.keys(): - if channel in IRC.channels: + if channel in context.channels: if not self.logs[channel].closed: self.closeLog(channel) for user in self.logs.keys(): - if user in IRC.users: + if user in context.users: if not self.logs[user].closed: self.closeLog(user) - if not self.logs[IRC].closed: - self.closeLog(IRC) - del self.labels[IRC] + if context in self.logs.keys() and not self.logs[context].closed: + self.closeLog(context) + del self.labels[context] def openLog(self, window): with self.rotatelock: if not self.isAlive(): self.start() + + if window in self.logs.keys(): + # Don't do anything + return + now = time.localtime() timestamp = reduce(lambda x, y: x + ":" + y, [ str(t).rjust(2, "0") for t in now[0:6]]) @@ -139,42 +147,27 @@ class Logger(Thread): log = self.logs[window] = codecs.open( os.path.join(self.logroot, self.labels[window], "console-%04d.%02d.%02d.log" % now[:3]), "a", encoding="utf8") print >>log, "%s ### Log file opened" % (irc.timestamp()) + elif type(window) == irc.Channel: label = self.labels[window.context] log = self.logs[window] = codecs.open(os.path.join(self.logroot, label, "channel-%s-%04d.%02d.%02d.log" % ( - (urllib2.quote(window.name.lower()).replace("/", "%2f"),) + now[:3])), "a", encoding="utf8") + (urllib2.quote(window.name.lower().decode("utf8")).replace("/", "%2f"),) + now[:3])), "a", encoding="utf8") print >>log, "%s ### Log file opened" % (irc.timestamp()) self.logs[window].flush() - if window.context.identity in window.users: + if window in window.context.identity.channels: 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) + for line in window.fmttopic(): + print >>log, "%s <<< %s" % (irc.timestamp(), line) 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])) + for line in window.fmtnames(sort="mode"): + print >>log, "%s <<< %s" % (irc.timestamp(), line) 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) + print >>log, "%s <<< %s" % ( + irc.timestamp(), window.fmtmodes()) if window.created: - print >>log, "%s <<< :%s 329 %s %s %s" % ( - irc.timestamp(), window.context.serv, window.context.identity.nick, window.name, window.created) + print >>log, "%s <<< %s" % ( + irc.timestamp(), window.fmtchancreated()) + 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])) @@ -194,7 +187,7 @@ class Logger(Thread): log.flush() def closeLog(self, window): - if window in self.logs.keys() and type(self.logs[window]) == file and not self.logs[window].closed: + if window in self.logs.keys() and isinstance(self.logs[window], codecs.StreamReaderWriter) and not self.logs[window].closed: print >>self.logs[ window], "%s ### Log file closed" % (irc.timestamp()) self.logs[window].close() @@ -205,56 +198,56 @@ class Logger(Thread): 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) + def onConnectAttempt(self, context): + if context not in self.logs.keys() or (not self.logs[context]) or self.logs[context].closed: + self.openLog(context) ts = irc.timestamp() - print >>self.logs[IRC], "%s *** Attempting connection to %s:%s." % ( - ts, IRC.server, IRC.port) + print >>self.logs[context], "%s *** Attempting connection to %s:%s." % ( + ts, context.server, context.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) + def onConnect(self, context): + if context not in self.logs.keys() or (not self.logs[context]) or self.logs[context].closed: + self.openLog(context) ts = irc.timestamp() - print >>self.logs[IRC], "%s *** Connection to %s:%s established." % ( - ts, IRC.server, IRC.port) + print >>self.logs[context], "%s *** Connection to %s:%s established." % ( + ts, context.server, context.port) - def onConnectFail(self, IRC, exc, excmsg, tb): + def onConnectFail(self, context, 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) + if context not in self.logs.keys() or (not self.logs[context]) or self.logs[context].closed: + self.openLog(context) ts = irc.timestamp() - print >>self.logs[IRC], "%s *** Connection to %s:%s failed: %s." % ( - ts, IRC.server, IRC.port, excmsg) + print >>self.logs[context], "%s *** Connection to %s:%s failed: %s." % ( + ts, context.server, context.port, excmsg) - def onDisconnect(self, IRC, expected=False): + def onDisconnect(self, context, expected=False): ts = irc.timestamp() for window in self.logs.keys(): - if type(window) in (irc.Channel, irc.User) and window.context == IRC: + if type(window) in (irc.Channel, irc.User) and window.context == context: print >>self.logs[window], "%s *** Connection to %s:%s terminated." % ( - ts, IRC.server, IRC.port) + ts, context.server, context.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) + print >>self.logs[context], "%s *** Connection %s:%s terminated." % ( + ts, context.server, context.port) + self.logs[context].flush() + self.closeLog(context) - def onJoin(self, IRC, user, channel): + def onJoin(self, context, user, channel): # Called when somebody joins a channel, includes bot. - ts = irc.timestamp() - if user == IRC.identity: + if user == context.identity: self.openLog(channel) + ts = irc.timestamp() 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): + def onChanMsg(self, context, 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]]) + for mode in context.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) @@ -267,16 +260,16 @@ class Logger(Thread): 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 onChanAction(self, context, user, channel, targetprefix, action): + self.onChanMsg(context, user, channel, + targetprefix, "\x01ACTION %s\x01" % action) - def onChanNotice(self, IRC, origin, channel, targetprefix, msg): + def onChanNotice(self, context, 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]]) + for mode in context.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) @@ -289,7 +282,7 @@ class Logger(Thread): ts, classes, origin, targetprefix, channel.name, msg) self.logs[channel].flush() - def onPart(self, IRC, user, channel, partmsg): + def onPart(self, context, user, channel, partmsg): # Called when somebody parts the channel, includes bot. ts = irc.timestamp() if partmsg: @@ -299,10 +292,10 @@ class Logger(Thread): 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: + if user == context.identity: self.closeLog(channel) - def onKick(self, IRC, kicker, channel, kicked, kickmsg): + def onKick(self, context, kicker, channel, kicked, kickmsg): # Called when somebody is kicked from the channel, includes bot. ts = irc.timestamp() if kickmsg: @@ -312,60 +305,61 @@ class Logger(Thread): 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 kicked == IRC.identity: + if kicked == context.identity: self.closeLog(channel) - def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg): + def onSendChanMsg(self, context, 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]]) + for mode in context.supports["PREFIX"][0] if mode in channel.modes.keys() and context.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) + ts, classes, context.identity.nick, context.identity.username, context.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) + ts, context.identity.nick, context.identity.username, context.identity.host, targetprefix, channel.name, msg) self.logs[channel].flush() - def onSendChanAction(self, IRC, origin, channel, targetprefix, action): + def onSendChanAction(self, context, 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) + context, origin, channel, targetprefix, "\x01ACTION %s\x01" % action) - def onPrivMsg(self, IRC, user, msg): + def onPrivMsg(self, context, 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) + ts, user.nick, user.username, user.host, context.identity.nick, msg) self.logs[user].flush() - def onPrivNotice(self, IRC, origin, msg): + def onPrivNotice(self, context, 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) + ts = irc.timestamp() print >>self.logs[origin], "%s <<< :%s!%s@%s NOTICE %s :%s" % ( - ts, origin.nick, origin.username, origin.host, IRC.identity.nick, msg) + ts, origin.nick, origin.username, origin.host, context.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() + print >>self.logs[context], "%s <<< :%s NOTICE %s :%s" % ( + ts, origin, context.identity.nick, msg) + self.logs[context].flush() - def onPrivAction(self, IRC, user, action): + def onPrivAction(self, context, user, action): # Called when someone sends an action (/me) to the bot. - self.onPrivMsg(IRC, user, "\x01ACTION %s\x01" % action) + self.onPrivMsg(context, user, "\x01ACTION %s\x01" % action) - def onSendPrivMsg(self, IRC, origin, user, msg): + def onSendPrivMsg(self, context, 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. @@ -373,16 +367,16 @@ class Logger(Thread): 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) + ts, context.identity.nick, context.identity.username, context.identity.host, user.nick, msg) self.logs[user].flush() - def onSendPrivAction(self, IRC, origin, user, action): + def onSendPrivAction(self, context, 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) + self.onSendPrivMsg(context, origin, user, "\x01ACTION %s\x01" % action) - def onNickChange(self, IRC, user, newnick): + def onNickChange(self, context, user, newnick): # Called when somebody changes nickname. ts = irc.timestamp() line = "%s <<< :%s!%s@%s NICK %s" % ( @@ -390,29 +384,30 @@ class Logger(Thread): # Print nick change in each channel the user is in. for channel in user.channels: - print >>self.logs[channel], line - self.logs[channel].flush() + if channel in context.identity.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): + def onMeNickChange(self, context, 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) + ts, context.identity.nick, context.identity.username, context.identity.host, newnick) for (window, log) in self.logs.items(): - if type(window) == irc.User and window != IRC.identity: + if type(window) == irc.User and window != context.identity: print >>log, line log.flush() - def onQuit(self, IRC, user, quitmsg): - # Called when somebody quits IRC. + def onQuit(self, context, user, quitmsg): + # Called when somebody quits context. ts = irc.timestamp() if quitmsg: line = "%s <<< :%s!%s@%s QUIT :%s" % ( @@ -423,7 +418,7 @@ class Logger(Thread): # 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: + if channel in context.identity.channels and channel in self.logs.keys() and not self.logs[channel].closed: print >>self.logs[channel], line self.logs[channel].flush() @@ -433,152 +428,152 @@ class Logger(Thread): self.logs[user].flush() self.closeLog(user) - def onNames(self, IRC, origin, channel, flag, channame, nameslist): + def onNames(self, context, 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] + log = self.logs[context] 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, + print >>log, "%s <<< :%s 353 %s %s %s :%s" % (ts, origin, context.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): + def onNamesEnd(self, context, 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] + log = self.logs[context] ts = irc.timestamp() print >>log, "%s <<< :%s 366 %s %s :%s" % ( - ts, origin, IRC.identity.nick, channame, endmsg) + ts, origin, context.identity.nick, channame, endmsg) log.flush() - def onWhoisStart(self, IRC, origin, user, nickname, username, host, realname): + def onWhoisStart(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, username, host, realname) - def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg): + def onWhoisRegisteredNick(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) - def onWhoisAway(self, IRC, origin, user, nickname, awaymsg): + def onWhoisAway(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, awaymsg) - def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg): + def onWhoisConnectingFrom(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) - def onWhoisChannels(self, IRC, origin, user, nickname, chanlist): + def onWhoisChannels(self, context, 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)) + irc.timestamp(), origin, context.identity.nick, nickname, " ".join(chanlist)) - def onWhoisAvailability(self, IRC, origin, user, nickname, msg): + def onWhoisAvailability(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) - def onWhoisServer(self, IRC, origin, user, nickname, server, servername): + def onWhoisServer(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, server, servername) - def onWhoisOp(self, IRC, origin, user, nickname, msg): + def onWhoisOp(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) - def onWhoisTimes(self, IRC, origin, user, nickname, idletime, signontime, msg): + def onWhoisTimes(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, idletime, signontime, msg) - def onWhoisSSL(self, IRC, origin, user, nickname, msg): + def onWhoisSSL(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) - def onWhoisModes(self, IRC, origin, user, nickname, msg): + def onWhoisModes(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) - def onWhoisLoggedInAs(self, IRC, origin, user, nickname, loggedinas, msg): + def onWhoisLoggedInAs(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, loggedinas, msg) - def onWhoisEnd(self, IRC, origin, user, nickname, msg): + def onWhoisEnd(self, context, 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) + irc.timestamp(), origin, context.identity.nick, nickname, msg) self.logs[user].flush() - def onWhoEntry(self, IRC, **kwargs): + def onWhoEntry(self, context, **kwargs): # Called when a WHO list is received. pass - def onWhoEnd(self, IRC, **kwargs): + def onWhoEnd(self, context, **kwargs): # Called when a WHO list is received. pass - def onList(self, IRC, chanlistbegin, chanlist, endmsg): + def onList(self, context, chanlistbegin, chanlist, endmsg): # Called when a channel list is received. pass - def onTopic(self, IRC, origin, channel, topic): + def onTopic(self, context, 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] + log = self.logs[context] print >>log, "%s <<< :%s 332 %s %s :%s" % ( - ts, origin, IRC.identity.nick, channel.name, topic) + ts, origin, context.identity.nick, channel.name, topic) log.flush() - def onTopicInfo(self, IRC, origin, channel, topicsetby, topictime): + def onTopicInfo(self, context, 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] + log = self.logs[context] print >>log, "%s <<< :%s 333 %s %s %s %d" % ( - ts, origin, IRC.identity.nick, channel.name, topicsetby, topictime) + ts, origin, context.identity.nick, channel.name, topicsetby, topictime) log.flush() - def onTopicSet(self, IRC, user, channel, topic): + def onTopicSet(self, context, user, channel, topic): # Called when channel topic is changed. ts = irc.timestamp() if type(user) == irc.User: @@ -589,7 +584,7 @@ class Logger(Thread): ts, user, channel.name, topic) self.logs[channel].flush() - def onChanModeSet(self, IRC, user, channel, modedelta): + def onChanModeSet(self, context, 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. @@ -620,13 +615,13 @@ class Logger(Thread): ts, user, channel.name, modestr) self.logs[channel].flush() - def onChannelModes(self, IRC, channel, modedelta): + def onChannelModes(self, context, origin, 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] + log = self.logs[context] modestr = "" params = [] sign = "" @@ -639,24 +634,24 @@ class Logger(Thread): 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)) + ts, origin, context.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) + ts, origin, context.identity.nick, channel.name, modestr) log.flush() - def onChanCreated(self, IRC, channel, created): + def onChanCreated(self, context, origin, 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] + log = self.logs[context] print >>log, "%s <<< :%s 329 %s %s %d" % ( - ts, IRC.serv, IRC.identity.nick, channel.name, created) + ts, origin, context.identity.nick, channel.name, created) log.flush() - def onUnhandled(self, IRC, line, origin, cmd, target, params, extinfo, targetprefix): + def onUnhandled(self, context, line, origin, cmd, target, params, extinfo, targetprefix): ts = irc.timestamp() - print >>self.logs[IRC], "%s <<< %s" % (ts, line) - self.logs[IRC].flush() + print >>self.logs[context], "%s <<< %s" % (ts, line) + self.logs[context].flush() diff --git a/modjson.py b/modjson.py new file mode 100644 index 0000000..f02c935 --- /dev/null +++ b/modjson.py @@ -0,0 +1,835 @@ +import json +import inspect +import re +import importlib +import collections +import inspect +import new + +from json.decoder import errmsg +from json.encoder import py_encode_basestring_ascii, ESCAPE, ESCAPE_ASCII, HAS_UTF8, ESCAPE_DCT, INFINITY, FLOAT_REPR, encode_basestring, encode_basestring_ascii + +FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL + +NUMBER_RE = re.compile(r'(-?(?:0|[1-9]\d*))(\.\d+)?([eE][-+]?\d+)?', FLAGS) +REF_RE = re.compile( + r'<([A-Z0-9_]+(?:\[[0-9]+(?:,[0-9]+)*\])?(?:\.[A-Z0-9_]+(?:\[[0-9]+(?:,[0-9]+)*\])?)*)>', flags=FLAGS | re.I) +PATH_RE = re.compile( + r'([A-Z0-9_]+)(?:\[([0-9]+(?:,[0-9]+)*)\])?', flags=FLAGS | re.I) +WHITESPACE = re.compile(r'[ \t\n\r]*', FLAGS) +WHITESPACE_STR = ' \t\n\r' + + +match_number = NUMBER_RE.match +match_reference = REF_RE.match + + +datetime_regex = re.compile( + '\"dt\((\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})Z\)\"') +timedelta_regex = re.compile('\"td\((\d+)\)\"') + + + #parse_object = context.parse_object + #parse_array = context.parse_array + #parse_string = context.parse_string + #match_number = NUMBER_RE.match + #match_reference = REF_RE.match + #encoding = context.encoding + #strict = context.strict + #parse_float = context.parse_float + #parse_int = context.parse_int + #parse_constant = context.parse_constant + #object_hook = context.object_hook + #object_pairs_hook = context.object_pairs_hook +class ModJSONDecoder(json.JSONDecoder): + + def __init__(self, encoding=None, object_hook=None, parse_float=None, + parse_int=None, parse_constant=None, strict=True, + object_pairs_hook=None): + """``encoding`` determines the encoding used to interpret any ``str`` + objects decoded by this instance (utf-8 by default). It has no + effect when decoding ``unicode`` objects. + + Note that currently only encodings that are a superset of ASCII work, + strings of other encodings should be passed in as ``unicode``. + + ``object_hook``, if specified, will be called with the result + of every JSON object decoded and its return value will be used in + place of the given ``dict``. This can be used to provide custom + deserializations (e.g. to support JSON-RPC class hinting). + + ``object_pairs_hook``, if specified will be called with the result of + every JSON object decoded with an ordered list of pairs. The return + value of ``object_pairs_hook`` will be used instead of the ``dict``. + This feature can be used to implement custom decoders that rely on the + order that the key and value pairs are decoded (for example, + collections.OrderedDict will remember the order of insertion). If + ``object_hook`` is also defined, the ``object_pairs_hook`` takes + priority. + + ``parse_float``, if specified, will be called with the string + of every JSON float to be decoded. By default this is equivalent to + float(num_str). This can be used to use another datatype or parser + for JSON floats (e.g. decimal.Decimal). + + ``parse_int``, if specified, will be called with the string + of every JSON int to be decoded. By default this is equivalent to + int(num_str). This can be used to use another datatype or parser + for JSON integers (e.g. float). + + ``parse_constant``, if specified, will be called with one of the + following strings: -Infinity, Infinity, NaN. + This can be used to raise an exception if invalid JSON numbers + are encountered. + + If ``strict`` is false (true is the default), then control + characters will be allowed inside strings. Control characters in + this context are those with character codes in the 0-31 range, + including ``'\\t'`` (tab), ``'\\n'``, ``'\\r'`` and ``'\\0'``. + + """ + self.encoding = encoding + self.object_pairs_hook = object_pairs_hook + self.parse_float = parse_float or float + self.parse_int = parse_int or int + self.parse_constant = parse_constant or json.decoder._CONSTANTS.__getitem__ + self.strict = strict + self.parse_string = json.decoder.scanstring + self.object_dict_hook = None + + def object_hook(self, d): + if 'class' in d: + class_path = d.pop('class') + modname, clsname = class_path.rsplit(".", 1) + #module_name = d.pop('__module__') + module = __import__(modname) + class_ = getattr(module, clsname) + args = dict((key.encode('ascii'), value) + for key, value in d.items()) + inst = class_(**args) + else: + inst = d + return inst + + def parse_object(self, s_and_end, root, working, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + s, end = s_and_end + pairs = [] + pairs_append = pairs.append + # Use a slice to prevent IndexError from being raised, the following + # check will raise a more specific ValueError if the string is empty + nextchar = s[end:end + 1] + # Normally we expect nextchar == '"' + if nextchar != '"': + if nextchar in _ws: + end = _w(s, end).end() + nextchar = s[end:end + 1] + # Trivial empty object + if nextchar == '}': + if self.object_dict_hook is not None: + result = self.object_dict_hook(working) + return result, end + 1 + if self.object_hook is not None: + working = self.object_hook(working) + return working, end + 1 + elif nextchar != '"': + raise ValueError(errmsg( + "Expecting property name enclosed in double quotes", s, end)) + end += 1 + while True: + key, end = self.parse_string(s, end) + + # To skip some function call overhead we optimize the fast paths where + # the JSON key separator is ": " or just ":". + if s[end:end + 1] != ':': + end = _w(s, end).end() + if s[end:end + 1] != ':': + raise ValueError(errmsg("Expecting ':' delimiter", s, end)) + end += 1 + + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + try: + nextchar = s[end] + except IndexError: + raise ValueError(errmsg("Expecting object", s, end)) + + if nextchar == '{': + nextitem = {} + elif nextchar == '[': + nextitem = [] + else: + nextitem = None + working[key] = nextitem + + try: + value, end = self.scan_once(s, end, root, nextitem) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + # pairs_append((key, value)) + working[key] = value + + try: + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + end += 1 + + if nextchar == '}': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting ',' delimiter", s, end - 1)) + + try: + nextchar = s[end] + if nextchar in _ws: + end += 1 + nextchar = s[end] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end] + except IndexError: + nextchar = '' + + end += 1 + if nextchar != '"': + raise ValueError(errmsg( + "Expecting property name enclosed in double quotes", s, end - 1)) + if self.object_pairs_hook is not None: + result = self.object_dict_hook(dict) + return result, end + if self.object_hook is not None: + working = self.object_hook(working) + return working, end + + def parse_array(self, s_and_end, root, working, _w=WHITESPACE.match, _ws=WHITESPACE_STR): + s, end = s_and_end + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + # Look-ahead for trivial empty array + if nextchar == ']': + return working, end + 1 + _append = working.append + while True: + try: + nextchar = s[end] + except IndexError: + raise ValueError(errmsg("Expecting object", s, end)) + + if nextchar == '{': + nextitem = {} + elif nextchar == '[': + nextitem = [] + else: + nextitem = None + _append(nextitem) + + try: + value, end = self.scan_once(s, end, root, nextitem) + except StopIteration: + raise ValueError(errmsg("Expecting object", s, end)) + if value is not nextitem: + del working[-1] + _append(value) + nextchar = s[end:end + 1] + if nextchar in _ws: + end = _w(s, end + 1).end() + nextchar = s[end:end + 1] + end += 1 + if nextchar == ']': + break + elif nextchar != ',': + raise ValueError(errmsg("Expecting ',' delimiter", s, end)) + try: + if s[end] in _ws: + end += 1 + if s[end] in _ws: + end = _w(s, end + 1).end() + except IndexError: + pass + + return working, end + + def scan_once(self, string, idx, root, working): + try: + nextchar = string[idx] + except IndexError: + raise StopIteration + + if nextchar == '"': + return self.parse_string(string, idx + 1) + elif nextchar == '{': + return self.parse_object((string, idx + 1), root, working) + elif nextchar == '[': + return self.parse_array((string, idx + 1), root, working) + elif nextchar == 'n' and string[idx:idx + 4] == 'null': + return None, idx + 4 + elif nextchar == 't' and string[idx:idx + 4] == 'true': + return True, idx + 4 + elif nextchar == 'f' and string[idx:idx + 5] == 'false': + return False, idx + 5 + m = match_number(string, idx) + if m is not None: + integer, frac, exp = m.groups() + if frac or exp: + res = self.parse_float(integer + (frac or '') + (exp or '')) + else: + res = self.parse_int(integer) + return res, m.end() + r = match_reference(string, idx) + if r is not None: + refname = r.groups() + obj = root + for name in refname[0].split("."): + name, indices = PATH_RE.match(name).groups() + if name: + if type(obj) == dict: + obj = obj[name] + elif type(obj) == list: + obj = obj[int(name)] + else: + obj = getattr(obj, name) + if indices: + for index in indices.split("."): + obj = obj[int(index)] + return obj, r.end() + elif nextchar == 'N' and string[idx:idx + 3] == 'NaN': + return self.parse_constant('NaN'), idx + 3 + elif nextchar == 'I' and string[idx:idx + 8] == 'Infinity': + return self.parse_constant('Infinity'), idx + 8 + elif nextchar == '-' and string[idx:idx + 9] == '-Infinity': + return self.parse_constant('-Infinity'), idx + 9 + else: + raise StopIteration + + def decode(self, s, _w=WHITESPACE.match): + """Return the Python representation of ``s`` (a ``str`` or ``unicode`` + instance containing a JSON document) + + """ + obj, end = self.raw_decode(s, idx=_w(s, 0).end()) + end = _w(s, end).end() + if end != len(s): + raise ValueError(errmsg("Extra data", s, end, len(s))) + return obj + + def raw_decode(self, s, idx=0): + """Decode a JSON document from ``s`` (a ``str`` or ``unicode`` + beginning with a JSON document) and return a 2-tuple of the Python + representation and the index in ``s`` where the document ended. + + This can be used to decode a JSON document from a string that may + have extraneous data at the end. + + """ + try: + nextchar = s[idx] + except IndexError: + raise ValueError(errmsg("Expecting object", s, idx)) + + if nextchar == '{': + root = {} + elif nextchar == '[': + root = [] + else: + root = None + + try: + obj, end = self.scan_once(s, idx, root, root) + except StopIteration: + raise ValueError("No JSON object could be decoded") + return obj, end + + +class JSONEncoder(object): + + """Extensible JSON <http://json.org> encoder for Python data structures. + + Supports the following objects and types by default: + + +-------------------+---------------+ + | Python | JSON | + +===================+===============+ + | dict | object | + +-------------------+---------------+ + | list, tuple | array | + +-------------------+---------------+ + | str, unicode | string | + +-------------------+---------------+ + | int, long, float | number | + +-------------------+---------------+ + | True | true | + +-------------------+---------------+ + | False | false | + +-------------------+---------------+ + | None | null | + +-------------------+---------------+ + + To extend this to recognize other objects, subclass and implement a + ``.default()`` method with another method that returns a serializable + object for ``o`` if possible, otherwise it should call the superclass + implementation (to raise ``TypeError``). + + """ + item_separator = ', ' + key_separator = ': ' + + def __init__(self, skipkeys=False, ensure_ascii=True, + check_circular=True, allow_nan=True, sort_keys=False, + indent=None, separators=None, encoding='utf-8', default=None): + """Constructor for JSONEncoder, with sensible defaults. + + If skipkeys is false, then it is a TypeError to attempt + encoding of keys that are not str, int, long, float or None. If + skipkeys is True, such items are simply skipped. + + If *ensure_ascii* is true (the default), all non-ASCII + characters in the output are escaped with \uXXXX sequences, + and the results are str instances consisting of ASCII + characters only. If ensure_ascii is False, a result may be a + unicode instance. This usually happens if the input contains + unicode strings or the *encoding* parameter is used. + + If check_circular is true, then lists, dicts, and custom encoded + objects will be checked for circular references during encoding to + prevent an infinite recursion (which would cause an OverflowError). + Otherwise, no such check takes place. + + If allow_nan is true, then NaN, Infinity, and -Infinity will be + encoded as such. This behavior is not JSON specification compliant, + but is consistent with most JavaScript based encoders and decoders. + Otherwise, it will be a ValueError to encode such floats. + + If sort_keys is true, then the output of dictionaries will be + sorted by key; this is useful for regression tests to ensure + that JSON serializations can be compared on a day-to-day basis. + + If indent is a non-negative integer, then JSON array + elements and object members will be pretty-printed with that + indent level. An indent level of 0 will only insert newlines. + None is the most compact representation. Since the default + item separator is ', ', the output might include trailing + whitespace when indent is specified. You can use + separators=(',', ': ') to avoid this. + + If specified, separators should be a (item_separator, key_separator) + tuple. The default is (', ', ': '). To get the most compact JSON + representation you should specify (',', ':') to eliminate whitespace. + + If specified, default is a function that gets called for objects + that can't otherwise be serialized. It should return a JSON encodable + version of the object or raise a ``TypeError``. + + If encoding is not None, then all input strings will be + transformed into unicode using that encoding prior to JSON-encoding. + The default is UTF-8. + + """ + + self.skipkeys = skipkeys + self.ensure_ascii = ensure_ascii + self.check_circular = check_circular + self.allow_nan = allow_nan + self.sort_keys = sort_keys + self.indent = indent + if separators is not None: + self.item_separator, self.key_separator = separators + if default is not None: + self.default = default + self.encoding = encoding + + if self.ensure_ascii: + self._encoder = encode_basestring_ascii + else: + self._encoder = encode_basestring + if self.encoding != 'utf-8': + def _encoder(o, _orig_encoder=self._encoder, _encoding=self.encoding): + if isinstance(o, str): + o = o.decode(_encoding) + return _orig_encoder(o) + self._encoder = _encoder + + def default(self, o, refs, path): + """Implement this method in a subclass such that it returns + a serializable object for ``o``, or calls the base implementation + (to raise a ``TypeError``). + + For example, to support arbitrary iterators, you could + implement default like this:: + + def default(self, o): + try: + iterable = iter(o) + except TypeError: + pass + else: + return list(iterable) + # Let the base class default method raise the TypeError + return JSONEncoder.default(self, o) + + """ + if path: + pathstr = str(path[0]) + numindices = [] + for index in path[1:]: + if type(index) == int: + numindices.append(str(index)) + else: + if numindices: + pathstr += "[%s]" % (",".join(numindices)) + numindices = [] + pathstr += ".%s" % index + if numindices: + pathstr += "[%s]" % (",".join(numindices)) + numindices = [] + if pathstr not in refs.keys(): + refs[pathstr] = o + if "json" in dir(o) and callable(o.json): + return o.json() + + conf = collections.OrderedDict() + conf["class"] = "{o.__class__.__module__}.{o.__class__.__name__}".format( + **vars()) + + if "__init__" in dir(o) and type(o.__init__) == new.instancemethod: + try: + arginspect = inspect.getargspec(o.__init__) + except: + raise TypeError(repr(o) + " is not JSON serializable") + + if arginspect.defaults: + requiredargs = arginspect.args[ + 1:len(arginspect.args) - len(arginspect.defaults)] + argswithdefaults = arginspect.args[ + len(arginspect.args) - len(arginspect.defaults):] + defaultvalues = arginspect.defaults + else: + requiredargs = arginspect.args[1:] + argswithdefaults = [] + defaultvalues = [] + + for key in requiredargs: + try: + conf[key] = getattr(o, key) + except AttributeError: + print key + raise TypeError( + repr(o) + " is not JSON serializable (Cannot recover required argument '%s')" % key) + + for key, default in zip(argswithdefaults, defaultvalues): + try: + value = getattr(o, key) + if value != default: + conf[key] = getattr(o, key) + except AttributeError: + pass + return conf + + def encode(self, o): + """Return a JSON string representation of a Python data structure. + + >>> JSONEncoder().encode({"foo": ["bar", "baz"]}) + '{"foo": ["bar", "baz"]}' + + """ + # This is for extremely simple cases and benchmarks. + if isinstance(o, basestring): + if isinstance(o, str): + _encoding = self.encoding + if (_encoding is not None + and not (_encoding == 'utf-8')): + o = o.decode(_encoding) + if self.ensure_ascii: + return encode_basestring_ascii(o) + else: + return encode_basestring(o) + # This doesn't pass the iterator directly to ''.join() because the + # exceptions aren't as detailed. The list call should be roughly + # equivalent to the PySequence_Fast that ''.join() would do. + + chunks = self.iterencode(o, {}, _one_shot=True) + if not isinstance(chunks, (list, tuple)): + chunks = list(chunks) + return ''.join(chunks) + + def iterencode(self, o, refs, _one_shot=False): + """Encode the given object and yield each string + representation as available. + + For example:: + + for chunk in JSONEncoder().iterencode(bigobject): + mysocket.write(chunk) + + """ + if self.check_circular: + markers = {} + else: + markers = None + + def floatstr(o, allow_nan=self.allow_nan, + _repr=FLOAT_REPR, _inf=INFINITY, _neginf=-INFINITY): + # Check for specials. Note that this type of test is processor + # and/or platform-specific, so do tests which don't depend on the + # internals. + + if o != o: + text = 'NaN' + elif o == _inf: + text = 'Infinity' + elif o == _neginf: + text = '-Infinity' + else: + return _repr(o) + + if not allow_nan: + raise ValueError( + "Out of range float values are not JSON compliant: " + + repr(o)) + + return text + + # if (_one_shot and c_make_encoder is not None + # and self.indent is None and not self.sort_keys): + #_iterencode = c_make_encoder( + #markers, self.default, _encoder, self.indent, + #self.key_separator, self.item_separator, self.sort_keys, + # self.skipkeys, self.allow_nan) + # else: + #_iterencode = _make_iterencode( + #markers, self.default, _encoder, self.indent, floatstr, + #self.key_separator, self.item_separator, self.sort_keys, + # self.skipkeys, _one_shot) + return self._iterencode(o, 0, markers, refs, ()) + + def _iterencode(self, o, _current_indent_level, markers, refs, path): + if isinstance(o, basestring): + yield self._encoder(o) + elif o is None: + yield 'null' + elif o is True: + yield 'true' + elif o is False: + yield 'false' + elif isinstance(o, (int, long)): + yield str(o) + elif isinstance(o, float): + yield _floatstr(o) + else: + ref = self._iterencode_ref( + o, _current_indent_level, markers, refs, path) + if ref: + yield ref + elif isinstance(o, (list, tuple)): + for chunk in self._iterencode_list(o, _current_indent_level, markers, refs, path): + yield chunk + elif isinstance(o, dict): + for chunk in self._iterencode_dict(o, _current_indent_level, markers, refs, path): + yield chunk + else: + if markers is not None: + markerid = id(o) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = o + o = self.default(o, refs, path) + for chunk in self._iterencode(o, _current_indent_level, markers, refs, path): + yield chunk + if markers is not None: + del markers[markerid] + + def _iterencode_ref(self, o, _current_indent_level, markers, refs, path): + for key, value in refs.items(): + if value is o: + return "<%s>" % key + + def _iterencode_list(self, lst, _current_indent_level, markers, refs, path): + if path: + pathstr = str(path[0]) + numindices = [] + for index in path[1:]: + if type(index) == int: + numindices.append(str(index)) + else: + if numindices: + pathstr += "[%s]" % (",".join(numindices)) + numindices = [] + pathstr += ".%s" % index + if numindices: + pathstr += "[%s]" % (",".join(numindices)) + numindices = [] + if pathstr not in refs.keys(): + refs[pathstr] = lst + + if not lst: + yield '[]' + return + if markers is not None: + markerid = id(lst) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = lst + buf = '[' + if self.indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + \ + (' ' * (self.indent * _current_indent_level)) + separator = self.item_separator + newline_indent + buf += newline_indent + else: + newline_indent = None + separator = self.item_separator + first = True + for (k, value) in enumerate(lst): + if first: + first = False + else: + buf = separator + if isinstance(value, basestring): + yield buf + self._encoder(value) + elif value is None: + yield buf + 'null' + elif value is True: + yield buf + 'true' + elif value is False: + yield buf + 'false' + elif isinstance(value, (int, long)): + yield buf + str(value) + elif isinstance(value, float): + yield buf + _floatstr(value) + else: + ref = self._iterencode_ref( + value, _current_indent_level, markers, refs, path) + if ref and False: + yield buf + ref + else: + yield buf + if isinstance(value, (list, tuple)): + chunks = self._iterencode_list( + value, _current_indent_level, markers, refs, path + (k,)) + elif isinstance(value, dict): + chunks = self._iterencode_dict( + value, _current_indent_level, markers, refs, path + (k,)) + else: + chunks = self._iterencode( + value, _current_indent_level, markers, refs, path + (k,)) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (self.indent * _current_indent_level)) + yield ']' + if markers is not None: + del markers[markerid] + + def _iterencode_dict(self, dct, _current_indent_level, markers, refs, path): + if path: + pathstr = str(path[0]) + numindices = [] + for index in path[1:]: + if type(index) == int: + numindices.append(str(index)) + else: + if numindices: + pathstr += "[%s]" % (",".join(numindices)) + numindices = [] + pathstr += ".%s" % index + if numindices: + pathstr += "[%s]" % (",".join(numindices)) + numindices = [] + if pathstr not in refs.keys(): + refs[pathstr] = dct + + if not dct: + yield '{}' + return + if markers is not None: + markerid = id(dct) + if markerid in markers: + raise ValueError("Circular reference detected") + markers[markerid] = dct + yield '{' + if self.indent is not None: + _current_indent_level += 1 + newline_indent = '\n' + \ + (' ' * (self.indent * _current_indent_level)) + item_separator = self.item_separator + newline_indent + yield newline_indent + else: + newline_indent = None + item_separator = self.item_separator + first = True + if self.sort_keys: + items = sorted(dct.items(), key=lambda kv: kv[0]) + else: + items = dct.iteritems() + for key, value in items: + if isinstance(key, basestring): + pass + # JavaScript is weakly typed for these, so it makes sense to + # also allow them. Many encoders seem to do something like this. + elif isinstance(key, float): + key = _floatstr(key) + elif key is True: + key = 'true' + elif key is False: + key = 'false' + elif key is None: + key = 'null' + elif isinstance(key, (int, long)): + key = str(key) + elif self.skipkeys: + continue + else: + raise TypeError("key " + repr(key) + " is not a string") + if first: + first = False + else: + yield item_separator + yield self._encoder(key) + yield self.key_separator + if isinstance(value, basestring): + yield self._encoder(value) + elif value is None: + yield 'null' + elif value is True: + yield 'true' + elif value is False: + yield 'false' + elif isinstance(value, (int, long)): + yield str(value) + elif isinstance(value, float): + yield _floatstr(value) + else: + ref = self._iterencode_ref( + value, _current_indent_level, markers, refs, path) + if ref: + yield ref + else: + if isinstance(value, (list, tuple)): + chunks = self._iterencode_list( + value, _current_indent_level, markers, refs, path + (key,)) + elif isinstance(value, dict): + chunks = self._iterencode_dict( + value, _current_indent_level, markers, refs, path + (key,)) + else: + chunks = self._iterencode( + value, _current_indent_level, markers, refs, path + (key,)) + for chunk in chunks: + yield chunk + if newline_indent is not None: + _current_indent_level -= 1 + yield '\n' + (' ' * (self.indent * _current_indent_level)) + yield '}' + if markers is not None: + del markers[markerid] @@ -44,7 +44,7 @@ class quote(object): else: channel.msg("%s: What am I removing?" % user.nick) elif cmd == "--flush": - with codecs.open(self.quotefile, "w", encoding=encoding) as f: + with codecs.open(self.quotefile, "w", encoding=self.encoding) as f: for line in self.quotes: print >>f, line else: diff --git a/startirc.py b/startirc.py index c3a6cec..111d9d0 100755 --- a/startirc.py +++ b/startirc.py @@ -54,14 +54,14 @@ signal.signal(signal.SIGTERM, sigterm) logroot = os.path.join(os.environ["HOME"], "IRC") InsomniaIRC = networks["InsomniaIRC"] = irc.Connection( - server="perseus.insomniairc.net", ipv6=False, ssl=True, log=open("/dev/null", "w")) + server="irc.insomniairc.net", nick="pyIRC", secure=True) 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", autoaway="I'm off to see the wizard!") + "", 16698, secure=True, certfile="cert.pem", keyfile="key.pem", autoaway="I'm off to see the wizard!") for (label, IRC) in networks.items(): IRC.addAddon(log, label=label) @@ -71,4 +71,4 @@ for (label, IRC) in networks.items(): InsomniaIRC.addAddon(ax, label="InsomniaIRC", autojoin=["#chat"]) for (label, IRC) in networks.items(): - IRC.start()
\ No newline at end of file + IRC.start() |