summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autoexec.py144
-rw-r--r--bouncer.py1550
-rw-r--r--cannon.py23
-rw-r--r--figlet.py1
-rw-r--r--irc.conf48
-rw-r--r--irc.py4668
-rw-r--r--ircapp.conf64
-rwxr-xr-xircapp.py125
-rw-r--r--logger.py594
-rw-r--r--modjson.py839
-rw-r--r--quote.py55
-rw-r--r--sedbot.py73
-rwxr-xr-xstartirc.py74
-rw-r--r--testaddon.py142
-rw-r--r--wallet.py26
15 files changed, 6127 insertions, 2299 deletions
diff --git a/autoexec.py b/autoexec.py
index 978501e..0b30894 100644
--- a/autoexec.py
+++ b/autoexec.py
@@ -1,82 +1,102 @@
#!/usr/bin/python
import re
import irc
+import fnmatch
+
+
+def AutoexecReload(old_ax):
+ ax = Autoexec()
+ for (context, conf) in old_ax.networks.items():
+ context.rmAddon(old_ax)
+ context.addAddon(ax, **conf.__dict__)
+ return ax
class Autoexec(object):
+
def __init__(self):
self.networks = {}
+ self._rejoinchannels = {}
+ # Saved channels for when a connection is lost
- def onAddonAdd(self, IRC, label, onconnect=None, onregister=None, autojoin=None, usermodes=None, wallet=None, opername=None, opermodes=None, snomasks=None, operexec=None, operjoin=None):
- labels = [v[0] for v in self.networks.values()]
+ def onAddonAdd(self, context, label, onconnect=[], onregister=[], autojoin=[], usermodes=None, nsautojoin=[], nsmatch=None, wallet=None, opername=None, opermodes=None, snomasks=None, operexec=None, operjoin=[], autorejoin=True):
+ labels = [v.label for v in self.networks.values()]
if label in labels:
- raise BaseException("Label already exists")
- if IRC in self.networks.keys():
- raise BaseException("Network already exists")
- self.networks[IRC] = (label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin)
-
- def onAddonRem(self, IRC):
- del self.networks[IRC]
+ raise BaseException, "Label already exists"
+ if context in self.networks.keys():
+ raise BaseException, "Network already exists"
+ self.networks[context] = irc.Config(
+ self, label=label, onconnect=list(onconnect), onregister=list(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 onConnect(self, IRC):
- (label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin) = self.networks[IRC]
- if onconnect:
- for line in onconnect:
- IRC.raw(line, origin=self)
+ def onDisconnect(self, context, expected):
+ conf = self.networks[context]
+ if conf.autorejoin and not expected and context.identity and context.identity.channels:
+ self._rejoinchannels[context] = irc.ChanList(
+ context.identity.channels, context=context) # Store a *copy* of the list of channels
- def onRegistered(self, IRC):
- (label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin) = self.networks[IRC]
- if onregister:
- for line in onregister:
- IRC.raw(line, origin=self)
- if usermodes:
- IRC.raw("MODE %s %s"%(IRC.identity.nick, usermodes), origin=self)
- if opername and wallet and "%s/opers/%s"%(label, opername) in wallet.keys():
- IRC.raw("OPER %s %s"%(opername, wallet[
- "%s/opers/%s"%(label, opername)]), origin=self)
- if autojoin:
- IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self)
+ 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 on381(self, IRC, line, origin, target, params, extinfo):
- (label, onconnect, onregister, autojoin, usermodes, wallet, opername, opermodes, snomasks, operexec, operjoin) = self.networks[IRC]
- if operexec:
- for line in operexec:
- IRC.raw(line, origin=self)
- if opermodes:
- IRC.raw("MODE %s %s"%(IRC.identity.nick, opermodes), origin=self)
- if snomasks:
- IRC.raw("MODE %s +s %s"%(IRC.identity.nick, snomasks), origin=self)
- if operjoin:
- IRC.raw("JOIN %s"%(",".join(operjoin)), origin=self)
+ def onAddonRem(self, context):
+ del self.networks[context], self._rejoinchannels[context]
+ def onConnect(self, context):
+ conf = self.networks[context]
+ if conf.onconnect:
+ for line in conf.onconnect:
+ context.raw(line, origin=self)
-class NickServ(object):
- def __init__(self):
- self.networks = {}
-
- def onAddonAdd(self, IRC, label, wallet=None, autojoin=None):
- labels = [v[0] for v in self.networks.values()]
- #print labels
- if label in labels:
- raise BaseException("Label already exists")
- if IRC in self.networks.keys():
- raise BaseException("Network already exists")
- self.networks[IRC] = (label, wallet, autojoin)
+ def onRegistered(self, context):
+ conf = self.networks[context]
+ if conf.onregister:
+ for line in conf.onregister:
+ context.raw(line, origin=self)
+ if conf.usermodes:
+ context.raw("MODE %s %s" %
+ (context.identity.nick, conf.usermodes), origin=self)
+ if conf.opername and conf.wallet and "%s/opers/%s" % (conf.label, conf.opername) in conf.wallet.keys():
+ context.raw("OPER %s %s" %
+ (conf.opername, conf.wallet["%s/opers/%s" % (conf.label, conf.opername)]), origin=self)
+ if conf.autojoin:
+ conf.autojoin.join(origin=self)
+ if conf.autorejoin and self._rejoinchannels[context]:
+ rejoin = irc.ChanList([channel for channel in self._rejoinchannels[
+ context] if channel not in conf.autojoin + conf.nsautojoin + conf.operjoin], context=context)
+ if len(rejoin):
+ rejoin.join(origin=self)
+ self._rejoinchannels[context] = None
- def onAddonRem(self, IRC):
- del self.networks[IRC]
+ def on381(self, context, line, origin, target, params, extinfo):
+ conf = self.networks[context]
+ if conf.operexec:
+ for line in conf.operexec:
+ context.raw(line, origin=self)
+ if conf.opermodes:
+ context.raw("MODE %s %s" %
+ (context.identity.nick, conf.opermodes), origin=self)
+ if conf.snomasks:
+ context.raw("MODE %s +s %s" %
+ (context.identity.nick, conf.snomasks), origin=self)
+ if conf.operjoin:
+ conf.operjoin.join(origin=self)
- def onPrivNotice(self, IRC, origin, msg):
- label, wallet, autojoin = self.networks[IRC]
+ def onPrivNotice(self, context, origin, msg):
+ conf = self.networks[context]
if type(origin) == irc.User and origin.nick.lower() == "nickserv":
- if re.match("This nickname is registered( and protected)?", msg) and wallet and "%s/NickServ/%s"%(label, IRC.identity.nick.lower()) in wallet.keys():
- origin.msg("identify %s" % wallet["%s/NickServ/%s" %
- (label, IRC.identity.nick.lower())])
+ if re.match("This nickname is registered( and protected)?", msg) and (not conf.nsmatch or fnmatch.fnmatch("%s!%s@%s" % (origin.nick, origin.username, origin.host), conf.nsmatch)) and conf.wallet and "%s/NickServ/%s" % (conf.label, context.identity.nick.lower()) in conf.wallet.keys():
+ origin.msg("identify %s" %
+ conf.wallet["%s/NickServ/%s" % (conf.label, context.identity.nick.lower())])
if re.match("You are now identified", msg):
- if autojoin:
- IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self)
+ if conf.nsautojoin:
+ conf.nsautojoin.join(origin=self)
- def on900(self, IRC, line, origin, target, params, extinfo):
- label, wallet, autojoin = self.networks[IRC]
- if autojoin:
- IRC.raw("JOIN %s"%(",".join(autojoin)), origin=self)
+ def on900(self, context, line, origin, target, params, extinfo):
+ conf = self.networks[context]
+ if conf.nsautojoin:
+ conf.nsautojoin.join(origin=self)
diff --git a/bouncer.py b/bouncer.py
index 3c321fa..8db9dd8 100644
--- a/bouncer.py
+++ b/bouncer.py
@@ -9,40 +9,67 @@ import string
import hashlib
import traceback
import irc
-from threading import Thread, Lock
+import getpass
+from threading import Thread
+from threading import RLock as Lock
import Queue
+import chardet
+import modjson
+
+dec = modjson.ModJSONDecoder()
+enc = modjson.ModJSONEncoder(indent=3)
+
+# 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):
+ networks, configs = zip(*BNC.conf.items())
+ json = enc.encode([BNC, configs])
+ if BNC.isAlive():
+ BNC.stop()
+ newBNC, newconfs = dec.decode(json)
+ for network, newconf in zip(networks, newconfs):
+ network.rmAddon(BNC)
+ network.addAddon(**newconf)
+ return newBNC
class Bouncer (Thread):
- def __init__(self, addr="", port=16667, ssl=False, ipv6=False, certfile=None, keyfile=None, ignore=None, debug=False, log=sys.stderr, timeout=300, BNC=None, autoaway=None):
- self.__name__ = "Bouncer for pyIRC"
- self.__version__ = "1.1"
- self.__author__ = "Brian Sherson"
- self.__date__ = "December 1, 2013"
+ __name__ = "Bouncer for pyIRC"
+ __version__ = "2.0"
+ __author__ = "Brian Sherson"
+ __date__ = "February 21, 2014"
+ 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.servers = {}
+ 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.ssl = ssl
+ self.socket = None
+ self.secure = secure
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
-
- ### Keep track of what extensions/connections are requesting WHO, WHOIS, and LIST, because we don't want to spam every bouncer connection with the server's replies.
- ### In the future, MAY implement this idea in the irc module.
- self.whoexpected = {}
- self.whoisexpected = {}
- self.listexpected = {}
+ self.servname = servname
+ self._stopexpected = False
+
+ # 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 = {}
+ self._listexpected = {}
self.lock = Lock()
self.starttime = int(time.time())
Thread.__init__(self)
@@ -50,22 +77,33 @@ 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:
- connection = ssl.wrap_socket(connection, server_side=True, certfile=self.certfile, keyfile=self.keyfile, ssl_version=ssl.PROTOCOL_SSLv23)
+ 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))
except socket.error:
- #print "Shutting down Listener"
+ # print "Shutting down Listener"
self.socket.close()
- #raise
+ if not self._stopexpected:
+ raise
sys.exit()
+ except:
+ tb = traceback.format_exc()
+ print >>sys.stderr, tb
+ continue
connection.settimeout(self.timeout)
bouncer = BouncerConnection(
self, connection, addr, debug=self.debug)
@@ -74,282 +112,333 @@ class Bouncer (Thread):
self.socket.close()
except:
pass
+ self.socket = None
+ Thread.__init__(self)
+ self.daemon = True
- def onAddonAdd(self, IRC, label, passwd, hashtype="md5"):
- if IRC in [connection for (connection, passwd, hashtype) in self.servers.values()]:
- return # Silently do nothing
- if label in self.servers.keys():
- return
- self.servers[label] = (IRC, passwd, hashtype)
- self.whoexpected[IRC] = []
+ def onAddonAdd(self, context, label, passwd=None, hashtype="sha512", ignore=None, autoaway=None, translations=[], hidden=[]):
+ for (context2, conf2) in self.conf.items():
+ if context == context2:
+ raise ValueError, "Context already exists in config."
+ if label == conf2.label:
+ raise ValueError, "Unique label required."
+ if passwd == None:
+ while True:
+ passwd = getpass.getpass("Enter new password: ")
+ if passwd == getpass.getpass("Confirm new password: "):
+ break
+ print "Passwords do not match!"
+ passwd = hashlib.new(hashtype, passwd).hexdigest()
+ conf = irc.Config(self, label=label, passwd=passwd, hashtype=hashtype, ignore=ignore, autoaway=autoaway, translations=[
+ (key if type(key) == irc.Channel else context[key], value) for key, value in translations], hidden=irc.ChanList(hidden, context=context))
+ self.conf[context] = conf
+ self._whoexpected[context] = []
if self.debug:
- IRC.logwrite("dbg [Bouncer.onAddonAdd] Clearing WHO expected list." % vars())
- self.whoisexpected[IRC] = []
- self.listexpected[IRC] = []
-
- def onAddonRem(self, IRC):
- for bouncerconnection in self.connections:
- if bouncerconnection.IRC == IRC:
- bouncerconnection.quit(quitmsg="Bouncer extension removed")
- for (label, (connection, passwd, hashtype)) in self.servers.items():
- if connection == IRC:
- del self.servers[label]
-
- def stop(self):
+ context.logwrite(
+ "dbg [Bouncer.onAddonAdd] Clearing WHO expected list." % vars())
+ self._whoisexpected[context] = []
+ self._listexpected[context] = []
+ return conf
+
+ def onAddonRem(self, context):
+ 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, 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.stop(quitmsg=quitmsg)
-
- def onDisconnect(self, IRC, expected=False):
- self.whoexpected[IRC] = []
- if self.debug:
- IRC.logwrite("dbg [Bouncer.onDisconnect] Clearing WHO expected list.")
- self.whoisexpected[IRC] = []
- self.listexpected[IRC] = []
- for bouncerconnection in self.connections:
- if bouncerconnection.IRC == IRC:
- bouncerconnection.quit(quitmsg="IRC connection lost")
-
- def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg):
- ### Called when bot sends a PRIVMSG to channel.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
- for bouncerconnection in self.connections:
- if IRC == bouncerconnection.IRC and origin != bouncerconnection:
- bouncerconnection.send(":%s!%s@%s PRIVMSG %s%s :%s\n"%(IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg))
-
- def onSendChanAction(self, IRC, origin, channel, targetprefix, action):
- self.onSendChanMsg(IRC, origin, channel, targetprefix,
- "\x01ACTION %s\x01"%action)
-
- def onSendChanNotice(self, IRC, origin, channel, targetprefix, msg):
- ### Called when bot sends a NOTICE to channel.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
- for bouncerconnection in self.connections:
- if IRC == bouncerconnection.IRC and origin != bouncerconnection:
- bouncerconnection.send(":%s!%s@%s NOTICE %s%s :%s\n"%(IRC.identity.nick, IRC.identity.username, IRC.identity.host, targetprefix, channel.name, msg))
-
- def onSend(self, IRC, origin, line, cmd, target, params, extinfo):
+ for client in self.clients:
+ client.quit(quitmsg=quitmsg)
+
+ def onDisconnect(self, context, expected=False):
+ self._whoexpected[context] = []
+ self._whoisexpected[context] = []
+ self._listexpected[context] = []
+ 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.
+ 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):
+ 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):
+ 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 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):
+ 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 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 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.
+ 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, 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.
+ 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, targetprefix, params, extinfo):
if cmd.upper() == "WHO":
- self.whoexpected[IRC].append(origin)
+ self._whoexpected[context].append(origin)
if self.debug:
- with IRC.loglock:
- timestamp = irc.timestamp()
- if issubclass(type(origin), Thread):
- name = origin.name
- print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] Adding %(origin)s (%(name)s) to WHO expected list." % vars()
- else:
- print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] Adding %(origin)s to WHO expected list." % vars()
- for obj in self.whoexpected[IRC]:
- if issubclass(type(obj), Thread):
- name = obj.name
- print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] WHO expected: %(obj)s (%(name)s)" % vars()
- else:
- print >>IRC.log, "%(timestamp)s dbg [Bouncer.onSend] WHO expected: %(obj)s" % vars()
- IRC.log.flush()
+ if issubclass(type(origin), Thread):
+ name = origin.name
+ context.logwrite(
+ "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())
+ context.logwrite(
+ "dbg [Bouncer.onSend] WHO expected list size: %d" % len(self._whoexpected[context]))
elif cmd.upper() == "WHOIS":
- self.whoisexpected[IRC].append(origin)
+ self._whoisexpected[context].append(origin)
elif cmd.upper() == "LIST":
- self.listexpected[IRC].append(origin)
-
- def onWhoEntry(self, IRC, origin, channel, user, channame, username, host, serv, nick, flags, hops, realname):
- ### Called when a WHO list is received.
- if len(self.whoexpected[IRC]) and self.whoexpected[IRC][0] in self.connections:
- bncconnection = self.whoexpected[IRC][0]
- try:
- bncconnection.send(":%s 352 %s %s %s %s %s %s %s :%s %s\n"%(origin, IRC.identity.nick, channame, username, host, serv, nick, flags, hops, realname))
- except socket.error:
- pass
-
- def onWhoEnd(self, IRC, origin, param, endmsg):
- ### Called when a WHO list is received.
- if len(self.whoexpected[IRC]) and self.whoexpected[IRC][0] in self.connections:
- bncconnection = self.whoexpected[IRC][0]
- try:
- bncconnection.send(":%s 315 %s %s :%s\n"%(
- origin, IRC.identity.nick, param, endmsg))
- except socket.error:
- pass
- finally:
- del self.whoexpected[IRC][0]
-
- def onWho(self, IRC, params, wholist, endmsg):
- ### Called when a WHO list is received.
- if len(self.whoexpected[IRC]):
- try:
- if self.whoexpected[IRC][0] in self.connections:
- bncconnection = self.whoexpected[IRC][0]
- lines = [":%s 352 %s %s %s %s %s %s %s :%s %s\n"%(IRC.serv, IRC.identity.nick, channame, username, host, serv, nick, flags, hops, realname) for (channel, user, channame, username, host, serv, nick, flags, hops, realname) in wholist]+[":%s 315 %s %s :%s\n"%(IRC.serv, IRC.identity.nick, params, endmsg)]
- bncconnection.send("".join(lines))
- finally:
- del self.whoexpected[IRC][0]
-
- def onListStart(self, IRC, origin, params, extinfo):
- ### Called when a WHO list is received.
- if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections:
- bncconnection = self.listexpected[IRC][0]
- try:
- bncconnection.send(":%s 321 %s %s :%s\n"%(origin,
- IRC.identity.nick, params, extinfo))
- except socket.error:
- pass
-
- def onListEntry(self, IRC, origin, channel, population, extinfo):
- ### Called when a WHO list is received.
- if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections:
- bncconnection = self.listexpected[IRC][0]
- try:
- bncconnection.send(":%s 322 %s %s %d :%s\n"%(origin, IRC.identity.nick, channel.name, population, extinfo))
- except socket.error:
- pass
-
- def onListEnd(self, IRC, origin, endmsg):
- ### Called when a WHO list is received.
- if len(self.listexpected[IRC]) and self.listexpected[IRC][0] in self.connections:
- bncconnection = self.listexpected[IRC][0]
- try:
- bncconnection.send(":%s 323 %s :%s\n"%(
- origin, IRC.identity.nick, endmsg))
- except socket.error:
- pass
- finally:
- del self.listexpected[IRC][0]
-
- def onWhoisStart(self, IRC, origin, user, nickname, username, host, realname):
- ### Called when a WHOIS reply is received.
- if len(self.whoisexpected[IRC]):
- if self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 311 %s %s %s %s * :%s\n" % (origin, IRC.identity.nick, nickname, username, host, realname))
- except socket.error:
- pass
-
- def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg):
- ### Called when a WHOIS reply is received.
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 307 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
-
- def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg):
- ### Called when a WHOIS reply is received.
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 378 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
-
- def onWhoisChannels(self, IRC, origin, user, nickname, chanlist):
- ### Called when a WHOIS reply is received.
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 319 %s %s :%s\n" % (origin, IRC.identity.nick, nickname, " ".join(chanlist)))
- except socket.error:
- pass
-
- def onWhoisAvailability(self, IRC, origin, user, nickname, msg):
- ### Called when a WHOIS reply is received.
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 310 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
-
- def onWhoisServer(self, IRC, origin, user, nickname, server, servername):
- ### Called when a WHOIS reply is received.
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 312 %s %s %s :%s\n" % (origin, IRC.identity.nick, nickname, server, servername))
- except socket.error:
- pass
-
- def onWhoisOp(self, IRC, origin, user, nickname, msg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 313 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
-
- def onWhoisAway(self, IRC, origin, user, nickname, awaymsg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 301 %s %s :%s\n" % (origin,
- IRC.identity.nick, nickname, awaymsg))
- except socket.error:
- pass
-
- def onWhoisTimes(self, IRC, origin, user, nickname, idletime, signontime, msg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 317 %s %s %d %d :%s\n" % (origin, IRC.identity.nick, nickname, idletime, signontime, msg))
- except socket.error:
- pass
-
- def onWhoisSSL(self, IRC, origin, user, nickname, msg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 671 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
-
- def onWhoisModes(self, IRC, origin, user, nickname, msg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 339 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
-
- def onWhoisLoggedInAs(self, IRC, origin, user, nickname, loggedinas, msg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 330 %s %s %s :%s\n" % (origin, IRC.identity.nick, nickname, loggedinas, msg))
- except socket.error:
- pass
-
- def onWhoisEnd(self, IRC, origin, user, nickname, msg):
- if len(self.whoisexpected[IRC]) and self.whoisexpected[IRC][0] in self.connections:
- bncconnection = self.whoisexpected[IRC][0]
- try:
- bncconnection.send(":%s 318 %s %s :%s\n" % (
- origin, IRC.identity.nick, nickname, msg))
- except socket.error:
- pass
- finally:
- del self.whoisexpected[IRC][0]
-
- def onUnhandled(self, IRC, line, origin, cmd, target, params, extinfo):
- for bouncerconnection in self.connections:
- if bouncerconnection.IRC == IRC:
- bouncerconnection.send("%s\n"%line)
+ self._listexpected[context].append(origin)
+
+ def onWhoEntry(self, context, origin, channel, user, channame, username, host, serv, nick, flags, hops, realname):
+ # Called when a WHO list is received.
+ if len(self._whoexpected[context]):
+ client = self._whoexpected[context][0]
+ if client in self.clients:
+ 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.
+ 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
+ context.logwrite(
+ "dbg [Bouncer.onWhoEnd] Removing %s (%s) from WHO expected list." %
+ (self._whoexpected[context][0], name))
+ else:
+ context.logwrite(
+ "dbg [Bouncer.onWhoEnd] Removing %s from WHO expected list." % self._whoexpected[context][0])
+ del self._whoexpected[context][0]
+ if self.debug:
+ context.logwrite(
+ "dbg [Bouncer.onWhoEnd] WHO expected list size: %d" %
+ len(self._whoexpected[context]))
+
+ 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.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.
+ 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"{channel.name} {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.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]) 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.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.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.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.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.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.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.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.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.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.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.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.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):
+ self.broadcast(context, origin=user, cmd="JOIN", target=channel, clients=[
+ client for client in self.clients if channel not in client.hidden])
+
+ def onOther(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])
+
+ 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):
+
def __init__(self, bouncer, connection, addr, debug=False):
- #print "Initializing ListenThread..."
+ # print "Initializing ListenThread..."
self.bouncer = bouncer
self.connection = connection
self.host, self.port = self.addr = addr[:2]
- self.IRC = None
+ self.context = None
self.pwd = None
self.nick = None
self.label = None
@@ -360,59 +449,286 @@ class BouncerConnection (Thread):
self.lock = Lock()
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:
- self.connection.send(data)
- except socket.error:
- exc, excmsg, tb = sys.exc_info()
- print >>self.IRC.logwrite(*["!!! [BouncerConnection.send] Exception in thread %(self)s" % vars()]+["!!! [BouncerConnection.send] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")])
- self.quit(quitmsg=excmsg.message)
-
- def __repr__(self):
- server = self.IRC.server if self.IRC else "*"
- port = self.IRC.port if self.IRC else "*"
- if self.IRC and self.IRC.identity:
- nick = self.IRC.identity.nick
- ident = self.IRC.identity.username if self.IRC.identity.username else "*"
- host = self.IRC.identity.host if self.IRC.identity.host else "*"
- else:
- nick = "*"
- ident = "*"
- host = "*"
- if self.IRC.ssl and self.IRC.ipv6:
- protocol = "ircs6"
- elif self.IRC.ssl:
- protocol = "ircs"
- elif self.IRC.ipv6:
- protocol = "irc6"
+ 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)
+
+ # 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
+ target = targetprefix + target.name
+ elif type(target) == irc.User:
+ target = target.nick
+
+ 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.IRC.identity.nick if self.IRC else "*", self.host, quitmsg))
+ self.send(cmd="ERROR", extinfo="Closing link: (%s@%s) [%s]\n" % (
+ self.context.identity.nick if self.context else "*", self.host, quitmsg))
except:
pass
try:
- self.connection.shutdown(1)
+ self.connection.shutdown(socket.SHUT_WR)
self.connection.close()
except:
pass
self.quitting = True
+ def 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
+ # Name loopup should happen here instead
ipv4match = re.findall(
r"^::ffff:((\d+)\.(\d+)\.(\d+)\.(\d+))$", self.host)
if self.bouncer.ipv6 and ipv4match:
@@ -428,14 +744,7 @@ class BouncerConnection (Thread):
except:
pass
- ### 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"))
+ # Add connection to connection list.
passwd = None
nick = None
@@ -447,10 +756,18 @@ class BouncerConnection (Thread):
try:
while True:
- ### Read data (appending) into readbuf, then break lines and append lines to linebuf
+ # Read data (appending) into readbuf, then break lines and
+ # append lines to linebuf
while len(linebuf) == 0:
timestamp = irc.timestamp()
- read = self.connection.recv(512)
+ 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()
@@ -460,10 +777,18 @@ class BouncerConnection (Thread):
if lastlf >= 0:
linebuf.extend(string.split(readbuf[0:lastlf], "\n"))
- readbuf = readbuf[lastlf+1:]
+ readbuf = readbuf[lastlf + 1:]
line = string.rstrip(linebuf.pop(0))
- match = re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I)
+ try:
+ line = line.decode("utf8")
+ except UnicodeDecodeError:
+ # Attempt to figure encoding
+ charset = chardet.detect(line)['encoding']
+ line = line.decode(charset)
+ match = re.findall(
+ "^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I)
+ # print match
if len(match) == 0:
continue
@@ -474,218 +799,176 @@ class BouncerConnection (Thread):
passwd = target if target else extinfo
else:
self.quit("Access Denied")
+ print "*** [BouncerConnection] Incoming connection from %s failed: Expected PASS." % (self.host)
break
- elif not nick: # Bouncer expects a NICK command
+ elif not self.nick: # Bouncer expects a NICK command
if cmd.upper() == "NICK":
- nick = target if target else extinfo
+ self.nick = target if target else extinfo
else:
self.quit("Access Denied")
+ print "*** [BouncerConnection] Incoming connection from %s failed: Expected NICK." % (self.host)
break
elif not self.username: # Bouncer expects a USER command to finish registration
if cmd.upper() == "USER":
self.username = target
- #print self.username
- if self.username in self.bouncer.servers.keys():
- self.IRC, passwdhash, hashtype = self.bouncer.servers[self.username]
- passmatch = hashlib.new(hashtype, passwd).hexdigest() == passwdhash
- with self.IRC.lock:
- self.IRC.logwrite("*** [BouncerConnection] Incoming connection from %s to %s." % (self.host, self.IRC))
- with self.bouncer.lock:
- ### Announce connection to all other bouncer connections.
- if self.debug:
- self.IRC.logwrite("dbg [BouncerConnection] Attempting to broadcast incoming connection %(self)s." % vars())
- for bouncerconnection in self.bouncer.connections:
- if bouncerconnection.IRC != self.IRC:
- continue
- if self.debug:
- self.IRC.logwrite("dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars())
- if not bouncerconnection.quitting:
- bouncerconnection.send(":*Bouncer* NOTICE %s :Incoming connection from %s to %s\n" % (bouncerconnection.IRC.identity.nick, self.host, self.IRC))
- if self.debug:
- self.IRC.logwrite("dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars())
- if len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.IRC == self.IRC]) == 0 and self.IRC.identity.away:
- ### Bouncer connection should automatically return from away status.
- self.IRC.raw("AWAY")
- self.bouncer.connections.append(self)
-
- if not (self.IRC.connected and self.IRC.registered and type(self.IRC.supports) == dict and "CHANMODES" in self.IRC.supports.keys() and passmatch):
- self.quit("Access Denied")
- break
-
- ### If we have made it to this point, then access has been granted.
- labels = [bouncerconnection.label for bouncerconnection in self.bouncer.connections if bouncerconnection.IRC == self.IRC and bouncerconnection.label]
- n = 1
- while "*%s_%d"%(self.username, n) in labels:
- n += 1
- self.label = "*%s_%d"%(self.username, n)
-
- ### Request Version info.
- #self.connection.send(":$bouncer PRIVMSG %s :\x01VERSION\x01\n" % (self.IRC.identity.nick))
-
- ### Log incoming connection
-
- ### Send Greeting.
- with self.lock:
- self.connection.send(":%s 001 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.welcome))
- self.connection.send(":%s 002 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.hostinfo))
- self.connection.send(":%s 003 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.servcreated))
- self.connection.send(":%s 004 %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.servinfo))
-
- ### Send 005 response.
- supports = ["CHANMODES=%s"%(",".join(value)) if name == "CHANMODES" else "PREFIX=(%s)%s"%value if name == "PREFIX" else "%s=%s"%(name, value) if value else name for name, value in self.IRC.supports.items()]
- supports.sort()
- supportsreply = []
- supportsstr = " ".join(supports)
- index = 0
- while True:
- if len(supportsstr)-index > 196:
- nextindex = supportsstr.rfind(" ", index, index+196)
- supportsreply.append(supportsstr[index:nextindex])
- index = nextindex+1
- else:
- supportsreply.append(supportsstr[index:])
- break
- for support in supportsreply:
- self.connection.send(":%s 005 %s %s :are supported by this server\n" % (self.IRC.serv, self.IRC.identity.nick, support))
-
- ### Send MOTD
- if self.IRC.motdgreet and self.IRC.motd and self.IRC.motdend:
- self.connection.send(":%s 375 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdgreet))
- for motdline in self.IRC.motd:
- self.connection.send(":%s 372 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, motdline))
- try:
- self.connection.send(":%s 376 %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.motdend))
- except AttributeError:
- self.connection.send(":%s 376 %s\n" % (self.IRC.serv, self.IRC.identity.nick))
- else:
- self.connection.send(":%s 422 %s :MOTD File is missing\n" % (self.IRC.serv, self.IRC.identity.nick))
-
- ### Send user modes and snomasks.
- self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes))
- if "s" in self.IRC.identity.modes and self.IRC.identity.snomask:
- self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.server, self.IRC.identity.nick, self.IRC.identity.snomask))
-
- # ### Join user to internal bouncer channel.
- # self.connection.send(":%s!%s@%s JOIN :$bouncer\n" % (self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host))
-
- # ### Set internal bouncer topic.
- # self.connection.send(":$bouncer 332 %s $bouncer :Bouncer internal channel. Enter bouncer commands here.\n" % (self.IRC.identity.nick))
- # self.connection.send(":$bouncer 333 %s $bouncer $bouncer %s\n" % (self.IRC.identity.nick, self.bouncer.starttime))
-
- # ### Send NAMES for internal bouncer channel.
- # self.connection.send(":$bouncer 353 %s @ $bouncer :%s\n" % (
- # self.IRC.identity.nick,
- # string.join(["@*Bouncer*"]+["@%s"%bouncerconnection.label for bouncerconnection in self.bouncer.connections]))
- # )
- # self.connection.send(":$bouncer 366 %s $bouncer :End of /NAMES list.\n" % (self.IRC.identity.nick))
-
- # ### Give operator mode to user.
- # self.connection.send(":*Bouncer* MODE $bouncer +o %s\n" % (self.IRC.identity.nick))
-
- ### Join user to channels.
- for channel in self.IRC.identity.channels:
- ### JOIN command
- self.connection.send(":%s!%s@%s JOIN :%s\n" % (self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host, channel.name))
-
- ### Topic
- self.connection.send(":%s 332 %s %s :%s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topic))
- self.connection.send(":%s 333 %s %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.topicsetby, channel.topictime))
-
- ### Determine if +s or +p modes are set in channel
- secret = "s" in channel.modes.keys() and channel.modes["s"]
- private = "p" in channel.modes.keys() and channel.modes["p"]
-
- ### Construct NAMES for channel.
- namesusers = []
- modes, symbols = self.IRC.supports["PREFIX"]
- self.connection.send(":%s 353 %s %s %s :%s\n" % (
- self.IRC.serv,
- self.IRC.identity.nick,
- "@" if secret else ("*" if private else "="),
- channel.name,
- string.join([string.join([symbols[k] if modes[k] in channel.modes.keys() and user in channel.modes[modes[k]] else "" for k in xrange(len(modes))], "")+user.nick for user in channel.users]))
- )
- self.connection.send(":%s 366 %s %s :End of /NAMES list.\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name))
-
- else: # User not found
+ contextfound = False
+ for self.context, conf in self.bouncer.conf.items():
+ # print conf.label, self.username
+ if conf.label == self.username:
+ contextfound = True
+ break
+ if not contextfound:
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:
+ if not passmatch:
+ self.quit("Access Denied")
+ self.context.logwrite(
+ "*** [BouncerConnection] Incoming connection from %s to %s denied: Invalid password." % (self.host, self.context))
+ self.bouncer.broadcast(
+ self.context, origin=self.bouncer.servname, cmd="NOTICE", target=self.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 established." % (self.host, self.context))
+ with self.bouncer.lock:
+ self.translations = dict(
+ self.bouncer.conf[self.context].translations)
+ # Announce connection to all other bouncer
+ # 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.clients.append(self)
+
+ if self.context.registered:
+ # Send Greeting.
+ with self.lock:
+ self.sendgreeting()
+ self.sendsupports()
+ self.sendmotd()
+ self.sendusermodes()
+
+ # Join user to channels.
+ for channel in self.context.identity.channels:
+ if channel not in self.hidden:
+ self.showchannel(channel)
+ else:
+ self.send(
+ 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() == "PING":
- self.send(":%s PONG %s :%s\n" % (self.IRC.serv, self.IRC.serv, self.IRC.identity.nick))
-
- elif cmd.upper() in ("PRIVMSG", "NOTICE"):
- ### Check if CTCP
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",
- extinfo)
-
- if ctcp: # If CTCP, only want to
- (ctcptype, ext) = ctcp[0] # Unpack CTCP info
-
- if ctcptype == "LAGCHECK": # Client is doing a lag check. No need to send to IRC network, just reply back.
- self.send(":%s!%s@%s %s\n" % (self.IRC.identity.nick, self.IRC.identity.username, self.IRC.identity.host, line))
- else:
- self.IRC.raw(line, origin=self)
- else:
- self.IRC.raw(line, origin=self)
-
- elif cmd.upper() == "MODE": # Will want to determine is requesting modes, or attempting to modify modes.
- if target and "CHANTYPES" in self.IRC.supports.keys() and target[0] in self.IRC.supports["CHANTYPES"]:
- if params == "":
- channel = self.IRC.channel(target)
- modes = channel.modes.keys()
- modestr = "".join([mode for mode in modes if mode not in self.IRC.supports["CHANMODES"][0]+self.IRC.supports["PREFIX"][0] and channel.modes[mode]])
- params = " ".join([channel.modes[mode] for mode in modes if mode in self.IRC.supports["CHANMODES"][1]+self.IRC.supports["CHANMODES"][2] and channel.modes[mode]])
- with self.lock:
- if len(modestr):
- self.connection.send(":%s 324 %s %s +%s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, modestr, params))
- if channel.created:
- self.connection.send(":%s 329 %s %s %s\n" % (self.IRC.serv, self.IRC.identity.nick, channel.name, channel.created))
- elif re.match("^\\+?[%s]+$"%self.IRC.supports["CHANMODES"][0], params) and extinfo == "":
- #print "ddd Mode List Request", params
- channel = self.IRC.channel(target)
- redundant = []
- for mode in params.lstrip("+"):
- if mode in redundant or mode not in listnumerics.keys():
- continue
- i, e, l = listnumerics[mode]
- with self.lock:
- if mode in channel.modes.keys():
- for (mask, setby, settime) in channel.modes[mode]:
- self.connection.send(":%s %d %s %s %s %s %s\n" % (self.IRC.serv, i, channel.context.identity.nick, channel.name, mask, setby, settime))
- self.connection.send(":%s %d %s %s :End of %s\n" % (self.IRC.serv, e, channel.context.identity.nick, channel.name, l))
- redundant.append(mode)
- else:
- self.IRC.raw(line, origin=self)
- elif params == "" and target.lower() == self.IRC.identity.nick.lower():
- with self.lock:
- self.connection.send(":%s 221 %s +%s\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.modes))
- if "s" in self.IRC.identity.modes and self.IRC.identity.snomask:
- self.connection.send(":%s 008 %s +%s :Server notice mask\n" % (self.IRC.serv, self.IRC.identity.nick, self.IRC.identity.snomask))
- else:
- self.IRC.raw(line, origin=self)
else:
- self.IRC.raw(line, origin=self)
+ chantypes = self.context.supports.get(
+ "CHANTYPES", irc._defaultchantypes)
+ # Disable translating for now.
+ if False and cmd.upper() not in ("SETTRANSLATE", "RMTRANSLATE"):
+ translated = []
+ for targ in target.split(","):
+ 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:
+ 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:
exc, excmsg, tb = sys.exc_info()
self.quitmsg = str(excmsg)
- if self.IRC:
+ if self.context:
exc, excmsg, tb = sys.exc_info()
- self.IRC.logwrite(*["!!! [BouncerConnection] Exception in thread %(self)s" % vars()]+["!!! [BouncerConnection] %(tbline)s" % vars() for tbline in traceback.format_exc().split("\n")])
+ self.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()
finally:
- ### Juuuuuuust in case.
+ # Juuuuuuust in case.
with self.lock:
try:
self.connection.shutdown(1)
@@ -693,29 +976,208 @@ class BouncerConnection (Thread):
except:
pass
- if self.IRC:
- self.IRC.logwrite("*** [BouncerConnection] Connection from %s terminated (%s)." % (self.host, self.quitmsg))
-
- if self in self.bouncer.connections:
- with self.bouncer.lock:
- self.bouncer.connections.remove(self)
- if self.IRC.connected and self.IRC.identity and len([bncconnection for bncconnection in self.bouncer.connections if bncconnection.IRC == self.IRC]) == 0 and not self.IRC.identity.away and self.bouncer.autoaway:
- ### Bouncer automatically sets away status.
- self.IRC.raw("AWAY :%s"%self.bouncer.autoaway)
- if self.debug:
- self.IRC.logwrite("dbg [BouncerConnection] Attempting to broadcast terminated connection %(self)s." % vars())
- for bouncerconnection in self.bouncer.connections:
- if bouncerconnection.IRC == self.IRC:
- if self.debug:
- self.IRC.logwrite("dbg [BouncerConnection] Broadcasting to %(bouncerconnection)s." % vars())
- if not bouncerconnection.quitting:
- bouncerconnection.connection.send(":*Bouncer* NOTICE %s :Connection from %s to %s terminated (%s)\n" % (bouncerconnection.IRC.identity.nick, self.host, self.IRC, self.quitmsg))
- if self.debug:
- self.IRC.logwrite("dbg [BouncerConnection] Success: %(bouncerconnection)s." % vars())
-
-# ### Announce QUIT to other bouncer connections.
-# for bouncerconnection in self.bouncer.connections:
-# try:
-# bouncerconnection.connection.send(":%s!%s@%s QUIT :%s\n" % (self.label, self.username, self.host, self.quitmsg))
-# except:
-# pass
+ if self.context:
+ self.context.logwrite(
+ "*** [BouncerConnection] Connection from %s terminated (%s)." % (self.host, self.quitmsg))
+
+ 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)
+ 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.context.lock, 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.context.lock, 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.context.lock, 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.context.lock, 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)
diff --git a/cannon.py b/cannon.py
index fe02fdc..0911245 100644
--- a/cannon.py
+++ b/cannon.py
@@ -5,34 +5,37 @@ import os
class Cannon(object):
+
def __init__(self):
self.firecount = {}
- def onChanMsg(self, IRC, user, channel, targetprefix, msg):
+ def onChanMsg(self, context, user, channel, targetprefix, msg):
matches = re.findall("^!fire\\s+(.*)$", msg)
if matches:
nickname = matches[0]
if any([nickname.lower() == usr.nick.lower() for usr in channel.users]):
- vic = IRC.user(nickname)
+ vic = context.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)
+ def onSendChanMsg(self, context, origin, channel, targetprefix, msg):
+ self.onChanMsg(context, context.identity, channel, targetprefix, msg)
diff --git a/figlet.py b/figlet.py
index d36a2ed..1212f0d 100644
--- a/figlet.py
+++ b/figlet.py
@@ -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
diff --git a/irc.py b/irc.py
index 0cb82fb..bb2a03f 100644
--- a/irc.py
+++ b/irc.py
@@ -1,5 +1,6 @@
#!/usr/bin/python
-from threading import Thread, Event, Lock
+from threading import Thread, Condition, currentThread
+from threading import RLock as Lock
import re
import time
import sys
@@ -10,23 +11,51 @@ import platform
import traceback
import ssl
import glob
-import iqueue as Queue
+from collections import deque, OrderedDict
+import chardet
+import codecs
+import new
+import inspect
+import warnings
+import random
+
+__all__ = ["Connection", "Channel", "ChanList",
+ "User", "UserList", "Config", "timestamp"]
+
+
+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")
-def timestamp():
- t = time.time()
- ms = 1000*t%1000
- ymdhms = time.localtime(t)
- tz = time.altzone if ymdhms.tm_isdst else time.timezone
- sgn = "-" if tz >= 0 else "+"
- return "%04d-%02d-%02d %02d:%02d:%02d.%03d%s%02d:%02d"%(ymdhms[:6]+(1000*t%1000, sgn, abs(tz)/3600, abs(tz)/60%60))
+class AddonWarning(Warning):
+ pass
+
+
+class ConnectionWarning(Warning):
+ pass
+
+
+class AddonError(BaseException):
+ pass
class InvalidName(BaseException):
+
+ """Raised when an invalid string is passed of as a nickname."""
pass
class InvalidPrefix(BaseException):
+
+ """Raised when an string with an invalid prefix is passed of as a channel name."""
pass
@@ -34,1231 +63,3142 @@ class InvalidCharacter(BaseException):
pass
-class Connection(Thread):
- def __init__(self, server, nick="ircbot", username="python", realname="Python IRC Library", passwd=None, port=None, ipv6=False, ssl=False, autoreconnect=True, log=sys.stderr, timeout=300, retrysleep=5, maxretries=15, onlogin=None):
- self.__name__ = "pyIRC"
- self.__version__ = "1.1"
- self.__author__ = "Brian Sherson"
- self.__date__ = "December 1, 2013"
+class ConnectionTimedOut(BaseException):
- if port is None:
- self.port = 6667 if not ssl else 6697
- else:
+ """Raised when the connection times out during a blocked Channel.join() or Channel.part() call."""
+ pass
+
+
+class ConnectionClosed(BaseException):
+ pass
+
+
+class RequestTimedOut(BaseException):
+
+ """Raised when a timeout is reached during a blocked Channel.join() or Channel.part() call."""
+ pass
+
+
+class NotConnected(BaseException):
+
+ """Raised when attempting to send data to a server when not connected."""
+ pass
+
+
+class BannedFromChannel(BaseException):
+
+ """Raised in a blocked Channel.join() call when server returns a 474 reply (Banned from Channel)."""
+ pass
+
+
+class RedirectedJoin(BaseException):
+
+ """Raised in a blocked Channel.join() call when server returns a 470 reply (Channel redirect)."""
+ pass
+
+
+class ChannelFull(BaseException):
+ pass
+
+
+class InviteOnly(BaseException):
+ pass
+
+
+class NotOnChannel(BaseException):
+ pass
+
+
+class NoSuchChannel(BaseException):
+ pass
+
+
+class BadChannelKey(BaseException):
+ pass
+
+
+class BadChannelMask(BaseException):
+ pass
+
+
+class TooManyChannels(BaseException):
+ pass
+
+
+class Unavailable(BaseException):
+ pass
+
+
+class Cbaned(BaseException):
+ pass
+
+
+class ActionAlreadyRequested(BaseException):
+ pass
+
+
+class OpersOnly(BaseException):
+ pass
+
+
+class OperCreateOnly(BaseException):
+ pass
+
+
+class SSLOnly(BaseException):
+ pass
+
+
+class AlreadyJoined(BaseException):
+ pass
+
+
+class AlreadyConnected(BaseException):
+ pass
+
+
+class RegistrationRequired(BaseException):
+ pass
+
+
+class RejoinDelay(BaseException):
+ pass
+
+_rfc1459casemapping = string.maketrans(string.ascii_uppercase + r'\[]~',
+ string.ascii_lowercase + r'|{}^').decode("ISO-8859-2")
+
+# The IRC RFC does not permit the first character in a nickname to be a
+# numeral. However, this is not always adhered to.
+_nickmatch = r"^[A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]+$"
+
+_intmatch = r"^\d+$"
+_chanmatch = r"^[%%s][^%s\s\n]*$" % re.escape("\x07,")
+_targchanmatch = r"^([%%s]?)([%%s][^%s\s\n]*)$" % re.escape("\x07,")
+_usermatch = r"^[A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]+$"
+_realnamematch = r"^[^\n]*$"
+_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 = "&#+!"
+_capmodifiers = "~=-"
+
+_privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=(
+ "Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice"))
+_maskmodeeventnames = dict(b=("Ban", "Unban"), e=(
+ "BanExcept", "UnbanExcept"), I=("Invite", "Uninvite"))
+
+exceptcodes = {489: SSLOnly, 384: Cbaned, 403: NoSuchChannel, 405: TooManyChannels, 442: NotOnChannel, 470: RedirectedJoin, 471: ChannelFull, 473: InviteOnly, 474:
+ BannedFromChannel, 475: BadChannelKey, 476: BadChannelMask, 520: OpersOnly, 437: Unavailable, 477: RegistrationRequired, 495: RejoinDelay, 530: OperCreateOnly}
+
+
+def timestamp():
+ t = time.time()
+ ms = 1000 * t % 1000
+ ymdhms = time.localtime(t)
+ tz = time.altzone if ymdhms.tm_isdst else time.timezone
+ sgn = "-" if tz >= 0 else "+"
+ return "%04d-%02d-%02d %02d:%02d:%02d.%03d%s%02d:%02d" % (ymdhms[:6] + (1000 * t % 1000, sgn, abs(tz) / 3600, abs(tz) / 60 % 60))
+
+
+class Connection(object):
+ __doc__ = "Manages a connection to an IRC network. Includes support for addons."
+ __name__ = "pyIRC"
+ __version__ = "2.1"
+ __author__ = "Brian Sherson"
+ __date__ = "February 21, 2014"
+
+ def __init__(
+ self, server, port=None, ipvers=(socket.AF_INET6, socket.AF_INET), secure=False, passwd=None,
+ nick="ircbot", username="python", realname="Python IRC Library",
+ requestcaps=[], starttls=False, protoctl=[],
+ autoreconnect=True, retrysleep=5, maxretries=15,
+ timeout=300, quietpingpong=True, pinginterval=60, addons=[], autostart=False):
+ """__init__(server[, ...])
+
+ Constructor for the Connection class.
+
+ Arguments:
+
+ server: Server name. Can provide host name or IP address.
+ port: Port to use, or automatically selected if port=None.
+ ipvers: Tuple of IP protocols to try.
+ secure: Use SSL.
+ passwd: Password to be sent with PASS during registration process, or None.
+ nick: A nickname, or list of nicknames.
+ username: Username that is requested with USER command during registration process.
+ realname: Desired GECOS.
+ requestcaps: List of capabilities to request on connect.
+ protoctl: Protocols to request when support is detected in 005 response.
+ autoreconnect: Reconnect automatically when disconnected unexpectedly.
+ retrysleep: Number of seconds to wait between connection attempts.
+ maxretries: Number of connection attempts before giving up, or -1 to try indefinitely.
+ timeout: Read timeout.
+ quietpingpong: Suppress logging and events on PING and PONG events.
+ pinginterval: Amount of time of not receiving data from a server aftr which a ping request is to be sent.
+ addons: List of addons that should be initialized with this instance. Items of this list are either instances of addons or dict objects
+ containing keyword arguments to be used to configure addons.
+ autostart: Automatically start connection to IRC server upon initialization.
+ """
+ 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):
- self.nick = [nick]
- elif type(nick) in (list, tuple):
+ if re.match(_nickmatch, nick) if isinstance(nick, (str, unicode)) else all([re.match(_nickmatch, n) for n in nick]) if isinstance(nick, (list, tuple)) else False:
self.nick = nick
+ else:
+ raise ValueError, "Invalid value for 'nick'"
+
+ if re.match(_realnamematch, realname):
+ self.realname = realname
+ else:
+ raise InvalidCharacter
+
+ if re.match(_usermatch, username):
+ self.username = username
+ else:
+ raise InvalidCharacter
+
+ if passwd == None or "\n" not in passwd:
+ self.passwd = passwd
+ else:
+ raise InvalidCharacter
- self.realname = realname
- self.username = username
- self.passwd = passwd
self.server = server
- self.ssl = ssl
- self.ipv6 = ipv6
+ self.secure = secure
+ self.ipvers = ipvers if type(ipvers) == tuple else (ipvers,)
+
+ self.protoctl = protoctl
+
+ if type(autoreconnect) == bool:
+ self.autoreconnect = autoreconnect
+ else:
+ raise ValueError, "Invalid value for 'autoreconnect'"
+
+ if isinstance(maxretries, (int, long)):
+ self.maxretries = maxretries
+ else:
+ raise ValueError, "Invalid value for 'maxretries'"
+
+ if isinstance(timeout, (int, long)):
+ self.timeout = timeout
+ else:
+ raise ValueError, "Invalid value for 'timeout'"
+
+ if isinstance(retrysleep, (int, long, float)) and retrysleep >= 0:
+ 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(starttls) == bool:
+ if starttls and secure:
+ warnings.warn(
+ "Cannot use STARTTLS when secure=True", ConnectionWarning)
+ self.starttls = starttls
+ else:
+ raise ValueError, "Invalid value for 'starttls'"
+
+ if isinstance(requestcaps, (list, tuple)):
+ self.requestcaps = list(requestcaps)
+ else:
+ raise ValueError, "Invalid value for 'requestcaps'"
+
+ 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.connected = False
- self.registered = False
- self.connection = None
+ 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.autoreconnect = autoreconnect
- self.maxretries = maxretries
- self.timeout = timeout
- self.retrysleep = retrysleep
+ self._sendhandlerthread = None
+ self._recvhandlerthread = None
- self.quitexpected = False
- self.log = log
+ # Initialize IRC environment variables
+ self.users = UserList(context=self, withdict=True)
+ self.channels = ChanList(context=self, withdict=True)
+ # We are going to try something different, to try to make searching quicker.
+ # self.users={}
+ # self.channels={}
+
+ self.servers = ServerList(context=self)
self.addons = []
- self.trusted = []
- ### Initialize IRC environment variables
- self.motdgreet = None
- self.motd = None
- self.motdend = None
+ self._init()
+ for conf in addons:
+ try:
+ if type(conf) == dict:
+ self.addAddon(**conf)
+ else:
+ self.addAddon(conf)
+ except:
+ pass
+
+ if autostart:
+ self.connect()
+
+ def _init(self):
+ self.ipver = None
+ self.addr = None
+ self._connected = False
+ self._registered = False
+ self._connection = None
+ self._starttls = False
+ self.trynick = 0
+
self.identity = None
- self.users = []
- self.channels = []
+
+ self.serv = None
+ self.welcome = None
+ self.hostinfo = None
+ self.servcreated = None
+ self.servinfo = None
+ self.serv005 = None
self.supports = {}
+ self.throttledata = []
+ self.throttled = False
+ self.enabledcaps = []
+ self.supportedcaps = []
+ self._requestedcaps = []
+ self._caplsrequested = False
- self.lock = Lock()
- self.loglock = Lock()
- self.sendlock = Lock()
- self.outgoing = Queue.Queue()
- self.outgoingthread = None
+ @property
+ def motdgreet(self):
+ return self.identity.server.motdgreet
- Thread.__init__(self)
+ @property
+ def motd(self):
+ return self.identity.server.motd
+
+ @property
+ def motdend(self):
+ return self.identity.server.motdend
+
+ @property
+ def connected(self):
+ return self._connected
+
+ @property
+ def registered(self):
+ return self._registered
def logwrite(self, *lines):
- with self.loglock:
+ """logwrite(*lines)
+
+ Writes one or more line to the log file, signed with a timestamp."""
+ with self._loglock:
ts = timestamp()
for line in lines:
- print >>self.log, "%s %s"%(ts, line)
+ print >>self.log, u"%s %s" % (ts, line)
self.log.flush()
- def logopen(self, filename):
- with self.loglock:
+ def logerror(self, *lines):
+ """logerror(*lines)
+
+ Prints lines and traceback sys.stderr and to the log file."""
+ exc, excmsg, tb = sys.exc_info()
+ lines = lines + tuple(traceback.format_exc().split("\n"))
+
+ # Print to log AND stderr
+ self.logwrite(*[u"!!! {line}".format(**vars()) for line in lines])
+ for line in lines:
+ print >>sys.stderr, line
+
+ def logopen(self, filename, encoding="utf8"):
+ """logopen(filename[, encoding])
+
+ Sets the log file to 'filename.'"""
+
+ with self._loglock:
ts = timestamp()
- newlog = open(filename, "a")
- if type(self.log) == file and not self.log.closed:
- print >>self.log, "%s ### Log file closed" % (ts)
+ newlog = codecs.open(filename, "a", encoding=encoding)
+ 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()
self.log = newlog
print >>self.log, "%s ### Log file opened" % (ts)
self.log.flush()
- def event(self, method, modlist, exceptions=False, **params):
- timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2,
- "0") for t in time.localtime()[0:6]])
+ # 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 + [self]):
+ if addon in addons and addons.index(addon) < k:
+ # Duplicate
continue
- if method in dir(addon) and callable(getattr(addon, method)):
+
+ 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:
try:
- getattr(addon, method)(self, **params)
+ f = getattr(addon, method)
+ except AttributeError:
+ if fallback and not fellback and data:
+ try:
+ f = getattr(addon, "onOther")
+ except AttributeError:
+ unhandled.append(addon)
+ continue
+ args = dict(line=line, **data)
+ fellback = True
+ 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))
- 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()
+
+ self.logerror(u"Exception in addon {addon}".format(
+ **vars()), u"Function: %s" % f, u"Arguments: %s" % args)
+
if exceptions: # If set to true, we raise the exception.
raise
else:
handled.append(addon)
- else:
- unhandled.append(addon)
return (handled, unhandled, errors)
- def addAddon(self, addon, trusted=False, **params):
- if addon in self.addons:
- raise BaseException("Addon already added.")
+ def validateAddon(self, addon):
+ """validateAddon(addon)
+
+ Checks the addon's methods and issues warnings when a method's arguments do not line up with what is expected."""
+ 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:
+ continue
+ 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:
+ 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, **params):
+ """addAddon(addon[, ...])
+
+ Configures and appends addon to self.addons.
+ Additional keyword arguments are passed onto addon.onAddonAdd whenever the method exists."""
+ self.validateAddon(addon)
+
with self.lock:
- self.event("onAddonAdd", [addon], exceptions=True, **params)
- self.addons.append(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.")
+ addoninstances = [
+ conf.addon if type(conf) == Config else conf for conf in self.addons]
+ if addon in addoninstances:
+ raise AddonError, "Addon already added."
+ conf = self._configureAddon(addon, **params)
+ self.addons.append(conf)
+ self.logwrite("*** Addon %s added." % repr(addon))
+
+ def insertAddon(self, index, addon, **params):
+ """insertAddon(index, addon[, ...])
+
+ The 'list.insert' version of addAddon."""
+ self.validateAddon(addon)
+
with self.lock:
- self.event("onAddonAdd", [addon], exceptions=True, **params)
- self.addons.insert(index, addon)
- if trusted:
- self.trusted.append(addon)
+ addoninstances = [
+ conf.addon if type(conf) == Config else conf for conf in self.addons]
+ if addon in addoninstances:
+ raise AddonError, "Addon already added."
+ conf = self._configureAddon(addon, **params)
+ self.addons.insert(index, conf)
+ self.logwrite("*** Addon %s inserted into index %d." %
+ (repr(addon), index))
+
+ # Configures an addon by calling the addon's onAddonAdd instance (if it
+ # exists) and returns the appropriate config object (or just the addon
+ # instance if no config) to put into self.addons
+ def _configureAddon(self, addon, **params):
+ if hasattr(addon, "onAddonAdd") and callable(addon.onAddonAdd):
+ try:
+ conf = addon.onAddonAdd(self, **params)
+ except:
+ self.logerror(
+ u"An exception has occurred while trying to configure addon {addon}.".format(**vars()))
+ raise
+ if conf is None:
+ return addon
+ return conf
+ elif params:
+ return Config(addon, **params)
+ else:
+ return addon
+
+ # Removes addon from self.addons
+ def rmAddon(self, addon):
+ """rmAddon(addon)
- def rmAddon(self, addon, **params):
+ Removes addon from self.addons."""
with self.lock:
- self.addons.remove(addon)
- self.event("onAddonRem", [addon], exceptions=True, **params)
- if addon in self.trusted:
- self.trusted.remove(addon)
-
- def run(self):
- privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=("Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice"))
- maskmodeeventnames = dict(b=("Ban", "Unban"), e=(
- "BanExcept", "UnbanExcept"), I=("Invite", "Uninvite"))
- self.quitexpected = False
- whoisstarted = False
- nameslist = []
- wholist = []
- lists = {}
- nameschan = None
- server = self.server
- if self.ipv6 and ":" in server:
- server = "[%s]"%server
- port = self.port
+ addoninstances = [
+ conf.addon if type(conf) == Config else conf for conf in self.addons]
+
+ del self.addons[addoninstances.index(addon)]
+ self.logwrite("*** Addon %s removed." % repr(addon))
+ if hasattr(addon, "onAddonRem") and callable(addon.onAddonAdd):
+ try:
+ addon.onAddonRem(self)
+ except:
+ self.logerror(
+ u"An exception has occurred while trying to configure addon {addon}.".format(**vars()))
+
+ def connect(self, server=None, port=None, secure=None, ipvers=None, forcereconnect=False, blocking=False):
+ """connect([...])
+
+ Starts connection to the IRC server. Optional arguments server, port, secure, and ipvers can be
+ provided to override the current settings.
+ Use 'forcereconnect=True' to quit existing connection if already connected.
+ Use 'blocking=True' to wait until connection is established (or maxretries is exhausted)."""
+ 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, addr, ipver, secure, hostname=None):
+ """Makes a single attempt to connect to server."""
+ with self.lock:
+ if self._connected:
+ raise AlreadyConnected
+
+ 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 {addrstr}.".format(**vars()))
+ self._event(self.getalladdons(), [
+ ("onConnectAttempt", dict(), False)])
+
try:
+ 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 = connection
+ self._connection.settimeout(self.timeout)
+ 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._event(self.getalladdons(), [
+ ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)])
+ raise
+ else:
+ # Run onConnect on all addons to signal connection was established.
+ self._connected = True
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(), [("onConnect", dict(), False)])
+ self.logwrite(
+ "*** Connection to {addrstr} established.".format(**vars()))
+ self.addr = addr
+ with self._connecting:
+ self._connecting.notifyAll()
+
+ def _tryaddrs(self, server, addrs, ipver, secure):
+ """Iterates through addrs until a connection is successful, returning True, or returning False when no connections are made.
+ Raises an exception when it detects Network is unreachable (e.g., IPv6 network is not available)."""
+ 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):
+ """Attempts to resolve 'server' to a one or more IP addresses, then tries to establish a connection."""
+ 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()))
- self.logwrite("### Log session started")
+ 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)
- attempt = 1
- while True: # An entire connection lives within this while loop. When the connection fails, will try to reestablish, unless self.autoreconnect is set to False.
- while True: # Enter retry loop
- self.logwrite("*** Attempting connection to %(server)s:%(port)s." % vars())
+ return self._tryaddrs(server, addrs, ipver, secure)
- with self.lock:
- self.event("onConnectAttempt", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
+ def _tryipvers(self, server, port, ipvers, secure):
+ """Attempts to try a connection for each IP version in ipvers until a connection is successful."""
+ 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):
+ """Called whenever a line of data is received from the IRC server."""
+ matches = re.findall(_ircmatch, line)
+
+ # We have a match!
+ if len(matches):
+ (origin, username, host, cmd, target, params, extinfo) = matches[0]
+ unhandled = []
+
+ if re.match(_intmatch, cmd):
+ cmd = int(cmd) # Code is a numerical response
+ else:
+ cmd = cmd.upper()
- try:
- if self.ssl:
- s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM)
- s.settimeout(self.timeout)
- self.connection = ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE)
- else:
- self.connection = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM)
- self.connection.connect((self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port))
- except socket.error:
- exc, excmsg, tb = sys.exc_info()
- self.event("onConnectFail", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb)
- self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars())
- else:
- self.connected = True
- self.connection.settimeout(self.timeout)
+ if cmd not in ("PING", "PONG") or not self.quietpingpong:
+ self.logwrite("<<< %s" % line)
+
+ if origin == "" and cmd == "PING":
+ self.send(u"PONG :%s" % extinfo)
- ### Setting up thread responsible for sending data back to IRC server.
- self.outgoing._interrupted = False
- self.outgoingthread = Outgoing(self)
- self.outgoingthread.daemon = True
- self.outgoingthread.start()
+ with self.lock:
+ data = dict(origin=origin, cmd=cmd, target=target,
+ targetprefix=None, params=params, extinfo=extinfo)
+
+ if username and host and self._registered:
+ nickname = origin
+ origin = self.user(origin)
+ if origin.nick != nickname:
+ # Origin nickname case has changed
+ origin.user = nickname
+ if origin.username != username:
+ # Origin username has changed
+ origin.username = username
+ if origin.host != host:
+ # Origin host has changed
+ origin.host = host
+ else:
+ origin = self.getserver(origin)
+
+ # 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(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 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 in ("PRIVMSG", "NOTICE", "MODE", "INVITE", "KILL") and self._registered:
+ targetprefix = ""
+ target = self.user(target)
+
+ # Otherwise, target is just left as a string
+ else:
+ targetprefix = ""
+
+ data = dict(origin=origin, cmd=cmd, target=target,
+ targetprefix=targetprefix, params=params, extinfo=extinfo)
- ### Run onConnect on all addons to signal connection was established.
- self.event("onConnect", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
- self.logwrite("*** Connection to %(server)s:%(port)s established." % vars())
- break
+ # Parse
- if self.quitexpected:
+ # 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
+
+ # 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:
+ ret = parsemethod(
+ origin, target, targetprefix, params, extinfo)
+ addons, events = ret if ret is not None else (
+ self.addons, [])
+ except:
+ self.logerror(
+ u"There was an error in parsing the following line:", line)
+ 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:
+ events = [
+ ("on%s" % cmd.upper(), dict(line=line, origin=origin, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), True)]
+
+ # Suppress pings and pongs if self.quietpingpong is set to True
+ if cmd in ("PING", "PONG") and self.quietpingpong:
+ return
+
+ # Send parsed data to addons having onRecv method first
+ self._event(
+ addons, [("onRecv", dict(line=line, **data), False)], line, data)
+
+ # 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, events, line, data)
+
+ def _recvhandler(self, server, port, ipvers, secure):
+ """Function that is run as a separate thread, both managing the connection and handling data coming from the IRC server."""
+ 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."
+
+ try:
+ with self.lock:
+ self._event(self.getalladdons(), [
+ ("onSessionOpen", dict(), False)])
+
+ self.logwrite("### Session started")
+
+ ipvers = ipvers if type(ipvers) == tuple else (ipvers,)
+
+ # Autoreconnect loop
+ while True:
+ attempt = 1
+
+ # 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
+ if self._quitexpected:
sys.exit()
- if attempt < self.maxretries or self.maxretries == -1:
+ if self.retrysleep > 0:
time.sleep(self.retrysleep)
- if self.quitexpected:
+ if self._quitexpected:
+ sys.exit()
+ if attempt < self.maxretries or self.maxretries < 0:
+ if self._quitexpected:
sys.exit()
attempt += 1
else:
- self.logwrite("*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars())
+ self.logwrite(
+ "*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars())
+ with self._connecting:
+ self._connecting.notifyAll()
sys.exit()
- ### Connection succeeded
+ # Connection succeeded
try:
- ### Attempt initial registration.
- nick = self.nick[0]
- trynick = 0
- if self.passwd:
- self.raw("PASS :%s" % self.passwd.split(
- "\n")[0].rstrip())
- self.raw("NICK :%s" % nick.split("\n")[0].rstrip())
- self.raw("USER %s * * :%s" % (self.username.split("\n")[0].rstrip(), self.realname.split("\n")[0].rstrip()))
-
- ### Initialize buffers
+ pingreq = None
+ with self._sendline:
+ self._sendline.notify()
+
+ # Attempt initial registration.
+ # nick=self.nick[0]
+ # if self.passwd:
+ #self.send(u"PASS %s" % self.passwd)
+ # self._trynick()
+
+ # Initialize buffers
linebuf = []
readbuf = ""
while True: # Main loop of IRC connection.
while len(linebuf) == 0: # Need Moar Data
- read = self.connection.recv(512)
-
- ### If read was empty, connection is terminated.
+ read = self._connection.recv(512)
+ with self._sendline:
+ if pingreq and pingreq in self._outgoing:
+ self._outgoing.remove(pingreq)
+ pingreq = (time.time() + self.pinginterval, u"PING %s %s" % (
+ (self.identity.nick, self.identity.server) if self.identity else ("*", self.server)), self)
+ self._outgoing.append(pingreq)
+ self._sendline.notify()
+
+ # If read was empty, connection is terminated.
if read == "":
sys.exit()
- ### If read was successful, parse away!
+ # If read was successful, parse away!
readbuf += read
lastlf = readbuf.rfind("\n")
if lastlf >= 0:
- linebuf.extend(string.split(readbuf[0:lastlf],
- "\n"))
- readbuf = readbuf[lastlf+1:]
+ linebuf.extend(
+ string.split(readbuf[0:lastlf], "\n"))
+ readbuf = readbuf[lastlf + 1:]
- line = string.rstrip(linebuf.pop(0))
-
- ### If received PING, then just pong back transparently.
- ping = re.findall("^PING :?(.*)$", line)
- if len(ping):
- with self.lock:
- self.connection.send("PONG :%s\n" % ping[0])
- continue
-
- self.logwrite("<<< %(line)s" % vars())
-
- ### Attempts to match against pattern ":src cmd target params :extinfo"
- matches = re.findall(r"^:(.+?)(?:!(.+?)@(.+?))?\s+(.+?)(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$", line)
-
- ### We have a match!
- if len(matches):
- parsed = (origin, username, host, cmd, target, params, extinfo) = matches[0]
- unhandled = []
-
- if re.match("^\\d+$", cmd):
- cmd = int(cmd) # Code is a numerical response
- else:
- cmd = cmd.upper()
-
- if not self.registered:
- if type(cmd) == int and target != "*": # Registration complete!
- with self.lock:
- self.registered = True
- self.identity = self.user(target)
- self.serv = origin
- self.event("onRegistered", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
-
- elif cmd == 433 and target == "*": # Server reports nick taken, so we need to try another.
- trynick += 1
- (q, s) = divmod(trynick, len(self.nick))
- nick = self.nick[s]
- if q > 0:
- nick += str(q)
- self.raw("NICK :%s" % nick.split("\n")[0].rstrip())
- if not self.registered: # Registration is not yet complete
- continue
+ line = linebuf.pop(0).rstrip("\r")
+ line = autodecode(line)
+ self._procrecvline(line)
- if username and host:
- nickname = origin
- origin = self.user(origin)
- if origin.nick != nickname:
- ### Origin nickname has changed
- origin.user = nickname
- if origin.username != username:
- ### Origin username has changed
- origin.username = username
- if origin.host != host:
- ### Origin host has changed
- origin.host = host
-
- chanmatch = re.findall(r"([%s]?)([%s]\S*)"%(re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target)
- if chanmatch:
- targetprefix, channame = chanmatch[0]
- target = self.channel(channame)
- if target.name != channame:
- ### Target channel name has changed
- target.name = channame
- elif len(target) and target[0] != "$" and cmd != "NICK":
- targetprefix = ""
- target = self.user(target)
-
- data = dict(origin=origin, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo)
-
- ### Major codeblock here! Track IRC state.
- ### Send line to addons first
- with self.lock:
- self.event("onRecv", self.addons, line=line,
- **data)
- if cmd == 1:
- (handled, unhandled, exceptions) = self.event("onWelcome", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo)
- self.welcome = extinfo # Welcome message
- elif cmd == 2:
- (handled, unhandled, exceptions) = self.event("onYourHost", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo)
- self.hostinfo = extinfo # Your Host
- elif cmd == 3:
- (handled, unhandled, exceptions) = self.event("onServerCreated", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo)
- self.servcreated = extinfo # Server Created
- elif cmd == 4:
- (handled, unhandled, exceptions) = self.event("onServInfo", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, servinfo=params)
- self.servinfo = params # What is this code?
- elif cmd == 5: # Server Supports
- support = dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params))
- if "CHANMODES" in support:
- support["CHANMODES"] = support["CHANMODES"].split(",")
- if "PREFIX" in support:
- matches = re.findall("\\((.*)\\)(.*)", support["PREFIX"])
- if matches:
- support["PREFIX"] = matches[0]
- else:
- del support["PREFIX"] # Might as well delete the info if it doesn't match expected pattern
- (handled, unhandled, exceptions) = self.event("onSupports", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, supports=support)
- self.supports.update(support)
- if "serv005" in dir(self) and type(self.serv005) == list:
- self.serv005.append(params)
- else:
- self.serv005 = [params]
- elif cmd == 8: # Snomask
- snomask = params.lstrip("+")
- (handled, unhandled, exceptions) = self.event("onSnoMask", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, snomask=snomask)
- self.identity.snomask = snomask
- if "s" not in self.identity.modes:
- self.snomask = ""
- elif cmd == 221: # User Modes
- modes = (params if params else extinfo).lstrip("+")
- (handled, unhandled, exceptions) = self.event("onUserModes", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, snomask=modes)
- self.identity.modes = modes
- if "s" not in self.identity.modes:
- self.snomask = ""
- elif cmd == 251: # Net Stats
- (handled, unhandled, exceptions) = self.event("onNetStats", self.addons, origin=origin, netstats=extinfo)
- self.netstats = extinfo
- elif cmd == 252:
- opcount = int(params)
- (handled, unhandled, exceptions) = self.event("onOpCount", self.addons, origin=origin, opcount=opcount)
- self.opcount = opcount
- elif cmd == 254:
- chancount = int(params)
- (handled, unhandled, exceptions) = self.event("onChanCount", self.addons, origin=origin, chancount=chancount)
- self.chancount = chancount
-
- elif cmd == 305: # Returned from away status
- (handled, unhandled, exceptions) = self.event("onReturn", self.addons, origin=origin, msg=extinfo)
- self.identity.away = False
-
- elif cmd == 306: # Entered away status
- (handled, unhandled, exceptions) = self.event("onAway", self.addons, origin=origin, msg=extinfo)
- self.identity.away = True
-
- elif cmd == 311: # Start of WHOIS data
- nickname, username, host, star = params.split()
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisStart", self.addons, origin=origin, user=user, nickname=nickname, username=username, host=host, realname=extinfo)
- user.nick = nickname
- user.username = username
- user.host = host
-
- elif cmd == 301: # Away Message
- user = self.user(params)
- (handled, unhandled, exceptions) = self.event("onWhoisAway", self.addons, origin=origin, user=user, nickname=params, awaymsg=extinfo)
- user.away = True
- user.awaymsg = extinfo
-
- elif cmd == 303: # ISON Reply
- users = [self.user(user) for user in extinfo.split(" ")]
- (handled, unhandled, exceptions) = self.event("onIsonReply", self.addons, origin=origin, isonusers=users)
-
- elif cmd == 307: # Is a registered nick
- (handled, unhandled, exceptions) = self.event("onWhoisRegisteredNick", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 378: # Connecting From
- (handled, unhandled, exceptions) = self.event("onWhoisConnectingFrom", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 319: # Channels
- (handled, unhandled, exceptions) = self.event("onWhoisChannels", self.addons, origin=origin, user=self.user(params), nickname=params, chanlist=extinfo.split(" "))
- elif cmd == 310: # Availability
- (handled, unhandled, exceptions) = self.event("onWhoisAvailability", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 312: # Server
- nickname, server = params.split(" ")
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisServer", self.addons, origin=origin, user=user, nickname=nickname, server=server, servername=extinfo)
- user.server = server
- elif cmd == 313: # IRC Op
- user = self.user(params)
- (handled, unhandled, exceptions) = self.event("onWhoisOp", self.addons, origin=origin, user=user, nickname=params, msg=extinfo)
- user.ircop = True
- user.ircopmsg = extinfo
- elif cmd == 317: # Idle and Signon times
- nickname, idletime, signontime = params.split(" ")
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisTimes", self.addons, origin=origin, user=user, nickname=nickname, idletime=int(idletime), signontime=int(signontime), msg=extinfo)
- user.idlesince = int(time.time())-int(idletime)
- user.signontime = int(signontime)
- elif cmd == 671: # SSL
- user = self.user(params)
- (handled, unhandled, exceptions) = self.event("onWhoisSSL", self.addons, origin=origin, user=user, nickname=params, msg=extinfo)
- user.ssl = True
- elif cmd == 379: # User modes
- (handled, unhandled, exceptions) = self.event("onWhoisModes", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 330: # Logged in as
- nickname, loggedinas = params.split(" ")
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisLoggedInAs", self.addons, origin=origin, user=user, nickname=nickname, loggedinas=loggedinas, msg=extinfo)
- user.loggedinas = loggedinas
- elif cmd == 318: # End of WHOIS
- (handled, unhandled, exceptions) = self.event("onWhoisEnd", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
-
- elif cmd == 321: # Start LIST
- (handled, unhandled, exceptions) = self.event("onListStart", self.addons, origin=origin, params=params, extinfo=extinfo)
- elif cmd == 322: # LIST item
- (chan, pop) = params.split(" ", 1)
- (handled, unhandled, exceptions) = self.event("onListEntry", self.addons, origin=origin, channel=self.channel(chan), population=int(pop), extinfo=extinfo)
- elif cmd == 323: # End of LIST
- (handled, unhandled, exceptions) = self.event("onListEnd", self.addons, origin=origin, endmsg=extinfo)
-
- elif cmd == 324: # Channel Modes
- modeparams = params.split()
- channame = modeparams.pop(0)
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- if channel.name != channame:
- channel.name = channame # Server seems to have changed the idea of the case of the channel name
- setmodes = modeparams.pop(0)
- modedelta = []
- for mode in setmodes:
- if mode == "+":
- continue
- elif mode in self.supports["CHANMODES"][2]:
- param = modeparams.pop(0)
- modedelta.append(("+%s"%mode, param))
- elif mode in self.supports["CHANMODES"][3]:
- modedelta.append(("+%s"%mode, None))
- (handled, unhandled, exceptions) = self.event("onChannelModes", self.addons+channel.addons, channel=channel, modedelta=modedelta)
- for ((modeset, mode), param) in modedelta:
- if mode in self.supports["CHANMODES"][2]:
- channel.modes[mode] = param
- elif mode in self.supports["CHANMODES"][3]:
- channel.modes[mode] = True
-
- elif cmd == 329: # Channel created
- channame, created = params.split()
- created = int(created)
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onChanCreated", self.addons+channel.addons, channel=channel, created=created)
- channel.created = int(created)
-
- elif cmd == 332: # Channel Topic
- channame = params
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onTopic", self.addons+channel.addons, origin=origin, channel=channel, topic=extinfo)
- channel.topic = extinfo
-
- elif cmd == 333: # Channel Topic info
- (channame, nick, dt) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onTopicInfo", self.addons+channel.addons, origin=origin, channel=channel, topicsetby=nick, topictime=int(dt))
- channel.topicsetby = nick
- channel.topictime = int(dt)
-
- elif cmd == 352: # WHO reply
- (channame, username, host, serv, nick, flags) = params.split()
- try:
- (hops, realname) = extinfo.split(" ", 1)
- except ValueError:
- hops = extinfo
- realname = None
-
- if channame[0] in self.supports.get("CHANTYPES", "#"):
- channel = self.channel(channame)
- else:
- channel = None
-
- user = self.user(nick)
-
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onWhoEntry", self.addons+channel.addons, origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname)
- user.hops = hops
- user.realname = realname
- user.username = username
- user.host = host
- user.server = serv
- user.away = "G" in flags
- user.ircop = "*" in flags
- if type(channel) == Channel:
- if user not in channel.users:
- channel.users.append(user)
- if channel not in user.channels:
- user.channels.append(channel)
- for (mode, prefix) in zip(*self.supports["PREFIX"]):
- if prefix in flags:
- if mode in channel.modes.keys() and user not in channel.modes[mode]:
- channel.modes[mode].append(user)
- elif mode not in channel.modes.keys():
- channel.modes[mode] = [user]
-
- elif cmd == 315: # End of WHO reply
- (handled, unhandled, exceptions) = self.event("onWhoEnd", self.addons+channel.addons, origin=origin, param=params, endmsg=extinfo)
-
- elif cmd == 353: # NAMES reply
- (flag, channame) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
-
- if "PREFIX" in self.supports:
- names = re.findall(r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?"%re.escape(self.supports["PREFIX"][1]), extinfo)
- else:
- names = re.findall(r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo) # Still put it into tuple form for compatibility in the next structure
- (handled, unhandled, exceptions) = self.event("onNames", self.addons+channel.addons, origin=origin, channel=channel, flag=flag, channame=channame, nameslist=names)
-
- for (symbs, nick, username, host) in names:
- user = self.user(nick)
- if user.nick != nick:
- user.nick = nick
- if username and user.username != username:
- user.username = username
- if host and user.host != host:
- user.host = host
- with channel.lock:
- if channel not in user.channels:
- user.channels.append(channel)
- if user not in channel.users:
- channel.users.append(user)
- if "PREFIX" in self.supports:
- for symb in symbs:
- mode = self.supports["PREFIX"][0][self.supports["PREFIX"][1].index(symb)]
- if mode not in channel.modes:
- channel.modes[mode] = [user]
- elif user not in channel.modes[mode]:
- channel.modes[mode].append(user)
-
- elif cmd == 366: # End of NAMES reply
- channel = self.channel(params)
- (handled, unhandled, exceptions) = self.event("onNamesEnd", self.addons+channel.addons, origin=origin, channel=channel, channame=params, endmsg=extinfo)
-
- elif cmd == 372: # MOTD line
- (handled, unhandled, exceptions) = self.event("onMOTDLine", self.addons, origin=origin, motdline=extinfo)
- self.motd.append(extinfo)
- elif cmd == 375: # Begin MOTD
- (handled, unhandled, exceptions) = self.event("onMOTDStart", self.addons, origin=origin, motdgreet=extinfo)
- self.motdgreet = extinfo
- self.motd = []
- elif cmd == 376:
- (handled, unhandled, exceptions) = self.event("onMOTDEnd", self.addons, origin=origin, motdend=extinfo)
- self.motdend = extinfo # End of MOTD
-
- elif cmd == 386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal)
- (channame, owner) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- if channel.name != channame:
- channel.name = channame # Server seems to have changed the idea of the case of the channel name
- user = self.user(owner)
- if user.nick != owner:
- user.nick = owner
- if "q" in channel.modes:
- if user not in channel.modes["q"]:
- channel.modes["q"].append(user)
- else:
- channel.modes["q"] = [user]
-
- elif cmd == 388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal)
- (channame, admin) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- if channel.name != channame:
- channel.name = channame # Server seems to have changed the idea of the case of the channel name
- user = self.user(admin)
- if user.nick != admin:
- user.nick = admin
- if "a" in channel.modes:
- if user not in channel.modes["a"]:
- channel.modes["a"].append(user)
- else:
- channel.modes["a"] = [user]
-
- elif cmd == "NICK":
- newnick = extinfo if len(extinfo) else target
-
- addons = reduce(lambda x, y: x+y, [chan.addons for chan in origin.channels], [])
- self.event("onRecv", addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onNickChange", self.addons+addons, user=origin, newnick=newnick)
- if origin == self.identity:
- (handled, unhandled, exceptions) = self.event("onMeNickChange", self.addons+addons, newnick=newnick)
-
- for u in self.users:
- if u.nick.lower() == newnick.lower():
- self.users.remove(u) # Nick collision, safe to assume this orphaned user is offline, so we shall remove the old instance.
- for channel in self.channels:
- ### If for some odd reason, the old user still appears common channels, then we will remove the user anyway.
- if u in channel.users:
- channel.users.remove(u)
- origin.nick = newnick
-
- elif cmd == "JOIN":
- channel = target if type(target) == Channel else self.channel(extinfo)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onJoin", self.addons+channel.addons, user=origin, channel=channel)
-
- if origin == self.identity: # This means the bot is entering the room,
- # and will reset all the channel data, on the assumption that such data may have changed.
- # Also, the bot must request modes
- channel.topic = ""
- channel.topicmod = ""
- channel.modes = {}
- channel.users = []
- self.event("onMeJoin", self.addons+channel.addons, channel=channel)
- self.raw("MODE %s" % channel.name)
- self.raw("WHO %s" % channel.name)
- if "CHANMODES" in self.supports.keys():
- self.raw("MODE %s :%s" % (channel.name, self.supports["CHANMODES"][0]))
-
- if channel not in origin.channels:
- origin.channels.append(channel)
- if origin not in channel.users:
- channel.users.append(origin)
-
- elif cmd == "KICK":
- kicked = self.user(params)
- if kicked.nick != params:
- kicked.nick = params
-
- self.event("onRecv", target.addons, line=line, **data)
- if origin == self.identity:
- self.event("onMeKick", self.addons+target.addons, channel=target, kicked=kicked, kickmsg=extinfo)
- if kicked == self.identity:
- self.event("onMeKicked", self.addons+target.addons, kicker=origin, channel=target, kickmsg=extinfo)
- (handled, unhandled, exceptions) = self.event("onKick", self.addons+target.addons, kicker=origin, channel=target, kicked=kicked, kickmsg=extinfo)
-
- if target in kicked.channels:
- kicked.channels.remove(target)
- if kicked in target.users:
- target.users.remove(kicked)
- if "PREFIX" in self.supports:
- for mode in self.supports["PREFIX"][0]:
- if mode in target.modes and kicked in target.modes[mode]:
- target.modes[mode].remove(kicked)
-
- elif cmd == "PART":
- self.event("onRecv", target.addons, line=line, **data)
- if origin == self.identity:
- self.event("onMePart", self.addons+target.addons, channel=target, partmsg=extinfo)
- (handled, unhandled, exceptions) = self.event("onPart", self.addons+target.addons, user=origin, channel=target, partmsg=extinfo)
-
- if target in origin.channels:
- origin.channels.remove(target)
- if origin in target.users:
- target.users.remove(origin)
- if "PREFIX" in self.supports:
- for mode in self.supports["PREFIX"][0]:
- if mode in target.modes and origin in target.modes[mode]:
- target.modes[mode].remove(origin)
-
- elif cmd == "QUIT":
- channels = list(origin.channels)
- addons = reduce(lambda x, y: x+y, [chan.addons for chan in origin.channels], [])
- self.event("onRecv", addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onQuit", self.addons+addons, user=origin, quitmsg=extinfo)
- for channel in origin.channels:
- with channel.lock:
- if origin in channel.users:
- channel.users.remove(origin)
- if "PREFIX" in self.supports:
- for mode in self.supports["PREFIX"][0]:
- if mode in channel.modes and origin in channel.modes[mode]:
- channel.modes[mode].remove(origin)
- origin.channels = []
-
- elif cmd == "MODE":
- if type(target) == Channel:
- self.event("onRecv", target.addons, line=line, **data)
- modedelta = []
- modeparams = params.split()
- setmodes = modeparams.pop(0)
- modeset = "+"
- for mode in setmodes:
- if mode in "+-":
- modeset = mode
- else:
- if mode in self.supports["CHANMODES"][0]+self.supports["CHANMODES"][1]:
- param = modeparams.pop(0)
- modedelta.append(("%s%s"%(modeset, mode), param))
- if mode in maskmodeeventnames.keys():
- if modeset == "+":
- eventname = maskmodeeventnames[mode][0]
- if modeset == "-":
- eventname = maskmodeeventnames[mode][1]
- matchesbot = glob.fnmatch.fnmatch("%s!%s@%s".lower()%(self.identity.nick, self.identity.username, self.identity.host), param.lower())
- self.event("on%s"%eventname, self.addons+target.addons, user=origin, channel=target, banmask=param)
- if matchesbot:
- self.event("onMe%s"%eventname, self.addons+target.addons, user=origin, channel=target, banmask=param)
- elif mode in self.supports["CHANMODES"][2]:
- if modeset == "+":
- param = modeparams.pop(0)
- modedelta.append(("%s%s"%(modeset, mode), param))
- else:
- modedelta.append(("%s%s"%(modeset, mode), None))
- elif mode in self.supports["CHANMODES"][3]:
- modedelta.append(("%s%s"%(modeset, mode), None))
- elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]:
- modenick = modeparams.pop(0)
- modeuser = self.user(modenick)
- if mode in privmodeeventnames.keys():
- if modeset == "+":
- eventname = privmodeeventnames[mode][0]
- if modeset == "-":
- eventname = privmodeeventnames[mode][1]
- self.event("on%s"%eventname, self.addons+target.addons, user=origin, channel=target, modeuser=modeuser)
- if modeuser == self.identity:
- self.event("onMe%s"%eventname, self.addons+target.addons, user=origin, channel=target)
- modedelta.append(("%s%s"%(modeset, mode), modeuser))
- (handled, unhandled, exceptions) = self.event("onChanModeSet", self.addons+target.addons, user=origin, channel=target, modedelta=modedelta)
- with target.lock:
- for ((modeset, mode), param) in modedelta:
- if mode in self.supports["CHANMODES"][0]:
- if modeset == "+":
- if mode in target.modes:
- if param.lower() not in [mask.lower() for (mask, setby, settime) in target.modes[mode]]:
- target.modes[mode].append((param, origin, int(time.time())))
- else:
- target.modes[mode] = [(param, origin, int(time.time()))]
- else:
- if mode in target.modes.keys():
- if mode == "b": # Inspircd mode is case insentive when unsetting the mode
- masks = [mask.lower() for (mask, setby, settime) in target.modes[mode]]
- if param.lower() in masks:
- index = masks.index(param.lower())
- #print "Index: %d"%index
- del target.modes[mode][index]
- else:
- masks = [mask for (mask, setby, settime) in target.modes[mode]]
- if param in masks:
- index = masks.index(param)
- del target.modes[mode][index]
- elif mode in self.supports["CHANMODES"][1]:
- if modeset == "+":
- target.modes[mode] = param
- else:
- target.modes[mode] = None
- elif mode in self.supports["CHANMODES"][2]:
- if modeset == "+":
- target.modes[mode] = param
- else:
- target.modes[mode] = None
- elif mode in self.supports["CHANMODES"][3]:
- if modeset == "+":
- target.modes[mode] = True
- else:
- target.modes[mode] = False
- elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]:
- if modeset == "+":
- if mode in target.modes and param not in target.modes[mode]:
- target.modes[mode].append(param)
- if mode not in target.modes:
- target.modes[mode] = [param]
- elif mode in target.modes and param in target.modes[mode]:
- target.modes[mode].remove(param)
- elif type(target) == User:
- modeparams = (params if params else extinfo).split()
- setmodes = modeparams.pop(0)
- modeset = "+"
- for mode in setmodes:
- if mode in "+-":
- modeset = mode
- continue
- if modeset == "+":
- if mode not in target.modes:
- target.modes += mode
- if mode == "s" and len(modeparams):
- snomask = modeparams.pop(0)
- for snomode in snomask:
- if snomode in "+-":
- snomodeset = snomode
- continue
- if snomodeset == "+" and snomode not in target.snomask:
- target.snomask += snomode
- if snomodeset == "-" and snomode in target.snomask:
- target.snomask = target.snomask.replace(snomode, "")
- if modeset == "-":
- if mode in target.modes:
- target.modes = target.modes.replace(mode, "")
- if mode == "s":
- target.snomask = ""
-
- elif cmd == "TOPIC":
- self.event("onRecv", target.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onTopicSet", self.addons+target.addons, user=origin, channel=target, topic=extinfo)
-
- with target.lock:
- target.topic = extinfo
- target.topicsetby = origin
- target.topictime = int(time.time())
-
- elif cmd == "INVITE":
- channel = self.channel(extinfo if extinfo else params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onInvite", self.addons+channel.addons, user=origin, channel=channel)
-
- elif cmd == "PRIVMSG":
- if type(target) == Channel:
- self.event("onRecv", target.addons, line=line, **data)
-
- ### CTCP handling
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo)
- if ctcp:
- (ctcptype, ext) = ctcp[0]
- if ctcptype.upper() == "ACTION":
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanAction", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, action=ext)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivAction", self.addons, user=origin, action=ext)
- else:
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanCTCP", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivCTCP", self.addons, user=origin, ctcptype=ctcptype, params=ext)
- if ctcptype.upper() == "VERSION":
- origin.ctcpreply("VERSION", self.ctcpversion())
- if ctcptype.upper() == "TIME":
- tformat = time.ctime()
- tz = time.tzname[0]
- origin.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars())
- if ctcptype.upper() == "PING":
- origin.ctcpreply("PING", "%(ext)s" % vars())
- if ctcptype.upper() == "FINGER":
- origin.ctcpreply("FINGER", "%(ext)s" % vars())
- else:
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanMsg", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, msg=extinfo)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivMsg", self.addons, user=origin, msg=extinfo)
-
- elif cmd == "NOTICE":
- if type(target) == Channel:
- self.event("onRecv", target.addons, line=line, **data)
-
- ### CTCP handling
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo)
- if ctcp and target == self.identity:
- (ctcptype, ext) = ctcp[0]
- (handled, unhandled, exceptions) = self.event("onCTCPReply", self.addons, origin=origin, ctcptype=ctcptype, params=ext)
- else:
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanNotice", self.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivNotice", self.addons, origin=origin, msg=extinfo)
-
- elif cmd == 367: # Channel Ban list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "b" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]:
- channel.modes["b"].append((mask, setby, int(settime)))
- else:
- channel.modes["b"] = [(mask, setby, int(settime))]
- elif cmd == 368:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 346: # Channel Invite list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onInviteListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "I" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]:
- channel.modes["I"].append((mask, setby, int(settime)))
- else:
- channel.modes["I"] = [(mask, setby, int(settime))]
- elif cmd == 347:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onInviteListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 348: # Channel Ban Exception list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanExceptListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "e" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]:
- channel.modes["e"].append((mask, setby, int(settime)))
- else:
- channel.modes["e"] = [(mask, setby, int(settime))]
- elif cmd == 349:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanExceptListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 910: # Channel Access List
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onAccessListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "w" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]:
- channel.modes["w"].append((mask, setby, int(settime)))
- else:
- channel.modes["w"] = [(mask, setby, int(settime))]
- elif cmd == 911:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onAccessListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 941: # Spam Filter list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onSpamfilterListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "g" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]:
- channel.modes["g"].append((mask, setby, int(settime)))
- else:
- channel.modes["g"] = [(mask, setby, int(settime))]
- elif cmd == 940:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onSpamfilterListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 954: # Channel exemptchanops list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onExemptChanOpsListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "X" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]:
- channel.modes["X"].append((mask, setby, int(settime)))
- else:
- channel.modes["X"] = [(mask, setby, int(settime))]
- elif cmd == 953:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onExemptChanOpsListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 728: # Channel quiet list
- (channame, modechar, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onQuietListEntry", self.addons+channel.addons, origin=origin, channel=channel, modechar=modechar, mask=mask, setby=setby, settime=int(settime))
- if "q" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["q"]]:
- channel.modes["q"].append((mask, setby, int(settime)))
- else:
- channel.modes["q"] = [(mask, setby, int(settime))]
- elif cmd == 729:
- channame, modechar = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onQuietListEnd", self.addons+channel.addons, channel=channel, endmsg=extinfo)
-
- elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params
- for param in params.split():
- if len(param) and param[0] in self.supports["CHANTYPES"]:
- channel = self.channel(param)
- self.event("onRecv", channel.addons, line=line, **data)
-
- elif type(cmd) == int:
- (handled, unhandled, exceptions) = self.event("on%03d"%cmd, self.addons, line=line, origin=origin, target=target, params=params, extinfo=extinfo)
- else:
- (handled, unhandled, exceptions) = self.event("on%s"%cmd, self.addons, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo)
-
- self.event("onUnhandled", unhandled, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo)
-
- else: # Line does NOT match ":origin cmd target params :extinfo"
- self.event("onRecv", self.addons, line=line)
except SystemExit: # Connection lost normally.
pass
+
except socket.error: # Connection lost due to either ping timeout or connection reset by peer. Not a fatal error.
exc, excmsg, tb = sys.exc_info()
- self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars())
+ with self.lock:
+ self.logwrite(
+ "*** Connection to {self:uri} failed: {excmsg}.".format(**vars()))
+ 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 trying.
- self.quitexpected = True
+ # Quit with a (hopefully) useful quit message, or die
+ # trying.
+ self._quitexpected = True
try:
- self.quit("%s" % traceback.format_exc()
- .rstrip().split("\n")[-1])
+ self.quit(
+ "%s" % traceback.format_exc().rstrip().split("\n")[-1])
except:
pass
raise
- finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred.
- with self.lock:
- (handled, unhandled, exceptions) = self.event("onDisconnect", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), expected=self.quitexpected)
- self.connected = False
- self.registered = False
- self.identity = None
- ### Tell outgoing thread to quit.
- self.outgoing.interrupt()
+ finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred.
+ with self._sendline: # Notify _outgoingthread that the connection has been terminated.
+ self._outgoing.clear()
+ self._sendline.notify()
+ with self._disconnecting:
+ self._disconnecting.notifyAll()
+ self._event(self.getalladdons(), [
+ ("onDisconnect", dict(expected=self._quitexpected), False)])
- ### Wait until the outgoing thread dies.
- if self.outgoingthread and self.outgoingthread.isAlive():
- self.outgoingthread.join()
- self.outgoingthread = None
+ self._init()
try:
- self.connection.close()
+ self._connection.close()
except:
pass
self.logwrite("*** Connection Terminated.")
- if self.quitexpected or not self.autoreconnect:
+ if self._quitexpected or not self.autoreconnect:
+ self._quitexpected = False
sys.exit()
- ### If we make it to this point, then it is because connection was lost unexpectedly, and will attempt to reconnect if self.autoreconnect is True.
- time.sleep(self.retrysleep)
-
except SystemExit:
pass
except: # Print exception to log file
- self.logwrite(*["!!! FATAL Exception"]+["!!! %s"%line for line in traceback.format_exc().split("\n")])
- print >>sys.stderr, "FATAL Exception" % vars()
- print >>sys.stderr, traceback.format_exc()
+ self.logerror(u"FATAL Exception")
sys.exit()
finally:
- self.logwrite("### Log session ended")
- (handled, unhandled, exceptions) = self.event("onSessionClose", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
- Thread.__init__(self) # Makes thread restartable
+ 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()
+
+ def lower(self, s):
+ """lower(s)
+
+ Transforms a string into lowercase, using whatever casemapping the server is using, whether ascii or rfc1459."""
+ if self.supports.get("CASEMAPPING", "rfc1459") == "ascii":
+ return s.lower()
+ else:
+ return s.translate(_rfc1459casemapping)
+
+ def getalladdons(self):
+ """getalladdons() --> list
+
+ Returns list of *all* addons, including channel-specific addons."""
+ 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 parseCAP(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [
+ ("onCapLS", dict(origin=None, capabilities=None), True),
+ ("onCapAck", dict(origin=None, capabilities=None), True),
+ ])
+ if params.upper() == "LS":
+ return (self.getalladdons(), [("onCapLS", dict(capabilities=extinfo.split()), True)])
+ if params.upper() == "ACK":
+ return (self.getalladdons(), [("onCapAck", dict(capabilities=extinfo.split()), True)])
+ return ([], [])
+
+ 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(), [("onMeReturn", 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(), [("onMeAway", 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)
+ server = self.getserver(server)
+ 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 (self.addons, [("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 (self.addons, [("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 chanmodes[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)
+ serv = self.getserver(serv)
+
+ 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 parseACCOUNT(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [
+ ("onAccountLogin", dict(user=None, account=None), True),
+ ("onMeAccountLogin", dict(account=None), False),
+ ("onAccountLogout", dict(user=None), True),
+ ("onMeAccountLogout", dict(), False)
+ ])
+
+ addons = reduce(
+ lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], [])
+
+ if origin == self.identity:
+ if target == "*":
+ return (self.addons + addons, [
+ ("onAccountLogout", dict(user=origin), True),
+ ("onMeAccountLogout", dict(), False)
+ ])
+ else:
+ return (self.addons + addons, [
+ ("onAccountLogin", dict(
+ user=origin, account=target), True),
+ ("onMeAccountLogin", dict(account=target), False)
+ ])
+ else:
+ if target == "*":
+ return (self.addons + addons, [("onAccountLogout", dict(user=origin), True)])
+ else:
+ return (self.addons + addons, [("onAccountLogin", dict(user=origin, account=target), True)])
+
+ def parseAWAY(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+
+ if origin == None:
+ return (None, [("onAway", dict(user=None, awaymsg=None), True), ("onReturn", dict(user=None), True)])
+
+ addons = reduce(
+ lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], [])
+
+ if extinfo:
+ return (self.addons + addons, [("onAway", dict(user=origin, awaymsg=extinfo), True)])
+ else:
+ return (self.addons + addons, [("onReturn", dict(user=origin), 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, loggedinas=None, realname=None), False),
+ ("onJoin", dict(user=None, channel=None, loggedinas=None, realname=None), True)
+ ])
+
+ if type(target) == Channel:
+ channel = target
+ else:
+ channel = self.channel(extinfo)
+ channel.name = extinfo
+
+ if "extended-join" in self.enabledcaps:
+ loggedinas = params if params != "*" else None
+ realname = extinfo
+ else:
+ loggedinas = realname = None
+
+ if origin == self.identity:
+ return (self.addons + channel.addons, [
+ ("onMeJoin", dict(channel=channel, loggedinas=loggedinas, realname=realname), False),
+ ("onJoin", dict(user=origin, channel=channel,
+ loggedinas=loggedinas, realname=realname), True),
+ ])
+
+ return (self.addons + channel.addons, [("onJoin", dict(user=origin, channel=channel, loggedinas=loggedinas, realname=realname), 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, identified=None), True),
+ ("onChanMsg", dict(user=None, channel=None,
+ targetprefix=None, msg=None, identified=None), True),
+ ("onCTCP", dict(user=None, ctcptype=None, params=None, identified=None), True),
+ ("onChanCTCP", dict(user=None, channel=None, targetprefix=None,
+ ctcptype=None, params=None, identified=None), True),
+ ("onPrivAction", dict(
+ user=None, action=None, identified=None), True),
+ ("onChanAction", dict(user=None, channel=None,
+ targetprefix=None, action=None, identified=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),
+ ])
+ if "identify-msg" in self.enabledcaps and extinfo[0] in "+-":
+ identified, extinfo = extinfo.startswith("+"), extinfo[1:]
+ else:
+ identified = None
+ 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, identified=identified), True)])
+ return (self.addons, [("onCTCP", dict(user=origin, ctcptype=ctcptype, params=ext, identified=identified), True)])
+ if type(target) == Channel:
+ if ctcptype.upper() == "ACTION":
+ return (self.addons, [("onChanAction", dict(user=origin, channel=target, targetprefix=targetprefix, action=ext, identified=identified), True)])
+ return (self.addons, [("onChanCTCP", dict(user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext, identified=identified), True)])
+ else:
+ if type(target) == Channel:
+ return (self.addons + target.addons, [("onChanMsg", dict(user=origin, channel=target, targetprefix=targetprefix, msg=extinfo, identified=identified), True)])
+ elif target == self.identity:
+ return (self.addons, [("onPrivMsg", dict(user=origin, msg=extinfo, identified=identified), 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, identified=None), True),
+ ("onServNotice", dict(
+ origin=None, msg=None, identified=None), True),
+ ("onChanNotice", dict(origin=None, channel=None,
+ targetprefix=None, msg=None, identified=None), True),
+ ("onCTCPReply", dict(
+ origin=None, ctcptype=None, params=None, identified=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),
+ ])
+ if "identify-msg" in self.enabledcaps and extinfo[0] in "+-":
+ identified, extinfo = extinfo.startswith("+"), extinfo[1:]
+ else:
+ identified = None
+ ctcp = re.findall(_ctcpmatch, extinfo)
+ if ctcp and target == self.identity:
+ (ctcptype, ext) = ctcp[0]
+ return (self.addons, [("onCTCPReply", dict(origin=origin, ctcptype=ctcptype, params=ext, identified=identified), True)])
+ else:
+ if type(origin) == Server:
+ return (self.addons, [("onServNotice", dict(origin=origin, msg=extinfo, identified=identified), True)])
+ if type(target) == Channel:
+ return (self.addons + target.addons, [("onChanNotice", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo, identified=identified), True)])
+ elif target == self.identity:
+ return (self.addons, [("onPrivNotice", dict(origin=origin, msg=extinfo, identified=identified), 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):
+ """eventsupports() --> {eventname: arguments, ...}
+
+ Generates and returns a dict of supported events and associated arguments. Good for attempting to validate addon events."""
+ 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
+
+ def _register(self):
+ if self.passwd:
+ self.send(u"PASS %s" % self.passwd)
+ self._trynick()
+ self.send(
+ u"USER {self.username} * * :{self.realname}".format(**vars()))
+
+ def requestcapls(self, origin=None):
+ """requestcapls(...)
+
+ Sends "CAP LS" to the server to request supported capabilities. Please use this method instead of send()."""
+ if not self._caplsrequested:
+ self.send("CAP LS", origin=origin)
+ self._caplsrequested = True
+
+ # Here are the builtin event handlers.
+
+ def onRecv(self, context, line, origin, cmd, target, targetprefix, params, extinfo):
+ if not self._registered:
+ if type(cmd) == int and cmd < 100 and target != "*": # Registration complete!
+ self.identity = self.user(target, init=True)
+ self.identity.server = origin
+ self._event(self.getalladdons(), [
+ ("onRegistered", dict(), False)])
+
+ def onRegistered(self, context):
+ self._registered = True
+
+ def onConnect(self, context):
+ if self.requestcaps:
+ self.requestcapls()
+ elif self.starttls:
+ self.send("STARTLS")
+ elif len(self._requestedcaps) == 0 and not self._caplsrequested:
+ self._register()
+
+ def onCapLS(self, context, capabilities):
+ self.supportedcaps = capabilities
+ self._caplsrequested = False
+ if self.starttls and "tls" in capabilities and not self.secure and not self._starttls:
+ self.send("STARTTLS")
+ elif not self.registered:
+ requestcaps = [
+ cap for cap in self.requestcaps if cap in capabilities]
+ if requestcaps:
+ self.sendcapsrequest(requestcaps)
+ elif len(self._requestedcaps) == 0:
+ self.send("CAP END")
+ self._register()
+
+ def onCapAck(self, context, capabilities):
+ for cap in capabilities:
+ mods, capname = re.findall(
+ "^([%s]*)(.+)$" % re.escape(_capmodifiers), cap)[0]
+ if "-" in mods and capname in self.enabledcaps:
+ self.enabledcaps.remove(capname)
+ elif cap not in self.enabledcaps:
+ self.enabledcaps.append(capname)
+ if cap in self._requestedcaps:
+ self._requestedcaps.remove(cap)
+ if not self.registered and len(self._requestedcaps) == 0:
+ self.send("CAP END")
+ self._register()
+
+ def onCapNak(self, context, capabilities):
+ for cap in capabilities:
+ mods, capname = re.findall(
+ "^([%s]*)(.+)$" % re.escape(_capmodifiers), cap)[0]
+ if cap in self._requestedcaps:
+ self._requestedcaps.remove(cap)
+ if not self.registered and len(self._requestedcaps) == 0:
+ self.send("CAP END")
+ self._register()
+
+ def on433(self, context, line, origin, target, params, extinfo):
+ if not self._registered: # Server reports nick taken, so we need to try another.
+ self._trynick()
+
+ def on670(self, context, line, origin, target, params, extinfo):
+ self.logwrite("*** Attempting StartTLS")
+ self._connection = ssl.wrap_socket(
+ self._connection, cert_reqs=ssl.CERT_NONE) # Server says go ahead with starttls.
+ self._event(self.getalladdons(), [("onStartTLS", dict(), False)])
+
+ def on691(self, context, line, origin, target, params, extinfo): # STARTTLS Failure
+ self.logwrite("*** StartTLS Failed")
+ if self.requestcaps:
+ self.send("CAP END")
+ self._register()
+
+ def onStartTLS(self, context):
+ self._starttls = True
+ self.onConnect(self)
+
+ 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, context, user): # Returned from away status
+ user.away = False
+ user.awaymsg = None
+
+ def onAway(self, context, user, awaymsg): # Entered away status
+ user.away = True
+ user.awaymsg = awaymsg
+
+ def onMeReturn(self, context, origin): # Returned from away status
+ self.identity.away = False
+ self.identity.awaymsg = None
+
+ def onMeAway(self, context, origin, msg): # Entered away status
+ self.identity.away = True
+
+ 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
+ origin.motd.append(motdline)
+
+ def onMOTDStart(self, context, origin, motdgreet): # Begin MOTD
+ origin.motdgreet = motdgreet
+ origin.motd = []
+
+ def onMOTDEnd(self, context, origin, motdend):
+ origin.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 onNickChange(self, context, user, newnick):
+ if self.lower(user.nick) == self.lower(newnick):
+ user.nick = newnick
+ else:
+ try:
+ other = self.users[newnick]
+ except KeyError:
+ pass
+ else:
+ 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)
+ self.users.remove(other)
+ self.users.remove(user)
+ user.nick = newnick
+ self.users.append(user)
+
+ def onAccountLogin(self, context, user, account):
+ user.loggedinas = account
+
+ def onAccountLogout(self, context, user):
+ user.loggedinas = None
+
+ def onJoin(self, context, user, channel, loggedinas, realname):
+ if "extended-join" in self.enabledcaps:
+ user.loggedinas = loggedinas
+ user.realname = realname
+ 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):
+ """send(line[, ...])
+
+ Sends 'line' to IRC server. Try to use this method sparingly by using other methods designed to format requests correctly.
+ Supported optional arguments:
+
+ 'origin': Used (voluntarily) by addons to identify origin of sent data. Good for helping addons ignore lines they send
+ so as to avoid infinite loops.
+
+ 'T': Specifies what time to send the data if not immediately. This method currently throttles PRIVMSGs to avoid floods."""
+ 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:
+ T = time.time()
+ if cmd == "PRIVMSG":
+ # Hard-coding a throttling mechanism for PRIVMSGs only here. Will later build support for custom throttlers.
+ # The throttle will be triggered when it attempts to send a sixth PRIVMSG in a four-second interval.
+ # When the throttle is active, PRIVMSGs will be sent in at least one-second intervals.
+ # The throttle is deactivated when three seconds elapse without
+ # sending a PRIVMSG.
+ while len(self.throttledata) and self.throttledata[0] < T - 4:
+ del self.throttledata[0]
+ if not self.throttled:
+ if len(self.throttledata) >= 5:
+ self.throttled = True
+ T = self.throttledata[-1] + 1
+ else:
+ if len(self.throttledata) == 0 or self.throttledata[-1] < T - 2:
+ self.throttled = False
+ else:
+ T = max(T, self.throttledata[-1] + 1)
+ self.throttledata.append(T)
+ elif cmd in ("WHO", "MODE"):
+ # Might as well throttle WHOs and MODEs too.
+ while len(self.throttledata) and self.throttledata[0] < T - 4:
+ del self.throttledata[0]
+ if not self.throttled:
+ if len(self.throttledata) >= 5:
+ self.throttled = True
+ T = self.throttledata[-1] + 0.125
+ else:
+ if len(self.throttledata) == 0 or self.throttledata[-1] < T - 2:
+ self.throttled = False
+ else:
+ T = max(T, self.throttledata[-1] + 0.125)
+ self.throttledata.append(T)
+ with self._sendline:
+ self._outgoing.append((T, line, origin))
+ self._sendline.notify()
+
+ def _cancelsend(self, line, origin=None, T=None):
+ with self._sendline:
+ self._outgoing.remove((T, line, origin))
+ self._sendline.notify()
+
+ def _procsendline(self, line, origin=None):
+ """Function responsible for sending data to the IRC server and calling all applicable event methods."""
+ match = re.findall(_ircmatch, line)
+ if len(match) == 0:
+ return
+ (null, username, host, cmd, target, params, extinfo) = match[0]
+ cmd = cmd.upper()
+ with self.lock:
+ if cmd == "QUIT":
+ self._quitexpected = True
+ if self._connection == None:
+ return
+ origline = line
+
+ # Modify line if it contains a password so that the password is not
+ # logged or sent to any potentially untrustworthy addons
+ if cmd == "PRIVMSG":
+ if target.upper() == "NICKSERV":
+ nscmd = re.findall(
+ r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
+ if nscmd:
+ nscmd = nscmd[0]
+ if nscmd[0].upper() in ("IDENTIFY", "REGISTER"):
+ extinfo = "%s ********" % nscmd[0]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
+ extinfo = "%s %s ********" % nscmd[:2]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif nscmd[0].upper() == "SET":
+ if nscmd[1].upper() == "PASSWORD":
+ extinfo = "%s %s ********" % nscmd[:2]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
+ extinfo = "********"
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ if target.upper() == "CHANSERV":
+ cscmd = re.findall(
+ r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
+ if cscmd:
+ cscmd = cscmd[0]
+ if cscmd[0].upper() in ("IDENTIFY", "REGISTER"):
+ extinfo = "%s %s ********" % cscmd[:2]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
+ extinfo = "%s %s %s ********" % cscmd[:3]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cscmd[0].upper() == "SET":
+ if cscmd[2].upper() == "PASSWORD":
+ extinfo = "%s %s %s ********" % cscmd[:3]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
+ extinfo = "********"
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cmd.upper() in ("NS", "NICKSERV"):
+ if target.upper() in ("IDENTIFY", "REGISTER"):
+ params = params.split(" ")
+ while "" in params:
+ params.remove("")
+ if len(params):
+ params[0] = "********"
+ params = " ".join(params)
+ line = "%s %s %s" % (cmd, target, params)
+ elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
+ params = params.split(" ")
+ while "" in params:
+ params.remove("")
+ if len(params) > 1:
+ params[1] = "********"
+ params = " ".join(params)
+ line = "%s %s %s" % (cmd, target, params)
+ elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
+ params = ""
+ target = "********"
+ line = "%s %s" % (cmd, target)
+ elif cmd.upper() == "OPER":
+ params = "********"
+ line = "%s %s %s" % (cmd, target, params)
+ elif cmd.upper() == "PASS":
+ extinfo = "********"
+ target = ""
+ line = "%s :%s" % (cmd, extinfo)
+ elif cmd.upper() == "IDENTIFY":
+ target = "********"
+ line = "%s %s" % (cmd, target)
+
+ 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 in ("PRIVMSG", "NOTICE", "MODE", "INVITE", "CHGHOST", "CHGIDENT", "CHGNAME", "WHOIS", "KILL", "SAMODE", "SETHOST", "WHO"):
+ 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:
+ ret = parsemethod(
+ origin, target, targetprefix, params, extinfo, outgoing=True)
+ addons, events = ret if ret is not None else (
+ self.events, [])
+ except:
+ self.logerror(
+ u"There was an error in parsing the following line:", line)
+ 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, [("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, events, line)
+
+ if not (cmd in ("PING", "PONG") and self.quietpingpong):
+ #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'))
+
+ def _sendhandler(self):
+ # Enforce that this function must only be run from within
+ # self._sendhandlerthread.
+ if currentThread() != self._sendhandlerthread:
+ raise RuntimeError, "This function is designed to run in its own thread."
+
+ try:
+ while True:
+ with self._sendline:
+ if "quit" in self._outgoing:
+ sys.exit()
+ S = time.time()
+ if len(self._outgoing):
+ T, line, origin = min(self._outgoing)
+ if T > S:
+ # The next item in the queue (by time) is still
+ # scheduled to be sent later. We wait until then,
+ # or when another item is put into the queue,
+ # whichever is first.
+ self._sendline.wait(T - S)
+ continue
+ else:
+ # The next item in the queue (by time) should be
+ # sent now.
+ self._outgoing.remove((T, line, origin))
+ else:
+ # The queue is empty, so we will wait until something
+ # is put into the queue, then restart the while loop.
+ self._sendline.wait()
+ continue
+
+ try:
+ self._procsendline(line, origin=origin)
+ except socket.error:
+ exc, excmsg, tb = sys.exc_info()
+ with self.lock:
+ self.logwrite(
+ 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:
+ self._connection.close()
+ except:
+ pass
+
+ except SystemExit:
+ pass
+
+ except:
+ self._quitexpected = True
+ self.logerror("FATAL Exception in {self}".format(**vars()))
+ with self._sendline:
+ try:
+ self._connection.send(
+ "QUIT :%s\n" % tb.rstrip().split("\n")[-1])
+ self._connection.shutdown(socket.SHUT_WR)
+ except:
+ pass
+ finally:
+ with self._sendline:
+ self._outgoing.clear() # Clear out _outgoing.
+
+ def isAlive(self):
+ """For compatibility, when modules still expect irc.Connection to be a subclass of threading.Thread."""
+ return type(self._recvhandlerthread) == Thread and self._recvhandlerthread.isAlive() and type(self._sendhandlerthread) == Thread and self._sendhandlerthread.isAlive()
+
+ def start(self):
+ """For compatibility, when modules still expect irc.Connection to be a subclass of threading.Thread."""
+ return self.connect()
def __repr__(self):
server = self.server
- if self.ipv6 and ":" in server:
- server = "[%s]"%server
- port = self.port
+ if self.ipver == socket.AF_INET6 and ":" in server:
+ server = "[%s]" % server
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 "*"
+ return "<IRC Context: {self.identity:full} on {self:uri}>".format(**locals())
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: *!*@* 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())
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 repr(self)
def oper(self, name, passwd, origin=None):
- self.raw("OPER %s %s" % (re.findall("^([^\r\n\\s]*)", name)[0],
- re.findall("^([^\r\n\\s]*)", passwd)[0]), origin=origin)
+ """oper(name, passwd[, origin])
+
+ Sends an OPER request to the server. Warning: Invalid oper credentials may be reported to IRC network admins!"""
+ 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.raw("LIST %s" % (re.findall(
- "^([^\r\n\\s]*)", params)[0]), origin=origin)
+ """list(...)
+
+ Sends a LIST request to the server.
+ TODO: Implement optional blocking."""
+ if re.match(".*[\n\r\\s]", params):
+ raise InvalidCharacter
+ if params:
+ self.send(u"LIST {params}".format(**vars()), origin=origin)
else:
- self.raw("LIST", origin=origin)
+ self.send(u"LIST", origin=origin)
- def getmotd(self, target="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", target)[0]):
- self.raw("MOTD %s" % (re.findall(
- "^([^\r\n\\s]*)", target)[0]), origin=origin)
+ def getmotd(self, server=None, origin=None):
+ """getmotd(...)
+
+ Sends an MOTD request to the server, optionally specifying server.
+ TODO: Implement optional blocking."""
+ if server:
+ self.send(u"MOTD %s" % server.name, origin=origin)
else:
- self.raw("MOTD", origin=origin)
+ self.send(u"MOTD", origin=origin)
+
+ def version(self, server=None, origin=None):
+ """version(...)
- def version(self, target="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", target)[0]):
- self.raw("VERSION %s" % (re.findall(
- "^([^\r\n\\s]*)", target)[0]), origin=origin)
+ Sends an VERSION request to the server, optionally specifying server.
+ This is NOT the same as requesting CTCP version from another user.
+ TODO: Implement optional blocking."""
+ if server:
+ self.send(u"VERSION %s" % server.name, origin=origin)
else:
- self.raw("VERSION", origin=origin)
+ self.send(u"VERSION", origin=origin)
- def stats(self, query, target="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", target)[0]):
- self.raw("STATS %s %s" % (query, re.findall(
- "^([^\r\n\\s]*)", target)[0]), origin=origin)
+ def stats(self, query, server=None, origin=None):
+ """stats(query[,...])
+
+ Sends an STATS request to the server, optionally specifying server.
+ STATS requests may be logged by IRC network admins. Use responsibly!
+ TODO: Implement optional blocking."""
+ if server:
+ self.send(u"STATS %s %s" % (query, server.name), origin=origin)
else:
- self.raw("STATS %s"%query, origin=origin)
+ self.send(u"STATS %s" % query, origin=origin)
+
+ def sendcapsrequest(self, capabilities, origin=None):
+ """sendcapsrequest(capabilities)
- def quit(self, msg="", origin=None):
+ Request capabilities with "CAP REQ". Please use this method instead of using send(...)."""
with self.lock:
- if self.connected:
- if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.raw("QUIT :%s" % re.findall("^([^\r\n]*)",
- msg)[0], origin=origin)
- else:
- self.raw("QUIT", origin=origin)
+ for cap in capabilities:
+ if cap not in self._requestedcaps:
+ self._requestedcaps.append(cap)
+ self.send("CAP REQ {cap}".format(**vars()), origin=origin)
+
+ def quit(self, msg="", origin=None, blocking=False):
+ """quit(...)
+
+ Quit IRC session gracefully by first sending a QUIT request to the server.
+
+ Optional arguments:
+ 'msg': Quit message
+ 'origin': See help on method 'send'
+ 'blocking': Wait until connection is terminated."""
+ 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()
+
+ def disconnect(self):
+ """disconnect()
+
+ Force disconnect -- Goes right for the jugular, not even sending QUIT to server."""
+ with self.lock:
+ self._quitexpected = True
+ self._connection.shutdown(2)
def ctcpversion(self):
+ """ctcpversion() --> string
+
+ Formats a CTCP version reply from this instance and all attached addons."""
+
reply = []
- ### Prepare reply for addon
- reply.append("%(__name__)s %(__version__)s, %(__author__)s" %
- vars(self))
+ # Prepare reply for this module
+ reply.append(
+ u"{self.__name__} {self.__version__}, {self.__author__}".format(**vars()))
- ### Prepare reply for Python and OS versions
+ # Prepare reply for Python and OS versions
pyver = sys.version.split("\n")
- pyver[0] = "Python "+pyver[0]
+ pyver[0] = "Python " + pyver[0]
reply.extend(pyver)
reply.extend(platform.platform().split("\n"))
- ### Prepare reply for extension addons
+
+ # Prepare reply for 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):
- if "\r" in line or "\n" in line:
- raise InvalidCharacter
- self.outgoing.put((line, origin))
+ """raw(line[, origin])
+
+ Deprecated. Use send() instead."""
+ self.send(line, origin=origin)
+
+ def user(self, nick, init=False):
+ """user(nick)
+
+ Return a User object associated with a nickname.
+ Specify init=True to reset all that is known about user."""
+ with self.lock:
+ try:
+ return self.users[nick]
+ except KeyError:
+ user = User(nick, self)
+ self.users.append(user)
+ return user
- def user(self, nick):
- users = [user for user in self.users if user.nick.lower(
- ) == nick.lower()]
- if len(users):
- return users[0]
+ def channel(self, name, init=False):
+ """channel(name)
+
+ Return a Channel object associated with a channel name.
+ Specify init=True to reset all that is known about the channel."""
+ with self.lock:
+ try:
+ return self.channels[name]
+ except KeyError:
+ channel = Channel(name, self)
+ self.channels.append(channel)
+ return channel
+
+ def getserver(self, name, init=False):
+ """server(name)
+
+ Return a Server object associated with a server name.
+ Specify init=True to reset all that is known about the server."""
+ with self.lock:
+ if type(name) == str:
+ name = autodecode(name)
+ servers = [server for server in self.servers if self.lower(
+ server.name) == self.lower(name)]
+
+ if len(servers):
+ if init:
+ servers[0]._init()
+ return servers[0]
+ else:
+ server = Server(name, self)
+ self.servers.append(server)
+ return server
+
+ def __getitem__(self, item):
+ chantypes = self.supports.get("CHANTYPES", _defaultchantypes)
+ if "\r" in item or "\n" in item or " " in item:
+ raise InvalidCharacter
+ if re.match(_chanmatch % re.escape(chantypes), item):
+ return self.channel(item)
+ elif re.match(_usermatch, item):
+ return self.user(item)
else:
- user = User(nick, self)
- self.users.append(user)
- timestamp = reduce(lambda x, y: x+":"+y, [str(
- t).rjust(2, "0") for t in time.localtime()[0:6]])
- return user
-
- def channel(self, name):
- channels = [chan for chan in self.channels if name.lower(
- ) == chan.name.lower()]
- if len(channels):
- return channels[0]
+ return self.getserver(item)
+
+ def fmtsupports(self):
+ """fmtsupports() --> list
+
+ Formats a valid 005 response from known information."""
+ 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):
+ """fmtgreeting() --> list
+
+ Formats a valid greeting from known information (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):
+ """fmtusermodes() --> list
+
+ Formats a valid user modes reply from known information (Response 221)."""
+ return u":{self.serv} 221 {self.identity.nick} +{self.identity.modes}".format(**vars())
+
+ def fmtsnomasks(self):
+ """fmtsnomasks() --> list
+
+ Formats a valid snomasks reply from known information (Response 008)."""
+ return u":{self.serv} 008 {self.identity.nick} +{self.identity.snomask} :Server notice mask".format(**vars())
+
+ def fmtmotd(self):
+ """fmtmotd() --> list
+
+ Formats a valid MOTD reply from known information (Response 375, 372, and 376; Response 422 if no MOTD)."""
+ 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:
- timestamp = reduce(lambda x, y: x+":"+y, [str(
- t).rjust(2, "0") for t in time.localtime()[0:6]])
- chan = Channel(name, self)
- self.channels.append(chan)
- return chan
+ return [u":{self.serv} 422 {self.identity.nick} :MOTD File is missing".format(**vars())]
class Channel(object):
- def __init__(self, name, context):
- if not re.match(r"^[%s]\S*$" % context.supports.get("CHANTYPES", "#"), name):
- raise InvalidName(repr(name))
+
+ def __init__(self, name, context, key=None):
+ 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._dict.values():
+ 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 = []
+ self.users = UserList(context=self.context)
self.created = None
- self.lock = Lock()
def msg(self, msg, target="", origin=None):
if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]:
raise InvalidPrefix
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("PRIVMSG %s%s :%s" % (target,
- self.name, line), origin=origin)
+ self.context.send(u"PRIVMSG %s%s :%s" %
+ (target, self.name, line), origin=origin)
- def who(self, origin=None):
- self.context.raw("WHO %s" % (self.name), origin=origin)
+ 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.raw("NAMES %s" % (self.name), origin=origin)
+ 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", "@%+")))], self.context.lower(user.nick)))
+ elif sort == "nick":
+ users.sort(key=lambda user: self.context.lower(user.nick))
+ 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
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("NOTICE %s%s :%s" % (target,
- self.name, line), origin=origin)
+ self.context.send(u"NOTICE %s%s :%s" %
+ (target, self.name, line), origin=origin)
def settopic(self, msg, origin=None):
- self.context.raw("TOPIC %s :%s" % (self.name, re.findall(
- "^([^\r\n]*)", msg)[0]), origin=origin)
+ self.context.send(u"TOPIC %s :%s" %
+ (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
def ctcp(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.msg("\01%s %s\01" % (act.upper(), re.findall(
- "^([^\r\n]*)", msg)[0]), origin=origin)
+ self.msg("\01%s %s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
self.msg("\01%s\01" % act.upper())
def ctcpreply(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.notice("\01%s %(msg)s\01" % (act.upper(),
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ self.notice("\01%s %(msg)s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
self.notice("\01%s\01" % act.upper(), origin=origin)
def me(self, msg="", origin=None):
self.ctcp("ACTION", msg, origin=origin)
- def part(self, msg="", origin=None):
- if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.context.raw("PART %s :%s" % (self.name,
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
- else:
- self.context.raw("PART %s" % self.name, origin=origin)
+ def part(self, msg="", blocking=False, timeout=30, origin=None):
+ with self.context.lock:
+ if self.context.identity not in self.users:
+ # Bot is not on the channel
+ raise NotOnChannel
+ with self._parting:
+ try:
+ if self._partrequested:
+ raise ActionAlreadyRequested
+ self._partrequested = True
+ if len(re.findall("^([^\r\n]*)", msg)[0]):
+ self.context.send(
+ u"PART %s :%s" % (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ else:
+ self.context.send(u"PART %s" % self.name, origin=origin)
+
+ # Anticipated Numeric Replies:
+
+ # ERR_NEEDMOREPARAMS ERR_NOSUCHCHANNEL
+ # ERR_NOTONCHANNEL
+
+ if blocking:
+ endtime = time.time() + timeout
+ while True:
+ self._parting.wait(max(0, endtime - time.time()))
+ t = time.time()
+ if not self.context.connected:
+ raise NotConnected
+ elif self._partreply in ("PART", "KICK"):
+ return
+ elif type(self._partreply) == tuple and len(self._partreply) == 2:
+ cmd, extinfo = self._partreply
+ raise exceptcodes[cmd], extinfo
+ if t > endtime:
+ raise RequestTimedOut
+ finally:
+ self._partrequested = False
+ self._partreply = None
def invite(self, user, origin=None):
nickname = user.nick if type(
user) == User else re.findall("^([^\r\n\\s]*)", user)[0]
if nickname == "":
raise InvalidName
- self.context.raw("INVITE %s %s" % (nickname, self.name), origin=origin)
-
- def join(self, key="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", key)[0]):
- self.context.raw("JOIN %s %s" % (self.name, re.findall(
- "^([^\r\n\\s]*)", key)[0]), origin=origin)
- else:
- self.context.raw("JOIN %s" % self.name, origin=origin)
+ self.context.send(u"INVITE %s %s" %
+ (nickname, self.name), origin=origin)
+
+ def join(self, key="", blocking=False, timeout=30, origin=None):
+ with self.context.lock:
+ if self.context.identity in self.users:
+ # Bot is already on the channel
+ raise AlreadyJoined
+ if not self.context.connected:
+ raise NotConnected
+ with self._joining:
+ try:
+ if self._joinrequested:
+ raise ActionAlreadyRequested
+ self._joinrequested = True
+ if len(re.findall("^([^\r\n\\s]*)", key)[0]):
+ self.context.send(
+ u"JOIN %s %s" % (self.name, re.findall("^([^\r\n\\s]*)", key)[0]), origin=origin)
+ else:
+ self.context.send(u"JOIN %s" % self.name, origin=origin)
+
+ # Anticipated Numeric Replies:
+
+ # ERR_NEEDMOREPARAMS ERR_BANNEDFROMCHAN
+ # ERR_INVITEONLYCHAN ERR_BADCHANNELKEY
+ # ERR_CHANNELISFULL ERR_BADCHANMASK
+ # ERR_NOSUCHCHANNEL ERR_TOOMANYCHANNELS
+ # ERR_TOOMANYTARGETS ERR_UNAVAILRESOURCE
+
+ if blocking:
+ endtime = time.time() + timeout
+ while True:
+ self._joining.wait(max(0, endtime - time.time()))
+ t = time.time()
+ if not self.context.connected:
+ raise NotConnected
+ elif self._joinreply == "JOIN":
+ return
+ elif type(self._joinreply) == tuple and len(self._joinreply) == 2:
+ cmd, extinfo = self._joinreply
+ raise exceptcodes[cmd], extinfo
+ if t > endtime:
+ raise RequestTimedOut
+ finally:
+ self._joinrequested = False
+ self._joinreply = None
def kick(self, user, msg="", origin=None):
nickname = user.nick if type(
@@ -1266,25 +3206,38 @@ class Channel(object):
if nickname == "":
raise InvalidName
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.context.raw("KICK %s %s :%s" % (self.name, nickname,
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ self.context.send(u"KICK %s %s :%s" %
+ (self.name, nickname, re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
- self.context.raw("KICK %s %s" % (self.name,
- nickname), origin=origin)
+ self.context.send(u"KICK %s %s" %
+ (self.name, nickname), origin=origin)
def __repr__(self):
- return "<Channel: "+self.name+"@"+self.context.server+"/"+str(self.context.port)+">"
+ 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):
+
def __init__(self, nick, context):
- if not re.match(r"^\S+$", nick):
+ if not re.match(_nickmatch, nick):
raise InvalidName
self.nick = nick
+ self.context = context
+ self._init()
+
+ def _init(self):
self.username = ""
self.host = ""
- self.channels = []
- self.context = context
+ self.channels = ChanList(context=self.context)
self.modes = ""
self.snomask = ""
self.server = None
@@ -1293,210 +3246,457 @@ class User(object):
self.ircopmsg = ""
self.idlesince = None
self.signontime = None
- self.ssl = None
+ self.secure = None
self.away = None
+ self.loggedinas = None
def __repr__(self):
- return "<User: %(nick)s!%(username)s@%(host)s>" % vars(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.raw("PRIVMSG %s :%s" % (self.nick,
- line), origin=origin)
+ self.context.send(u"PRIVMSG %s :%s" %
+ (self.nick, line), origin=origin)
def notice(self, msg, origin=None):
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("NOTICE %s :%s" % (self.nick,
- line), origin=origin)
+ self.context.send(u"NOTICE %s :%s" %
+ (self.nick, line), origin=origin)
def ctcp(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.msg("\01%s %s\01" % (act.upper(), re.findall(
- "^([^\r\n]*)", msg)[0]), origin=origin)
+ self.msg(u"\01%s %s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
- self.msg("\01%s\01" % act.upper())
+ self.msg(u"\01%s\01" % act.upper())
def ctcpreply(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.notice("\01%s %s\01" % (act.upper(),
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ self.notice("\01%s %s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
self.notice("\01%s\01" % act.upper(), origin=origin)
def me(self, msg="", origin=None):
self.ctcp("ACTION", msg, origin=origin)
+ def json(self):
+ return self.nick
-class Outgoing(Thread):
- def __init__(self, IRC, throttle=0.25, lines=10, t=5):
- self.IRC = IRC
- self.throttle = throttle
- self.lines = lines
- self.time = t
- #self.queue=Queue()
- Thread.__init__(self)
- def run(self):
- try:
- throttled = False
- timestamps = []
- while True:
+class Config(object):
+
+ 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:
- q = self.IRC.outgoing.get()
- except Queue.Interrupted:
- break
- if q == "quit" or not self.IRC.connected:
- break
- line, origin = q
- match = re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I)
- (cmd, target, params, extinfo) = match[0]
- timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(
- 2, "0") for t in time.localtime()[0:6]])
- with self.IRC.lock:
- try:
- self.IRC.connection.send("%(line)s\n" % vars())
- except socket.error:
- try:
- self.IRC.connection.shutdown(0)
- except:
- pass
- raise
+ conf[key] = getattr(self, key)
+ except AttributeError:
+ print key
+ raise TypeError(
+ repr(self) + " is not JSON serializable (Cannot recover required argument '%s')" % key)
- ### Modify line if it contains a password so that the password is not logged or sent to any potentially untrustworthy addons
- #if re.match("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I):
- if cmd.upper() == "PRIVMSG":
- if target.upper() == "NICKSERV":
- nscmd = re.findall(r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
- if nscmd:
- nscmd = nscmd[0]
- if nscmd[0].upper() in ("IDENTIFY", "REGISTER"):
- extinfo = "%s ********"%nscmd[0]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
- extinfo = "%s %s ********"%nscmd[:2]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif nscmd[0].upper() == "SET":
- if nscmd[1].upper() == "PASSWORD":
- extinfo = "%s %s ********"%nscmd[:2]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
- extinfo = "********"
- line = "%s %s :%s"%(cmd, target, extinfo)
- if target.upper() == "CHANSERV":
- cscmd = re.findall(r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
- if cscmd:
- cscmd = cscmd[0]
- if cscmd[0].upper() in ("IDENTIFY", "REGISTER"):
- extinfo = "%s %s ********"%cscmd[:2]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
- extinfo = "%s %s %s ********"%cscmd[:3]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif cscmd[0].upper() == "SET":
- if cscmd[2].upper() == "PASSWORD":
- extinfo = "%s %s %s ********"%cscmd[:3]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
- extinfo = "********"
- line = "%s %s :%s"%(cmd, target, extinfo)
-
- chanmatch = re.findall("([%s]?)([%s].+)"%(re.escape(self.IRC.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.IRC.supports.get("CHANTYPES", "#"))), target)
- if chanmatch:
- targetprefix, channame = chanmatch[0]
- target = self.IRC.channel(channame)
- if target.name != channame:
- ### Target channel name has changed
- target.name = channame
- elif len(target) and target[0] != "$" and cmd != "NICK":
- targetprefix = ""
- target = self.IRC.user(target)
-
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",
- extinfo)
- if ctcp:
- (ctcptype, ext) = ctcp[0]
- if ctcptype.upper() == "ACTION":
- if type(target) == Channel:
- self.IRC.event("onSendChanAction", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, action=ext)
- elif type(target) == User:
- self.IRC.event("onSendPrivAction", self.IRC.addons, origin=origin, user=target, action=ext)
- else:
- if type(target) == Channel:
- self.IRC.event("onSendChanCTCP", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext)
- elif type(target) == User:
- self.IRC.event("onSendPrivCTCP", self.IRC.addons, origin=origin, user=target, ctcptype=ctcptype, params=ext)
- else:
- if type(target) == Channel:
- self.IRC.event("onSendChanMsg", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo)
- elif type(target) == User:
- self.IRC.event("onSendPrivMsg", self.IRC.addons, origin=origin, user=target, msg=extinfo)
-
- #elif target.upper()=="CHANSERV":
- #msg=extinfo.split(" ")
- #if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2:
- #msg[2]="********"
- #extinfo=" ".join(msg)
- #line="%s %s :%s"%(cmd, target, extinfo)
- elif cmd.upper() == "NS":
- if target.upper() in ("IDENTIFY", "REGISTER"):
- params = params.split(" ")
- while "" in params:
- params.remove("")
- if len(params):
- params[0] = "********"
- params = " ".join(params)
- line = "%s %s %s"%(cmd, target, params)
- elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
- params = params.split(" ")
- while "" in params:
- params.remove("")
- if len(params) > 1:
- params[1] = "********"
- params = " ".join(params)
- line = "%s %s %s"%(cmd, target, params)
- elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
- params = ""
- target = "********"
- line = "%s %s"%(cmd, target)
- elif cmd.upper() == "OPER":
- params = "********"
- line = "%s %s %s"%(cmd, target, params)
- elif cmd.upper() == "PASS":
- extinfo = "********"
- target = ""
- line = "%s :%s"%(cmd, extinfo)
- elif cmd.upper() == "IDENTIFY":
- target = "********"
- line = "%s %s"%(cmd, target)
- self.IRC.event("onSend", self.IRC.addons, origin=origin, line=line, cmd=cmd, target=target, params=params, extinfo=extinfo)
- self.IRC.logwrite(">>> %(line)s" % vars())
- if cmd.upper() == "QUIT":
- self.IRC.quitexpected = True
- timestamps.append(time.time())
- while timestamps[0] < timestamps[-1]-self.time-0.1:
- del timestamps[0]
- if throttled:
- if len(timestamps) < 2:
- throttled = False
+ 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):
+
+ def __init__(self, iterable=None, context=None, withdict=False):
+ self._dict = {} if withdict else None
+ if context != None and type(context) != Connection:
+ raise TypeError, "context must be irc.Connection object or None"
+ self.context = context
+ if iterable:
+ chanlist = []
+ for channel in iterable:
+ if type(channel) == Channel:
+ chanlist.append(channel)
+ if context and channel.context != context:
+ raise ValueError, "Channel object does not belong to context."
+ elif type(channel) in (str, unicode):
+ if context == None:
+ raise ValueError, "No context given for string object."
+ chanlist.append(context.channel(channel))
+ list.__init__(self, chanlist)
+ if self._dict is not None:
+ if self.context:
+ self._dict.update(
+ {self.context.lower(channel.name): channel for channel in chanlist})
else:
- if len(timestamps) >= self.lines:
- throttled = True
- if throttled:
- time.sleep(max(timestamps[-1] +
- self.throttle-time.time(), 0))
- except:
- self.IRC.connection.send("QUIT :%s\n" %
- traceback.format_exc().rstrip().split("\n")[-1])
- self.IRC.connection.close()
- self.IRC.connection.shutdown(0)
+ self._dict.update(
+ {(channel.context, channel.context.lower(channel.name)): channel for channel in chanlist})
+ else:
+ list.__init__(self)
+
+ def append(self, item):
+ if type(item) in (str, unicode):
+ if self.context:
+ channel = self.context.channel(item)
+ list.append(self, channel)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = channel
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Channel:
+ raise TypeError, "Only channel objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Channel object does not belong to context."
+ list.append(self, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.name)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.name)] = item
+
+ def insert(self, index, item):
+ if type(item) in (str, unicode):
+ if self.context:
+ channel = self.context.channel(item)
+ list.insert(self, index, channel)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = channel
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Channel:
+ raise TypeError, "Only channel objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Channel object does not belong to context."
+ list.insert(self, index, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.name)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.name)] = item
+
+ def extend(self, iterable):
+ list.extend(self, ChanList(iterable, context=self.context))
+ def join(self, origin=None):
+ if not self.context:
+ raise ValueError, "No context defined."
+ if any([channel.key for channel in self]):
+ self.context.send(u"JOIN %s %s" %
+ (self, ",".join([channel.key if channel.key else "" for channel in self])), origin=origin)
+ else:
+ self.context.send(u"JOIN %s" % self, origin=origin)
+
+ def part(self, partmsg=None, origin=None):
+ if not self.context:
+ raise ValueError, "No context defined."
+ if partmsg:
+ self.context.send(u"PART %s :%s" %
+ (",".join([channel.name for channel in self]), partmsg), origin=origin)
+ else:
+ self.context.send(u"PART %s" % self, origin=origin)
-class Pinger(Thread):
- def __init__(self, connection, lock=None):
- self.connection = connection
- self.lock = lock
- self.daemon = True
- Thread.__init__(self)
+ def msg(self, msg, origin=None):
+ if not self.context:
+ raise ValueError, "No context defined."
+ self.context.send(u"PRIVMSG %s :%s" % (self, msg), origin=origin)
- def run(self):
- pass
+ def __str__(self):
+ return ",".join([channel.name for channel in self])
+
+ def __getitem__(self, key):
+ if type(key) in (int, long):
+ return list.__getitem__(self, key)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ keylower = self.context.lower(key)
+ return self._dict[keylower]
+ else:
+ raise ValueError, "No dict available."
+
+ def __delitem__(self, key):
+ if type(key) in (int, long):
+ channel = self[key]
+ del self._dict[self.context.lower(channel.name)]
+ list.__delitem__(self, channel)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ keylower = self.context.lower(key)
+ list.__delitem__(self, self._dict[keylower])
+ del self._dict[keylower]
+ else:
+ raise ValueError, "No dict available."
+
+
+class UserList(list):
+ __doc__ = "Subclass of list, with builtin validation."
+
+ def __init__(self, iterable=None, context=None, withdict=False):
+ self._dict = {} if withdict else None
+ if context != None and type(context) != Connection:
+ raise TypeError, "context must be irc.Connection object or None"
+ self.context = context
+ if iterable:
+ userlist = []
+ for user in iterable:
+ if type(user) == User:
+ if context and user.context != context:
+ raise ValueError, "User object does not belong to context."
+ userlist.append(user)
+ elif type(user) in (str, unicode):
+ if context == None:
+ raise ValueError, "No context given for string object."
+ userlist.append(context.user(user))
+ list.__init__(self, userlist)
+ if self._dict is not None:
+ if self.context:
+ self._dict.update(
+ {self.context.lower(user.nick): user for user in userlist})
+ else:
+ self._dict.update(
+ {(user.context, user.context.lower(user.nick)): user for user in userlist})
+ else:
+ list.__init__(self)
+
+ def append(self, item):
+ """append(item)
+
+ Like list.append, but enforces that the appended item must be a User instance.
+ If item is a string, then a User instance will be appended in its place."""
+ if type(item) in (str, unicode):
+ if self.context:
+ user = self.context.user(item)
+ list.append(self, user)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = user
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != User:
+ raise TypeError, "Only user objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "User object does not belong to context."
+ list.append(self, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.nick)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.nick)] = item
+
+ def insert(self, index, item):
+ """insert(index, item)
+
+ Like list.insert."""
+ if type(item) in (str, unicode):
+ if self.context:
+ user = self.context.user(item)
+ list.insert(self, index, user)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = user
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != User:
+ raise TypeError, "Only user objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "User object does not belong to context."
+ list.insert(self, index, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.nick)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.nick)] = item
+
+ def extend(self, iterable):
+ """extend(iterable)
+
+ Like list.extend."""
+ list.extend(self, UserList(iterable, context=self.context))
+
+ def msg(self, msg, origin=None):
+ """msg(msg[, origin])
+
+ Sends a PRIVMSG to all users on list."""
+ if not self.context:
+ raise ValueError, "No context defined."
+ self.context.send(u"PRIVMSG %s :%s" % (self, msg), origin=origin)
+
+ def __str__(self):
+ return ",".join([user.nick for user in self])
+
+ def __getitem__(self, index):
+ if type(index) in (int, long):
+ return list.__getitem__(self, index)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ return self._dict[self.context.lower(index)]
+ else:
+ raise ValueError, "No dict available."
+
+ def __delitem__(self, index):
+ if type(index) in (int, long):
+ user = self[index]
+ del self._dict[self.context.lower(user.name)]
+ list.__delitem__(self, user)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ index = self.context.lower(index)
+ list.__delitem__(self, self._dict[index])
+ del self._dict[index]
+ else:
+ raise ValueError, "No dict available."
+
+ def remove(self, item):
+ if type(item) == User:
+ list.remove(self, item)
+ if self._dict is not None:
+ if self.context:
+ del self._dict[self.context.lower(item.nick)]
+ else:
+ del self._dict[item.context, item.context.lower(item.nick)]
+ else:
+ self.remove(self[item])
+
+
+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
+
+ def stats(self, query, origin=None):
+ self.context(query, self, origin=origin)
+
+ def __repr__(self):
+ return u"<Server: {self.name} on {self.context:uri}>".format(**vars())
+
+ def __str__(self):
+ return self.name
+
+
+class ServerList(list):
+ __doc__ = "Subclass of list, with builtin validation."
+
+ def __init__(self, iterable=None, context=None):
+ if context != None and type(context) != Connection:
+ raise TypeError, "context must be irc.Connection object or None"
+ self.context = context
+ if iterable:
+ serverlist = []
+ for server in iterable:
+ if type(server) == Server:
+ if self.context and server.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ serverlist.append(server)
+ elif type(server) in (str, unicode):
+ if context == None:
+ raise ValueError, "No context given for string object."
+ serverlist.append(context.getserver(server))
+ list.__init__(self, serverlist)
+ else:
+ list.__init__(self)
+
+ def append(self, item):
+ """append(item)
+
+ Like list.append, but enforces that the appended item must be a Server instance.
+ If item is a string, then a Server instance will be appended in its place."""
+ if type(item) in (str, unicode):
+ if self.context:
+ list.append(self, self.context.getserver(item))
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Server:
+ raise TypeError, "Only Server objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ list.append(self, item)
+
+ def insert(self, index, item):
+ """insert(index, item)
+
+ Like list.insert."""
+ if type(item) in (str, unicode):
+ if self.context:
+ list.insert(self, index, self.context.getserver(item))
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Server:
+ raise TypeError, "Only Server objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ list.insert(self, index, item)
+
+ def extend(self, iterable):
+ """extend(iterable)
+
+ Like list.extend."""
+ serverlist = []
+ for item in iterable:
+ if type(item) in (str, unicode):
+ if self.context:
+ serverlist.append(self.context.getserver(item))
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != User:
+ raise TypeError, "Only Server objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ serverlist.append(item)
+ list.extend(self, serverlist)
+
+ def __str__(self):
+ return ",".join([user.nick for user in self])
diff --git a/ircapp.conf b/ircapp.conf
new file mode 100644
index 0000000..72fe9a2
--- /dev/null
+++ b/ircapp.conf
@@ -0,0 +1,64 @@
+{
+ "addons": {
+ "ax": {
+ "class": "autoexec.Autoexec"
+ },
+ "log": {
+ "class": "logger.Logger",
+ "logroot": "~/pyIRC-logs"
+ }
+ },
+ "networks": {
+ "Freenode": {
+ "class": "irc.Connection",
+ "server": "irc.freenode.net",
+ "secure": true,
+ "nick": "pyIRC-user",
+ "requestcaps": [
+ "away-notify",
+ "multi-prefix",
+ "userhost-in-names",
+ "account-notify"
+ ],
+ "addons": [
+ {
+ "addon": <addons.log>,
+ "label": "Freenode"
+ },
+ {
+ "addon": <addons.ax>,
+ "label": "Freenode",
+ "autojoin": [
+ "#pyirc-ng"
+ ]
+ }
+ ]
+ },
+ "InsomniaIRC": {
+ "class": "irc.Connection",
+ "server": "irc.insomniairc.net",
+ "secure": true,
+ "nick": "pyIRC-user",
+ "requestcaps": [
+ "extended-join",
+ "away-notify",
+ "multi-prefix",
+ "userhost-in-names",
+ "account-notify"
+ ],
+ "addons": [
+ {
+ "addon": <addons.log>,
+ "label": "InsomniaIRC"
+ },
+ {
+ "addon": <addons.ax>,
+ "label": "InsomniaIRC",
+ "autojoin": [
+ "#chat"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/ircapp.py b/ircapp.py
new file mode 100755
index 0000000..3bc444f
--- /dev/null
+++ b/ircapp.py
@@ -0,0 +1,125 @@
+#!/usr/bin/python
+import os
+import re
+import time
+import signal
+import sys
+import irc
+import modjson
+import readline
+import rlcompleter
+import types
+import code
+
+nonaddontypes = (types.ModuleType, types.MethodType,
+ types.FunctionType, types.TypeType, irc.Connection)
+
+
+class IRCApplication:
+
+ def __init__(self, conffile=None):
+ self._quitting = False
+ self.conffile = conffile
+ self.termcaught = False
+ self.confdecoder = modjson.ModJSONDecoder()
+ self.confencoder = modjson.ModJSONEncoder(indent=3)
+ signal.signal(signal.SIGTERM, self.sigterm)
+ self.namespace = {}
+ if conffile and os.path.isfile(conffile):
+ with open(conffile, "r") as f:
+ pyirc = self.confdecoder.decode(f.read())
+ if "addons" in pyirc.keys():
+ self.namespace.update(pyirc["addons"])
+ if "networks" in pyirc.keys():
+ self.namespace.update(pyirc["networks"])
+ self.shell = code.InteractiveConsole(locals=self.namespace)
+ self.namespace["quit"] = self.quit
+ self.namespace["save"] = self.save
+ # self.namespace["exit"]=self.exit
+ self.namespace["irc"] = irc
+
+ def quit(self, quitmsg="Goodbye!"):
+ networks = [
+ o for o in self.namespace.values() if type(o) == irc.Connection]
+ for context in networks:
+ if type(context) == irc.Connection and context.isAlive():
+ context.quit(quitmsg)
+ for context in networks:
+ if type(context) == irc.Connection:
+ with context._disconnecting:
+ while context.connected:
+ context._disconnecting.wait(30)
+ if context._recvhandlerthread:
+ context._recvhandlerthread.join()
+ if context._sendhandlerthread:
+ context._sendhandlerthread.join()
+
+ def complete(self, text, state):
+ raise NotImplemented
+
+ def start(self):
+ sys.ps1 = "(ircapp) "
+ sys.ps2 = "........ "
+ readline.parse_and_bind("tab: complete")
+ completer = rlcompleter.Completer(self.namespace)
+ readline.set_completer(completer.complete)
+ for o in self.namespace.values():
+ if type(o) == irc.Connection:
+ o.connect()
+ while True:
+ try:
+ self.shell.interact(banner="Welcome to pyIRC!")
+ except SystemExit, quitmsg:
+ if not self._quitting:
+ if quitmsg.message:
+ self.quit(quitmsg.message)
+ else:
+ self.quit()
+ break
+ # In case CTRL+D is accidentally sent to the console.
+ print "Ooops... Did you mean to do that?"
+
+ def sigterm(self, signum, frame):
+ if not self.termcaught:
+ self.termcaught = True
+ self.exit("Caught SIGTERM")
+
+ def save(self, conffile=None):
+ addons = {key: o for (key, o) in self.namespace.items()
+ if not isinstance(o, nonaddontypes) and not key.startswith("_")}
+ extraaddons = []
+ networks = {key: o for (key, o) in self.namespace.items() if type(
+ o) == irc.Connection and not key.startswith("_")}
+ if not conffile:
+ conffile = self.conffile
+ with open(conffile, "w") as f:
+ print >>f, self.confencoder.encode(
+ dict(addons=addons, networks=networks))
+
+ def exit(self, quitmsg="Goodbye!"):
+ self.quit(quitmsg)
+ addons = [o for (key, o) in self.namespace.items()
+ if not isinstance(o, nonaddontypes) and not key.startswith("_")]
+ networks = [o for (key, o) in self.namespace.items() if type(
+ o) == irc.Connection and not key.startswith("_")]
+ for context in networks:
+ for conf in list(context.addons):
+ addon = conf.addon if type(conf) == irc.Config else conf
+ context.rmAddon(addon)
+ if addon not in addons:
+ addons.append(addon)
+ for addon in addons:
+ if "stop" in dir(addon) and callable(addon.stop) and "isAlive" in dir(addon) and callable(addon.isAlive) and addon.isAlive():
+ try:
+ addon.stop()
+ except:
+ pass
+ print "Quit: {quitmsg}".format(**vars())
+ self._quitting = True
+ sys.exit()
+
+if __name__ == "__main__":
+ ircapp = IRCApplication(
+ sys.argv[1] if len(sys.argv) > 1 else "ircapp.conf")
+ ircapp.start()
+ ircapp.exit()
diff --git a/logger.py b/logger.py
index 01f2bc1..779fb9d 100644
--- a/logger.py
+++ b/logger.py
@@ -13,15 +13,28 @@ import Queue
import ssl
import urllib2
import irc
+import codecs
-modemapping = dict(
- Y="ircop", q="owner", a="admin", o="op", h="halfop", v="voice")
+modemapping = dict(Y="ircop", q="owner",
+ a="admin", o="op", h="halfop", v="voice")
+
+
+def LoggerReload(log):
+ newlog = Logger(logroot=log.logroot)
+ with newlog.rotatelock, log.rotatelock:
+ newlog.logs = log.logs
+ log.logs = {}
+ for context, conf in log.conf.items():
+ context.rmAddon(log)
+ context.addAddon(newlog, label=conf.label)
+ return newlog
class Logger(Thread):
+
def __init__(self, logroot):
self.logroot = logroot
- path = [logroot]
+ path = [os.path.expanduser(logroot)]
while not os.path.isdir(path[0]):
split = os.path.split(path[0])
@@ -31,11 +44,11 @@ class Logger(Thread):
while len(path) > 1:
path[0] = os.path.join(*path[:2])
del path[1]
- #print path
+ # print path
os.mkdir(path[0])
self.logs = {}
- self.labels = {}
+ self.conf = {}
self.rotatelock = Lock()
Thread.__init__(self)
@@ -45,114 +58,134 @@ class Logger(Thread):
def run(self):
try:
Y, M, D, h, m, s, w, d, dst = time.localtime()
- nextrotate = int(time.mktime((Y, M, D+1, 0, 0, 0, 0, 0, -1)))
+ nextrotate = int(time.mktime((Y, M, D + 1, 0, 0, 0, 0, 0, -1)))
while True:
while nextrotate > time.time(): # May need to do this in a loop in case the following time.sleep command wakes up a second too early.
- time.sleep(max(0.1, min((nextrotate-time.time(), 3600))))
+ time.sleep(max(0.1, min((nextrotate - time.time(), 3600))))
with self.rotatelock:
if all([not log or log.closed for log in self.logs.values()]):
break
Y, M, D, h, m, s, w, d, dst = now = time.localtime()
- for IRC in self.labels.keys():
- if IRC.connected:
- with IRC.lock:
+
+ logroot = os.path.expanduser(self.logroot)
+ for context in self.conf.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]))
- nextrotate = int(time.mktime((Y, M, D+1, 0, 0, 0, 0, 0, -1)))
+ 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(logroot, self.conf[context].label, "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, context, label):
+ logroot = os.path.expanduser(self.logroot)
+
+ for (context2, conf2) in self.conf.items():
+ if context == context2:
+ raise ValueError, "Context already exists in config."
+ if label == conf2.label:
+ raise ValueError, "Unique label required."
+
+ conf = irc.Config(self, label=label)
- def onAddonAdd(self, IRC, label):
- if label in self.labels.values():
- raise BaseException("Label already exists")
- if IRC 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:
+ contextroot = os.path.join(logroot, label)
+ if not os.path.isdir(contextroot):
+ os.mkdir(contextroot)
+
+ 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]))
-
- def onAddonRem(self, IRC):
- if IRC.connected:
+ timestamp = reduce(lambda x, y: x + ":" + y, [
+ str(t).rjust(2, "0") for t in now[0:6]])
+ context.logopen(
+ os.path.join(contextroot, "rawdata-%04d.%02d.%02d.log" % now[:3]))
+ self.conf[context] = conf
+ return conf
+
+ 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.conf[context]
def openLog(self, window):
+ logroot = os.path.expanduser(self.logroot)
+
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]])
+ timestamp = reduce(lambda x, y: x + ":" + y, [
+ str(t).rjust(2, "0") for t in now[0:6]])
if type(window) == irc.Connection:
- log = self.logs[window] = open(os.path.join(self.logroot, self.labels[window], "console-%04d.%02d.%02d.log"%now[:3]), "a")
+ log = self.logs[window] = codecs.open(
+ os.path.join(logroot, self.conf[window].label, "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] = open(os.path.join(self.logroot, label, "channel-%s-%04d.%02d.%02d.log"%((urllib2.quote(window.name.lower()).replace("/", "%2f"),)+now[:3])), "a")
+ label = self.conf[window.context].label
+ log = self.logs[window] = codecs.open(os.path.join(logroot, label, "channel-%s-%04d.%02d.%02d.log" % (
+ (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]))
+ label = self.conf[window.context].label
+ logname = os.path.join(
+ logroot, label, "query-%s-%04d.%02d.%02d.log" %
+ ((urllib2.quote(window.nick.lower()).replace("/", "%2f"),) + now[:3]))
for (other, log) in self.logs.items():
if other == window:
continue
@@ -161,16 +194,17 @@ class Logger(Thread):
del self.logs[other]
self.logs[window] = log
if window not in self.logs.keys():
- log = self.logs[window] = open(logname, "a")
+ log = self.logs[window] = codecs.open(
+ logname, "a", encoding="utf8")
else:
log = self.logs[window]
print >>log, "%s ### Log file opened" % (irc.timestamp())
log.flush()
def closeLog(self, window):
- if window in self.logs.keys() and type(self.logs[window]) == file and not self.logs[window].closed:
- print >>self.logs[window], "%s ### Log file closed" % (
- irc.timestamp())
+ 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()
if window in self.logs.keys():
del self.logs[window]
@@ -179,193 +213,216 @@ 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):
- ### 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)
+ def onConnectFail(self, context, exc, excmsg, tb):
+ # Called when a connection attempt fails.
+ 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:
- print >>self.logs[window], "%s *** Connection to %s:%s terminated." % (ts, IRC.server, IRC.port)
+ if type(window) in (irc.Channel, irc.User) and window.context == context:
+ print >>self.logs[window], "%s *** Connection to %s:%s terminated." % (
+ 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)
-
- def onJoin(self, IRC, user, channel):
- ### Called when somebody joins a channel, includes bot.
- ts = irc.timestamp()
- if user == IRC.identity:
+ print >>self.logs[context], "%s *** Connection %s:%s terminated." % (
+ ts, context.server, context.port)
+ self.logs[context].flush()
+ self.closeLog(context)
+
+ def onJoin(self, context, user, channel):
+ # Called when somebody joins a channel, includes bot.
+ 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):
- ### Called when someone sends a PRIVMSG to channel.
+ 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]])
+ classes = " ".join([modemapping[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)
+ print >>self.logs[channel], "%s %s <<< :%s!%s@%s PRIVMSG %s%s :%s" % (
+ ts, classes, user.nick, user.username, user.host, targetprefix, channel.name, msg)
else:
- print >>self.logs[channel], "%s <<< :%s!%s@%s PRIVMSG %s%s :%s" % (ts, user.nick, user.username, user.host, targetprefix, channel.name, msg)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s PRIVMSG %s%s :%s" % (
+ ts, user.nick, user.username, user.host, targetprefix, channel.name, msg)
elif type(user) in (str, unicode):
classes = "server"
- print >>self.logs[channel], "%s %s <<< :%s PRIVMSG %s%s :%s" % (ts,
- classes, user, targetprefix, channel.name, msg)
+ print >>self.logs[channel], "%s %s <<< :%s PRIVMSG %s%s :%s" % (
+ ts, classes, user, targetprefix, channel.name, msg)
self.logs[channel].flush()
- def onChanAction(self, IRC, user, channel, targetprefix, action):
- self.onChanMsg(IRC, user, channel, targetprefix,
- "\x01ACTION %s\x01"%action)
+ 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):
- ### Called when someone sends a NOTICE to channel.
+ 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]])
+ classes = " ".join([modemapping[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)
+ print >>self.logs[channel], "%s %s <<< :%s!%s@%s NOTICE %s%s :%s" % (
+ ts, classes, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg)
else:
- print >>self.logs[channel], "%s <<< :%s!%s@%s NOTICE %s%s :%s" % (ts, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s NOTICE %s%s :%s" % (
+ ts, origin.nick, origin.username, origin.host, targetprefix, channel.name, msg)
elif type(origin) in (str, unicode):
classes = "server"
- print >>self.logs[channel], "%s %s <<< :%s NOTICE %s%s :%s" % (ts,
- classes, origin, targetprefix, channel.name, msg)
+ print >>self.logs[channel], "%s %s <<< :%s NOTICE %s%s :%s" % (
+ ts, classes, origin, targetprefix, channel.name, msg)
self.logs[channel].flush()
- def onPart(self, IRC, user, channel, partmsg):
- ### Called when somebody parts the channel, includes bot.
+ def onPart(self, context, user, channel, partmsg):
+ # Called when somebody parts the channel, includes bot.
ts = irc.timestamp()
if partmsg:
- print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s :%s" % (ts, user.nick, user.username, user.host, channel.name, partmsg)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s :%s" % (
+ ts, user.nick, user.username, user.host, channel.name, partmsg)
else:
- print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s" % (ts,
- user.nick, user.username, user.host, channel.name)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s PART %s" % (
+ ts, user.nick, user.username, user.host, channel.name)
self.logs[channel].flush()
- if user == IRC.identity:
+ if user == context.identity:
self.closeLog(channel)
- def onKick(self, IRC, kicker, channel, kicked, kickmsg):
- ### Called when somebody is kicked from the channel, includes bot.
+ def onKick(self, context, kicker, channel, kicked, kickmsg):
+ # Called when somebody is kicked from the channel, includes bot.
ts = irc.timestamp()
if kickmsg:
- print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s :%s" % (ts, kicker.nick, kicker.username, kicker.host, channel.name, kicked.nick, kickmsg)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s :%s" % (
+ ts, kicker.nick, kicker.username, kicker.host, channel.name, kicked.nick, kickmsg)
else:
- print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s" % (ts, user.nick, user.username, user.host, channel.name, kicked.nick)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s KICK %s %s" % (
+ ts, user.nick, user.username, user.host, channel.name, kicked.nick)
self.logs[channel].flush()
- if user == IRC.identity:
+ if kicked == context.identity:
self.closeLog(channel)
- def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg):
- ### Called when bot sends a PRIVMSG to channel.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
+ 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]])
+ classes = " ".join([modemapping[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)
+ print >>self.logs[channel], "%s %s >>> :%s!%s@%s PRIVMSG %s%s :%s" % (
+ 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)
+ print >>self.logs[channel], "%s >>> :%s!%s@%s PRIVMSG %s%s :%s" % (
+ 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):
- ### origin is the source of the channel message
- ### Called when bot sends an action (/me) to channel.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
- self.onSendChanMsg(IRC, origin, channel, targetprefix,
- "\x01ACTION %s\x01"%action)
+ def 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(
+ context, origin, channel, targetprefix, "\x01ACTION %s\x01" % action)
- def onPrivMsg(self, IRC, user, msg):
- ### Called when someone sends a PRIVMSG to the bot.
+ 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)
+ print >>self.logs[user], "%s <<< :%s!%s@%s PRIVMSG %s :%s" % (
+ ts, user.nick, user.username, user.host, context.identity.nick, msg)
self.logs[user].flush()
- def onPrivNotice(self, IRC, origin, msg):
- ### Called when someone sends a NOTICE to the bot.
+ 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)
- print >>self.logs[origin], "%s <<< :%s!%s@%s NOTICE %s :%s" % (ts, origin.nick, origin.username, origin.host, IRC.identity.nick, msg)
+ ts = irc.timestamp()
+ print >>self.logs[origin], "%s <<< :%s!%s@%s NOTICE %s :%s" % (
+ 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()
-
- def onPrivAction(self, IRC, user, action):
- ### Called when someone sends an action (/me) to the bot.
- self.onPrivMsg(IRC, user, "\x01ACTION %s\x01"%action)
-
- def onSendPrivMsg(self, IRC, origin, user, msg):
- ### Called when bot sends a PRIVMSG to a user.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
+ print >>self.logs[context], "%s <<< :%s NOTICE %s :%s" % (
+ ts, origin, context.identity.nick, msg)
+ self.logs[context].flush()
+
+ def onPrivAction(self, context, user, action):
+ # Called when someone sends an action (/me) to the bot.
+ self.onPrivMsg(context, user, "\x01ACTION %s\x01" % action)
+
+ 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.
if user not in self.logs.keys():
self.openLog(user)
ts = irc.timestamp()
- print >>self.logs[user], "%s >>> :%s!%s@%s PRIVMSG %s :%s" % (ts, IRC.identity.nick, IRC.identity.username, IRC.identity.host, user.nick, msg)
+ print >>self.logs[user], "%s >>> :%s!%s@%s PRIVMSG %s :%s" % (
+ ts, context.identity.nick, context.identity.username, context.identity.host, user.nick, msg)
self.logs[user].flush()
- def onSendPrivAction(self, IRC, origin, user, action):
- ### Called when bot sends an action (/me) to a user.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
- self.onSendPrivMsg(IRC, origin, user, "\x01ACTION %s\x01"%action)
+ def 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(context, origin, user, "\x01ACTION %s\x01" % action)
- def onNickChange(self, IRC, user, newnick):
- ### Called when somebody changes nickname.
+ def onNickChange(self, context, user, newnick):
+ # Called when somebody changes nickname.
ts = irc.timestamp()
line = "%s <<< :%s!%s@%s NICK %s" % (
ts, user.nick, user.username, user.host, newnick)
- ### Print nick change in each channel the user is in.
+ # 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.
+ # And in the query if open.
if user in self.logs.keys():
print >>self.logs[user], line
self.logs[user].flush()
- def onMeNickChange(self, IRC, newnick):
- ### Called when the bot changes nickname.
+ 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).
+ # Print nick change to all open queries, except for query with self
+ # (already done with onNickChange).
ts = irc.timestamp()
- line = "%s <<< :%s!%s@%s NICK %s" % (ts, IRC.identity.nick,
- IRC.identity.username, IRC.identity.host, newnick)
+ line = "%s <<< :%s!%s@%s NICK %s" % (
+ ts, 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" % (
@@ -374,169 +431,178 @@ class Logger(Thread):
line = "%s <<< :%s!%s@%s QUIT" % (
ts, user.nick, user.username, user.host)
- ### Print quit in each channel the user was in.
+ # Print quit in each channel the user was in.
for channel in user.channels:
- if channel in self.logs.keys() and not self.logs[channel].closed:
+ 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()
- ### And in the query if open.
+ # And in the query if open.
if user in self.logs.keys():
print >>self.logs[user], line
self.logs[user].flush()
self.closeLog(user)
- def onNames(self, IRC, origin, channel, flag, channame, nameslist):
- ### Called when a NAMES list is received.
+ 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,
- " ".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]))
+ 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):
- ### Called when a WHOIS reply is received.
+ 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)
+ print >>self.logs[user], "%s <<< :%s 311 %s %s %s %s * :%s" % (
+ irc.timestamp(), origin, context.identity.nick, nickname, username, host, realname)
- def onWhoisRegisteredNick(self, IRC, origin, user, nickname, msg):
- ### Called when a WHOIS reply is received.
+ 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):
- ### Called when a WHOIS reply is received.
+ 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)
+ print >>self.logs[user], "%s <<< :%s 301 %s %s :%s" % (
+ irc.timestamp(), origin, context.identity.nick, nickname, awaymsg)
- def onWhoisConnectingFrom(self, IRC, origin, user, nickname, msg):
- ### Called when a WHOIS reply is received.
+ 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):
- ### Called when a WHOIS reply is received.
+ 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))
+ print >>self.logs[user], "%s <<< :%s 319 %s %s :%s" % (
+ irc.timestamp(), origin, context.identity.nick, nickname, " ".join(chanlist))
- def onWhoisAvailability(self, IRC, origin, user, nickname, msg):
- ### Called when a WHOIS reply is received.
+ 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):
- ### Called when a WHOIS reply is received.
+ 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)
+ print >>self.logs[user], "%s <<< :%s 312 %s %s %s :%s" % (
+ 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)
+ print >>self.logs[user], "%s <<< :%s 317 %s %s %d %d :%s" % (
+ 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)
+ print >>self.logs[user], "%s <<< :%s 330 %s %s %s :%s" % (
+ 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):
- ### Called when a WHO list is received.
+ def onWhoEntry(self, context, **kwargs):
+ # Called when a WHO list is received.
pass
- def onWhoEnd(self, IRC, **kwargs):
- ### Called when a WHO list is received.
+ def onWhoEnd(self, context, **kwargs):
+ # Called when a WHO list is received.
pass
- def onList(self, IRC, chanlistbegin, chanlist, endmsg):
- ### Called when a channel list is received.
+ def onList(self, context, chanlistbegin, chanlist, endmsg):
+ # Called when a channel list is received.
pass
- def onTopic(self, IRC, origin, channel, topic):
- ### Called when channel topic is received via 332 response.
+ 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):
- ### Called when channel topic info is received via 333 response.
+ 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]
- print >>log, "%s <<< :%s 333 %s %s %s %d" % (ts, origin,
- IRC.identity.nick, channel.name, topicsetby, topictime)
+ log = self.logs[context]
+ print >>log, "%s <<< :%s 333 %s %s %s %d" % (
+ ts, origin, context.identity.nick, channel.name, topicsetby, topictime)
log.flush()
- def onTopicSet(self, IRC, user, channel, topic):
- ### Called when channel topic is changed.
+ def onTopicSet(self, context, user, channel, topic):
+ # Called when channel topic is changed.
ts = irc.timestamp()
- print >>self.logs[channel], "%s <<< :%s!%s@%s TOPIC %s :%s" % (ts,
- user.nick, user.username, user.host, channel.name, topic)
+ if type(user) == irc.User:
+ print >>self.logs[channel], "%s <<< :%s!%s@%s TOPIC %s :%s" % (
+ ts, user.nick, user.username, user.host, channel.name, topic)
+ else:
+ print >>self.logs[channel], "%s <<< :%s TOPIC %s :%s" % (
+ ts, user, channel.name, topic)
self.logs[channel].flush()
- def onChanModeSet(self, IRC, user, channel, modedelta):
- ### Called when channel modes are changed.
- ### modedelta is a list of tuples of the format ("+x", parameter), ("+x", None) if no parameter is provided.
+ 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.
ts = irc.timestamp()
modestr = ""
params = []
@@ -546,28 +612,31 @@ class Logger(Thread):
modestr += sgn
sign = sgn
modestr += modechar
- if param is not None:
+ if param != None:
params.append(param.nick if type(param) == irc.User else param)
if len(params):
if type(user) == irc.User:
- print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s %s" % (ts, user.nick, user.username, user.host, channel.name, modestr, " ".join(params))
+ print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s %s" % (
+ ts, user.nick, user.username, user.host, channel.name, modestr, " ".join(params))
else:
- print >>self.logs[channel], "%s <<< :%s MODE %s %s %s" % (ts, user, channel.name, modestr, " ".join(params))
+ print >>self.logs[channel], "%s <<< :%s MODE %s %s %s" % (
+ ts, user, channel.name, modestr, " ".join(params))
else:
if type(user) == irc.User:
- print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s" % (ts, user.nick, user.username, user.host, channel.name, modestr)
+ print >>self.logs[channel], "%s <<< :%s!%s@%s MODE %s %s" % (
+ ts, user.nick, user.username, user.host, channel.name, modestr)
else:
print >>self.logs[channel], "%s <<< :%s MODE %s %s" % (
ts, user, channel.name, modestr)
self.logs[channel].flush()
- def onChannelModes(self, IRC, channel, modedelta):
- ### Called when channel modes are received via 324 response.
+ 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 = ""
@@ -576,27 +645,28 @@ class Logger(Thread):
modestr += sgn
sign = sgn
modestr += modechar
- if param is not None:
+ if param != None:
params.append(param)
if len(params):
- print >>log, "%s <<< :%s 324 %s %s %s %s" % (ts, IRC.serv, IRC.identity.nick, channel.name, modestr, " ".join(params))
+ print >>log, "%s <<< :%s 324 %s %s %s %s" % (
+ ts, 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)
+ print >>log, "%s <<< :%s 324 %s %s %s" % (
+ ts, origin, context.identity.nick, channel.name, modestr)
log.flush()
- def onChanCreated(self, IRC, channel, created):
- ### Called when a 329 response is received.
+ 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):
+ 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..d52237e
--- /dev/null
+++ b/modjson.py
@@ -0,0 +1,839 @@
+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 ModJSONEncoder(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 "json" in dir(o) and callable(o.json):
+ conf = o.json()
+
+ else:
+ 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
+ print refs.keys()
+ 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
+
+ if path and not isinstance(conf, (int, long, bool, basestring)) and conf is not None:
+ 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
+
+ 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)) and "json" not in dir(o):
+ for chunk in self._iterencode_list(o, _current_indent_level, markers, refs, path):
+ yield chunk
+ elif isinstance(o, dict) and "json" not in dir(o):
+ 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)) and "json" not in dir(value):
+ chunks = self._iterencode_list(
+ value, _current_indent_level, markers, refs, path + (k,))
+ elif isinstance(value, dict) and "json" not in dir(value):
+ 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)) and "json" not in dir(value):
+ chunks = self._iterencode_list(
+ value, _current_indent_level, markers, refs, path + (key,))
+ elif isinstance(value, dict) and "json" not in dir(value):
+ 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]
diff --git a/quote.py b/quote.py
new file mode 100644
index 0000000..ee18660
--- /dev/null
+++ b/quote.py
@@ -0,0 +1,55 @@
+import os
+import random
+import re
+import codecs
+
+
+class quote(object):
+
+ def __init__(self, quotefile="quotes.txt", encoding="utf8"):
+ self.quotefile = quotefile
+ self.encoding = encoding
+ if os.path.isfile(quotefile):
+ with codecs.open(quotefile, encoding=encoding) as f:
+ self.quotes = [line for line in f]
+ else:
+ self.quotes = []
+
+ def onChanMsg(self, IRC, user, channel, targetprefix, msg):
+ matches = re.findall(
+ r"^!quote(?:\s+(--add|--rem|--flush))?(?:\s+(.+?)\s*)?$", msg)
+ if matches:
+ cmd, msg = matches[0]
+ if cmd == "" and msg == "":
+ if self.quotes:
+ channel.msg(random.choice(self.quotes))
+ else:
+ channel.msg("%s: There are no quotes!" % user.nick)
+ elif cmd == "--add":
+ if msg:
+ if msg not in self.quotes:
+ self.quotes.append(msg)
+ channel.msg("%s: Quote added." % user.nick)
+ else:
+ channel.msg("%s: Quote already exists." % user.nick)
+ else:
+ channel.msg("%s: What am I adding?" % user.nick)
+ elif cmd == "--rem":
+ if msg:
+ if msg in self.quotes:
+ self.quotes.remove(msg)
+ channel.msg("%s: Quote removed." % user.nick)
+ else:
+ channel.msg("%s: Quote does not exist." % user.nick)
+ else:
+ channel.msg("%s: What am I removing?" % user.nick)
+ elif cmd == "--flush":
+ with codecs.open(self.quotefile, "w", encoding=self.encoding) as f:
+ for line in self.quotes:
+ print >>f, line
+ else:
+ channel.msg(
+ "I am sorry, %s, but I cannot do that." % user.nick)
+
+ def onSendChanMsg(self, IRC, channel, targetprefix, msg, origin):
+ self.onChanMsg(IRC, IRC.identity, channel, targetprefix, msg)
diff --git a/sedbot.py b/sedbot.py
index e7d9d61..a4189ee 100644
--- a/sedbot.py
+++ b/sedbot.py
@@ -5,52 +5,48 @@ import time
class SED(object):
+
def __init__(self, expiry=1800):
self.__name__ = "SED Bot"
self.__version__ = "0.0.2"
self.expiry = expiry
self.history = []
- self.trigger = r"^!?s([,/#])(.*)$"
- self.pattern = r"^((?:[^%(delimiter)s]|\\.)*)%(delimiter)s((?:[^%(delimiter)s]|\\.)*)%(delimiter)s([igv]*)$"
+ self.pattern = r"^!?s([,/#])((?:.|\\\1)*)\1((?:.|\\\1)*)\1([ig]*)$"
def onChanMsg(self, IRC, user, channel, targetprefix, msg):
- matches = re.findall(self.trigger, msg)
+ matches = re.findall(self.pattern, msg)
if matches:
- delimiter, args = matches[0]
- validate = re.findall(self.pattern%vars(), args)
- if validate:
- find, replace, flags = validate[0]
- find = re.sub(r"\\([,/#\\])", r"\1", find)
- replace = re.sub(r"\\([,/#\\])", r"\1", replace)
- if "v" in flags:
- channel.msg("Delimiter: '%s', Search pattern: '%s', Replacement: '%s', Flags: '%s'" % (delimiter, find, replace, flags), origin=self)
- match = False
- for t, IRC2, user2, channel2, targetprefix2, msg2, isaction in self.history.__reversed__():
- if channel != channel2:
- continue
- try:
- if re.findall(find, msg2, flags=re.I if "i" in flags else 0):
- sub = re.sub(find, replace, msg2, flags=re.I if "i" in flags else 0)
- match = True
- else:
- continue
- except:
- channel.msg("%s: Invalid syntax" %
- user.nick, origin=self)
- raise
- if isaction:
- channel.msg("What %s really meant was: *%s %s" % (user2.nick, user2.nick, sub), origin=self)
+ separator, find, replace, flags = matches[0]
+ find = re.sub("\\\\([,/#\\\\])", "\\1", find)
+ replace = re.sub("\\\\(,/#\\\\)", "\\1", replace)
+ match = False
+ for t, IRC2, user2, channel2, msg2, isaction in self.history.__reversed__():
+ if channel != channel2:
+ continue
+ try:
+ if re.findall(find, msg2):
+ sub = re.sub(
+ find, replace, msg2, flags=re.I if "i" in flags else 0)
+ match = True
else:
- channel.msg("What %s really meant to say was: %s" % (user2.nick, sub), origin=self)
- break
- if not match:
- channel.msg("%s: I tried. I really tried! But I could not find the pattern: %s" % (user.nick, find), origin=self)
- else:
- channel.msg("%s: Invalid syntax. Did you forget a trailing delimiter?" % user.nick, origin=self)
+ continue
+ except:
+ channel.msg("%s: Invalid syntax" % user.nick, origin=self)
+ raise
+ if isaction:
+ channel.msg("What %s really meant was: *%s %s" %
+ (user2.nick, user2.nick, sub), origin=self)
+ else:
+ channel.msg("What %s really meant to say was: %s" %
+ (user2.nick, sub), origin=self)
+ break
+ if not match:
+ channel.msg(
+ "%s: I tried. I really tried! But I could not find the pattern: %s" %
+ (user.nick, find), origin=self)
else:
- self.history.append((time.time(
- ), IRC, user, channel, targetprefix, msg, False))
- while len(self.history) and self.history[0][0] < time.time()-1800:
+ self.history.append((time.time(), IRC, user, channel, msg, False))
+ while len(self.history) and self.history[0][0] < time.time() - 1800:
del self.history[0]
def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg):
@@ -58,9 +54,8 @@ class SED(object):
self.onChanMsg(IRC, IRC.identity, channel, targetprefix, msg)
def onChanAction(self, IRC, user, channel, targetprefix, action):
- self.history.append((time.time(
- ), IRC, user, channel, targetprefix, action, True))
- while len(self.history) and self.history[0][0] < time.time()-1800:
+ self.history.append((time.time(), IRC, user, channel, action, True))
+ while len(self.history) and self.history[0][0] < time.time() - 1800:
del self.history[0]
def onSendChanAction(self, IRC, origin, channel, targetprefix, action):
diff --git a/startirc.py b/startirc.py
deleted file mode 100755
index c3a6cec..0000000
--- a/startirc.py
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/python -i
-import os
-import re
-import time
-import logger
-import signal
-import figlet
-import cannon
-import wallet
-import autoexec
-import sys
-import irc
-import bouncer
-import readline
-import rlcompleter
-readline.parse_and_bind("tab: complete")
-
-networks = {}
-
-
-def quit(quitmsg="Goodbye!"):
- global networks
- addons = []
- for IRC in networks.values():
- if IRC.isAlive():
- IRC.quit(quitmsg)
- while any([IRC.isAlive() for IRC in networks.values()]):
- time.sleep(0.25)
- for IRC in networks.values():
- for addon in list(IRC.addons):
- IRC.rmAddon(addon)
- if addon not in addons:
- addons.append(addon)
- for addon in addons:
- if "stop" in dir(addon) and callable(addon.stop) and "isAlive" in dir(addon) and callable(addon.isAlive) and addon.isAlive():
- try:
- addon.stop()
- except:
- pass
- print "Goodbye!"
- sys.exit()
-
-termcaught = False
-
-
-def sigterm(signum, frame):
- global termcaught
- if not termcaught:
- termcaught = True
- quit("Caught SIGTERM")
-
-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"))
-
-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!")
-
-for (label, IRC) in networks.items():
- IRC.addAddon(log, label=label)
- ### The password is 'hunter2'
- IRC.addAddon(BNC, label=label, passwd="6b97ed68d14eb3f1aa959ce5d49c7dc612e1eb1dafd73b1e705847483fd6a6c809f2ceb4e8df6ff9984c6298ff0285cace6614bf8daa9f0070101b6c89899e22", hashtype="sha512")
-
-InsomniaIRC.addAddon(ax, label="InsomniaIRC", autojoin=["#chat"])
-
-for (label, IRC) in networks.items():
- IRC.start() \ No newline at end of file
diff --git a/testaddon.py b/testaddon.py
index 5379188..f0e14a7 100644
--- a/testaddon.py
+++ b/testaddon.py
@@ -1,213 +1,221 @@
class Addon(object):
+
def onAddonAdd(self, IRC, **params):
- ### Stuff you want the Addon to do when added or inserted into the addon list of an IRC instance.
+ # Stuff you want the Addon to do when added or inserted into the addon
+ # list of an IRC instance.
print "onAddonAdd:", IRC, params
def onAddonRem(self, IRC):
- ### Stuff you want the Addon to do when removed from the addon list of an IRC instance.
+ # Stuff you want the Addon to do when removed from the addon list of an
+ # IRC instance.
print "onAddonRem:", IRC
def onSessionOpen(self, IRC):
- ### Called when the thread handling the IRC instance is started.
+ # Called when the thread handling the IRC instance is started.
print "onSessionOpen:", IRC
def onSessionClose(self, IRC):
- ### Called when the thread handling the IRC instance is terminated.
+ # Called when the thread handling the IRC instance is terminated.
print "onSessionClose:", IRC
def onConnectAttempt(self, IRC):
- ### Called when a connection attempt is made.
+ # Called when a connection attempt is made.
print "onConnectAttempt:", IRC
def onConnectFail(self, IRC, exc, excmsg, tb):
- ### Called when a connection attempt fails.
+ # Called when a connection fails.
print "onConnectFail:", IRC, exc, excmsg, tb
def onConnect(self, IRC):
- ### Called when a connection is established.
+ # Called when a connection is established.
print "onConnect:", IRC
def onRegistered(self, IRC):
- ### Called when registration is complete.
+ # Called when registration is complete.
print "onRegistered:", IRC
- def onDisconnect(self, IRC):
- ### Called when a connection is terminated.
+ def onDisconnect(self, IRC, expected):
+ # Called when a connection is terminated.
print "onDisconnect:", IRC
def onRecv(self, IRC, line, origin=None, cmd=None, target=None, targetprefix=None, params=None, extinfo=None):
- ### Called when a line of data is received from the IRC server. If line also matches ":origin cmd target [params] :[extinfo]",
- ### then other variables are provided, with origin and target automatically converted to Channel and User instances as needed.
+ # Called when a line of data is received from the IRC server. If line also matches ":origin cmd target [params] :[extinfo]",
+ # then other variables are provided, with origin and target
+ # automatically converted to Channel and User instances as needed.
pass
def onSend(self, IRC, origin, line, cmd=None, target=None, targetprefix=None, params=None, extinfo=None):
- ### Called when a line of data is sent to the IRC server. If line also matches "cmd target [params] :[extinfo]",
- ### then other variables are provided, with target automatically converted to Channel and User instances as needed.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
+ # Called when a line of data is sent to the IRC server. If line also matches "cmd target [params] :[extinfo]",
+ # then other variables are provided, with target automatically converted to Channel and User instances as needed.
+ # The variable origin refers to a class instance voluntarily
+ # identifying itself as that which requested data be sent.
pass
def onMOTD(self, IRC, motdgreet, motd, motdend):
- ### Called when MOTD is received.
+ # Called when MOTD is received.
print "onMOTD:", motdgreet
for line in motd:
print line
print motdend
def onJoin(self, IRC, user, channel):
- ### Called when somebody joins a channel, includes bot.
+ # Called when somebody joins a channel, includes bot.
print "onJoin:", user, channel
def onMeJoin(self, IRC, channel):
- ### Called when the bot enters the channel.
+ # Called when the bot enters the channel.
print "onMeJoin:", channel
def onChanMsg(self, IRC, user, channel, targetprefix, msg):
- ### Called when someone sends a PRIVMSG to channel.
- print "onChanMsg: [%s%s] <%s> %s" % (
- targetprefix, channel.name, user.nick, msg)
+ # Called when someone sends a PRIVMSG to channel.
+ print "onChanMsg: [%s%s] <%s> %s" % (targetprefix, channel.name, user.nick, msg)
def onChanAction(self, IRC, user, channel, targetprefix, action):
- ### Called when someone sends an action (/me) to channel.
- print "onChanAction: [%s%s] *%s %s" % (
- targetprefix, channel.name, user.nick, action)
+ # Called when someone sends an action (/me) to channel.
+ print "onChanAction: [%s%s] *%s %s" % (targetprefix, channel.name, user.nick, action)
def onSendChanMsg(self, IRC, origin, channel, targetprefix, msg):
- ### Called when bot sends a PRIVMSG to channel.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
- print "onSendChanMsg: [%s%s] <%s> %s" % (
- targetprefix, channel.name, IRC.identity.nick, 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.
+ print "onSendChanMsg: [%s%s] <%s> %s" % (targetprefix, channel.name, IRC.identity.nick, msg)
def onSendChanAction(self, IRC, origin, channel, targetprefix, action):
- ### origin is the source of the channel message
- ### Called when bot sends an action (/me) to channel.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
- print "onSendChanAction: [%s%s] *%s %s" % (
- targetprefix, channel.name, IRC.identity.nick, 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.
+ print "onSendChanAction: [%s%s] *%s %s" % (targetprefix, channel.name, IRC.identity.nick, action)
def onPrivMsg(self, IRC, user, msg):
- ### Called when someone sends a PRIVMSG to the bot.
+ # Called when someone sends a PRIVMSG to the bot.
print "onPrivMsg: <%s> %s" % (user.nick, msg)
def onPrivAction(self, IRC, user, action):
- ### Called when someone sends an action (/me) to the bot.
+ # Called when someone sends an action (/me) to the bot.
print "onPrivAction: *%s %s" % (user.nick, action)
def onSendPrivMsg(self, IRC, origin, user, msg):
- ### Called when bot sends a PRIVMSG to a user.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
+ # Called when bot sends a PRIVMSG to a user.
+ # The variable origin refers to a class instance voluntarily
+ # identifying itself as that which requested data be sent.
print "onSendPrivMsg: <%s> %s" % (IRC.identity.nick, msg)
def onSendPrivAction(self, IRC, origin, user, action):
- ### Called when bot sends an action (/me) to a user.
- ### The variable origin refers to a class instance voluntarily identifying itself as that which requested data be sent.
+ # 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.
print "onSendPrivAction: *%s %s" % (IRC.identity.nick, action)
def onNickChange(self, IRC, user, newnick):
- ### Called when somebody changes nickname.
+ # Called when somebody changes nickname.
print "onNickChange:", user, newnick
def onMeNickChange(self, IRC, newnick):
- ### Called when the bot changes nickname.
+ # Called when the bot changes nickname.
print "onMeNickChange:", newnick
def onPart(self, IRC, user, channel, partmsg):
- ### Called when somebody parts the channel, includes bot.
+ # Called when somebody parts the channel, includes bot.
print "onPart:", user, channel, partmsg
def onMePart(self, IRC, channel, partmsg):
- ### Called when the bot parts the channel.
+ # Called when the bot parts the channel.
print "onMePart:", channel, partmsg
def onKick(self, IRC, kicker, channel, kicked, kickmsg):
- ### Called when somebody is kicked from the channel, includes bot.
+ # Called when somebody is kicked from the channel, includes bot.
print "onKick:", kicker, channel, kicked, kickmsg
def onMeKick(self, IRC, channel, kicked, kickmsg):
- ### Called when the bot kicks somebody from the channel.
+ # Called when the bot kicks somebody from the channel.
print "onMeKick:", channel, kicked, kickmsg
def onMeKicked(self, IRC, kicker, channel, kickmsg):
- ### Called when the bot is kicked from the channel.
+ # Called when the bot is kicked from the channel.
print "onMeKicked:", kicker, channel, kickmsg
def onQuit(self, IRC, user, quitmsg):
- ### Called when somebody quits IRC.
+ # Called when somebody quits IRC.
print "onQuit:", user, quitmsg
def onNames(self, IRC, channel, channame, endmsg, nameslist):
- ### Called when a NAMES list is received.
+ # Called when a NAMES list is received.
print "onNames:", channel, channame, endmsg, nameslist
def onWhois(self, IRC, **whoisdata):
- ### Called when a WHOIS reply is received.
+ # Called when a WHOIS reply is received.
print "onWhois:", whoisdata
def onWho(self, IRC, params, wholist, endmsg):
- ### Called when a WHO list is received.
+ # Called when a WHO list is received.
print "onWho:", params
for item in wholist:
print item
print endmsg
def onList(self, IRC, chanlistbegin, chanlist, endmsg):
- ### Called when a channel list is received.
+ # Called when a channel list is received.
print "onList:", chanlistbegin
for item in chanlist:
print item
print endmsg
def onTopic(self, IRC, channel, topic):
- ### Called when channel topic is received via 332 response.
+ # Called when channel topic is received via 332 response.
print "onTopic:", channel, topic
def onTopicSet(self, IRC, user, channel, topic):
- ### Called when channel topic is changed.
+ # Called when channel topic is changed.
print "onChannelSet:", user, channel, topic
def onChanModeSet(self, IRC, user, channel, modedelta):
- ### Called when channel modes are changed.
- ### modedelta is a list of tuples of the format ("+x", parameter), ("+x", None) if no parameter is provided.
+ # Called when channel modes are changed.
+ # modedelta is a list of tuples of the format ("+x", parameter), ("+x",
+ # None) if no parameter is provided.
print "onChanModeSet:", user, channel, modedelta
def onChannelModes(self, IRC, channel, modedelta):
- ### Called when channel modes are received via 324 response.
+ # Called when channel modes are received via 324 response.
print "onChannelModes:", channel, modedelta
def onBan(self, IRC, user, channel, banmask):
- ### Called when a ban is set.
+ # Called when a ban is set.
print "onBan:", user, channel, banmask
def onMeBan(self, IRC, user, channel, banmask):
- ### Called when a ban matching bot is set.
+ # Called when a ban matching bot is set.
print "Help! I'm being banned!", user, channel, banmask
def onUnban(self, IRC, user, channel, banmask):
- ### Called when a ban is removed.
+ # Called when a ban is removed.
print "onUnban:", user, channel, banmask
def onMeUnban(self, IRC, user, channel, banmask):
- ### Called when a ban on the bot is removed.
+ # Called when a ban on the bot is removed.
print "Squeee!!! I've been unbanned!", user, channel, banmask
def onOp(self, IRC, user, channel, modeuser):
- ### Called when user gives ops to modeuser is set.
+ # Called when user gives ops to modeuser is set.
print "onOp:", user, channel, modeuser
def onMeOp(self, IRC, user, channel):
- ### Called when user ops bot.
+ # Called when user ops bot.
print "onMeOp", user, channel
def onDeop(self, IRC, user, channel, modeuser):
- ### Called when user deops modeuser.
+ # Called when user deops modeuser.
print "onDeop:", user, channel, modeuser
def onMeDeop(self, IRC, user, channel):
- ### Called when user deops bot.
+ # Called when user deops bot.
print "onMeDeop", user, channel
- ### There are a few other event handlers supported by the irc.Connection class. Furthermore, one can program
- ### numeric-based handlers to handle numeric events that are not captured by a named event handler as follows:
+ # There are a few other event handlers supported by the irc.Connection class. Furthermore, one can program
+ # numeric-based handlers to handle numeric events that are not captured by
+ # a named event handler as follows:
def onNNN(self, IRC, params, extinfo):
- ### Where NNN is a three-digit (leading zeros if necessary) code used to handle a numeric NNN event.
+ # Where NNN is a three-digit (leading zeros if necessary) code used to
+ # handle a numeric NNN event.
pass
diff --git a/wallet.py b/wallet.py
index a5126d5..7f355db 100644
--- a/wallet.py
+++ b/wallet.py
@@ -4,27 +4,30 @@ import Crypto.Cipher.Blowfish
import os
import getpass
from threading import Lock
+from collections import OrderedDict
class Wallet(dict):
+
def __init__(self, filename):
+ self.filename = filename
self.lock = Lock()
if os.path.isfile(filename):
self.f = open(filename, "rb+")
self.passwd = getpass.getpass()
self.crypt = Crypto.Cipher.Blowfish.new(self.passwd)
contents_encrypted = self.f.read()
- contents = self.crypt.decrypt(contents_encrypted +
- "\x00"*((-len(contents_encrypted))%8))
+ contents = self.crypt.decrypt(
+ contents_encrypted + "\x00" * ((-len(contents_encrypted)) % 8))
if contents.startswith(self.passwd):
self.update(dict(pickle.loads(contents[len(self.passwd):])))
else:
self.f.close()
- raise BaseException("Incorrect Password")
+ raise BaseException, "Incorrect Password"
else:
self.f = open(filename, "wb+")
passwd = self.passwd = None
- while passwd is None or passwd != self.passwd:
+ while passwd == None or passwd != self.passwd:
passwd = getpass.getpass("Enter new password: ")
self.passwd = getpass.getpass("Confirm new password: ")
if passwd != self.passwd:
@@ -33,13 +36,22 @@ class Wallet(dict):
self.flush()
def flush(self):
- contents = self.passwd+pickle.dumps(self.items(), protocol=2)
+ contents = self.passwd + pickle.dumps(self.items(), protocol=2)
self.lock.acquire()
try:
self.f.seek(0)
- self.f.write(self.crypt.encrypt(
- contents+"\x00"*((-len(contents))%8)))
+ self.f.write(
+ self.crypt.encrypt(contents + "\x00" * ((-len(contents)) % 8)))
self.f.truncate()
self.f.flush()
finally:
self.lock.release()
+
+ def json(self):
+ return OrderedDict([("class", "wallet.Wallet"), ("filename", self.filename)])
+
+ def __repr__(self):
+ return "<Wallet: %s>" % self.filename
+
+ def __str__(self):
+ return "<Wallet: %s>" % self.filename