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