summaryrefslogtreecommitdiff
path: root/irc.py
diff options
context:
space:
mode:
Diffstat (limited to 'irc.py')
-rw-r--r--irc.py3198
1 files changed, 2000 insertions, 1198 deletions
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