summaryrefslogtreecommitdiff
path: root/irc.py
diff options
context:
space:
mode:
Diffstat (limited to 'irc.py')
-rw-r--r--irc.py4668
1 files changed, 3434 insertions, 1234 deletions
diff --git a/irc.py b/irc.py
index 0cb82fb..bb2a03f 100644
--- a/irc.py
+++ b/irc.py
@@ -1,5 +1,6 @@
#!/usr/bin/python
-from threading import Thread, Event, Lock
+from threading import Thread, Condition, currentThread
+from threading import RLock as Lock
import re
import time
import sys
@@ -10,23 +11,51 @@ import platform
import traceback
import ssl
import glob
-import iqueue as Queue
+from collections import deque, OrderedDict
+import chardet
+import codecs
+import new
+import inspect
+import warnings
+import random
+
+__all__ = ["Connection", "Channel", "ChanList",
+ "User", "UserList", "Config", "timestamp"]
+
+
+def autodecode(s):
+ try:
+ return s.decode("utf8")
+ except UnicodeDecodeError:
+ # Attempt to figure encoding
+ detected = chardet.detect(s)
+ try:
+ return s.decode(detected['encoding'])
+ except UnicodeDecodeError:
+ return s.decode("utf8", "replace")
-def timestamp():
- t = time.time()
- ms = 1000*t%1000
- ymdhms = time.localtime(t)
- tz = time.altzone if ymdhms.tm_isdst else time.timezone
- sgn = "-" if tz >= 0 else "+"
- return "%04d-%02d-%02d %02d:%02d:%02d.%03d%s%02d:%02d"%(ymdhms[:6]+(1000*t%1000, sgn, abs(tz)/3600, abs(tz)/60%60))
+class AddonWarning(Warning):
+ pass
+
+
+class ConnectionWarning(Warning):
+ pass
+
+
+class AddonError(BaseException):
+ pass
class InvalidName(BaseException):
+
+ """Raised when an invalid string is passed of as a nickname."""
pass
class InvalidPrefix(BaseException):
+
+ """Raised when an string with an invalid prefix is passed of as a channel name."""
pass
@@ -34,1231 +63,3142 @@ class InvalidCharacter(BaseException):
pass
-class Connection(Thread):
- def __init__(self, server, nick="ircbot", username="python", realname="Python IRC Library", passwd=None, port=None, ipv6=False, ssl=False, autoreconnect=True, log=sys.stderr, timeout=300, retrysleep=5, maxretries=15, onlogin=None):
- self.__name__ = "pyIRC"
- self.__version__ = "1.1"
- self.__author__ = "Brian Sherson"
- self.__date__ = "December 1, 2013"
+class ConnectionTimedOut(BaseException):
- if port is None:
- self.port = 6667 if not ssl else 6697
- else:
+ """Raised when the connection times out during a blocked Channel.join() or Channel.part() call."""
+ pass
+
+
+class ConnectionClosed(BaseException):
+ pass
+
+
+class RequestTimedOut(BaseException):
+
+ """Raised when a timeout is reached during a blocked Channel.join() or Channel.part() call."""
+ pass
+
+
+class NotConnected(BaseException):
+
+ """Raised when attempting to send data to a server when not connected."""
+ pass
+
+
+class BannedFromChannel(BaseException):
+
+ """Raised in a blocked Channel.join() call when server returns a 474 reply (Banned from Channel)."""
+ pass
+
+
+class RedirectedJoin(BaseException):
+
+ """Raised in a blocked Channel.join() call when server returns a 470 reply (Channel redirect)."""
+ pass
+
+
+class ChannelFull(BaseException):
+ pass
+
+
+class InviteOnly(BaseException):
+ pass
+
+
+class NotOnChannel(BaseException):
+ pass
+
+
+class NoSuchChannel(BaseException):
+ pass
+
+
+class BadChannelKey(BaseException):
+ pass
+
+
+class BadChannelMask(BaseException):
+ pass
+
+
+class TooManyChannels(BaseException):
+ pass
+
+
+class Unavailable(BaseException):
+ pass
+
+
+class Cbaned(BaseException):
+ pass
+
+
+class ActionAlreadyRequested(BaseException):
+ pass
+
+
+class OpersOnly(BaseException):
+ pass
+
+
+class OperCreateOnly(BaseException):
+ pass
+
+
+class SSLOnly(BaseException):
+ pass
+
+
+class AlreadyJoined(BaseException):
+ pass
+
+
+class AlreadyConnected(BaseException):
+ pass
+
+
+class RegistrationRequired(BaseException):
+ pass
+
+
+class RejoinDelay(BaseException):
+ pass
+
+_rfc1459casemapping = string.maketrans(string.ascii_uppercase + r'\[]~',
+ string.ascii_lowercase + r'|{}^').decode("ISO-8859-2")
+
+# The IRC RFC does not permit the first character in a nickname to be a
+# numeral. However, this is not always adhered to.
+_nickmatch = r"^[A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]+$"
+
+_intmatch = r"^\d+$"
+_chanmatch = r"^[%%s][^%s\s\n]*$" % re.escape("\x07,")
+_targchanmatch = r"^([%%s]?)([%%s][^%s\s\n]*)$" % re.escape("\x07,")
+_usermatch = r"^[A-Za-z0-9\-\^\`\\\|\_\{\}\[\]]+$"
+_realnamematch = r"^[^\n]*$"
+_ircmatch = r"^(?::(.+?)(?:!(.+?)@(.+?))?\s+)?([A-Za-z0-9]+?)\s*(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$"
+_ctcpmatch = "^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$"
+_prefixmatch = r"\((.*)\)(.*)"
+_defaultchanmodes = u"b,k,l,imnpst".split(",")
+_defaultprefix = ("ov", "@+")
+_defaultchantypes = "&#+!"
+_capmodifiers = "~=-"
+
+_privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=(
+ "Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice"))
+_maskmodeeventnames = dict(b=("Ban", "Unban"), e=(
+ "BanExcept", "UnbanExcept"), I=("Invite", "Uninvite"))
+
+exceptcodes = {489: SSLOnly, 384: Cbaned, 403: NoSuchChannel, 405: TooManyChannels, 442: NotOnChannel, 470: RedirectedJoin, 471: ChannelFull, 473: InviteOnly, 474:
+ BannedFromChannel, 475: BadChannelKey, 476: BadChannelMask, 520: OpersOnly, 437: Unavailable, 477: RegistrationRequired, 495: RejoinDelay, 530: OperCreateOnly}
+
+
+def timestamp():
+ t = time.time()
+ ms = 1000 * t % 1000
+ ymdhms = time.localtime(t)
+ tz = time.altzone if ymdhms.tm_isdst else time.timezone
+ sgn = "-" if tz >= 0 else "+"
+ return "%04d-%02d-%02d %02d:%02d:%02d.%03d%s%02d:%02d" % (ymdhms[:6] + (1000 * t % 1000, sgn, abs(tz) / 3600, abs(tz) / 60 % 60))
+
+
+class Connection(object):
+ __doc__ = "Manages a connection to an IRC network. Includes support for addons."
+ __name__ = "pyIRC"
+ __version__ = "2.1"
+ __author__ = "Brian Sherson"
+ __date__ = "February 21, 2014"
+
+ def __init__(
+ self, server, port=None, ipvers=(socket.AF_INET6, socket.AF_INET), secure=False, passwd=None,
+ nick="ircbot", username="python", realname="Python IRC Library",
+ requestcaps=[], starttls=False, protoctl=[],
+ autoreconnect=True, retrysleep=5, maxretries=15,
+ timeout=300, quietpingpong=True, pinginterval=60, addons=[], autostart=False):
+ """__init__(server[, ...])
+
+ Constructor for the Connection class.
+
+ Arguments:
+
+ server: Server name. Can provide host name or IP address.
+ port: Port to use, or automatically selected if port=None.
+ ipvers: Tuple of IP protocols to try.
+ secure: Use SSL.
+ passwd: Password to be sent with PASS during registration process, or None.
+ nick: A nickname, or list of nicknames.
+ username: Username that is requested with USER command during registration process.
+ realname: Desired GECOS.
+ requestcaps: List of capabilities to request on connect.
+ protoctl: Protocols to request when support is detected in 005 response.
+ autoreconnect: Reconnect automatically when disconnected unexpectedly.
+ retrysleep: Number of seconds to wait between connection attempts.
+ maxretries: Number of connection attempts before giving up, or -1 to try indefinitely.
+ timeout: Read timeout.
+ quietpingpong: Suppress logging and events on PING and PONG events.
+ pinginterval: Amount of time of not receiving data from a server aftr which a ping request is to be sent.
+ addons: List of addons that should be initialized with this instance. Items of this list are either instances of addons or dict objects
+ containing keyword arguments to be used to configure addons.
+ autostart: Automatically start connection to IRC server upon initialization.
+ """
+ if port is None or (type(port) == int and 0 < port < 65536):
self.port = port
+ else:
+ raise ValueError, "Invalid value for 'port'"
- if type(nick) in (str, unicode):
- self.nick = [nick]
- elif type(nick) in (list, tuple):
+ if re.match(_nickmatch, nick) if isinstance(nick, (str, unicode)) else all([re.match(_nickmatch, n) for n in nick]) if isinstance(nick, (list, tuple)) else False:
self.nick = nick
+ else:
+ raise ValueError, "Invalid value for 'nick'"
+
+ if re.match(_realnamematch, realname):
+ self.realname = realname
+ else:
+ raise InvalidCharacter
+
+ if re.match(_usermatch, username):
+ self.username = username
+ else:
+ raise InvalidCharacter
+
+ if passwd == None or "\n" not in passwd:
+ self.passwd = passwd
+ else:
+ raise InvalidCharacter
- self.realname = realname
- self.username = username
- self.passwd = passwd
self.server = server
- self.ssl = ssl
- self.ipv6 = ipv6
+ self.secure = secure
+ self.ipvers = ipvers if type(ipvers) == tuple else (ipvers,)
+
+ self.protoctl = protoctl
+
+ if type(autoreconnect) == bool:
+ self.autoreconnect = autoreconnect
+ else:
+ raise ValueError, "Invalid value for 'autoreconnect'"
+
+ if isinstance(maxretries, (int, long)):
+ self.maxretries = maxretries
+ else:
+ raise ValueError, "Invalid value for 'maxretries'"
+
+ if isinstance(timeout, (int, long)):
+ self.timeout = timeout
+ else:
+ raise ValueError, "Invalid value for 'timeout'"
+
+ if isinstance(retrysleep, (int, long, float)) and retrysleep >= 0:
+ self.retrysleep = retrysleep
+ else:
+ raise ValueError, "Invalid value for 'retrysleep'"
+
+ if type(quietpingpong) == bool:
+ self.quietpingpong = quietpingpong
+ else:
+ raise ValueError, "Invalid value for 'quietpingpong'"
+
+ if type(starttls) == bool:
+ if starttls and secure:
+ warnings.warn(
+ "Cannot use STARTTLS when secure=True", ConnectionWarning)
+ self.starttls = starttls
+ else:
+ raise ValueError, "Invalid value for 'starttls'"
+
+ if isinstance(requestcaps, (list, tuple)):
+ self.requestcaps = list(requestcaps)
+ else:
+ raise ValueError, "Invalid value for 'requestcaps'"
+
+ if type(pinginterval) in (int, long):
+ self.pinginterval = pinginterval
+ else:
+ raise ValueError, "Invalid value for 'pinginterval'"
+
+ self._quitexpected = False
+ self.log = sys.stdout
+
+ self.lock = Lock()
- self.connected = False
- self.registered = False
- self.connection = None
+ self._loglock = Lock()
+ self._outlock = Lock()
+ self._sendline = Condition(self._outlock)
+ self._connecting = Condition(self.lock)
+ self._disconnecting = Condition(self.lock)
+ self._outgoing = deque()
- self.autoreconnect = autoreconnect
- self.maxretries = maxretries
- self.timeout = timeout
- self.retrysleep = retrysleep
+ self._sendhandlerthread = None
+ self._recvhandlerthread = None
- self.quitexpected = False
- self.log = log
+ # Initialize IRC environment variables
+ self.users = UserList(context=self, withdict=True)
+ self.channels = ChanList(context=self, withdict=True)
+ # We are going to try something different, to try to make searching quicker.
+ # self.users={}
+ # self.channels={}
+
+ self.servers = ServerList(context=self)
self.addons = []
- self.trusted = []
- ### Initialize IRC environment variables
- self.motdgreet = None
- self.motd = None
- self.motdend = None
+ self._init()
+ for conf in addons:
+ try:
+ if type(conf) == dict:
+ self.addAddon(**conf)
+ else:
+ self.addAddon(conf)
+ except:
+ pass
+
+ if autostart:
+ self.connect()
+
+ def _init(self):
+ self.ipver = None
+ self.addr = None
+ self._connected = False
+ self._registered = False
+ self._connection = None
+ self._starttls = False
+ self.trynick = 0
+
self.identity = None
- self.users = []
- self.channels = []
+
+ self.serv = None
+ self.welcome = None
+ self.hostinfo = None
+ self.servcreated = None
+ self.servinfo = None
+ self.serv005 = None
self.supports = {}
+ self.throttledata = []
+ self.throttled = False
+ self.enabledcaps = []
+ self.supportedcaps = []
+ self._requestedcaps = []
+ self._caplsrequested = False
- self.lock = Lock()
- self.loglock = Lock()
- self.sendlock = Lock()
- self.outgoing = Queue.Queue()
- self.outgoingthread = None
+ @property
+ def motdgreet(self):
+ return self.identity.server.motdgreet
- Thread.__init__(self)
+ @property
+ def motd(self):
+ return self.identity.server.motd
+
+ @property
+ def motdend(self):
+ return self.identity.server.motdend
+
+ @property
+ def connected(self):
+ return self._connected
+
+ @property
+ def registered(self):
+ return self._registered
def logwrite(self, *lines):
- with self.loglock:
+ """logwrite(*lines)
+
+ Writes one or more line to the log file, signed with a timestamp."""
+ with self._loglock:
ts = timestamp()
for line in lines:
- print >>self.log, "%s %s"%(ts, line)
+ print >>self.log, u"%s %s" % (ts, line)
self.log.flush()
- def logopen(self, filename):
- with self.loglock:
+ def logerror(self, *lines):
+ """logerror(*lines)
+
+ Prints lines and traceback sys.stderr and to the log file."""
+ exc, excmsg, tb = sys.exc_info()
+ lines = lines + tuple(traceback.format_exc().split("\n"))
+
+ # Print to log AND stderr
+ self.logwrite(*[u"!!! {line}".format(**vars()) for line in lines])
+ for line in lines:
+ print >>sys.stderr, line
+
+ def logopen(self, filename, encoding="utf8"):
+ """logopen(filename[, encoding])
+
+ Sets the log file to 'filename.'"""
+
+ with self._loglock:
ts = timestamp()
- newlog = open(filename, "a")
- if type(self.log) == file and not self.log.closed:
- print >>self.log, "%s ### Log file closed" % (ts)
+ newlog = codecs.open(filename, "a", encoding=encoding)
+ if isinstance(self.log, codecs.StreamReaderWriter) and not self.log.closed:
if self.log not in (sys.stdout, sys.stderr):
+ print >>self.log, "%s ### Log file closed" % (ts)
self.log.close()
self.log = newlog
print >>self.log, "%s ### Log file opened" % (ts)
self.log.flush()
- def event(self, method, modlist, exceptions=False, **params):
- timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(2,
- "0") for t in time.localtime()[0:6]])
+ # Used to call event handlers on all attached addons, when applicable.
+ def _event(self, addons, events, line=None, data=None, exceptions=False):
handled = []
unhandled = []
errors = []
- for k, addon in enumerate(modlist):
- if modlist.index(addon) < k:
+ for k, addon in enumerate(addons + [self]):
+ if addon in addons and addons.index(addon) < k:
+ # Duplicate
continue
- if method in dir(addon) and callable(getattr(addon, method)):
+
+ if type(addon) == Config:
+ addon = addon.addon
+
+ fellback = False # Switch this to True when a fallback is used so that we only call onOther once.
+
+ # Iterate through all events.
+ for (method, args, fallback) in events:
try:
- getattr(addon, method)(self, **params)
+ f = getattr(addon, method)
+ except AttributeError:
+ if fallback and not fellback and data:
+ try:
+ f = getattr(addon, "onOther")
+ except AttributeError:
+ unhandled.append(addon)
+ continue
+ args = dict(line=line, **data)
+ fellback = True
+ else:
+ unhandled.append(addon)
+ continue
+
+ if type(f) == new.instancemethod:
+ argspec = inspect.getargspec(f.im_func)
+ else:
+ argspec = inspect.getargspec(f)
+ if argspec.keywords == None:
+ args = {
+ arg: val for arg, val in args.items() if arg in argspec.args}
+ try:
+ f(self, **args)
except:
+ # print f, args
exc, excmsg, tb = sys.exc_info()
errors.append((addon, exc, excmsg, tb))
- self.logwrite(*["!!! Exception in addon %(addon)s" % vars()]+["!!! %s"%line for line in traceback.format_exc().split("\n")])
- print >>sys.stderr, "Exception in addon %(addon)s" % vars()
- print >>sys.stderr, traceback.format_exc()
+
+ self.logerror(u"Exception in addon {addon}".format(
+ **vars()), u"Function: %s" % f, u"Arguments: %s" % args)
+
if exceptions: # If set to true, we raise the exception.
raise
else:
handled.append(addon)
- else:
- unhandled.append(addon)
return (handled, unhandled, errors)
- def addAddon(self, addon, trusted=False, **params):
- if addon in self.addons:
- raise BaseException("Addon already added.")
+ def validateAddon(self, addon):
+ """validateAddon(addon)
+
+ Checks the addon's methods and issues warnings when a method's arguments do not line up with what is expected."""
+ supported = self.eventsupports()
+ keys = supported.keys()
+ for fname in dir(addon):
+ if fname in keys:
+ supportedargs = supported[fname]
+ elif re.match(r"^on(?:Send)?[A-Z]+$", fname):
+ supportedargs = (
+ "line", "origin", "target", "targetprefix", "params", "extinfo")
+ elif re.match(r"^on\d{3}$", fname):
+ supportedargs = (
+ "line", "origin", "target", "params", "extinfo")
+ else:
+ continue
+ func = getattr(addon, fname)
+ argspec = inspect.getargspec(func)
+ if type(func) == new.instancemethod:
+ funcargs = argspec.args[1:]
+ if argspec.defaults:
+ requiredargs = funcargs[:-len(argspec.defaults)]
+ else:
+ requiredargs = funcargs
+ contextarg = funcargs[0]
+ unsupported = [
+ arg for arg in requiredargs[1:] if arg not in supportedargs]
+ if len(unsupported):
+ warnings.warn(
+ "Function '%s' requires unsupported arguments: %s" %
+ (func.__name__, ", ".join(unsupported)), AddonWarning)
+ self.logwrite(
+ "!!! AddonWarning: Function '%s' requires unsupported arguments: %s" %
+ (func.__name__, ", ".join(unsupported)))
+ if argspec.keywords == None:
+ unsupported = [
+ arg for arg in supportedargs if arg not in funcargs[1:]]
+ if len(unsupported):
+ warnings.warn(
+ "Function '%s' does not accept supported arguments: %s" %
+ (func.__name__, ", ".join(unsupported)), AddonWarning)
+ self.logwrite(
+ "!!! AddonWarning: Function '%s' does not accept supported arguments: %s" %
+ (func.__name__, ", ".join(unsupported)))
+
+ def addAddon(self, addon, **params):
+ """addAddon(addon[, ...])
+
+ Configures and appends addon to self.addons.
+ Additional keyword arguments are passed onto addon.onAddonAdd whenever the method exists."""
+ self.validateAddon(addon)
+
with self.lock:
- self.event("onAddonAdd", [addon], exceptions=True, **params)
- self.addons.append(addon)
- if trusted:
- self.trusted.append(addon)
-
- def insertAddon(self, index, addon, trusted=False, **params):
- if addon in self.addons:
- raise BaseException("Addon already added.")
+ addoninstances = [
+ conf.addon if type(conf) == Config else conf for conf in self.addons]
+ if addon in addoninstances:
+ raise AddonError, "Addon already added."
+ conf = self._configureAddon(addon, **params)
+ self.addons.append(conf)
+ self.logwrite("*** Addon %s added." % repr(addon))
+
+ def insertAddon(self, index, addon, **params):
+ """insertAddon(index, addon[, ...])
+
+ The 'list.insert' version of addAddon."""
+ self.validateAddon(addon)
+
with self.lock:
- self.event("onAddonAdd", [addon], exceptions=True, **params)
- self.addons.insert(index, addon)
- if trusted:
- self.trusted.append(addon)
+ addoninstances = [
+ conf.addon if type(conf) == Config else conf for conf in self.addons]
+ if addon in addoninstances:
+ raise AddonError, "Addon already added."
+ conf = self._configureAddon(addon, **params)
+ self.addons.insert(index, conf)
+ self.logwrite("*** Addon %s inserted into index %d." %
+ (repr(addon), index))
+
+ # Configures an addon by calling the addon's onAddonAdd instance (if it
+ # exists) and returns the appropriate config object (or just the addon
+ # instance if no config) to put into self.addons
+ def _configureAddon(self, addon, **params):
+ if hasattr(addon, "onAddonAdd") and callable(addon.onAddonAdd):
+ try:
+ conf = addon.onAddonAdd(self, **params)
+ except:
+ self.logerror(
+ u"An exception has occurred while trying to configure addon {addon}.".format(**vars()))
+ raise
+ if conf is None:
+ return addon
+ return conf
+ elif params:
+ return Config(addon, **params)
+ else:
+ return addon
+
+ # Removes addon from self.addons
+ def rmAddon(self, addon):
+ """rmAddon(addon)
- def rmAddon(self, addon, **params):
+ Removes addon from self.addons."""
with self.lock:
- self.addons.remove(addon)
- self.event("onAddonRem", [addon], exceptions=True, **params)
- if addon in self.trusted:
- self.trusted.remove(addon)
-
- def run(self):
- privmodeeventnames = dict(q=("Owner", "Deowner"), a=("Admin", "Deadmin"), o=("Op", "Deop"), h=("Halfop", "Dehalfop"), v=("Voice", "Devoice"))
- maskmodeeventnames = dict(b=("Ban", "Unban"), e=(
- "BanExcept", "UnbanExcept"), I=("Invite", "Uninvite"))
- self.quitexpected = False
- whoisstarted = False
- nameslist = []
- wholist = []
- lists = {}
- nameschan = None
- server = self.server
- if self.ipv6 and ":" in server:
- server = "[%s]"%server
- port = self.port
+ addoninstances = [
+ conf.addon if type(conf) == Config else conf for conf in self.addons]
+
+ del self.addons[addoninstances.index(addon)]
+ self.logwrite("*** Addon %s removed." % repr(addon))
+ if hasattr(addon, "onAddonRem") and callable(addon.onAddonAdd):
+ try:
+ addon.onAddonRem(self)
+ except:
+ self.logerror(
+ u"An exception has occurred while trying to configure addon {addon}.".format(**vars()))
+
+ def connect(self, server=None, port=None, secure=None, ipvers=None, forcereconnect=False, blocking=False):
+ """connect([...])
+
+ Starts connection to the IRC server. Optional arguments server, port, secure, and ipvers can be
+ provided to override the current settings.
+ Use 'forcereconnect=True' to quit existing connection if already connected.
+ Use 'blocking=True' to wait until connection is established (or maxretries is exhausted)."""
+ if ipvers != None:
+ ipvers = ipvers if type(ipvers) == tuple else (ipvers,)
+ else:
+ ipvers = self.ipvers
+
+ server = server if server else self.server
+ port = port if port else self.port
+ secure = secure if secure != None else self.secure
+
+ with self._connecting:
+ if self.isAlive():
+ if forcereconnect:
+ self.quit("Changing server...", blocking=True)
+ else:
+ raise AlreadyConnected
+ with self._sendline:
+ self._outgoing.clear()
+ self._recvhandlerthread = Thread(target=self._recvhandler, name="Receive Handler", kwargs=dict(
+ server=server, port=port, secure=secure, ipvers=ipvers))
+ self._sendhandlerthread = Thread(
+ target=self._sendhandler, name="Send Handler")
+ self._recvhandlerthread.start()
+ self._sendhandlerthread.start()
+ if blocking:
+ self._connecting.wait()
+ if not self.connected:
+ raise NotConnected
+
+ def _connect(self, addr, ipver, secure, hostname=None):
+ """Makes a single attempt to connect to server."""
+ with self.lock:
+ if self._connected:
+ raise AlreadyConnected
+
+ if hostname:
+ if ipver == socket.AF_INET6:
+ addrstr = "{hostname} ([{addr[0]}]:{addr[1]})".format(
+ **vars())
+ else:
+ addrstr = "{hostname} ({addr[0]}:{addr[1]})".format(
+ **vars())
+ else:
+ if ipver == socket.AF_INET6:
+ addrstr = "[{addr[0]}]:{addr[1]}".format(**vars())
+ else:
+ addrstr = "{addr[0]}:{addr[1]}".format(**vars())
+ self.logwrite(
+ "*** Attempting connection to {addrstr}.".format(**vars()))
+ self._event(self.getalladdons(), [
+ ("onConnectAttempt", dict(), False)])
+
try:
+ connection = socket.socket(ipver, socket.SOCK_STREAM)
+ if secure:
+ connection.settimeout(self.timeout)
+ self._connection = ssl.wrap_socket(
+ connection, cert_reqs=ssl.CERT_NONE)
+ else:
+ self._connection = connection
+ self._connection.settimeout(self.timeout)
+ self._connection.connect(addr)
+ except socket.error:
+ exc, excmsg, tb = sys.exc_info()
+ self.logwrite(
+ "*** Connection to {addrstr} failed: {excmsg}.".format(**vars()))
+ with self.lock:
+ self._event(self.getalladdons(), [
+ ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)])
+ raise
+ else:
+ # Run onConnect on all addons to signal connection was established.
+ self._connected = True
with self.lock:
- self.event("onSessionOpen", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
+ self._event(
+ self.getalladdons(), [("onConnect", dict(), False)])
+ self.logwrite(
+ "*** Connection to {addrstr} established.".format(**vars()))
+ self.addr = addr
+ with self._connecting:
+ self._connecting.notifyAll()
+
+ def _tryaddrs(self, server, addrs, ipver, secure):
+ """Iterates through addrs until a connection is successful, returning True, or returning False when no connections are made.
+ Raises an exception when it detects Network is unreachable (e.g., IPv6 network is not available)."""
+ for addr in addrs:
+ try:
+ if server == addr[0]:
+ self._connect(addr=addr, secure=secure, ipver=ipver)
+ else:
+ self._connect(
+ hostname=server, addr=addr, secure=secure, ipver=ipver)
+ except socket.error, msg:
+ if self._quitexpected:
+ sys.exit()
+ if msg.errno == 101: # Network is unreachable, will pass the exception on.
+ raise
+ if self.retrysleep > 0:
+ time.sleep(self.retrysleep)
+ if self._quitexpected:
+ sys.exit()
+ else:
+ return True
+ return False
+
+ def _tryipver(self, server, port, ipver, secure):
+ """Attempts to resolve 'server' to a one or more IP addresses, then tries to establish a connection."""
+ if ipver == socket.AF_INET6:
+ self.logwrite(
+ "*** Attempting to resolve {server} to an IPv6 address...".format(**vars()))
+ else:
+ self.logwrite(
+ "*** Attempting to resolve {server}...".format(**vars()))
- self.logwrite("### Log session started")
+ try:
+ addrs = socket.getaddrinfo(
+ server, port if port is not None else 6697 if self.secure else 6667, ipver)
+ except socket.gaierror, msg:
+ self.logwrite("*** Resolution failed: {msg}.".format(**vars()))
+ raise
+
+ # Weed out duplicates
+ addrs = list(
+ set([sockaddr for family, socktype, proto, canonname, sockaddr in addrs if family == ipver]))
+
+ n = len(addrs)
+ if n == 1:
+ addr = addrs[0]
+ self.logwrite(
+ "*** Name {server} resolves to {addr[0]}.".format(**vars()))
+ else:
+ self.logwrite(
+ "*** Name {server} resolves to {n} addresses, choosing one at random until success.".format(**vars()))
+ random.shuffle(addrs)
- attempt = 1
- while True: # An entire connection lives within this while loop. When the connection fails, will try to reestablish, unless self.autoreconnect is set to False.
- while True: # Enter retry loop
- self.logwrite("*** Attempting connection to %(server)s:%(port)s." % vars())
+ return self._tryaddrs(server, addrs, ipver, secure)
- with self.lock:
- self.event("onConnectAttempt", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
+ def _tryipvers(self, server, port, ipvers, secure):
+ """Attempts to try a connection for each IP version in ipvers until a connection is successful."""
+ for ipver in ipvers:
+ try:
+ ret = self._tryipver(server, port, ipver, secure)
+ except socket.gaierror, msg:
+ if msg.errno == -2: # Name or service not known. Again, just try next ipver.
+ continue
+ else:
+ raise
+ except socket.error, msg:
+ if msg.errno == 101: # Don't err out, just try next ipver.
+ continue
+ else:
+ raise
+ else:
+ if ret:
+ self.ipver = ipver
+ return True
+ return False
+
+ def _procrecvline(self, line):
+ """Called whenever a line of data is received from the IRC server."""
+ matches = re.findall(_ircmatch, line)
+
+ # We have a match!
+ if len(matches):
+ (origin, username, host, cmd, target, params, extinfo) = matches[0]
+ unhandled = []
+
+ if re.match(_intmatch, cmd):
+ cmd = int(cmd) # Code is a numerical response
+ else:
+ cmd = cmd.upper()
- try:
- if self.ssl:
- s = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM)
- s.settimeout(self.timeout)
- self.connection = ssl.wrap_socket(s, cert_reqs=ssl.CERT_NONE)
- else:
- self.connection = socket.socket(socket.AF_INET6 if self.ipv6 else socket.AF_INET, socket.SOCK_STREAM)
- self.connection.connect((self.server, self.port, 0, 0) if self.ipv6 else (self.server, self.port))
- except socket.error:
- exc, excmsg, tb = sys.exc_info()
- self.event("onConnectFail", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), exc=exc, excmsg=excmsg, tb=tb)
- self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars())
- else:
- self.connected = True
- self.connection.settimeout(self.timeout)
+ if cmd not in ("PING", "PONG") or not self.quietpingpong:
+ self.logwrite("<<< %s" % line)
+
+ if origin == "" and cmd == "PING":
+ self.send(u"PONG :%s" % extinfo)
- ### Setting up thread responsible for sending data back to IRC server.
- self.outgoing._interrupted = False
- self.outgoingthread = Outgoing(self)
- self.outgoingthread.daemon = True
- self.outgoingthread.start()
+ with self.lock:
+ data = dict(origin=origin, cmd=cmd, target=target,
+ targetprefix=None, params=params, extinfo=extinfo)
+
+ if username and host and self._registered:
+ nickname = origin
+ origin = self.user(origin)
+ if origin.nick != nickname:
+ # Origin nickname case has changed
+ origin.user = nickname
+ if origin.username != username:
+ # Origin username has changed
+ origin.username = username
+ if origin.host != host:
+ # Origin host has changed
+ origin.host = host
+ else:
+ origin = self.getserver(origin)
+
+ # Check to see if target matches a channel (optionally with
+ # prefix)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ chantypes = self.supports.get("CHANTYPES", _defaultchantypes)
+ chanmatch = re.findall(
+ _targchanmatch % (re.escape(prefix[1]), re.escape(chantypes)), target)
+
+ if chanmatch:
+ targetprefix, channame = chanmatch[0]
+ target = self.channel(channame)
+ if target.name != channame:
+ # Target channel name has changed case
+ target.name = channame
+
+ # Check to see if target matches a valid nickname. Do NOT
+ # convert target to User instance if cmd is NICK.
+ elif re.match(_nickmatch, target) and cmd in ("PRIVMSG", "NOTICE", "MODE", "INVITE", "KILL") and self._registered:
+ targetprefix = ""
+ target = self.user(target)
+
+ # Otherwise, target is just left as a string
+ else:
+ targetprefix = ""
+
+ data = dict(origin=origin, cmd=cmd, target=target,
+ targetprefix=targetprefix, params=params, extinfo=extinfo)
- ### Run onConnect on all addons to signal connection was established.
- self.event("onConnect", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
- self.logwrite("*** Connection to %(server)s:%(port)s established." % vars())
- break
+ # Parse
- if self.quitexpected:
+ # Takes the given data and runs it through a parse method to determine what addon methods should be called later, and prepares the arguments
+ # to be passed to each of these methods.
+ # This part does not update the IRC state.
+ parsename = (
+ "parse%03d" if type(cmd) == int else "parse%s") % cmd
+
+ # This is the case that there is a parse method specific to the
+ # given cmd.
+ if hasattr(self, parsename) and callable(getattr(self, parsename)):
+ parsemethod = getattr(self, parsename)
+ try:
+ ret = parsemethod(
+ origin, target, targetprefix, params, extinfo)
+ addons, events = ret if ret is not None else (
+ self.addons, [])
+ except:
+ self.logerror(
+ u"There was an error in parsing the following line:", line)
+ return
+ else:
+ addons = self.addons
+ if type(cmd) == int:
+ events = [
+ ("on%03d" % cmd, dict(line=line, origin=origin, target=target, params=params, extinfo=extinfo), True)]
+ else:
+ events = [
+ ("on%s" % cmd.upper(), dict(line=line, origin=origin, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), True)]
+
+ # Suppress pings and pongs if self.quietpingpong is set to True
+ if cmd in ("PING", "PONG") and self.quietpingpong:
+ return
+
+ # Send parsed data to addons having onRecv method first
+ self._event(
+ addons, [("onRecv", dict(line=line, **data), False)], line, data)
+
+ # Support for further addon events is taken care of here. We also treat the irc.Connection instance itself as an addon for the purpose of
+ # tracking the IRC state, and should be invoked *last*.
+ self._event(addons, events, line, data)
+
+ def _recvhandler(self, server, port, ipvers, secure):
+ """Function that is run as a separate thread, both managing the connection and handling data coming from the IRC server."""
+ if currentThread() != self._recvhandlerthread: # Enforce that this function must only be run from within self._sendhandlerthread.
+ raise RuntimeError, "This function is designed to run in its own thread."
+
+ try:
+ with self.lock:
+ self._event(self.getalladdons(), [
+ ("onSessionOpen", dict(), False)])
+
+ self.logwrite("### Session started")
+
+ ipvers = ipvers if type(ipvers) == tuple else (ipvers,)
+
+ # Autoreconnect loop
+ while True:
+ attempt = 1
+
+ # Autoretry loop
+ while True:
+ servisip = False
+ for ipver in ipvers: # Check to see if address is a valid ip address instead of host name
+ try:
+ socket.inet_pton(ipver, server)
+ except socket.error:
+ continue # Not a valid ip address under this ipver.
+ # Is a valid ip address under this ipver.
+ if ipver == socket.AF_INET6:
+ self._tryaddrs(
+ server, [(server, port, 0, 0)], ipver, secure)
+ else:
+ ret = self._tryaddrs(
+ server, [(server, port)], ipver, secure)
+ servisip = True
+ break
+ # Otherwise, we assume server is a hostname
+ if not servisip:
+ ret = self._tryipvers(server, port, ipvers, secure)
+ if ret:
+ self.server = server
+ self.port = port
+ self.ipvers = ipvers
+ self.secure = secure
+ break
+ if self._quitexpected:
sys.exit()
- if attempt < self.maxretries or self.maxretries == -1:
+ if self.retrysleep > 0:
time.sleep(self.retrysleep)
- if self.quitexpected:
+ if self._quitexpected:
+ sys.exit()
+ if attempt < self.maxretries or self.maxretries < 0:
+ if self._quitexpected:
sys.exit()
attempt += 1
else:
- self.logwrite("*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars())
+ self.logwrite(
+ "*** Maximum number of attempts reached. Giving up. (%(server)s:%(port)s)" % vars())
+ with self._connecting:
+ self._connecting.notifyAll()
sys.exit()
- ### Connection succeeded
+ # Connection succeeded
try:
- ### Attempt initial registration.
- nick = self.nick[0]
- trynick = 0
- if self.passwd:
- self.raw("PASS :%s" % self.passwd.split(
- "\n")[0].rstrip())
- self.raw("NICK :%s" % nick.split("\n")[0].rstrip())
- self.raw("USER %s * * :%s" % (self.username.split("\n")[0].rstrip(), self.realname.split("\n")[0].rstrip()))
-
- ### Initialize buffers
+ pingreq = None
+ with self._sendline:
+ self._sendline.notify()
+
+ # Attempt initial registration.
+ # nick=self.nick[0]
+ # if self.passwd:
+ #self.send(u"PASS %s" % self.passwd)
+ # self._trynick()
+
+ # Initialize buffers
linebuf = []
readbuf = ""
while True: # Main loop of IRC connection.
while len(linebuf) == 0: # Need Moar Data
- read = self.connection.recv(512)
-
- ### If read was empty, connection is terminated.
+ read = self._connection.recv(512)
+ with self._sendline:
+ if pingreq and pingreq in self._outgoing:
+ self._outgoing.remove(pingreq)
+ pingreq = (time.time() + self.pinginterval, u"PING %s %s" % (
+ (self.identity.nick, self.identity.server) if self.identity else ("*", self.server)), self)
+ self._outgoing.append(pingreq)
+ self._sendline.notify()
+
+ # If read was empty, connection is terminated.
if read == "":
sys.exit()
- ### If read was successful, parse away!
+ # If read was successful, parse away!
readbuf += read
lastlf = readbuf.rfind("\n")
if lastlf >= 0:
- linebuf.extend(string.split(readbuf[0:lastlf],
- "\n"))
- readbuf = readbuf[lastlf+1:]
+ linebuf.extend(
+ string.split(readbuf[0:lastlf], "\n"))
+ readbuf = readbuf[lastlf + 1:]
- line = string.rstrip(linebuf.pop(0))
-
- ### If received PING, then just pong back transparently.
- ping = re.findall("^PING :?(.*)$", line)
- if len(ping):
- with self.lock:
- self.connection.send("PONG :%s\n" % ping[0])
- continue
-
- self.logwrite("<<< %(line)s" % vars())
-
- ### Attempts to match against pattern ":src cmd target params :extinfo"
- matches = re.findall(r"^:(.+?)(?:!(.+?)@(.+?))?\s+(.+?)(?:\s+(.+?)(?:\s+(.+?))??)??(?:\s+:(.*))?$", line)
-
- ### We have a match!
- if len(matches):
- parsed = (origin, username, host, cmd, target, params, extinfo) = matches[0]
- unhandled = []
-
- if re.match("^\\d+$", cmd):
- cmd = int(cmd) # Code is a numerical response
- else:
- cmd = cmd.upper()
-
- if not self.registered:
- if type(cmd) == int and target != "*": # Registration complete!
- with self.lock:
- self.registered = True
- self.identity = self.user(target)
- self.serv = origin
- self.event("onRegistered", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
-
- elif cmd == 433 and target == "*": # Server reports nick taken, so we need to try another.
- trynick += 1
- (q, s) = divmod(trynick, len(self.nick))
- nick = self.nick[s]
- if q > 0:
- nick += str(q)
- self.raw("NICK :%s" % nick.split("\n")[0].rstrip())
- if not self.registered: # Registration is not yet complete
- continue
+ line = linebuf.pop(0).rstrip("\r")
+ line = autodecode(line)
+ self._procrecvline(line)
- if username and host:
- nickname = origin
- origin = self.user(origin)
- if origin.nick != nickname:
- ### Origin nickname has changed
- origin.user = nickname
- if origin.username != username:
- ### Origin username has changed
- origin.username = username
- if origin.host != host:
- ### Origin host has changed
- origin.host = host
-
- chanmatch = re.findall(r"([%s]?)([%s]\S*)"%(re.escape(self.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.supports.get("CHANTYPES", "#"))), target)
- if chanmatch:
- targetprefix, channame = chanmatch[0]
- target = self.channel(channame)
- if target.name != channame:
- ### Target channel name has changed
- target.name = channame
- elif len(target) and target[0] != "$" and cmd != "NICK":
- targetprefix = ""
- target = self.user(target)
-
- data = dict(origin=origin, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo)
-
- ### Major codeblock here! Track IRC state.
- ### Send line to addons first
- with self.lock:
- self.event("onRecv", self.addons, line=line,
- **data)
- if cmd == 1:
- (handled, unhandled, exceptions) = self.event("onWelcome", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo)
- self.welcome = extinfo # Welcome message
- elif cmd == 2:
- (handled, unhandled, exceptions) = self.event("onYourHost", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo)
- self.hostinfo = extinfo # Your Host
- elif cmd == 3:
- (handled, unhandled, exceptions) = self.event("onServerCreated", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, msg=extinfo)
- self.servcreated = extinfo # Server Created
- elif cmd == 4:
- (handled, unhandled, exceptions) = self.event("onServInfo", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, servinfo=params)
- self.servinfo = params # What is this code?
- elif cmd == 5: # Server Supports
- support = dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params))
- if "CHANMODES" in support:
- support["CHANMODES"] = support["CHANMODES"].split(",")
- if "PREFIX" in support:
- matches = re.findall("\\((.*)\\)(.*)", support["PREFIX"])
- if matches:
- support["PREFIX"] = matches[0]
- else:
- del support["PREFIX"] # Might as well delete the info if it doesn't match expected pattern
- (handled, unhandled, exceptions) = self.event("onSupports", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, supports=support)
- self.supports.update(support)
- if "serv005" in dir(self) and type(self.serv005) == list:
- self.serv005.append(params)
- else:
- self.serv005 = [params]
- elif cmd == 8: # Snomask
- snomask = params.lstrip("+")
- (handled, unhandled, exceptions) = self.event("onSnoMask", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, snomask=snomask)
- self.identity.snomask = snomask
- if "s" not in self.identity.modes:
- self.snomask = ""
- elif cmd == 221: # User Modes
- modes = (params if params else extinfo).lstrip("+")
- (handled, unhandled, exceptions) = self.event("onUserModes", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), origin=origin, snomask=modes)
- self.identity.modes = modes
- if "s" not in self.identity.modes:
- self.snomask = ""
- elif cmd == 251: # Net Stats
- (handled, unhandled, exceptions) = self.event("onNetStats", self.addons, origin=origin, netstats=extinfo)
- self.netstats = extinfo
- elif cmd == 252:
- opcount = int(params)
- (handled, unhandled, exceptions) = self.event("onOpCount", self.addons, origin=origin, opcount=opcount)
- self.opcount = opcount
- elif cmd == 254:
- chancount = int(params)
- (handled, unhandled, exceptions) = self.event("onChanCount", self.addons, origin=origin, chancount=chancount)
- self.chancount = chancount
-
- elif cmd == 305: # Returned from away status
- (handled, unhandled, exceptions) = self.event("onReturn", self.addons, origin=origin, msg=extinfo)
- self.identity.away = False
-
- elif cmd == 306: # Entered away status
- (handled, unhandled, exceptions) = self.event("onAway", self.addons, origin=origin, msg=extinfo)
- self.identity.away = True
-
- elif cmd == 311: # Start of WHOIS data
- nickname, username, host, star = params.split()
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisStart", self.addons, origin=origin, user=user, nickname=nickname, username=username, host=host, realname=extinfo)
- user.nick = nickname
- user.username = username
- user.host = host
-
- elif cmd == 301: # Away Message
- user = self.user(params)
- (handled, unhandled, exceptions) = self.event("onWhoisAway", self.addons, origin=origin, user=user, nickname=params, awaymsg=extinfo)
- user.away = True
- user.awaymsg = extinfo
-
- elif cmd == 303: # ISON Reply
- users = [self.user(user) for user in extinfo.split(" ")]
- (handled, unhandled, exceptions) = self.event("onIsonReply", self.addons, origin=origin, isonusers=users)
-
- elif cmd == 307: # Is a registered nick
- (handled, unhandled, exceptions) = self.event("onWhoisRegisteredNick", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 378: # Connecting From
- (handled, unhandled, exceptions) = self.event("onWhoisConnectingFrom", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 319: # Channels
- (handled, unhandled, exceptions) = self.event("onWhoisChannels", self.addons, origin=origin, user=self.user(params), nickname=params, chanlist=extinfo.split(" "))
- elif cmd == 310: # Availability
- (handled, unhandled, exceptions) = self.event("onWhoisAvailability", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 312: # Server
- nickname, server = params.split(" ")
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisServer", self.addons, origin=origin, user=user, nickname=nickname, server=server, servername=extinfo)
- user.server = server
- elif cmd == 313: # IRC Op
- user = self.user(params)
- (handled, unhandled, exceptions) = self.event("onWhoisOp", self.addons, origin=origin, user=user, nickname=params, msg=extinfo)
- user.ircop = True
- user.ircopmsg = extinfo
- elif cmd == 317: # Idle and Signon times
- nickname, idletime, signontime = params.split(" ")
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisTimes", self.addons, origin=origin, user=user, nickname=nickname, idletime=int(idletime), signontime=int(signontime), msg=extinfo)
- user.idlesince = int(time.time())-int(idletime)
- user.signontime = int(signontime)
- elif cmd == 671: # SSL
- user = self.user(params)
- (handled, unhandled, exceptions) = self.event("onWhoisSSL", self.addons, origin=origin, user=user, nickname=params, msg=extinfo)
- user.ssl = True
- elif cmd == 379: # User modes
- (handled, unhandled, exceptions) = self.event("onWhoisModes", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
- elif cmd == 330: # Logged in as
- nickname, loggedinas = params.split(" ")
- user = self.user(nickname)
- (handled, unhandled, exceptions) = self.event("onWhoisLoggedInAs", self.addons, origin=origin, user=user, nickname=nickname, loggedinas=loggedinas, msg=extinfo)
- user.loggedinas = loggedinas
- elif cmd == 318: # End of WHOIS
- (handled, unhandled, exceptions) = self.event("onWhoisEnd", self.addons, origin=origin, user=self.user(params), nickname=params, msg=extinfo)
-
- elif cmd == 321: # Start LIST
- (handled, unhandled, exceptions) = self.event("onListStart", self.addons, origin=origin, params=params, extinfo=extinfo)
- elif cmd == 322: # LIST item
- (chan, pop) = params.split(" ", 1)
- (handled, unhandled, exceptions) = self.event("onListEntry", self.addons, origin=origin, channel=self.channel(chan), population=int(pop), extinfo=extinfo)
- elif cmd == 323: # End of LIST
- (handled, unhandled, exceptions) = self.event("onListEnd", self.addons, origin=origin, endmsg=extinfo)
-
- elif cmd == 324: # Channel Modes
- modeparams = params.split()
- channame = modeparams.pop(0)
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- if channel.name != channame:
- channel.name = channame # Server seems to have changed the idea of the case of the channel name
- setmodes = modeparams.pop(0)
- modedelta = []
- for mode in setmodes:
- if mode == "+":
- continue
- elif mode in self.supports["CHANMODES"][2]:
- param = modeparams.pop(0)
- modedelta.append(("+%s"%mode, param))
- elif mode in self.supports["CHANMODES"][3]:
- modedelta.append(("+%s"%mode, None))
- (handled, unhandled, exceptions) = self.event("onChannelModes", self.addons+channel.addons, channel=channel, modedelta=modedelta)
- for ((modeset, mode), param) in modedelta:
- if mode in self.supports["CHANMODES"][2]:
- channel.modes[mode] = param
- elif mode in self.supports["CHANMODES"][3]:
- channel.modes[mode] = True
-
- elif cmd == 329: # Channel created
- channame, created = params.split()
- created = int(created)
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onChanCreated", self.addons+channel.addons, channel=channel, created=created)
- channel.created = int(created)
-
- elif cmd == 332: # Channel Topic
- channame = params
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onTopic", self.addons+channel.addons, origin=origin, channel=channel, topic=extinfo)
- channel.topic = extinfo
-
- elif cmd == 333: # Channel Topic info
- (channame, nick, dt) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onTopicInfo", self.addons+channel.addons, origin=origin, channel=channel, topicsetby=nick, topictime=int(dt))
- channel.topicsetby = nick
- channel.topictime = int(dt)
-
- elif cmd == 352: # WHO reply
- (channame, username, host, serv, nick, flags) = params.split()
- try:
- (hops, realname) = extinfo.split(" ", 1)
- except ValueError:
- hops = extinfo
- realname = None
-
- if channame[0] in self.supports.get("CHANTYPES", "#"):
- channel = self.channel(channame)
- else:
- channel = None
-
- user = self.user(nick)
-
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onWhoEntry", self.addons+channel.addons, origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname)
- user.hops = hops
- user.realname = realname
- user.username = username
- user.host = host
- user.server = serv
- user.away = "G" in flags
- user.ircop = "*" in flags
- if type(channel) == Channel:
- if user not in channel.users:
- channel.users.append(user)
- if channel not in user.channels:
- user.channels.append(channel)
- for (mode, prefix) in zip(*self.supports["PREFIX"]):
- if prefix in flags:
- if mode in channel.modes.keys() and user not in channel.modes[mode]:
- channel.modes[mode].append(user)
- elif mode not in channel.modes.keys():
- channel.modes[mode] = [user]
-
- elif cmd == 315: # End of WHO reply
- (handled, unhandled, exceptions) = self.event("onWhoEnd", self.addons+channel.addons, origin=origin, param=params, endmsg=extinfo)
-
- elif cmd == 353: # NAMES reply
- (flag, channame) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
-
- if "PREFIX" in self.supports:
- names = re.findall(r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?"%re.escape(self.supports["PREFIX"][1]), extinfo)
- else:
- names = re.findall(r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo) # Still put it into tuple form for compatibility in the next structure
- (handled, unhandled, exceptions) = self.event("onNames", self.addons+channel.addons, origin=origin, channel=channel, flag=flag, channame=channame, nameslist=names)
-
- for (symbs, nick, username, host) in names:
- user = self.user(nick)
- if user.nick != nick:
- user.nick = nick
- if username and user.username != username:
- user.username = username
- if host and user.host != host:
- user.host = host
- with channel.lock:
- if channel not in user.channels:
- user.channels.append(channel)
- if user not in channel.users:
- channel.users.append(user)
- if "PREFIX" in self.supports:
- for symb in symbs:
- mode = self.supports["PREFIX"][0][self.supports["PREFIX"][1].index(symb)]
- if mode not in channel.modes:
- channel.modes[mode] = [user]
- elif user not in channel.modes[mode]:
- channel.modes[mode].append(user)
-
- elif cmd == 366: # End of NAMES reply
- channel = self.channel(params)
- (handled, unhandled, exceptions) = self.event("onNamesEnd", self.addons+channel.addons, origin=origin, channel=channel, channame=params, endmsg=extinfo)
-
- elif cmd == 372: # MOTD line
- (handled, unhandled, exceptions) = self.event("onMOTDLine", self.addons, origin=origin, motdline=extinfo)
- self.motd.append(extinfo)
- elif cmd == 375: # Begin MOTD
- (handled, unhandled, exceptions) = self.event("onMOTDStart", self.addons, origin=origin, motdgreet=extinfo)
- self.motdgreet = extinfo
- self.motd = []
- elif cmd == 376:
- (handled, unhandled, exceptions) = self.event("onMOTDEnd", self.addons, origin=origin, motdend=extinfo)
- self.motdend = extinfo # End of MOTD
-
- elif cmd == 386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal)
- (channame, owner) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- if channel.name != channame:
- channel.name = channame # Server seems to have changed the idea of the case of the channel name
- user = self.user(owner)
- if user.nick != owner:
- user.nick = owner
- if "q" in channel.modes:
- if user not in channel.modes["q"]:
- channel.modes["q"].append(user)
- else:
- channel.modes["q"] = [user]
-
- elif cmd == 388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal)
- (channame, admin) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- if channel.name != channame:
- channel.name = channame # Server seems to have changed the idea of the case of the channel name
- user = self.user(admin)
- if user.nick != admin:
- user.nick = admin
- if "a" in channel.modes:
- if user not in channel.modes["a"]:
- channel.modes["a"].append(user)
- else:
- channel.modes["a"] = [user]
-
- elif cmd == "NICK":
- newnick = extinfo if len(extinfo) else target
-
- addons = reduce(lambda x, y: x+y, [chan.addons for chan in origin.channels], [])
- self.event("onRecv", addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onNickChange", self.addons+addons, user=origin, newnick=newnick)
- if origin == self.identity:
- (handled, unhandled, exceptions) = self.event("onMeNickChange", self.addons+addons, newnick=newnick)
-
- for u in self.users:
- if u.nick.lower() == newnick.lower():
- self.users.remove(u) # Nick collision, safe to assume this orphaned user is offline, so we shall remove the old instance.
- for channel in self.channels:
- ### If for some odd reason, the old user still appears common channels, then we will remove the user anyway.
- if u in channel.users:
- channel.users.remove(u)
- origin.nick = newnick
-
- elif cmd == "JOIN":
- channel = target if type(target) == Channel else self.channel(extinfo)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onJoin", self.addons+channel.addons, user=origin, channel=channel)
-
- if origin == self.identity: # This means the bot is entering the room,
- # and will reset all the channel data, on the assumption that such data may have changed.
- # Also, the bot must request modes
- channel.topic = ""
- channel.topicmod = ""
- channel.modes = {}
- channel.users = []
- self.event("onMeJoin", self.addons+channel.addons, channel=channel)
- self.raw("MODE %s" % channel.name)
- self.raw("WHO %s" % channel.name)
- if "CHANMODES" in self.supports.keys():
- self.raw("MODE %s :%s" % (channel.name, self.supports["CHANMODES"][0]))
-
- if channel not in origin.channels:
- origin.channels.append(channel)
- if origin not in channel.users:
- channel.users.append(origin)
-
- elif cmd == "KICK":
- kicked = self.user(params)
- if kicked.nick != params:
- kicked.nick = params
-
- self.event("onRecv", target.addons, line=line, **data)
- if origin == self.identity:
- self.event("onMeKick", self.addons+target.addons, channel=target, kicked=kicked, kickmsg=extinfo)
- if kicked == self.identity:
- self.event("onMeKicked", self.addons+target.addons, kicker=origin, channel=target, kickmsg=extinfo)
- (handled, unhandled, exceptions) = self.event("onKick", self.addons+target.addons, kicker=origin, channel=target, kicked=kicked, kickmsg=extinfo)
-
- if target in kicked.channels:
- kicked.channels.remove(target)
- if kicked in target.users:
- target.users.remove(kicked)
- if "PREFIX" in self.supports:
- for mode in self.supports["PREFIX"][0]:
- if mode in target.modes and kicked in target.modes[mode]:
- target.modes[mode].remove(kicked)
-
- elif cmd == "PART":
- self.event("onRecv", target.addons, line=line, **data)
- if origin == self.identity:
- self.event("onMePart", self.addons+target.addons, channel=target, partmsg=extinfo)
- (handled, unhandled, exceptions) = self.event("onPart", self.addons+target.addons, user=origin, channel=target, partmsg=extinfo)
-
- if target in origin.channels:
- origin.channels.remove(target)
- if origin in target.users:
- target.users.remove(origin)
- if "PREFIX" in self.supports:
- for mode in self.supports["PREFIX"][0]:
- if mode in target.modes and origin in target.modes[mode]:
- target.modes[mode].remove(origin)
-
- elif cmd == "QUIT":
- channels = list(origin.channels)
- addons = reduce(lambda x, y: x+y, [chan.addons for chan in origin.channels], [])
- self.event("onRecv", addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onQuit", self.addons+addons, user=origin, quitmsg=extinfo)
- for channel in origin.channels:
- with channel.lock:
- if origin in channel.users:
- channel.users.remove(origin)
- if "PREFIX" in self.supports:
- for mode in self.supports["PREFIX"][0]:
- if mode in channel.modes and origin in channel.modes[mode]:
- channel.modes[mode].remove(origin)
- origin.channels = []
-
- elif cmd == "MODE":
- if type(target) == Channel:
- self.event("onRecv", target.addons, line=line, **data)
- modedelta = []
- modeparams = params.split()
- setmodes = modeparams.pop(0)
- modeset = "+"
- for mode in setmodes:
- if mode in "+-":
- modeset = mode
- else:
- if mode in self.supports["CHANMODES"][0]+self.supports["CHANMODES"][1]:
- param = modeparams.pop(0)
- modedelta.append(("%s%s"%(modeset, mode), param))
- if mode in maskmodeeventnames.keys():
- if modeset == "+":
- eventname = maskmodeeventnames[mode][0]
- if modeset == "-":
- eventname = maskmodeeventnames[mode][1]
- matchesbot = glob.fnmatch.fnmatch("%s!%s@%s".lower()%(self.identity.nick, self.identity.username, self.identity.host), param.lower())
- self.event("on%s"%eventname, self.addons+target.addons, user=origin, channel=target, banmask=param)
- if matchesbot:
- self.event("onMe%s"%eventname, self.addons+target.addons, user=origin, channel=target, banmask=param)
- elif mode in self.supports["CHANMODES"][2]:
- if modeset == "+":
- param = modeparams.pop(0)
- modedelta.append(("%s%s"%(modeset, mode), param))
- else:
- modedelta.append(("%s%s"%(modeset, mode), None))
- elif mode in self.supports["CHANMODES"][3]:
- modedelta.append(("%s%s"%(modeset, mode), None))
- elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]:
- modenick = modeparams.pop(0)
- modeuser = self.user(modenick)
- if mode in privmodeeventnames.keys():
- if modeset == "+":
- eventname = privmodeeventnames[mode][0]
- if modeset == "-":
- eventname = privmodeeventnames[mode][1]
- self.event("on%s"%eventname, self.addons+target.addons, user=origin, channel=target, modeuser=modeuser)
- if modeuser == self.identity:
- self.event("onMe%s"%eventname, self.addons+target.addons, user=origin, channel=target)
- modedelta.append(("%s%s"%(modeset, mode), modeuser))
- (handled, unhandled, exceptions) = self.event("onChanModeSet", self.addons+target.addons, user=origin, channel=target, modedelta=modedelta)
- with target.lock:
- for ((modeset, mode), param) in modedelta:
- if mode in self.supports["CHANMODES"][0]:
- if modeset == "+":
- if mode in target.modes:
- if param.lower() not in [mask.lower() for (mask, setby, settime) in target.modes[mode]]:
- target.modes[mode].append((param, origin, int(time.time())))
- else:
- target.modes[mode] = [(param, origin, int(time.time()))]
- else:
- if mode in target.modes.keys():
- if mode == "b": # Inspircd mode is case insentive when unsetting the mode
- masks = [mask.lower() for (mask, setby, settime) in target.modes[mode]]
- if param.lower() in masks:
- index = masks.index(param.lower())
- #print "Index: %d"%index
- del target.modes[mode][index]
- else:
- masks = [mask for (mask, setby, settime) in target.modes[mode]]
- if param in masks:
- index = masks.index(param)
- del target.modes[mode][index]
- elif mode in self.supports["CHANMODES"][1]:
- if modeset == "+":
- target.modes[mode] = param
- else:
- target.modes[mode] = None
- elif mode in self.supports["CHANMODES"][2]:
- if modeset == "+":
- target.modes[mode] = param
- else:
- target.modes[mode] = None
- elif mode in self.supports["CHANMODES"][3]:
- if modeset == "+":
- target.modes[mode] = True
- else:
- target.modes[mode] = False
- elif "PREFIX" in self.supports and mode in self.supports["PREFIX"][0]:
- if modeset == "+":
- if mode in target.modes and param not in target.modes[mode]:
- target.modes[mode].append(param)
- if mode not in target.modes:
- target.modes[mode] = [param]
- elif mode in target.modes and param in target.modes[mode]:
- target.modes[mode].remove(param)
- elif type(target) == User:
- modeparams = (params if params else extinfo).split()
- setmodes = modeparams.pop(0)
- modeset = "+"
- for mode in setmodes:
- if mode in "+-":
- modeset = mode
- continue
- if modeset == "+":
- if mode not in target.modes:
- target.modes += mode
- if mode == "s" and len(modeparams):
- snomask = modeparams.pop(0)
- for snomode in snomask:
- if snomode in "+-":
- snomodeset = snomode
- continue
- if snomodeset == "+" and snomode not in target.snomask:
- target.snomask += snomode
- if snomodeset == "-" and snomode in target.snomask:
- target.snomask = target.snomask.replace(snomode, "")
- if modeset == "-":
- if mode in target.modes:
- target.modes = target.modes.replace(mode, "")
- if mode == "s":
- target.snomask = ""
-
- elif cmd == "TOPIC":
- self.event("onRecv", target.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onTopicSet", self.addons+target.addons, user=origin, channel=target, topic=extinfo)
-
- with target.lock:
- target.topic = extinfo
- target.topicsetby = origin
- target.topictime = int(time.time())
-
- elif cmd == "INVITE":
- channel = self.channel(extinfo if extinfo else params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onInvite", self.addons+channel.addons, user=origin, channel=channel)
-
- elif cmd == "PRIVMSG":
- if type(target) == Channel:
- self.event("onRecv", target.addons, line=line, **data)
-
- ### CTCP handling
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo)
- if ctcp:
- (ctcptype, ext) = ctcp[0]
- if ctcptype.upper() == "ACTION":
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanAction", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, action=ext)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivAction", self.addons, user=origin, action=ext)
- else:
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanCTCP", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivCTCP", self.addons, user=origin, ctcptype=ctcptype, params=ext)
- if ctcptype.upper() == "VERSION":
- origin.ctcpreply("VERSION", self.ctcpversion())
- if ctcptype.upper() == "TIME":
- tformat = time.ctime()
- tz = time.tzname[0]
- origin.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars())
- if ctcptype.upper() == "PING":
- origin.ctcpreply("PING", "%(ext)s" % vars())
- if ctcptype.upper() == "FINGER":
- origin.ctcpreply("FINGER", "%(ext)s" % vars())
- else:
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanMsg", self.addons+target.addons, user=origin, channel=target, targetprefix=targetprefix, msg=extinfo)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivMsg", self.addons, user=origin, msg=extinfo)
-
- elif cmd == "NOTICE":
- if type(target) == Channel:
- self.event("onRecv", target.addons, line=line, **data)
-
- ### CTCP handling
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$", extinfo)
- if ctcp and target == self.identity:
- (ctcptype, ext) = ctcp[0]
- (handled, unhandled, exceptions) = self.event("onCTCPReply", self.addons, origin=origin, ctcptype=ctcptype, params=ext)
- else:
- if type(target) == Channel:
- (handled, unhandled, exceptions) = self.event("onChanNotice", self.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo)
- elif target == self.identity:
- (handled, unhandled, exceptions) = self.event("onPrivNotice", self.addons, origin=origin, msg=extinfo)
-
- elif cmd == 367: # Channel Ban list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "b" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]:
- channel.modes["b"].append((mask, setby, int(settime)))
- else:
- channel.modes["b"] = [(mask, setby, int(settime))]
- elif cmd == 368:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 346: # Channel Invite list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onInviteListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "I" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]:
- channel.modes["I"].append((mask, setby, int(settime)))
- else:
- channel.modes["I"] = [(mask, setby, int(settime))]
- elif cmd == 347:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onInviteListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 348: # Channel Ban Exception list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanExceptListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "e" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]:
- channel.modes["e"].append((mask, setby, int(settime)))
- else:
- channel.modes["e"] = [(mask, setby, int(settime))]
- elif cmd == 349:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onBanExceptListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 910: # Channel Access List
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onAccessListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "w" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]:
- channel.modes["w"].append((mask, setby, int(settime)))
- else:
- channel.modes["w"] = [(mask, setby, int(settime))]
- elif cmd == 911:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onAccessListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 941: # Spam Filter list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onSpamfilterListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "g" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]:
- channel.modes["g"].append((mask, setby, int(settime)))
- else:
- channel.modes["g"] = [(mask, setby, int(settime))]
- elif cmd == 940:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onSpamfilterListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 954: # Channel exemptchanops list
- (channame, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onExemptChanOpsListEntry", self.addons+channel.addons, origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime))
- if "X" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]:
- channel.modes["X"].append((mask, setby, int(settime)))
- else:
- channel.modes["X"] = [(mask, setby, int(settime))]
- elif cmd == 953:
- channel = self.channel(params)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onExemptChanOpsListEnd", self.addons+channel.addons, origin=origin, channel=channel, endmsg=extinfo)
-
- elif cmd == 728: # Channel quiet list
- (channame, modechar, mask, setby, settime) = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onQuietListEntry", self.addons+channel.addons, origin=origin, channel=channel, modechar=modechar, mask=mask, setby=setby, settime=int(settime))
- if "q" in channel.modes.keys():
- if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["q"]]:
- channel.modes["q"].append((mask, setby, int(settime)))
- else:
- channel.modes["q"] = [(mask, setby, int(settime))]
- elif cmd == 729:
- channame, modechar = params.split()
- channel = self.channel(channame)
- self.event("onRecv", channel.addons, line=line, **data)
- (handled, unhandled, exceptions) = self.event("onQuietListEnd", self.addons+channel.addons, channel=channel, endmsg=extinfo)
-
- elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params
- for param in params.split():
- if len(param) and param[0] in self.supports["CHANTYPES"]:
- channel = self.channel(param)
- self.event("onRecv", channel.addons, line=line, **data)
-
- elif type(cmd) == int:
- (handled, unhandled, exceptions) = self.event("on%03d"%cmd, self.addons, line=line, origin=origin, target=target, params=params, extinfo=extinfo)
- else:
- (handled, unhandled, exceptions) = self.event("on%s"%cmd, self.addons, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo)
-
- self.event("onUnhandled", unhandled, line=line, origin=origin, cmd=cmd, target=target, params=params, extinfo=extinfo)
-
- else: # Line does NOT match ":origin cmd target params :extinfo"
- self.event("onRecv", self.addons, line=line)
except SystemExit: # Connection lost normally.
pass
+
except socket.error: # Connection lost due to either ping timeout or connection reset by peer. Not a fatal error.
exc, excmsg, tb = sys.exc_info()
- self.logwrite("*** Connection to %(server)s:%(port)s failed: %(excmsg)s." % vars())
+ with self.lock:
+ self.logwrite(
+ "*** Connection to {self:uri} failed: {excmsg}.".format(**vars()))
+ self._event(self.getalladdons(), [
+ ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)])
+
except: # Unknown exception, treated as FATAL. Try to quit IRC and terminate thread with exception.
- ### Quit with a (hopefully) useful quit message, or die trying.
- self.quitexpected = True
+ # Quit with a (hopefully) useful quit message, or die
+ # trying.
+ self._quitexpected = True
try:
- self.quit("%s" % traceback.format_exc()
- .rstrip().split("\n")[-1])
+ self.quit(
+ "%s" % traceback.format_exc().rstrip().split("\n")[-1])
except:
pass
raise
- finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred.
- with self.lock:
- (handled, unhandled, exceptions) = self.event("onDisconnect", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []), expected=self.quitexpected)
- self.connected = False
- self.registered = False
- self.identity = None
- ### Tell outgoing thread to quit.
- self.outgoing.interrupt()
+ finally: # Post-connection operations after connection is lost, and must be executed, even if exception occurred.
+ with self._sendline: # Notify _outgoingthread that the connection has been terminated.
+ self._outgoing.clear()
+ self._sendline.notify()
+ with self._disconnecting:
+ self._disconnecting.notifyAll()
+ self._event(self.getalladdons(), [
+ ("onDisconnect", dict(expected=self._quitexpected), False)])
- ### Wait until the outgoing thread dies.
- if self.outgoingthread and self.outgoingthread.isAlive():
- self.outgoingthread.join()
- self.outgoingthread = None
+ self._init()
try:
- self.connection.close()
+ self._connection.close()
except:
pass
self.logwrite("*** Connection Terminated.")
- if self.quitexpected or not self.autoreconnect:
+ if self._quitexpected or not self.autoreconnect:
+ self._quitexpected = False
sys.exit()
- ### If we make it to this point, then it is because connection was lost unexpectedly, and will attempt to reconnect if self.autoreconnect is True.
- time.sleep(self.retrysleep)
-
except SystemExit:
pass
except: # Print exception to log file
- self.logwrite(*["!!! FATAL Exception"]+["!!! %s"%line for line in traceback.format_exc().split("\n")])
- print >>sys.stderr, "FATAL Exception" % vars()
- print >>sys.stderr, traceback.format_exc()
+ self.logerror(u"FATAL Exception")
sys.exit()
finally:
- self.logwrite("### Log session ended")
- (handled, unhandled, exceptions) = self.event("onSessionClose", self.addons+reduce(lambda x, y: x+y, [chan.addons for chan in self.channels], []))
- Thread.__init__(self) # Makes thread restartable
+ self.logwrite("### Session ended")
+ self._event(self.getalladdons(), [
+ ("onSessionClose", dict(), False)])
+
+ # Tell _sendhandler to quit
+ with self._sendline:
+ self._outgoing.append("quit")
+ self._sendline.notify()
+
+ def lower(self, s):
+ """lower(s)
+
+ Transforms a string into lowercase, using whatever casemapping the server is using, whether ascii or rfc1459."""
+ if self.supports.get("CASEMAPPING", "rfc1459") == "ascii":
+ return s.lower()
+ else:
+ return s.translate(_rfc1459casemapping)
+
+ def getalladdons(self):
+ """getalladdons() --> list
+
+ Returns list of *all* addons, including channel-specific addons."""
+ return self.addons + reduce(lambda x, y: x + y, [chan.addons for chan in self.channels], [])
+
+ # The following methods matching parse* are used to determine what addon methods will be called, and prepares the arguments to be passed.
+ # These methods can also be used to determine event support by invoking
+ # them with no parameters. This allows for addition of event supports.
+ # Each is expected to return a tuple (addons, [(method, args, fallback), ...]).
+ # 'addons' refers to the list of addons whose methods should be called.
+ # [(method, args, fallback), ...] is a list of methods and parameters to be called, as well as a flag to determine when a fallback is permitted.
+ # 'method' refers to the name of the method to be invoked in the addons
+ # 'args' is a dict of arguments that should be passed as parameters to event.
+ # 'fallback' is a flag to determine when a fallback to 'onOther' is permitted.
+ # Each of these functions should allow passing None to all arguments, in
+ # which case, should report back *all* supported methods.
+ def parseCAP(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [
+ ("onCapLS", dict(origin=None, capabilities=None), True),
+ ("onCapAck", dict(origin=None, capabilities=None), True),
+ ])
+ if params.upper() == "LS":
+ return (self.getalladdons(), [("onCapLS", dict(capabilities=extinfo.split()), True)])
+ if params.upper() == "ACK":
+ return (self.getalladdons(), [("onCapAck", dict(capabilities=extinfo.split()), True)])
+ return ([], [])
+
+ def parse001(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ return (self.getalladdons(), [("onWelcome", dict(origin=origin, msg=extinfo), True)])
+
+ def parse002(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ return (self.getalladdons(), [("onYourHost", dict(origin=origin, msg=extinfo), True)])
+
+ def parse003(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ return (self.getalladdons(), [("onServerCreated", dict(origin=origin, msg=extinfo), True)])
+
+ def parse004(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ return (self.getalladdons(), [("onServInfo", dict(origin=origin, servinfo=params), True)])
+
+ def parse005(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Server Supports
+ if origin == None:
+ return (None, [("onSupports", dict(origin=None, supports=None, msg=None), True)])
+ support = dict(re.findall("([A-Za-z0-9]+)(?:=(\\S*))?", params))
+ if support.has_key("CHANMODES"):
+ support["CHANMODES"] = support["CHANMODES"].split(",")
+ if support.has_key("PREFIX"):
+ matches = re.findall(_prefixmatch, support["PREFIX"])
+ if matches:
+ support["PREFIX"] = matches[0]
+ else:
+ del support["PREFIX"]
+ # Might as well delete the info if it doesn't match
+ # expected pattern
+ return (self.getalladdons(), [("onSupports", dict(origin=origin, supports=support, msg=extinfo), True)])
+
+ def parse008(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Snomask
+ if origin == None:
+ return (None, [("onSnoMask", dict(origin=None, snomask=None), True)])
+ snomask = params.lstrip("+")
+ return (self.getalladdons(), [("onSnoMask", dict(origin=origin, snomask=snomask), True)])
+
+ def parse221(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # User Modes
+ if origin == None:
+ return (self.getalladdons(), [("onUserModes", dict(origin=None, modes=None), True)])
+ modes = (params if params else extinfo).lstrip("+")
+ return (self.getalladdons(), [("onUserModes", dict(origin=origin, modes=modes), True)])
+
+ def parse251(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Net Stats
+ return (self.addons, [("onNetStats", dict(origin=origin, netstats=extinfo), True)])
+
+ def parse252(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Operator count
+ if origin == None:
+ return (None, [("onOpCount", dict(origin=None, opcount=None), True)])
+ opcount = int(params)
+ return (self.addons, [("onOpCount", dict(origin=origin, opcount=opcount), True)])
+
+ def parse254(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Count
+ if origin == None:
+ return (self.addons, [("onChanCount", dict(origin=None, chancount=None), True)])
+ chancount = int(params)
+ return (self.addons, [("onChanCount", dict(origin=origin, chancount=chancount), True)])
+
+ def parse305(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Returned from away status
+ return (self.getalladdons(), [("onMeReturn", dict(origin=origin, msg=extinfo), True)])
+
+ def parse306(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Entered away status
+ return (self.getalladdons(), [("onMeAway", dict(origin=origin, msg=extinfo), True)])
+
+ def parse311(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Start of WHOIS data
+ if origin == None:
+ return (None, [("onWhoisStart", dict(origin=None, user=None, nickname=None, username=None, host=None, realname=None), True)])
+ nickname, username, host, star = params.split()
+ user = self.user(nickname)
+ return (self.addons, [("onWhoisStart", dict(origin=origin, user=user, nickname=nickname, username=username, host=host, realname=extinfo), True)])
+
+ def parse301(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Away Message
+ if origin == None:
+ return (None, [("onWhoisAway", dict(origin=None, user=None, nickname=None, awaymsg=None), True)])
+ user = self.user(params)
+ return (self.addons, [("onWhoisAway", dict(origin=origin, user=user, nickname=params, awaymsg=extinfo), True)])
+
+ def parse303(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # ISON Reply
+ if origin == None:
+ return (None, [("onIsonReply", dict(origin=None, isonusers=None), True)])
+ users = [self.user(user) for user in extinfo.split(" ")]
+ return (self.addons, [("onIsonReply", dict(origin=origin, isonusers=users), True)])
+
+ def parse307(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Is a registered nick
+ if origin == None:
+ return (None, [("onWhoisRegisteredNick", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ return (self.addons, [("onWhoisRegisteredNick", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)])
+
+ def parse378(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Connecting From
+ if origin == None:
+ return (None, [("onWhoisConnectingFrom", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ return (self.addons, [("onWhoisConnectingFrom", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)])
+
+ def parse319(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channels
+ if origin == None:
+ return (None, [("onWhoisChannels", dict(origin=None, user=None, nickname=None, chanlist=None), True)])
+ return (self.addons, [("onWhoisChannels", dict(origin=origin, user=self.user(params), nickname=params, chanlist=extinfo.split(" ")), True)])
+
+ def parse310(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Availability
+ if origin == None:
+ return (None, [("onWhoisAvailability", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ return (self.addons, [("onWhoisAvailability", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)])
+
+ def parse312(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Server
+ if origin == None:
+ return (None, [("onWhoisServer", dict(origin=None, user=None, nickname=None, server=None, servername=None), True)])
+ nickname, server = params.split(" ")
+ user = self.user(nickname)
+ server = self.getserver(server)
+ return (self.addons, [("onWhoisServer", dict(origin=origin, user=user, nickname=nickname, server=server, servername=extinfo), True)])
+
+ def parse313(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # IRC Op
+ if origin == None:
+ return (None, [("onWhoisOp", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ user = self.user(params)
+ return (self.addons, [("onWhoisOp", dict(origin=origin, user=user, nickname=params, msg=extinfo), True)])
+
+ def parse317(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Idle and Signon times
+ if origin == None:
+ return (None, [("onWhoisTimes", dict(origin=None, user=None, nickname=None, idletime=None, signontime=None, msg=None), True)])
+ nickname, idletime, signontime = params.split(" ")
+ user = self.user(nickname)
+ return (self.addons, [("onWhoisTimes", dict(origin=origin, user=user, nickname=nickname, idletime=int(idletime), signontime=int(signontime), msg=extinfo), True)])
+
+ def parse671(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # SSL
+ if origin == None:
+ return (None, [("onWhoisSSL", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ user = self.user(params)
+ return (self.addons, [("onWhoisSSL", dict(origin=origin, user=user, nickname=params, msg=extinfo), True)])
+
+ def parse379(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # User modes
+ if origin == None:
+ return (None, [("onWhoisModes", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ return (self.addons, [("onWhoisModes", dict(origin=origin, user=self.user(params), nickname=params, msg=extinfo), True)])
+
+ def parse330(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Logged in as
+ if origin == None:
+ return (None, [("onWhoisLoggedInAs", dict(origin=None, user=None, nickname=None, loggedinas=None, msg=None), True)])
+ nickname, loggedinas = params.split(" ")
+ user = self.user(nickname)
+ return (self.addons, [("onWhoisLoggedInAs", dict(origin=origin, user=user, nickname=nickname, loggedinas=loggedinas, msg=extinfo), True)])
+
+ def parse318(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of WHOIS
+ if origin == None:
+ return (None, [("onWhoisEnd", dict(origin=None, user=None, nickname=None, msg=None), True)])
+ try:
+ user = self.user(params)
+ except InvalidName:
+ user = params
+ return (self.addons, [("onWhoisEnd", dict(origin=origin, user=user, nickname=params, msg=extinfo), True)])
+
+ def parse321(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Start LIST
+ return (self.addons, [("onListStart", dict(origin=origin, params=params, extinfo=extinfo), True)])
+
+ def parse322(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # LIST item
+ if origin == None:
+ return (None, [("onListEntry", dict(origin=None, channel=None, population=None, extinfo=None), True)])
+ (chan, pop) = params.split(" ", 1)
+ try:
+ return (self.addons, [("onListEntry", dict(origin=origin, channel=self.channel(chan), population=int(pop), extinfo=extinfo), True)])
+ except:
+ return (self.addons, [("onListEntry", dict(origin=origin, channel=chan, population=int(pop), extinfo=extinfo), True)])
+
+ def parse323(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of LIST
+ return (self.addons, [("onListEnd", dict(origin=None, endmsg=None), True)])
+
+ def parse324(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Modes
+ if origin == None:
+ return (None, [("onChannelModes", dict(origin=None, channel=None, modedelta=None), True)])
+ modeparams = params.split()
+ channame = modeparams.pop(0)
+ channel = self.channel(channame)
+
+ chanmodes = self.supports.get("CHANMODES", _defaultchanmodes)
+ setmodes = modeparams.pop(0)
+ modedelta = []
+ for mode in setmodes:
+ if mode == "+":
+ continue
+ elif mode in chanmodes[2]:
+ param = modeparams.pop(0)
+ modedelta.append(("+%s" % mode, param))
+ elif mode in chanmodes[3]:
+ modedelta.append(("+%s" % mode, None))
+ return (self.addons + channel.addons, [("onChannelModes", dict(origin=origin, channel=channel, modedelta=modedelta), True)])
+
+ def parse329(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel created
+ if origin == None:
+ return (None, [("onChanCreated", dict(origin=None, channel=None, created=None), True)])
+ channame, created = params.split()
+ created = int(created)
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onChanCreated", dict(origin=origin, channel=channel, created=created), True)])
+
+ def parse332(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Topic
+ if origin == None:
+ return (None, [("onTopic", dict(origin=None, channel=None, topic=None), True)])
+ channame = params
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onTopic", dict(origin=origin, channel=channel, topic=extinfo), True)])
+
+ def parse333(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Topic info
+ if origin == None:
+ return (None, [("onTopicInfo", dict(origin=None, channel=None, topicsetby=None, topictime=None), True)])
+ (channame, nick, dt) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onTopicInfo", dict(origin=origin, channel=channel, topicsetby=nick, topictime=int(dt)), True)])
+
+ def parse352(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # WHO reply
+ if origin == None:
+ return (None, [("onWhoEntry", dict(origin=None, channel=None, user=None, channame=None, username=None, host=None, serv=None, nick=None, flags=None, hops=None, realname=None), True)])
+ (channame, username, host, serv, nick, flags) = params.split()
+ try:
+ (hops, realname) = extinfo.split(" ", 1)
+ except ValueError:
+ hops = extinfo
+ realname = None
+
+ chantypes = self.supports.get("CHANTYPES", _defaultchantypes)
+ if re.match(_chanmatch % re.escape(chantypes), channame):
+ channel = self.channel(channame)
+ else:
+ channel = None
+
+ user = self.user(nick)
+ serv = self.getserver(serv)
+
+ if type(channel) == Channel:
+ return (self.addons + channel.addons, [("onWhoEntry", dict(origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname), True)])
+ else:
+ return (self.addons, [("onWhoEntry", dict(origin=origin, channel=channel, user=user, channame=channame, username=username, host=host, serv=serv, nick=nick, flags=flags, hops=int(hops), realname=realname), True)])
+
+ def parse315(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of WHO reply
+ if origin == None:
+ return (None, [("onWhoEnd", dict(origin=None, param=None, endmsg=None), True)])
+ chantypes = self.supports.get("CHANTYPES", _defaultchantypes)
+ if re.match(_chanmatch % re.escape(chantypes), params):
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onWhoEnd", dict(origin=origin, param=params, endmsg=extinfo), True)])
+ else:
+ return (self.addons, [("onWhoEnd", dict(origin=origin, param=params, endmsg=extinfo), True)])
+
+ def parse353(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # NAMES reply
+ if origin == None:
+ return (None, [("onNames", dict(origin=None, channel=None, flag=None, channame=None, nameslist=None), True)])
+ (flag, channame) = params.split()
+ channel = self.channel(channame)
+
+ if self.supports.has_key("PREFIX"):
+ names = re.findall(r"([%s]*)([^@!\s]+)(?:!(\S+)@(\S+))?" %
+ re.escape(self.supports["PREFIX"][1]), extinfo)
+ else:
+ names = re.findall(r"()([^@!\s]+)(?:!(\S+)@(\S+))?", extinfo)
+ # Still put it into tuple form for compatibility
+ # in the next structure
+ return (self.addons + channel.addons, [("onNames", dict(origin=origin, channel=channel, flag=flag, channame=channame, nameslist=names), True)])
+
+ def parse366(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # End of NAMES reply
+ if origin == None:
+ return (None, [("onNamesEnd", dict(origin=None, channel=None, channame=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onNamesEnd", dict(origin=origin, channel=channel, channame=params, endmsg=extinfo), True)])
+
+ def parse372(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # MOTD line
+ return (self.addons, [("onMOTDLine", dict(origin=origin, motdline=extinfo), True)])
+ self.motd.append(extinfo)
+
+ def parse375(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Begin MOTD
+ return (self.addons, [("onMOTDStart", dict(origin=origin, motdgreet=extinfo), True)])
+
+ def parse376(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ return (self.addons, [("onMOTDEnd", dict(origin=origin, motdend=extinfo), True)])
+
+ def parseACCOUNT(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [
+ ("onAccountLogin", dict(user=None, account=None), True),
+ ("onMeAccountLogin", dict(account=None), False),
+ ("onAccountLogout", dict(user=None), True),
+ ("onMeAccountLogout", dict(), False)
+ ])
+
+ addons = reduce(
+ lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], [])
+
+ if origin == self.identity:
+ if target == "*":
+ return (self.addons + addons, [
+ ("onAccountLogout", dict(user=origin), True),
+ ("onMeAccountLogout", dict(), False)
+ ])
+ else:
+ return (self.addons + addons, [
+ ("onAccountLogin", dict(
+ user=origin, account=target), True),
+ ("onMeAccountLogin", dict(account=target), False)
+ ])
+ else:
+ if target == "*":
+ return (self.addons + addons, [("onAccountLogout", dict(user=origin), True)])
+ else:
+ return (self.addons + addons, [("onAccountLogin", dict(user=origin, account=target), True)])
+
+ def parseAWAY(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+
+ if origin == None:
+ return (None, [("onAway", dict(user=None, awaymsg=None), True), ("onReturn", dict(user=None), True)])
+
+ addons = reduce(
+ lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], [])
+
+ if extinfo:
+ return (self.addons + addons, [("onAway", dict(user=origin, awaymsg=extinfo), True)])
+ else:
+ return (self.addons + addons, [("onReturn", dict(user=origin), True)])
+
+ def parseNICK(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+
+ if origin == None:
+ return (None, [
+ ("onNickChange", dict(user=None, newnick=None), True),
+ ("onMeNickChange", dict(newnick=None), False)
+ ])
+
+ newnick = extinfo if len(extinfo) else target
+
+ addons = reduce(
+ lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], [])
+
+ if origin == self.identity:
+ return (self.addons + addons, [
+ ("onNickChange", dict(user=origin, newnick=newnick), True),
+ ("onMeNickChange", dict(newnick=newnick), False)
+ ])
+ return (self.addons + addons, [("onNickChange", dict(user=origin, newnick=newnick), True)])
+
+ def parseJOIN(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+
+ if origin == None:
+ return (None, [
+ ("onMeJoin", dict(channel=None, loggedinas=None, realname=None), False),
+ ("onJoin", dict(user=None, channel=None, loggedinas=None, realname=None), True)
+ ])
+
+ if type(target) == Channel:
+ channel = target
+ else:
+ channel = self.channel(extinfo)
+ channel.name = extinfo
+
+ if "extended-join" in self.enabledcaps:
+ loggedinas = params if params != "*" else None
+ realname = extinfo
+ else:
+ loggedinas = realname = None
+
+ if origin == self.identity:
+ return (self.addons + channel.addons, [
+ ("onMeJoin", dict(channel=channel, loggedinas=loggedinas, realname=realname), False),
+ ("onJoin", dict(user=origin, channel=channel,
+ loggedinas=loggedinas, realname=realname), True),
+ ])
+
+ return (self.addons + channel.addons, [("onJoin", dict(user=origin, channel=channel, loggedinas=loggedinas, realname=realname), True)])
+
+ def parseKICK(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [
+ ("onMeKick", dict(channel=None, kicked=None, kickmsg=None), True),
+ ("onMeKicked", dict(
+ kicker=None, channel=None, kickmsg=None), True),
+ ("onKick", dict(kicker=None, channel=None, kicked=None, kickmsg=None), True)
+ ])
+ events = []
+ if origin == self.identity:
+ events.append(
+ ("onMeKick", dict(channel=target, kicked=kicked, kickmsg=extinfo), False))
+
+ kicked = self.user(params)
+ if kicked.nick != params:
+ kicked.nick = params
+
+ if kicked == self.identity:
+ events.append(
+ ("onMeKicked", dict(kicker=origin, channel=target, kickmsg=extinfo), False))
+
+ events.append(
+ ("onKick", dict(kicker=origin, channel=target, kicked=kicked, kickmsg=extinfo), True))
+ return (self.addons + target.addons, events)
+
+ if target in kicked.channels:
+ kicked.channels.remove(target)
+ if kicked in target.users:
+ target.users.remove(kicked)
+ if self.supports.has_key("PREFIX"):
+ for mode in self.supports["PREFIX"][0]:
+ if target.modes.has_key(mode) and kicked in target.modes[mode]:
+ target.modes[mode].remove(kicked)
+
+ def parsePART(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [
+ ("onMePart", dict(channel=None, partmsg=None), True),
+ ("onPart", dict(user=None, channel=None, partmsg=None), True)
+ ])
+ if origin == self.identity:
+ return (self.addons + target.addons, [
+ ("onMePart", dict(channel=target, partmsg=extinfo), False),
+ ("onPart", dict(user=origin, channel=target, partmsg=extinfo), True)
+ ])
+ else:
+ return (self.addons + target.addons, [("onPart", dict(user=origin, channel=target, partmsg=extinfo), True)])
+
+ def parseQUIT(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [("onQuit", dict(user=None, quitmsg=None), True)])
+
+ # Include addons for channels that both user and bot are in
+ # simultaneously.
+ addons = reduce(
+ lambda x, y: x + y, [channel.addons for channel in origin.channels if self.identity in channel.users], [])
+ return (self.addons + addons, [("onQuit", dict(user=origin, quitmsg=extinfo), True)])
+
+ def parseMODE(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ events = [
+ ("onChanModeSet", dict(
+ user=None, channel=None, modedelta=None), True),
+ ("onUserModeSet", dict(origin=None, modedelta=None), True)
+ ]
+ for (mode, (setname, unsetname)) in _maskmodeeventnames.items():
+ events.append(
+ ("on%s" % setname, dict(user=None, channel=None, banmask=None), False))
+ events.append(
+ ("onMe%s" % setname, dict(user=None, channel=None, banmask=None), False))
+ events.append(
+ ("on%s" % unsetname, dict(user=None, channel=None, banmask=None), False))
+ events.append(
+ ("onMe%s" % unsetname, dict(user=None, channel=None, banmask=None), False))
+ for (mode, (setname, unsetname)) in _privmodeeventnames.items():
+ events.append(
+ ("on%s" % setname, dict(user=None, channel=None, modeuser=None), False))
+ events.append(
+ ("onMe%s" % setname, dict(user=None, channel=None), False))
+ events.append(
+ ("on%s" % unsetname, dict(user=None, channel=None, banmask=None), False))
+ events.append(
+ ("onMe%s" % unsetname, dict(user=None, channel=None), False))
+ return (None, events)
+ if type(target) == Channel:
+ events = []
+ modedelta = []
+ modeparams = params.split()
+ setmodes = modeparams.pop(0)
+ modeset = "+"
+ chanmodes = self.supports.get("CHANMODES", _defaultchanmodes)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ for mode in setmodes:
+ if mode in "+-":
+ modeset = mode
+ else:
+ if mode in chanmodes[0] + chanmodes[1]:
+ param = modeparams.pop(0)
+ modedelta.append(("%s%s" % (modeset, mode), param))
+ if mode in _maskmodeeventnames.keys():
+ if modeset == "+":
+ eventname = _maskmodeeventnames[mode][0]
+ if mode == "k":
+ target.key = param
+ if modeset == "-":
+ eventname = _maskmodeeventnames[mode][1]
+ if mode == "k":
+ target.key = None
+ matchesbot = glob.fnmatch.fnmatch(
+ "%s!%s@%s".lower() % (self.identity.nick, self.identity.username, self.identity.host), param.lower())
+ events.append(
+ ("on%s" % eventname, dict(user=origin, channel=target, banmask=param), False))
+ if matchesbot:
+ events.append(
+ ("onMe%s" % eventname, dict(user=origin, channel=target, banmask=param), False))
+ elif mode in chanmodes[2]:
+ if modeset == "+":
+ param = modeparams.pop(0)
+ modedelta.append(("%s%s" % (modeset, mode), param))
+ else:
+ modedelta.append(("%s%s" % (modeset, mode), None))
+ elif mode in chanmodes[3]:
+ modedelta.append(("%s%s" % (modeset, mode), None))
+ elif mode in prefix[0]:
+ modenick = modeparams.pop(0)
+ modeuser = self.user(modenick)
+ if mode in _privmodeeventnames.keys():
+ if modeset == "+":
+ eventname = _privmodeeventnames[mode][0]
+ if modeset == "-":
+ eventname = _privmodeeventnames[mode][1]
+ events.append(
+ ("on%s" % eventname, dict(user=origin, channel=target, modeuser=modeuser), False))
+ if modeuser == self.identity:
+ events.append(
+ ("onMe%s" % eventname, dict(user=origin, channel=target), False))
+ modedelta.append(("%s%s" % (modeset, mode), modeuser))
+ events.append(
+ ("onChanModeSet", dict(user=origin, channel=target, modedelta=modedelta), True))
+ return (self.addons + target.addons, events)
+ elif target == self.identity:
+ modeparams = (params if params else extinfo).split()
+ setmodes = modeparams.pop(0)
+ modedelta = []
+ modeset = "+"
+ for mode in setmodes:
+ if mode in "+-":
+ modeset = mode
+ continue
+ if modeset == "+":
+ if mode == "s":
+ if len(modeparams):
+ snomask = modeparams.pop(0)
+ snomaskdelta = []
+ snomodeset = "+"
+ for snomode in snomask:
+ if snomode in "+-":
+ snomodeset = snomode
+ continue
+ snomaskdelta.append(
+ "%s%s" % (snomodeset, snomode))
+ modedelta.append(("+s", snomaskdelta))
+ else:
+ modedelta.append(("+s", []))
+ else:
+ modedelta.append(("+%s" % mode, None))
+ if modeset == "-":
+ modedelta.append(("-%s" % mode, None))
+ return (self.addons, [("onUserModeSet", dict(origin=origin, modedelta=modedelta), True)])
+
+ def parseTOPIC(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [("onTopicSet", dict(user=None, channel=None, topic=None), True)])
+ return (self.addons + target.addons, [("onTopicSet", dict(user=origin, channel=target, topic=extinfo), True)])
+
+ def parseINVITE(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ return ([], [])
+ if origin == None:
+ return (None, [("onInvite", dict(user=None, channel=None), True)])
+ channel = self.channel(extinfo if extinfo else params)
+ return (self.addons + channel.addons, [("onInvite", dict(user=origin, channel=channel), True)])
+
+ def parsePRIVMSG(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ ctcp = re.findall(_ctcpmatch, extinfo)
+ if ctcp:
+ (ctcptype, ext) = ctcp[0]
+ if type(target) == User:
+ if ctcptype.upper() == "ACTION":
+ return (self.addons, [("onSendPrivAction", dict(origin=origin, user=target, action=ext), True)])
+ return (self.addons, [("onSendCTCP", dict(origin=origin, user=target, ctcptype=ctcptype, params=ext), True)])
+ elif type(target) == Channel:
+ if ctcptype.upper() == "ACTION":
+ return (self.addons, [("onSendChanAction", dict(origin=origin, channel=target, targetprefix=targetprefix, action=ext), True)])
+ return (self.addons, [("onSendChanCTCP", dict(origin=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext), True)])
+ else:
+ if type(target) == User:
+ return (self.addons, [("onSendPrivMsg", dict(origin=origin, user=target, msg=extinfo), True)])
+ elif type(target) == Channel:
+ return (self.addons + target.addons, [("onSendChanMsg", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo), True)])
+ if origin == None:
+ return (None, [
+ ("onPrivMsg", dict(user=None, msg=None, identified=None), True),
+ ("onChanMsg", dict(user=None, channel=None,
+ targetprefix=None, msg=None, identified=None), True),
+ ("onCTCP", dict(user=None, ctcptype=None, params=None, identified=None), True),
+ ("onChanCTCP", dict(user=None, channel=None, targetprefix=None,
+ ctcptype=None, params=None, identified=None), True),
+ ("onPrivAction", dict(
+ user=None, action=None, identified=None), True),
+ ("onChanAction", dict(user=None, channel=None,
+ targetprefix=None, action=None, identified=None), True),
+ ("onSendPrivMsg", dict(
+ origin=None, user=None, msg=None), True),
+ ("onSendChanMsg", dict(
+ origin=None, channel=None, targetprefix=None, msg=None), True),
+ ("onSendCTCP", dict(origin=None, user=None, ctcptype=None, params=None), True),
+ ("onSendPrivAction", dict(
+ origin=None, user=None, action=None), True),
+ ("onSendChanAction", dict(
+ origin=None, channel=None, targetprefix=None, action=None), True),
+ ("onSendChanCTCP", dict(origin=None, channel=None,
+ targetprefix=None, ctcptype=None, params=None), True),
+ ])
+ if "identify-msg" in self.enabledcaps and extinfo[0] in "+-":
+ identified, extinfo = extinfo.startswith("+"), extinfo[1:]
+ else:
+ identified = None
+ ctcp = re.findall(_ctcpmatch, extinfo)
+ if ctcp:
+ (ctcptype, ext) = ctcp[0]
+ if target == self.identity:
+ if ctcptype.upper() == "ACTION":
+ return (self.addons, [("onPrivAction", dict(user=origin, action=ext, identified=identified), True)])
+ return (self.addons, [("onCTCP", dict(user=origin, ctcptype=ctcptype, params=ext, identified=identified), True)])
+ if type(target) == Channel:
+ if ctcptype.upper() == "ACTION":
+ return (self.addons, [("onChanAction", dict(user=origin, channel=target, targetprefix=targetprefix, action=ext, identified=identified), True)])
+ return (self.addons, [("onChanCTCP", dict(user=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext, identified=identified), True)])
+ else:
+ if type(target) == Channel:
+ return (self.addons + target.addons, [("onChanMsg", dict(user=origin, channel=target, targetprefix=targetprefix, msg=extinfo, identified=identified), True)])
+ elif target == self.identity:
+ return (self.addons, [("onPrivMsg", dict(user=origin, msg=extinfo, identified=identified), True)])
+
+ def parseNOTICE(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None, outgoing=False):
+ if outgoing:
+ ctcp = re.findall(_ctcpmatch, extinfo)
+ if ctcp:
+ (ctcptype, ext) = ctcp[0]
+ return (self.addons, [("onSendCTCPReply", dict(origin=origin, ctcptype=ctcptype, params=ext), True)])
+ else:
+ if type(target) == Channel:
+ return (self.addons + target.addons, [("onSendChanNotice", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo), True)])
+ elif type(target) == User:
+ return (self.addons, [("onSendPrivNotice", dict(origin=origin, user=target, msg=extinfo), True)])
+ if origin == None:
+ return (None, [
+ ("onPrivNotice", dict(
+ origin=None, msg=None, identified=None), True),
+ ("onServNotice", dict(
+ origin=None, msg=None, identified=None), True),
+ ("onChanNotice", dict(origin=None, channel=None,
+ targetprefix=None, msg=None, identified=None), True),
+ ("onCTCPReply", dict(
+ origin=None, ctcptype=None, params=None, identified=None), True),
+ ("onSendPrivNotice", dict(origin=None, msg=None), True),
+ ("onSendChanNotice", dict(
+ origin=None, channel=None, targetprefix=None, msg=None), True),
+ ("onSendCTCPReply", dict(
+ origin=None, ctcptype=None, params=None), True),
+ ])
+ if "identify-msg" in self.enabledcaps and extinfo[0] in "+-":
+ identified, extinfo = extinfo.startswith("+"), extinfo[1:]
+ else:
+ identified = None
+ ctcp = re.findall(_ctcpmatch, extinfo)
+ if ctcp and target == self.identity:
+ (ctcptype, ext) = ctcp[0]
+ return (self.addons, [("onCTCPReply", dict(origin=origin, ctcptype=ctcptype, params=ext, identified=identified), True)])
+ else:
+ if type(origin) == Server:
+ return (self.addons, [("onServNotice", dict(origin=origin, msg=extinfo, identified=identified), True)])
+ if type(target) == Channel:
+ return (self.addons + target.addons, [("onChanNotice", dict(origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo, identified=identified), True)])
+ elif target == self.identity:
+ return (self.addons, [("onPrivNotice", dict(origin=origin, msg=extinfo, identified=identified), True)])
+
+ def parse367(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Ban list
+ if origin == None:
+ return (None, [("onBanListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)])
+ (channame, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onBanListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse368(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onBanListEnd", dict(origin=None, channel=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onBanListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)])
+
+ def parse346(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Invite list
+ if origin == None:
+ return (None, [("onInviteListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)])
+ (channame, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onInviteListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse347(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onInviteListEnd", dict(origin=None, channel=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onInviteListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)])
+
+ def parse348(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Ban Exception list
+ if origin == None:
+ return (None, [("onBanExceptListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)])
+ (channame, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onBanExceptListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse349(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onBanExceptListEnd", dict(origin=None, channel=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onBanExceptListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)])
+
+ def parse910(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel Access List
+ if origin == None:
+ return (None, [("onAccessListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)])
+ (channame, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onAccessListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse911(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onAccessListEnd", dict(origin=None, channel=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onAccessListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)])
+
+ def parse941(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Spam Filter list
+ if origin == None:
+ return (None, [("onSpamfilterListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)])
+ (channame, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onSpamfilterListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse940(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onSpamfilterListEnd", dict(origin=None, channel=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onSpamfilterListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)])
+
+ def parse954(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel exemptchanops list
+ if origin == None:
+ return (None, [("onExemptChanOpsListEntry", dict(origin=None, channel=None, mask=None, setby=None, settime=None), True)])
+ (channame, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onExemptChanOpsListEntry", dict(origin=origin, channel=channel, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse953(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onExemptChanOpsListEnd", dict(origin=None, channel=None, endmsg=None), True)])
+ channel = self.channel(params)
+ return (self.addons + channel.addons, [("onExemptChanOpsListEnd", dict(origin=origin, channel=channel, endmsg=extinfo), True)])
+
+ def parse728(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None): # Channel quiet list
+ if origin == None:
+ return (None, [("onQuietListEntry", dict(origin=None, channel=None, modechar=None, mask=None, setby=None, settime=None), True)])
+ (channame, modechar, mask, setby, settime) = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onQuietListEntry", dict(origin=origin, channel=channel, modechar=modechar, mask=mask, setby=setby, settime=int(settime)), True)])
+
+ def parse729(self, origin=None, target=None, targetprefix=None, params=None, extinfo=None):
+ if origin == None:
+ return (None, [("onQuietListEnd", dict(channel=None, endmsg=None), True)])
+ channame, modechar = params.split()
+ channel = self.channel(channame)
+ return (self.addons + channel.addons, [("onQuietListEnd", dict(channel=channel, endmsg=extinfo), True)])
+
+ def eventsupports(self):
+ """eventsupports() --> {eventname: arguments, ...}
+
+ Generates and returns a dict of supported events and associated arguments. Good for attempting to validate addon events."""
+ supports = {}
+ for item in dir(self):
+ if re.match(r"parse(\d{3}|[A-Z]+)", item):
+ parsemethod = getattr(self, item)
+ addons, events = parsemethod()
+ for (event, args, fallback) in events:
+ supports[event] = tuple(args.keys())
+ supports.update({"onConnect": (),
+ "onRegistered": (),
+ "onConnectAttempt": (),
+ "onConnectFail": ("exc", "excmsg", "tb"),
+ "onSessionOpen": (),
+ "onSessionClose": (),
+ "onDisconnect": ("expected",),
+ "onOther": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"),
+ "onUnhandled": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"),
+ "onRecv": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"),
+ "onSend": ("line", "origin", "cmd", "target", "targetprefix", "params", "extinfo"),
+ })
+ return supports
+
+ def _register(self):
+ if self.passwd:
+ self.send(u"PASS %s" % self.passwd)
+ self._trynick()
+ self.send(
+ u"USER {self.username} * * :{self.realname}".format(**vars()))
+
+ def requestcapls(self, origin=None):
+ """requestcapls(...)
+
+ Sends "CAP LS" to the server to request supported capabilities. Please use this method instead of send()."""
+ if not self._caplsrequested:
+ self.send("CAP LS", origin=origin)
+ self._caplsrequested = True
+
+ # Here are the builtin event handlers.
+
+ def onRecv(self, context, line, origin, cmd, target, targetprefix, params, extinfo):
+ if not self._registered:
+ if type(cmd) == int and cmd < 100 and target != "*": # Registration complete!
+ self.identity = self.user(target, init=True)
+ self.identity.server = origin
+ self._event(self.getalladdons(), [
+ ("onRegistered", dict(), False)])
+
+ def onRegistered(self, context):
+ self._registered = True
+
+ def onConnect(self, context):
+ if self.requestcaps:
+ self.requestcapls()
+ elif self.starttls:
+ self.send("STARTLS")
+ elif len(self._requestedcaps) == 0 and not self._caplsrequested:
+ self._register()
+
+ def onCapLS(self, context, capabilities):
+ self.supportedcaps = capabilities
+ self._caplsrequested = False
+ if self.starttls and "tls" in capabilities and not self.secure and not self._starttls:
+ self.send("STARTTLS")
+ elif not self.registered:
+ requestcaps = [
+ cap for cap in self.requestcaps if cap in capabilities]
+ if requestcaps:
+ self.sendcapsrequest(requestcaps)
+ elif len(self._requestedcaps) == 0:
+ self.send("CAP END")
+ self._register()
+
+ def onCapAck(self, context, capabilities):
+ for cap in capabilities:
+ mods, capname = re.findall(
+ "^([%s]*)(.+)$" % re.escape(_capmodifiers), cap)[0]
+ if "-" in mods and capname in self.enabledcaps:
+ self.enabledcaps.remove(capname)
+ elif cap not in self.enabledcaps:
+ self.enabledcaps.append(capname)
+ if cap in self._requestedcaps:
+ self._requestedcaps.remove(cap)
+ if not self.registered and len(self._requestedcaps) == 0:
+ self.send("CAP END")
+ self._register()
+
+ def onCapNak(self, context, capabilities):
+ for cap in capabilities:
+ mods, capname = re.findall(
+ "^([%s]*)(.+)$" % re.escape(_capmodifiers), cap)[0]
+ if cap in self._requestedcaps:
+ self._requestedcaps.remove(cap)
+ if not self.registered and len(self._requestedcaps) == 0:
+ self.send("CAP END")
+ self._register()
+
+ def on433(self, context, line, origin, target, params, extinfo):
+ if not self._registered: # Server reports nick taken, so we need to try another.
+ self._trynick()
+
+ def on670(self, context, line, origin, target, params, extinfo):
+ self.logwrite("*** Attempting StartTLS")
+ self._connection = ssl.wrap_socket(
+ self._connection, cert_reqs=ssl.CERT_NONE) # Server says go ahead with starttls.
+ self._event(self.getalladdons(), [("onStartTLS", dict(), False)])
+
+ def on691(self, context, line, origin, target, params, extinfo): # STARTTLS Failure
+ self.logwrite("*** StartTLS Failed")
+ if self.requestcaps:
+ self.send("CAP END")
+ self._register()
+
+ def onStartTLS(self, context):
+ self._starttls = True
+ self.onConnect(self)
+
+ def onWelcome(self, context, origin, msg):
+ self.welcome = msg # Welcome message
+
+ def onYourHost(self, context, origin, msg):
+ self.hostinfo = msg # Your Host
+
+ def onServerCreated(self, context, origin, msg):
+ self.servcreated = msg # Server Created
+
+ def onServInfo(self, context, origin, servinfo):
+ self.servinfo = servinfo # What is this code?
+
+ def onSupports(self, context, origin, supports, msg): # Server Supports
+ protos = u" ".join(
+ [proto for proto in self.protoctl if proto in supports.keys()])
+ if protos:
+ self.send(u"PROTOCTL {protos}".format(**vars()))
+ self.supports.update(supports)
+
+ def onSnoMask(self, context, origin, snomask): # Snomask
+ self.identity.snomask = snomask
+ if "s" not in self.identity.modes:
+ self.identity.snomask = ""
+
+ def onUserModes(self, context, origin, modes): # User Modes
+ self.identity.modes = modes
+ if "s" not in self.identity.modes:
+ self.identity.snomask = ""
+
+ def onNetStats(self, context, origin, netstats): # Net Stats
+ self.netstats = netstats
+
+ def onOpCount(self, context, origin, opcount):
+ self.opcount = opcount
+
+ def onChanCount(self, context, origin, chancount):
+ self.chancount = chancount
+
+ def onReturn(self, context, user): # Returned from away status
+ user.away = False
+ user.awaymsg = None
+
+ def onAway(self, context, user, awaymsg): # Entered away status
+ user.away = True
+ user.awaymsg = awaymsg
+
+ def onMeReturn(self, context, origin): # Returned from away status
+ self.identity.away = False
+ self.identity.awaymsg = None
+
+ def onMeAway(self, context, origin, msg): # Entered away status
+ self.identity.away = True
+
+ def onWhoisStart(self, context, origin, user, nickname, username, host, realname): # Start of WHOIS data
+ user.nick = nickname
+ user.username = username
+ user.host = host
+
+ def onWhoisAway(self, context, origin, user, nickname, awaymsg): # Away Message
+ user.away = True
+ user.awaymsg = awaymsg
+
+ def onWhoisServer(self, context, origin, user, nickname, server, servername): # Server
+ user.server = server
+
+ def onWhoisOp(self, context, origin, user, nickname, msg): # IRC Op
+ user.ircop = True
+ user.ircopmsg = msg
+
+ def onWhoisTimes(self, context, origin, user, nickname, idletime, signontime, msg): # Idle and Signon times
+ user.idlesince = int(time.time()) - idletime
+ user.signontime = signontime
+
+ def onWhoisSSL(self, context, origin, user, nickname, msg): # SSL
+ user.secure = True
+
+ def onWhoisLoggedInAs(self, context, origin, user, nickname, loggedinas, msg): # Logged in as
+ user.loggedinas = loggedinas
+
+ def onChannelModes(self, context, origin, channel, modedelta): # Channel Modes
+ chanmodes = self.supports.get("CHANMODES", _defaultchanmodes)
+ for ((modeset, mode), param) in modedelta:
+ if mode in chanmodes[2]:
+ channel.modes[mode] = param
+ elif mode in chanmodes[3]:
+ channel.modes[mode] = True
+
+ def onChanCreated(self, context, origin, channel, created): # Channel created
+ channel.created = created
+
+ def onTopic(self, context, origin, channel, topic): # Channel Topic
+ channel.topic = topic
+
+ def onTopicInfo(self, context, origin, channel, topicsetby, topictime): # Channel Topic info
+ channel.topicsetby = topicsetby
+ channel.topictime = topictime
+
+ def onWhoEntry(self, context, origin, channel, user, channame, username, host, serv, nick, flags, hops, realname): # WHO reply
+ user.hops = hops
+ user.realname = realname
+ user.username = username
+ user.host = host
+ user.server = serv
+ user.away = "G" in flags
+ user.ircop = "*" in flags
+ if type(channel) == Channel:
+ if user not in channel.users:
+ channel.users.append(user)
+ if channel not in user.channels:
+ user.channels.append(channel)
+ for (mode, prefix) in zip(*self.supports.get("PREFIX", _defaultprefix)):
+ if prefix in flags:
+ if mode in channel.modes.keys() and user not in channel.modes[mode]:
+ channel.modes[mode].append(user)
+ elif mode not in channel.modes.keys():
+ channel.modes[mode] = [user]
+
+ def onNames(self, context, origin, channel, flag, channame, nameslist): # NAMES reply
+ for (symbs, nick, username, host) in nameslist:
+ user = self.user(nick)
+ if user.nick != nick:
+ user.nick = nick
+ if username and user.username != username:
+ user.username = username
+ if host and user.host != host:
+ user.host = host
+ with channel.lock:
+ if channel not in user.channels:
+ user.channels.append(channel)
+ if user not in channel.users:
+ channel.users.append(user)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ for symb in symbs:
+ mode = prefix[0][prefix[1].index(symb)]
+ if not channel.modes.has_key(mode):
+ channel.modes[mode] = [user]
+ elif user not in channel.modes[mode]:
+ channel.modes[mode].append(user)
+
+ def onMOTDLine(self, context, origin, motdline): # MOTD line
+ origin.motd.append(motdline)
+
+ def onMOTDStart(self, context, origin, motdgreet): # Begin MOTD
+ origin.motdgreet = motdgreet
+ origin.motd = []
+
+ def onMOTDEnd(self, context, origin, motdend):
+ origin.motdend = motdend # End of MOTD
+
+ # elif cmd==386 and "q" in self.supports["PREFIX"][0]: # Channel Owner (Unreal)
+ #(channame,owner)=params.split()
+ # channel=self.channel(channame)
+ #self._event("onRecv", channel.addons, **data)
+ # if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name
+ # user=self.user(owner)
+ #if user.nick!=owner: user.nick=owner
+ # if channel.modes.has_key("q"):
+ #if user not in channel.modes["q"]: channel.modes["q"].append(user)
+ # else: channel.modes["q"]=[user]
+
+ # elif cmd==388 and "a" in self.supports["PREFIX"][0]: # Channel Admin (Unreal)
+ #(channame,admin)=params.split()
+ # channel=self.channel(channame)
+ #self._event("onRecv", channel.addons, **data)
+ # if channel.name!=channame: channel.name=channame ### Server seems to have changed the idea of the case of the channel name
+ # user=self.user(admin)
+ #if user.nick!=admin: user.nick=admin
+ # if channel.modes.has_key("a"):
+ #if user not in channel.modes["a"]: channel.modes["a"].append(user)
+ # else: channel.modes["a"]=[user]
+
+ # def onNickChange(self, context, user, newnick):
+ # for other in self.users:
+ # if self.supports.get("CASEMAPPING", "rfc1459")=="ascii":
+ # collision=other.nick.lower()==newnick.lower()
+ # else:
+ # collision=other.nick.translate(_rfc1459casemapping)==newnick.translate(_rfc1459casemapping)
+ # if collision:
+ # self.users.remove(other) ### Nick collision, safe to assume this orphaned user is offline, so we shall remove the old instance.
+ # for channel in self.channels:
+ # If for some odd reason, the old user still appears common channels, then we will remove the user anyway.
+ # if other in channel.users:
+ # channel.users.remove(other)
+ # user.nick=newnick
+
+ def onNickChange(self, context, user, newnick):
+ if self.lower(user.nick) == self.lower(newnick):
+ user.nick = newnick
+ else:
+ try:
+ other = self.users[newnick]
+ except KeyError:
+ pass
+ else:
+ for channel in self.channels:
+ # If for some odd reason, the old user still appears common
+ # channels, then we will remove the user anyway.
+ if other in channel.users:
+ channel.users.remove(other)
+ self.users.remove(other)
+ self.users.remove(user)
+ user.nick = newnick
+ self.users.append(user)
+
+ def onAccountLogin(self, context, user, account):
+ user.loggedinas = account
+
+ def onAccountLogout(self, context, user):
+ user.loggedinas = None
+
+ def onJoin(self, context, user, channel, loggedinas, realname):
+ if "extended-join" in self.enabledcaps:
+ user.loggedinas = loggedinas
+ user.realname = realname
+ if channel not in user.channels:
+ user.channels.append(channel)
+ if user not in channel.users:
+ channel.users.append(user)
+
+ def onMeJoin(self, context, channel):
+ channel._init()
+ with channel._joining:
+ if channel._joinrequested:
+ channel._joinreply = "JOIN"
+ channel._joining.notify()
+ self.send(u"MODE %s" % channel.name)
+ self.send(u"WHO %s" % channel.name)
+ self.send(u"MODE %s :%s" %
+ (channel.name, self.supports.get("CHANMODES", _defaultchanmodes)[0]))
+
+ def onKick(self, context, kicker, channel, kicked, kickmsg):
+ if channel in kicked.channels:
+ kicked.channels.remove(channel)
+ if kicked in channel.users:
+ channel.users.remove(kicked)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ for mode in prefix[0]:
+ if mode in channel.modes.keys() and kicked in channel.modes[mode]:
+ channel.modes[mode].remove(kicked)
+
+ def onPart(self, context, user, channel, partmsg):
+ if channel in user.channels:
+ user.channels.remove(channel)
+ if user in channel.users:
+ channel.users.remove(user)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ for mode in prefix[0]:
+ if mode in channel.modes.keys() and user in channel.modes[mode]:
+ channel.modes[mode].remove(user)
+
+ def onMePart(self, context, channel, partmsg):
+ with channel._parting:
+ if channel._partrequested:
+ channel._partreply = "PART"
+ channel._parting.notify()
+
+ def onMeKicked(self, context, kicker, channel, kickmsg):
+ with channel._parting:
+ if channel._partrequested:
+ channel._partreply = "KICK"
+ channel._parting.notify()
+
+ def onQuit(self, context, user, quitmsg):
+ channels = list(user.channels)
+ for channel in channels:
+ with channel.lock:
+ if user in channel.users:
+ channel.users.remove(user)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ for mode in prefix[0]:
+ if mode in channel.modes.keys() and user in channel.modes[mode]:
+ channel.modes[mode].remove(user)
+ user._init()
+
+ def onChanModeSet(self, context, user, channel, modedelta):
+ chanmodes = self.supports.get("CHANMODES", _defaultchanmodes)
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ with channel.lock:
+ for ((modeset, mode), param) in modedelta:
+ if mode in chanmodes[0] + prefix[0]:
+ if mode not in channel.modes.keys():
+ channel.modes[mode] = []
+ if mode in chanmodes[0]:
+ if modeset == "+":
+ if param.lower() not in [mask.lower() for (mask, setby, settime) in channel.modes[mode]]:
+ channel.modes[mode].append(
+ (param, user, int(time.time())))
+ else:
+ if mode == "b": # Inspircd mode is case insentive when unsetting the mode
+ masks = [mask.lower()
+ for (mask, setby, settime) in channel.modes[mode]]
+ if param.lower() in masks:
+ index = masks.index(param.lower())
+ del channel.modes[mode][index]
+ else:
+ masks = [
+ mask for (mask, setby, settime) in channel.modes[mode]]
+ if param in masks:
+ index = masks.index(param)
+ del channel.modes[mode][index]
+ elif mode in chanmodes[1]:
+ if modeset == "+":
+ channel.modes[mode] = param
+ else:
+ channel.modes[mode] = None
+ elif mode in chanmodes[2]:
+ if modeset == "+":
+ channel.modes[mode] = param
+ else:
+ channel.modes[mode] = None
+ elif mode in chanmodes[3]:
+ if modeset == "+":
+ channel.modes[mode] = True
+ else:
+ channel.modes[mode] = False
+ elif mode in prefix[0]:
+ if modeset == "+":
+ if param not in channel.modes[mode]:
+ channel.modes[mode].append(param)
+ elif param in channel.modes[mode]:
+ channel.modes[mode].remove(param)
+
+ def onUserModeSet(self, context, origin, modedelta):
+ for ((modeset, mode), param) in modedelta:
+ if modeset == "+":
+ if mode not in self.identity.modes:
+ self.identity.modes += mode
+ if mode == "s":
+ for snomodeset, snomode in param:
+ if snomodeset == "+" and snomode not in self.identity.snomask:
+ self.identity.snomask += snomode
+ if snomodeset == "-" and snomode in self.identity.snomask:
+ self.identity.snomask = self.identity.snomask.replace(
+ snomode, "")
+ if modeset == "-":
+ if mode in self.identity.modes:
+ self.identity.modes = self.identity.modes.replace(mode, "")
+ if mode == "s":
+ self.identity.snomask = ""
+
+ def onTopicSet(self, context, user, channel, topic):
+ with channel.lock:
+ channel.topic = topic
+ channel.topicsetby = user
+ channel.topictime = int(time.time())
+
+ def onCTCP(self, context, user, ctcptype, params):
+ if ctcptype.upper() == "VERSION":
+ user.ctcpreply("VERSION", self.ctcpversion())
+ elif ctcptype.upper() == "TIME":
+ tformat = time.ctime()
+ tz = time.tzname[0]
+ user.ctcpreply("TIME", "%(tformat)s %(tz)s" % vars())
+ elif ctcptype.upper() == "PING":
+ user.ctcpreply("PING", params)
+ elif ctcptype.upper() == "FINGER":
+ user.ctcpreply("FINGER", params)
+
+ def onChanCTCP(self, context, user, channel, targetprefix, ctcptype, params):
+ self.onCTCP(context, user, ctcptype, params)
+
+ def onBanListEntry(self, context, origin, channel, mask, setby, settime): # Channel Ban list
+ if "b" not in channel.modes.keys():
+ channel.modes["b"] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["b"]]:
+ channel.modes["b"].append((mask, setby, int(settime)))
+
+ def onInviteListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list
+ if "I" not in channel.modes.keys():
+ channel.modes["I"] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["I"]]:
+ channel.modes["I"].append((mask, setby, int(settime)))
+
+ def onBanExceptListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list
+ if "e" not in channel.modes.keys():
+ channel.modes["e"] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["e"]]:
+ channel.modes["e"].append((mask, setby, int(settime)))
+
+ def onAccessListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list
+ if "w" not in channel.modes.keys():
+ channel.modes["w"] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["w"]]:
+ channel.modes["w"].append((mask, setby, int(settime)))
+
+ def onSpamfilterListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list
+ if "g" not in channel.modes.keys():
+ channel.modes["g"] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["g"]]:
+ channel.modes["g"].append((mask, setby, int(settime)))
+
+ def onExemptChanOpsListEntry(self, context, origin, channel, mask, setby, settime): # Channel Invite Exception list
+ if "X" not in channel.modes.keys():
+ channel.modes["X"] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes["X"]]:
+ channel.modes["X"].append((mask, setby, int(settime)))
+
+ def onQuietListEntry(self, context, origin, channel, modechar, mask, setby, settime): # Channel quiet list (Freenode)
+ if modechar not in channel.modes.keys():
+ channel.modes[modechar] = []
+ if mask.lower() not in [m.lower() for (m, s, t) in channel.modes[modechar]]:
+ channel.modes[modechar].append((mask, setby, int(settime)))
+
+ def onOther(self, context, line, origin, cmd, target, targetprefix, params, extinfo):
+ if cmd in (384, 403, 405, 471, 473, 474, 475, 476, 520, 477, 489, 495): # Channel Join denied
+ try:
+ channel = self.channel(params)
+ except InvalidName:
+ pass
+ else:
+ with channel._joining:
+ if channel._joinrequested:
+ channel._joinreply = (cmd, extinfo)
+ channel._joining.notify()
+
+ elif cmd == 470: # Channel Join denied due to redirect
+ channelname, redirect = params.split()
+ try:
+ channel = self.channel(channelname)
+ except InvalidName:
+ pass
+ else:
+ with channel._joining:
+ if channel._joinrequested:
+ channel._joinreply = (
+ cmd, "%s (%s)" % (extinfo, redirect))
+ channel._joining.notify()
+
+ # elif cmd in (495, 384, 385, 386, 468, 470, 366, 315, 482, 484, 953, 368, 482, 349, 940, 911, 489, 490, 492, 520, 530): # Channels which appear in params
+ # for param in params.split():
+ # if len(param) and param[0] in self.supports["CHANTYPES"]:
+ # channel=self.channel(param)
+ #self._event("onRecv", channel.addons, **data)
+
+ def _trynick(self):
+ (q, s) = divmod(self.trynick, len(self.nick)
+ if type(self.nick) in (list, tuple) else 1)
+ nick = self.nick[s] if type(self.nick) in (list, tuple) else self.nick
+ if q > 0:
+ nick = "%s%d" % (nick, q)
+ self.send(u"NICK %s" % nick)
+ self.trynick += 1
+
+ def send(self, line, origin=None, T=None):
+ """send(line[, ...])
+
+ Sends 'line' to IRC server. Try to use this method sparingly by using other methods designed to format requests correctly.
+ Supported optional arguments:
+
+ 'origin': Used (voluntarily) by addons to identify origin of sent data. Good for helping addons ignore lines they send
+ so as to avoid infinite loops.
+
+ 'T': Specifies what time to send the data if not immediately. This method currently throttles PRIVMSGs to avoid floods."""
+ with self.lock:
+ if not self.connected:
+ raise NotConnected
+ if "\r" in line or "\n" in line:
+ raise InvalidCharacter
+ if type(line) == str:
+ line = autodecode(line)
+ cmd = line.split(" ")[0].upper()
+
+ if T == None:
+ T = time.time()
+ if cmd == "PRIVMSG":
+ # Hard-coding a throttling mechanism for PRIVMSGs only here. Will later build support for custom throttlers.
+ # The throttle will be triggered when it attempts to send a sixth PRIVMSG in a four-second interval.
+ # When the throttle is active, PRIVMSGs will be sent in at least one-second intervals.
+ # The throttle is deactivated when three seconds elapse without
+ # sending a PRIVMSG.
+ while len(self.throttledata) and self.throttledata[0] < T - 4:
+ del self.throttledata[0]
+ if not self.throttled:
+ if len(self.throttledata) >= 5:
+ self.throttled = True
+ T = self.throttledata[-1] + 1
+ else:
+ if len(self.throttledata) == 0 or self.throttledata[-1] < T - 2:
+ self.throttled = False
+ else:
+ T = max(T, self.throttledata[-1] + 1)
+ self.throttledata.append(T)
+ elif cmd in ("WHO", "MODE"):
+ # Might as well throttle WHOs and MODEs too.
+ while len(self.throttledata) and self.throttledata[0] < T - 4:
+ del self.throttledata[0]
+ if not self.throttled:
+ if len(self.throttledata) >= 5:
+ self.throttled = True
+ T = self.throttledata[-1] + 0.125
+ else:
+ if len(self.throttledata) == 0 or self.throttledata[-1] < T - 2:
+ self.throttled = False
+ else:
+ T = max(T, self.throttledata[-1] + 0.125)
+ self.throttledata.append(T)
+ with self._sendline:
+ self._outgoing.append((T, line, origin))
+ self._sendline.notify()
+
+ def _cancelsend(self, line, origin=None, T=None):
+ with self._sendline:
+ self._outgoing.remove((T, line, origin))
+ self._sendline.notify()
+
+ def _procsendline(self, line, origin=None):
+ """Function responsible for sending data to the IRC server and calling all applicable event methods."""
+ match = re.findall(_ircmatch, line)
+ if len(match) == 0:
+ return
+ (null, username, host, cmd, target, params, extinfo) = match[0]
+ cmd = cmd.upper()
+ with self.lock:
+ if cmd == "QUIT":
+ self._quitexpected = True
+ if self._connection == None:
+ return
+ origline = line
+
+ # Modify line if it contains a password so that the password is not
+ # logged or sent to any potentially untrustworthy addons
+ if cmd == "PRIVMSG":
+ if target.upper() == "NICKSERV":
+ nscmd = re.findall(
+ r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
+ if nscmd:
+ nscmd = nscmd[0]
+ if nscmd[0].upper() in ("IDENTIFY", "REGISTER"):
+ extinfo = "%s ********" % nscmd[0]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
+ extinfo = "%s %s ********" % nscmd[:2]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif nscmd[0].upper() == "SET":
+ if nscmd[1].upper() == "PASSWORD":
+ extinfo = "%s %s ********" % nscmd[:2]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
+ extinfo = "********"
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ if target.upper() == "CHANSERV":
+ cscmd = re.findall(
+ r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
+ if cscmd:
+ cscmd = cscmd[0]
+ if cscmd[0].upper() in ("IDENTIFY", "REGISTER"):
+ extinfo = "%s %s ********" % cscmd[:2]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
+ extinfo = "%s %s %s ********" % cscmd[:3]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cscmd[0].upper() == "SET":
+ if cscmd[2].upper() == "PASSWORD":
+ extinfo = "%s %s %s ********" % cscmd[:3]
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
+ extinfo = "********"
+ line = "%s %s :%s" % (cmd, target, extinfo)
+ elif cmd.upper() in ("NS", "NICKSERV"):
+ if target.upper() in ("IDENTIFY", "REGISTER"):
+ params = params.split(" ")
+ while "" in params:
+ params.remove("")
+ if len(params):
+ params[0] = "********"
+ params = " ".join(params)
+ line = "%s %s %s" % (cmd, target, params)
+ elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
+ params = params.split(" ")
+ while "" in params:
+ params.remove("")
+ if len(params) > 1:
+ params[1] = "********"
+ params = " ".join(params)
+ line = "%s %s %s" % (cmd, target, params)
+ elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
+ params = ""
+ target = "********"
+ line = "%s %s" % (cmd, target)
+ elif cmd.upper() == "OPER":
+ params = "********"
+ line = "%s %s %s" % (cmd, target, params)
+ elif cmd.upper() == "PASS":
+ extinfo = "********"
+ target = ""
+ line = "%s :%s" % (cmd, extinfo)
+ elif cmd.upper() == "IDENTIFY":
+ target = "********"
+ line = "%s %s" % (cmd, target)
+
+ prefix = self.supports.get("PREFIX", _defaultprefix)
+ chantypes = self.supports.get("CHANTYPES", _defaultchantypes)
+ chanmatch = re.findall(_targchanmatch %
+ (re.escape(prefix[1]), re.escape(chantypes)), target)
+
+ # Check to see if target matches a channel (optionally with prefix)
+ if chanmatch:
+ targetprefix, channame = chanmatch[0]
+ target = self.channel(channame)
+ if target.name != channame:
+ # Target channel name has changed
+ target.name = channame
+ # Check to see if target matches a valid nickname. Do NOT convert
+ # target to User instance if cmd is NICK.
+ elif re.match(_nickmatch, target) and cmd in ("PRIVMSG", "NOTICE", "MODE", "INVITE", "CHGHOST", "CHGIDENT", "CHGNAME", "WHOIS", "KILL", "SAMODE", "SETHOST", "WHO"):
+ targetprefix = ""
+ target = self.user(target)
+
+ # Otherwise, target is just left as a string
+ else:
+ targetprefix = ""
+
+ parsename = ("parse%03d" if type(cmd) == int else "parse%s") % cmd
+ if hasattr(self, parsename):
+ parsemethod = getattr(self, parsename)
+ if callable(parsemethod):
+ try:
+ ret = parsemethod(
+ origin, target, targetprefix, params, extinfo, outgoing=True)
+ addons, events = ret if ret is not None else (
+ self.events, [])
+ except:
+ self.logerror(
+ u"There was an error in parsing the following line:", line)
+ return
+ else:
+ addons = self.addons
+ if type(cmd) == unicode:
+ events = [(
+ "onSend%s" % cmd.upper(), dict(line=line, origin=origin if origin else self,
+ target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), True)]
+ else:
+ events = []
+ if addons == None:
+ addons = []
+
+ if cmd not in ("PING", "PONG") or not self.quietpingpong: # Supress pings and pongs if self.quietpingpong is set to True
+ self._event(
+ addons, [("onSend", dict(origin=origin if origin else self, line=line, cmd=cmd, target=target, targetprefix=targetprefix, params=params, extinfo=extinfo), False)], line)
+ self._event(addons, events, line)
+
+ if not (cmd in ("PING", "PONG") and self.quietpingpong):
+ #self._event(self.addons, [("onSend" , dict(origin=origin, line=line, cmd=cmd, target=target, params=params, extinfo=extinfo), False)])
+ self.logwrite(">>> %s" % line)
+ self._connection.send("%s\n" % origline.encode('utf8'))
+
+ def _sendhandler(self):
+ # Enforce that this function must only be run from within
+ # self._sendhandlerthread.
+ if currentThread() != self._sendhandlerthread:
+ raise RuntimeError, "This function is designed to run in its own thread."
+
+ try:
+ while True:
+ with self._sendline:
+ if "quit" in self._outgoing:
+ sys.exit()
+ S = time.time()
+ if len(self._outgoing):
+ T, line, origin = min(self._outgoing)
+ if T > S:
+ # The next item in the queue (by time) is still
+ # scheduled to be sent later. We wait until then,
+ # or when another item is put into the queue,
+ # whichever is first.
+ self._sendline.wait(T - S)
+ continue
+ else:
+ # The next item in the queue (by time) should be
+ # sent now.
+ self._outgoing.remove((T, line, origin))
+ else:
+ # The queue is empty, so we will wait until something
+ # is put into the queue, then restart the while loop.
+ self._sendline.wait()
+ continue
+
+ try:
+ self._procsendline(line, origin=origin)
+ except socket.error:
+ exc, excmsg, tb = sys.exc_info()
+ with self.lock:
+ self.logwrite(
+ u"*** Connection to {self:uri} failed: {excmsg}.".format(**vars()))
+ self._event(self.getalladdons(), [
+ ("onConnectFail", dict(exc=exc, excmsg=excmsg, tb=tb), False)])
+ with self._sendline:
+ self._outgoing.clear()
+ try:
+ self._connection.close()
+ except:
+ pass
+
+ except SystemExit:
+ pass
+
+ except:
+ self._quitexpected = True
+ self.logerror("FATAL Exception in {self}".format(**vars()))
+ with self._sendline:
+ try:
+ self._connection.send(
+ "QUIT :%s\n" % tb.rstrip().split("\n")[-1])
+ self._connection.shutdown(socket.SHUT_WR)
+ except:
+ pass
+ finally:
+ with self._sendline:
+ self._outgoing.clear() # Clear out _outgoing.
+
+ def isAlive(self):
+ """For compatibility, when modules still expect irc.Connection to be a subclass of threading.Thread."""
+ return type(self._recvhandlerthread) == Thread and self._recvhandlerthread.isAlive() and type(self._sendhandlerthread) == Thread and self._sendhandlerthread.isAlive()
+
+ def start(self):
+ """For compatibility, when modules still expect irc.Connection to be a subclass of threading.Thread."""
+ return self.connect()
def __repr__(self):
server = self.server
- if self.ipv6 and ":" in server:
- server = "[%s]"%server
- port = self.port
+ if self.ipver == socket.AF_INET6 and ":" in server:
+ server = "[%s]" % server
if self.identity:
- nick = self.identity.nick
- user = self.identity.username if self.identity.username else "*"
- host = self.identity.host if self.identity.host else "*"
+ return "<IRC Context: {self.identity:full} on {self:uri}>".format(**locals())
else:
- nick = "*"
- user = "*"
- host = "*"
- if self.ssl and self.ipv6:
- protocol = "ircs6"
- elif self.ssl:
- protocol = "ircs"
- elif self.ipv6:
- protocol = "irc6"
+ return "<IRC Context: *!*@* on {self:uri}>".format(**locals())
+
+ def __format__(self, fmt):
+ port = self.port if self.port is not None else 6697 if self.secure else 6667
+ if fmt == "uri":
+ ssl = "s" if self.secure else ""
+ proto = "6" if self.ipver == socket.AF_INET6 else ""
+ if self.ipver == socket.AF_INET6 and ":" in self.server:
+ return "irc{ssl}{proto}://[{self.server}]:{port}".format(**locals())
+ else:
+ return "irc{ssl}{proto}://{self.server}:{port}".format(**locals())
else:
- protocol = "irc"
- return "<IRC Context: %(nick)s!%(user)s@%(host)s on %(protocol)s://%(server)s:%(port)s>" % locals()
- #else: return "<IRC Context: irc%(ssl)s://%(server)s:%(port)s>" % locals()
+ return repr(self)
def oper(self, name, passwd, origin=None):
- self.raw("OPER %s %s" % (re.findall("^([^\r\n\\s]*)", name)[0],
- re.findall("^([^\r\n\\s]*)", passwd)[0]), origin=origin)
+ """oper(name, passwd[, origin])
+
+ Sends an OPER request to the server. Warning: Invalid oper credentials may be reported to IRC network admins!"""
+ if re.match(".*[\n\r\\s]", name) or re.match(".*[\n\r\\s]", passwd):
+ raise InvalidCharacter
+ self.send(u"OPER {name} {passwd}".format(**vars()), origin=origin)
def list(self, params="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", params)[0]):
- self.raw("LIST %s" % (re.findall(
- "^([^\r\n\\s]*)", params)[0]), origin=origin)
+ """list(...)
+
+ Sends a LIST request to the server.
+ TODO: Implement optional blocking."""
+ if re.match(".*[\n\r\\s]", params):
+ raise InvalidCharacter
+ if params:
+ self.send(u"LIST {params}".format(**vars()), origin=origin)
else:
- self.raw("LIST", origin=origin)
+ self.send(u"LIST", origin=origin)
- def getmotd(self, target="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", target)[0]):
- self.raw("MOTD %s" % (re.findall(
- "^([^\r\n\\s]*)", target)[0]), origin=origin)
+ def getmotd(self, server=None, origin=None):
+ """getmotd(...)
+
+ Sends an MOTD request to the server, optionally specifying server.
+ TODO: Implement optional blocking."""
+ if server:
+ self.send(u"MOTD %s" % server.name, origin=origin)
else:
- self.raw("MOTD", origin=origin)
+ self.send(u"MOTD", origin=origin)
+
+ def version(self, server=None, origin=None):
+ """version(...)
- def version(self, target="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", target)[0]):
- self.raw("VERSION %s" % (re.findall(
- "^([^\r\n\\s]*)", target)[0]), origin=origin)
+ Sends an VERSION request to the server, optionally specifying server.
+ This is NOT the same as requesting CTCP version from another user.
+ TODO: Implement optional blocking."""
+ if server:
+ self.send(u"VERSION %s" % server.name, origin=origin)
else:
- self.raw("VERSION", origin=origin)
+ self.send(u"VERSION", origin=origin)
- def stats(self, query, target="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", target)[0]):
- self.raw("STATS %s %s" % (query, re.findall(
- "^([^\r\n\\s]*)", target)[0]), origin=origin)
+ def stats(self, query, server=None, origin=None):
+ """stats(query[,...])
+
+ Sends an STATS request to the server, optionally specifying server.
+ STATS requests may be logged by IRC network admins. Use responsibly!
+ TODO: Implement optional blocking."""
+ if server:
+ self.send(u"STATS %s %s" % (query, server.name), origin=origin)
else:
- self.raw("STATS %s"%query, origin=origin)
+ self.send(u"STATS %s" % query, origin=origin)
+
+ def sendcapsrequest(self, capabilities, origin=None):
+ """sendcapsrequest(capabilities)
- def quit(self, msg="", origin=None):
+ Request capabilities with "CAP REQ". Please use this method instead of using send(...)."""
with self.lock:
- if self.connected:
- if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.raw("QUIT :%s" % re.findall("^([^\r\n]*)",
- msg)[0], origin=origin)
- else:
- self.raw("QUIT", origin=origin)
+ for cap in capabilities:
+ if cap not in self._requestedcaps:
+ self._requestedcaps.append(cap)
+ self.send("CAP REQ {cap}".format(**vars()), origin=origin)
+
+ def quit(self, msg="", origin=None, blocking=False):
+ """quit(...)
+
+ Quit IRC session gracefully by first sending a QUIT request to the server.
+
+ Optional arguments:
+ 'msg': Quit message
+ 'origin': See help on method 'send'
+ 'blocking': Wait until connection is terminated."""
+ if "\r" in msg or "\n" in msg:
+ raise InvalidCharacter
+ if msg:
+ self.send(u"QUIT :%s" % msg, origin=origin)
+ else:
+ self.send(u"QUIT", origin=origin)
+ if blocking:
+ with self._disconnecting:
+ while self.connected:
+ self._disconnecting.wait()
+ self._recvhandlerthread.join()
+ self._sendhandlerthread.join()
+
+ def disconnect(self):
+ """disconnect()
+
+ Force disconnect -- Goes right for the jugular, not even sending QUIT to server."""
+ with self.lock:
+ self._quitexpected = True
+ self._connection.shutdown(2)
def ctcpversion(self):
+ """ctcpversion() --> string
+
+ Formats a CTCP version reply from this instance and all attached addons."""
+
reply = []
- ### Prepare reply for addon
- reply.append("%(__name__)s %(__version__)s, %(__author__)s" %
- vars(self))
+ # Prepare reply for this module
+ reply.append(
+ u"{self.__name__} {self.__version__}, {self.__author__}".format(**vars()))
- ### Prepare reply for Python and OS versions
+ # Prepare reply for Python and OS versions
pyver = sys.version.split("\n")
- pyver[0] = "Python "+pyver[0]
+ pyver[0] = "Python " + pyver[0]
reply.extend(pyver)
reply.extend(platform.platform().split("\n"))
- ### Prepare reply for extension addons
+
+ # Prepare reply for each addons
for addon in self.addons:
try:
- r = "%(__name__)s %(__version__)s" % vars(addon)
- if "__extinfo__" in vars(addon):
- r += ", %(__extinfo__)s" % vars()
- reply.append(r)
+ if hasattr(addon, "__extinfo__"):
+ reply.append(
+ u"{addon.__name__} {addon.__version__}, {addon.__extinfo__}".format(**vars()))
+ else:
+ reply.append(
+ u"{addon.__name__} {addon.__version__}".format(**vars()))
except:
pass
- return reduce(lambda x, y: "%s; %s" % (x, y), reply)
+ return u"; ".join(reply)
def raw(self, line, origin=None):
- if "\r" in line or "\n" in line:
- raise InvalidCharacter
- self.outgoing.put((line, origin))
+ """raw(line[, origin])
+
+ Deprecated. Use send() instead."""
+ self.send(line, origin=origin)
+
+ def user(self, nick, init=False):
+ """user(nick)
+
+ Return a User object associated with a nickname.
+ Specify init=True to reset all that is known about user."""
+ with self.lock:
+ try:
+ return self.users[nick]
+ except KeyError:
+ user = User(nick, self)
+ self.users.append(user)
+ return user
- def user(self, nick):
- users = [user for user in self.users if user.nick.lower(
- ) == nick.lower()]
- if len(users):
- return users[0]
+ def channel(self, name, init=False):
+ """channel(name)
+
+ Return a Channel object associated with a channel name.
+ Specify init=True to reset all that is known about the channel."""
+ with self.lock:
+ try:
+ return self.channels[name]
+ except KeyError:
+ channel = Channel(name, self)
+ self.channels.append(channel)
+ return channel
+
+ def getserver(self, name, init=False):
+ """server(name)
+
+ Return a Server object associated with a server name.
+ Specify init=True to reset all that is known about the server."""
+ with self.lock:
+ if type(name) == str:
+ name = autodecode(name)
+ servers = [server for server in self.servers if self.lower(
+ server.name) == self.lower(name)]
+
+ if len(servers):
+ if init:
+ servers[0]._init()
+ return servers[0]
+ else:
+ server = Server(name, self)
+ self.servers.append(server)
+ return server
+
+ def __getitem__(self, item):
+ chantypes = self.supports.get("CHANTYPES", _defaultchantypes)
+ if "\r" in item or "\n" in item or " " in item:
+ raise InvalidCharacter
+ if re.match(_chanmatch % re.escape(chantypes), item):
+ return self.channel(item)
+ elif re.match(_usermatch, item):
+ return self.user(item)
else:
- user = User(nick, self)
- self.users.append(user)
- timestamp = reduce(lambda x, y: x+":"+y, [str(
- t).rjust(2, "0") for t in time.localtime()[0:6]])
- return user
-
- def channel(self, name):
- channels = [chan for chan in self.channels if name.lower(
- ) == chan.name.lower()]
- if len(channels):
- return channels[0]
+ return self.getserver(item)
+
+ def fmtsupports(self):
+ """fmtsupports() --> list
+
+ Formats a valid 005 response from known information."""
+ supports = [
+ "CHANMODES=%s" % (",".join(value)) if name == "CHANMODES" else "PREFIX=(%s)%s" %
+ value if name == "PREFIX" else "%s=%s" % (name, value) if value else name for name, value in self.supports.items()]
+ supports.sort()
+ supports = " ".join(supports)
+ lines = []
+ while len(supports) > 196:
+ index = supports.rfind(" ", 0, 196)
+ slice = supports[:index]
+ lines.append(
+ u":{self.serv} 005 {self.identity.nick} {slice} :are supported by this server".format(**vars()))
+ supports = supports[index + 1:]
+ if supports:
+ lines.append(
+ u":{self.serv} 005 {self.identity.nick} {supports} :are supported by this server".format(**vars()))
+ return lines
+
+ def fmtgreeting(self):
+ """fmtgreeting() --> list
+
+ Formats a valid greeting from known information (Responses 001 through 004)."""
+ lines = []
+ if self.welcome:
+ lines.append(
+ u":{self.serv} 001 {self.identity.nick} :{self.welcome}".format(**vars()))
+ if self.hostinfo:
+ lines.append(
+ u":{self.serv} 002 {self.identity.nick} :{self.hostinfo}".format(**vars()))
+ if self.servcreated:
+ lines.append(
+ u":{self.serv} 003 {self.identity.nick} :{self.servcreated}".format(**vars()))
+ if self.servinfo:
+ lines.append(
+ u":{self.serv} 004 {self.identity.nick} {self.servinfo}".format(**vars()))
+ return lines
+
+ def fmtusermodes(self):
+ """fmtusermodes() --> list
+
+ Formats a valid user modes reply from known information (Response 221)."""
+ return u":{self.serv} 221 {self.identity.nick} +{self.identity.modes}".format(**vars())
+
+ def fmtsnomasks(self):
+ """fmtsnomasks() --> list
+
+ Formats a valid snomasks reply from known information (Response 008)."""
+ return u":{self.serv} 008 {self.identity.nick} +{self.identity.snomask} :Server notice mask".format(**vars())
+
+ def fmtmotd(self):
+ """fmtmotd() --> list
+
+ Formats a valid MOTD reply from known information (Response 375, 372, and 376; Response 422 if no MOTD)."""
+ if self.motdgreet and self.motd and self.motdend:
+ lines = []
+ lines.append(
+ u":{self.serv} 375 {self.identity.nick} :{self.motdgreet}".format(**vars()))
+ for motdline in self.motd:
+ lines.append(
+ u":{self.serv} 372 {self.identity.nick} :{motdline}".format(**vars()))
+ lines.append(
+ u":{self.serv} 376 {self.identity.nick} :{self.motdend}".format(**vars()))
+ return lines
else:
- timestamp = reduce(lambda x, y: x+":"+y, [str(
- t).rjust(2, "0") for t in time.localtime()[0:6]])
- chan = Channel(name, self)
- self.channels.append(chan)
- return chan
+ return [u":{self.serv} 422 {self.identity.nick} :MOTD File is missing".format(**vars())]
class Channel(object):
- def __init__(self, name, context):
- if not re.match(r"^[%s]\S*$" % context.supports.get("CHANTYPES", "#"), name):
- raise InvalidName(repr(name))
+
+ def __init__(self, name, context, key=None):
+ chantypes = context.supports.get("CHANTYPES", _defaultchantypes)
+ if not re.match(_chanmatch % re.escape(chantypes), name):
+ raise InvalidName, repr(name)
self.name = name
self.context = context
+ self.key = key
+ self.lock = Lock()
+ self._init()
+ self._joining = Condition(self.lock)
+ self._parting = Condition(self.lock)
+ self._joinrequested = False
+ self._joinreply = None
+ self._partrequested = False
+ self._partreply = None
+
+ def _init(self):
+ for user in self.context.users._dict.values():
+ if self in user.channels:
+ user.channels.remove(self)
self.addons = []
self.topic = ""
self.topicsetby = ""
- self.topictime = ()
+ self.topictime = None
self.topicmod = ""
self.modes = {}
- self.users = []
+ self.users = UserList(context=self.context)
self.created = None
- self.lock = Lock()
def msg(self, msg, target="", origin=None):
if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]:
raise InvalidPrefix
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("PRIVMSG %s%s :%s" % (target,
- self.name, line), origin=origin)
+ self.context.send(u"PRIVMSG %s%s :%s" %
+ (target, self.name, line), origin=origin)
- def who(self, origin=None):
- self.context.raw("WHO %s" % (self.name), origin=origin)
+ def who(self, origin=None, blocking=False):
+ # Send WHO request to server
+ self.context.send(u"WHO %s" % (self.name), origin=origin)
+
+ def fmtwho(self):
+ # Create WHO reply from current data. TODO
+ pass
def names(self, origin=None):
- self.context.raw("NAMES %s" % (self.name), origin=origin)
+ self.context.send(u"NAMES %s" % (self.name), origin=origin)
+
+ def fmtnames(self, sort=None, uhnames=False, namesx=False):
+ # Create NAMES reply from current data.
+ secret = "s" in self.modes.keys() and self.modes["s"]
+ private = "p" in self.modes.keys() and self.modes["p"]
+ flag = "@" if secret else ("*" if private else "=")
+
+ modes, symbols = self.context.supports.get("PREFIX", ("ohv", "@%+"))
+ users = list(self.users)
+ if sort == "mode":
+ users.sort(key=lambda user: ([user not in self.modes.get(mode, [])
+ for mode, char in zip(*self.context.supports.get("PREFIX", ("ohv", "@%+")))], self.context.lower(user.nick)))
+ elif sort == "nick":
+ users.sort(key=lambda user: self.context.lower(user.nick))
+ if uhnames:
+ template = u"{prefixes}{user:full}"
+ else:
+ template = u"{prefixes}{user}"
+
+ nameslist = []
+ for user in users:
+ prefixes = u"".join(
+ [prefix if mode in self.modes.keys() and user in self.modes[mode] else "" for prefix, mode in zip(symbols, modes)])
+ if not namesx:
+ prefixes = prefixes[:1]
+ nameslist.append(template.format(**vars()))
+ names = " ".join(nameslist)
+
+ lines = []
+ while len(names) > 196:
+ index = names.rfind(" ", 0, 196)
+ slice = names[:index]
+ lines.append(
+ u":{self.context.identity.server} 353 {self.context.identity.nick} {flag} {self.name} :{slice}".format(**vars()))
+ names = names[index + 1:]
+ if len(names):
+ lines.append(
+ u":{self.context.identity.server} 353 {self.context.identity.nick} {flag} {self.name} :{names}".format(**vars()))
+
+ lines.append(
+ u":{self.context.identity.server} 366 {self.context.identity.nick} {self.name} :End of /NAMES list.".format(**vars()))
+ return lines
+
+ def fmttopic(self):
+ # Prepares 332 and 333 responses
+ if self.topic and self.topictime:
+ response332 = u":{self.context.identity.server} 332 {self.context.identity.nick} {self.name} :{self.topic}".format(
+ **vars())
+ if type(self.topicsetby) == User:
+ response333 = u":{self.context.identity.server} 333 {self.context.identity.nick} {self.name} {self.topicsetby.nick} {self.topictime}".format(
+ **vars())
+ else:
+ response333 = u":{self.context.identity.server} 333 {self.context.identity.nick} {self.name} {self.topicsetby} {self.topictime}".format(
+ **vars())
+ return [response332, response333]
+ else:
+ return [u":{self.context.identity.server} 331 {self.context.identity.nick} {self.name} :No topic is set".format(**vars())]
+
+ def fmtchancreated(self):
+ # Prepares 329 responses
+ return u":{self.context.identity.server} 329 {self.context.identity.nick} {self.name} {self.created}".format(**vars())
+
+ def fmtmodes(self):
+ items = self.modes.items()
+ chanmodes = self.context.supports.get("CHANMODES", _defaultchanmodes)
+ modes = "".join(
+ [mode for (mode, val) in items if mode not in chanmodes[0] + self.context.supports["PREFIX"][0] and val])
+ params = " ".join(
+ [val for (mode, val) in items if mode in chanmodes[1] + chanmodes[2] and val])
+ if modes and params:
+ return u":{self.context.identity.server} 324 {self.context.identity.nick} {self.name} +{modes} {params}".format(**vars())
+ elif modes:
+ return u":{self.context.identity.server} 324 {self.context.identity.nick} {self.name} +{modes}".format(**vars())
+ else:
+ return None
def notice(self, msg, target="", origin=None):
if target and target not in self.context.supports.get("PREFIX", ("ohv", "@%+"))[1]:
raise InvalidPrefix
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("NOTICE %s%s :%s" % (target,
- self.name, line), origin=origin)
+ self.context.send(u"NOTICE %s%s :%s" %
+ (target, self.name, line), origin=origin)
def settopic(self, msg, origin=None):
- self.context.raw("TOPIC %s :%s" % (self.name, re.findall(
- "^([^\r\n]*)", msg)[0]), origin=origin)
+ self.context.send(u"TOPIC %s :%s" %
+ (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
def ctcp(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.msg("\01%s %s\01" % (act.upper(), re.findall(
- "^([^\r\n]*)", msg)[0]), origin=origin)
+ self.msg("\01%s %s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
self.msg("\01%s\01" % act.upper())
def ctcpreply(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.notice("\01%s %(msg)s\01" % (act.upper(),
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ self.notice("\01%s %(msg)s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
self.notice("\01%s\01" % act.upper(), origin=origin)
def me(self, msg="", origin=None):
self.ctcp("ACTION", msg, origin=origin)
- def part(self, msg="", origin=None):
- if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.context.raw("PART %s :%s" % (self.name,
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
- else:
- self.context.raw("PART %s" % self.name, origin=origin)
+ def part(self, msg="", blocking=False, timeout=30, origin=None):
+ with self.context.lock:
+ if self.context.identity not in self.users:
+ # Bot is not on the channel
+ raise NotOnChannel
+ with self._parting:
+ try:
+ if self._partrequested:
+ raise ActionAlreadyRequested
+ self._partrequested = True
+ if len(re.findall("^([^\r\n]*)", msg)[0]):
+ self.context.send(
+ u"PART %s :%s" % (self.name, re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ else:
+ self.context.send(u"PART %s" % self.name, origin=origin)
+
+ # Anticipated Numeric Replies:
+
+ # ERR_NEEDMOREPARAMS ERR_NOSUCHCHANNEL
+ # ERR_NOTONCHANNEL
+
+ if blocking:
+ endtime = time.time() + timeout
+ while True:
+ self._parting.wait(max(0, endtime - time.time()))
+ t = time.time()
+ if not self.context.connected:
+ raise NotConnected
+ elif self._partreply in ("PART", "KICK"):
+ return
+ elif type(self._partreply) == tuple and len(self._partreply) == 2:
+ cmd, extinfo = self._partreply
+ raise exceptcodes[cmd], extinfo
+ if t > endtime:
+ raise RequestTimedOut
+ finally:
+ self._partrequested = False
+ self._partreply = None
def invite(self, user, origin=None):
nickname = user.nick if type(
user) == User else re.findall("^([^\r\n\\s]*)", user)[0]
if nickname == "":
raise InvalidName
- self.context.raw("INVITE %s %s" % (nickname, self.name), origin=origin)
-
- def join(self, key="", origin=None):
- if len(re.findall("^([^\r\n\\s]*)", key)[0]):
- self.context.raw("JOIN %s %s" % (self.name, re.findall(
- "^([^\r\n\\s]*)", key)[0]), origin=origin)
- else:
- self.context.raw("JOIN %s" % self.name, origin=origin)
+ self.context.send(u"INVITE %s %s" %
+ (nickname, self.name), origin=origin)
+
+ def join(self, key="", blocking=False, timeout=30, origin=None):
+ with self.context.lock:
+ if self.context.identity in self.users:
+ # Bot is already on the channel
+ raise AlreadyJoined
+ if not self.context.connected:
+ raise NotConnected
+ with self._joining:
+ try:
+ if self._joinrequested:
+ raise ActionAlreadyRequested
+ self._joinrequested = True
+ if len(re.findall("^([^\r\n\\s]*)", key)[0]):
+ self.context.send(
+ u"JOIN %s %s" % (self.name, re.findall("^([^\r\n\\s]*)", key)[0]), origin=origin)
+ else:
+ self.context.send(u"JOIN %s" % self.name, origin=origin)
+
+ # Anticipated Numeric Replies:
+
+ # ERR_NEEDMOREPARAMS ERR_BANNEDFROMCHAN
+ # ERR_INVITEONLYCHAN ERR_BADCHANNELKEY
+ # ERR_CHANNELISFULL ERR_BADCHANMASK
+ # ERR_NOSUCHCHANNEL ERR_TOOMANYCHANNELS
+ # ERR_TOOMANYTARGETS ERR_UNAVAILRESOURCE
+
+ if blocking:
+ endtime = time.time() + timeout
+ while True:
+ self._joining.wait(max(0, endtime - time.time()))
+ t = time.time()
+ if not self.context.connected:
+ raise NotConnected
+ elif self._joinreply == "JOIN":
+ return
+ elif type(self._joinreply) == tuple and len(self._joinreply) == 2:
+ cmd, extinfo = self._joinreply
+ raise exceptcodes[cmd], extinfo
+ if t > endtime:
+ raise RequestTimedOut
+ finally:
+ self._joinrequested = False
+ self._joinreply = None
def kick(self, user, msg="", origin=None):
nickname = user.nick if type(
@@ -1266,25 +3206,38 @@ class Channel(object):
if nickname == "":
raise InvalidName
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.context.raw("KICK %s %s :%s" % (self.name, nickname,
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ self.context.send(u"KICK %s %s :%s" %
+ (self.name, nickname, re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
- self.context.raw("KICK %s %s" % (self.name,
- nickname), origin=origin)
+ self.context.send(u"KICK %s %s" %
+ (self.name, nickname), origin=origin)
def __repr__(self):
- return "<Channel: "+self.name+"@"+self.context.server+"/"+str(self.context.port)+">"
+ return u"<Channel: {self.name} on {self.context:uri}>".format(**vars())
+
+ def __contains__(self, item):
+ return item in self.users
+
+ def __format__(self, fmt):
+ return self.name
+
+ def json(self):
+ return self.name
class User(object):
+
def __init__(self, nick, context):
- if not re.match(r"^\S+$", nick):
+ if not re.match(_nickmatch, nick):
raise InvalidName
self.nick = nick
+ self.context = context
+ self._init()
+
+ def _init(self):
self.username = ""
self.host = ""
- self.channels = []
- self.context = context
+ self.channels = ChanList(context=self.context)
self.modes = ""
self.snomask = ""
self.server = None
@@ -1293,210 +3246,457 @@ class User(object):
self.ircopmsg = ""
self.idlesince = None
self.signontime = None
- self.ssl = None
+ self.secure = None
self.away = None
+ self.loggedinas = None
def __repr__(self):
- return "<User: %(nick)s!%(username)s@%(host)s>" % vars(self)
+ return (u"<User: %(nick)s!%(username)s@%(host)s>" % vars(self)).encode("utf8")
+
+ def __format__(self, fmt):
+ if fmt == "full":
+ return u"{self.nick}!{self.username}@{self.host}".format(**locals())
+ else:
+ return self.nick
def msg(self, msg, origin=None):
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("PRIVMSG %s :%s" % (self.nick,
- line), origin=origin)
+ self.context.send(u"PRIVMSG %s :%s" %
+ (self.nick, line), origin=origin)
def notice(self, msg, origin=None):
for line in re.findall("([^\r\n]+)", msg):
- self.context.raw("NOTICE %s :%s" % (self.nick,
- line), origin=origin)
+ self.context.send(u"NOTICE %s :%s" %
+ (self.nick, line), origin=origin)
def ctcp(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.msg("\01%s %s\01" % (act.upper(), re.findall(
- "^([^\r\n]*)", msg)[0]), origin=origin)
+ self.msg(u"\01%s %s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
- self.msg("\01%s\01" % act.upper())
+ self.msg(u"\01%s\01" % act.upper())
def ctcpreply(self, act, msg="", origin=None):
if len(re.findall("^([^\r\n]*)", msg)[0]):
- self.notice("\01%s %s\01" % (act.upper(),
- re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
+ self.notice("\01%s %s\01" %
+ (act.upper(), re.findall("^([^\r\n]*)", msg)[0]), origin=origin)
else:
self.notice("\01%s\01" % act.upper(), origin=origin)
def me(self, msg="", origin=None):
self.ctcp("ACTION", msg, origin=origin)
+ def json(self):
+ return self.nick
-class Outgoing(Thread):
- def __init__(self, IRC, throttle=0.25, lines=10, t=5):
- self.IRC = IRC
- self.throttle = throttle
- self.lines = lines
- self.time = t
- #self.queue=Queue()
- Thread.__init__(self)
- def run(self):
- try:
- throttled = False
- timestamps = []
- while True:
+class Config(object):
+
+ def __init__(self, addon, **kwargs):
+ self.addon = addon
+ self.__dict__.update(kwargs)
+
+ def json(self):
+ if "onAddonAdd" in dir(self.addon) and type(self.addon.onAddonAdd) == new.instancemethod:
+ conf = OrderedDict(addon=self.addon)
+ try:
+ arginspect = inspect.getargspec(self.addon.onAddonAdd)
+ except:
+ raise TypeError(
+ repr(self.addon.onAddonAdd) + " is not JSON serializable")
+
+ if arginspect.defaults:
+ requiredargs = arginspect.args[
+ 2:len(arginspect.args) - len(arginspect.defaults)]
+ argswithdefaults = arginspect.args[
+ len(arginspect.args) - len(arginspect.defaults):]
+ defaultvalues = arginspect.defaults
+ else:
+ requiredargs = arginspect.args[2:]
+ argswithdefaults = []
+ defaultvalues = []
+
+ for key in requiredargs:
try:
- q = self.IRC.outgoing.get()
- except Queue.Interrupted:
- break
- if q == "quit" or not self.IRC.connected:
- break
- line, origin = q
- match = re.findall("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I)
- (cmd, target, params, extinfo) = match[0]
- timestamp = reduce(lambda x, y: x+":"+y, [str(t).rjust(
- 2, "0") for t in time.localtime()[0:6]])
- with self.IRC.lock:
- try:
- self.IRC.connection.send("%(line)s\n" % vars())
- except socket.error:
- try:
- self.IRC.connection.shutdown(0)
- except:
- pass
- raise
+ conf[key] = getattr(self, key)
+ except AttributeError:
+ print key
+ raise TypeError(
+ repr(self) + " is not JSON serializable (Cannot recover required argument '%s')" % key)
- ### Modify line if it contains a password so that the password is not logged or sent to any potentially untrustworthy addons
- #if re.match("^(.+?)(?:\\s+(.+?)(?:\\s+(.+?))??)??(?:\\s+:(.*))?$", line, re.I):
- if cmd.upper() == "PRIVMSG":
- if target.upper() == "NICKSERV":
- nscmd = re.findall(r"^\s*(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
- if nscmd:
- nscmd = nscmd[0]
- if nscmd[0].upper() in ("IDENTIFY", "REGISTER"):
- extinfo = "%s ********"%nscmd[0]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif nscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
- extinfo = "%s %s ********"%nscmd[:2]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif nscmd[0].upper() == "SET":
- if nscmd[1].upper() == "PASSWORD":
- extinfo = "%s %s ********"%nscmd[:2]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif nscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
- extinfo = "********"
- line = "%s %s :%s"%(cmd, target, extinfo)
- if target.upper() == "CHANSERV":
- cscmd = re.findall(r"^\s*(\S+)\s+(\S+)\s+(\S+)(?:\s*(\S+)(?:\s*(.+))?)?$", extinfo, re.I)
- if cscmd:
- cscmd = cscmd[0]
- if cscmd[0].upper() in ("IDENTIFY", "REGISTER"):
- extinfo = "%s %s ********"%cscmd[:2]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif cscmd[0].upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
- extinfo = "%s %s %s ********"%cscmd[:3]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif cscmd[0].upper() == "SET":
- if cscmd[2].upper() == "PASSWORD":
- extinfo = "%s %s %s ********"%cscmd[:3]
- line = "%s %s :%s"%(cmd, target, extinfo)
- elif cscmd[0].upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
- extinfo = "********"
- line = "%s %s :%s"%(cmd, target, extinfo)
-
- chanmatch = re.findall("([%s]?)([%s].+)"%(re.escape(self.IRC.supports.get("PREFIX", ("ohv", "@%+"))[1]), re.escape(self.IRC.supports.get("CHANTYPES", "#"))), target)
- if chanmatch:
- targetprefix, channame = chanmatch[0]
- target = self.IRC.channel(channame)
- if target.name != channame:
- ### Target channel name has changed
- target.name = channame
- elif len(target) and target[0] != "$" and cmd != "NICK":
- targetprefix = ""
- target = self.IRC.user(target)
-
- ctcp = re.findall("^\x01(.*?)(?:\\s+(.*?)\\s*)?\x01$",
- extinfo)
- if ctcp:
- (ctcptype, ext) = ctcp[0]
- if ctcptype.upper() == "ACTION":
- if type(target) == Channel:
- self.IRC.event("onSendChanAction", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, action=ext)
- elif type(target) == User:
- self.IRC.event("onSendPrivAction", self.IRC.addons, origin=origin, user=target, action=ext)
- else:
- if type(target) == Channel:
- self.IRC.event("onSendChanCTCP", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, ctcptype=ctcptype, params=ext)
- elif type(target) == User:
- self.IRC.event("onSendPrivCTCP", self.IRC.addons, origin=origin, user=target, ctcptype=ctcptype, params=ext)
- else:
- if type(target) == Channel:
- self.IRC.event("onSendChanMsg", self.IRC.addons+target.addons, origin=origin, channel=target, targetprefix=targetprefix, msg=extinfo)
- elif type(target) == User:
- self.IRC.event("onSendPrivMsg", self.IRC.addons, origin=origin, user=target, msg=extinfo)
-
- #elif target.upper()=="CHANSERV":
- #msg=extinfo.split(" ")
- #if msg[0].upper() in ("IDENTIFY", "REGISTER") and len(msg)>2:
- #msg[2]="********"
- #extinfo=" ".join(msg)
- #line="%s %s :%s"%(cmd, target, extinfo)
- elif cmd.upper() == "NS":
- if target.upper() in ("IDENTIFY", "REGISTER"):
- params = params.split(" ")
- while "" in params:
- params.remove("")
- if len(params):
- params[0] = "********"
- params = " ".join(params)
- line = "%s %s %s"%(cmd, target, params)
- elif target.upper() in ("GROUP", "GHOST", "RECOVER", "RELEASE"):
- params = params.split(" ")
- while "" in params:
- params.remove("")
- if len(params) > 1:
- params[1] = "********"
- params = " ".join(params)
- line = "%s %s %s"%(cmd, target, params)
- elif target.upper() not in ("GLIST", "ACCESS", "SASET", "DROP", "SENDPASS", "ALIST", "INFO", "LIST", "LOGOUT", "STATUS", "UPDATE", "GETPASS", "FORBID", "SUSPEND", "UNSUSPEND", "OINFO"):
- params = ""
- target = "********"
- line = "%s %s"%(cmd, target)
- elif cmd.upper() == "OPER":
- params = "********"
- line = "%s %s %s"%(cmd, target, params)
- elif cmd.upper() == "PASS":
- extinfo = "********"
- target = ""
- line = "%s :%s"%(cmd, extinfo)
- elif cmd.upper() == "IDENTIFY":
- target = "********"
- line = "%s %s"%(cmd, target)
- self.IRC.event("onSend", self.IRC.addons, origin=origin, line=line, cmd=cmd, target=target, params=params, extinfo=extinfo)
- self.IRC.logwrite(">>> %(line)s" % vars())
- if cmd.upper() == "QUIT":
- self.IRC.quitexpected = True
- timestamps.append(time.time())
- while timestamps[0] < timestamps[-1]-self.time-0.1:
- del timestamps[0]
- if throttled:
- if len(timestamps) < 2:
- throttled = False
+ for key, default in zip(argswithdefaults, defaultvalues):
+ try:
+ value = getattr(self, key)
+ if value != default:
+ conf[key] = getattr(self, key)
+ except AttributeError:
+ pass
+ return conf
+ else:
+ return self.addon
+
+
+class ChanList(list):
+
+ def __init__(self, iterable=None, context=None, withdict=False):
+ self._dict = {} if withdict else None
+ if context != None and type(context) != Connection:
+ raise TypeError, "context must be irc.Connection object or None"
+ self.context = context
+ if iterable:
+ chanlist = []
+ for channel in iterable:
+ if type(channel) == Channel:
+ chanlist.append(channel)
+ if context and channel.context != context:
+ raise ValueError, "Channel object does not belong to context."
+ elif type(channel) in (str, unicode):
+ if context == None:
+ raise ValueError, "No context given for string object."
+ chanlist.append(context.channel(channel))
+ list.__init__(self, chanlist)
+ if self._dict is not None:
+ if self.context:
+ self._dict.update(
+ {self.context.lower(channel.name): channel for channel in chanlist})
else:
- if len(timestamps) >= self.lines:
- throttled = True
- if throttled:
- time.sleep(max(timestamps[-1] +
- self.throttle-time.time(), 0))
- except:
- self.IRC.connection.send("QUIT :%s\n" %
- traceback.format_exc().rstrip().split("\n")[-1])
- self.IRC.connection.close()
- self.IRC.connection.shutdown(0)
+ self._dict.update(
+ {(channel.context, channel.context.lower(channel.name)): channel for channel in chanlist})
+ else:
+ list.__init__(self)
+
+ def append(self, item):
+ if type(item) in (str, unicode):
+ if self.context:
+ channel = self.context.channel(item)
+ list.append(self, channel)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = channel
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Channel:
+ raise TypeError, "Only channel objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Channel object does not belong to context."
+ list.append(self, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.name)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.name)] = item
+
+ def insert(self, index, item):
+ if type(item) in (str, unicode):
+ if self.context:
+ channel = self.context.channel(item)
+ list.insert(self, index, channel)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = channel
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Channel:
+ raise TypeError, "Only channel objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Channel object does not belong to context."
+ list.insert(self, index, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.name)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.name)] = item
+
+ def extend(self, iterable):
+ list.extend(self, ChanList(iterable, context=self.context))
+ def join(self, origin=None):
+ if not self.context:
+ raise ValueError, "No context defined."
+ if any([channel.key for channel in self]):
+ self.context.send(u"JOIN %s %s" %
+ (self, ",".join([channel.key if channel.key else "" for channel in self])), origin=origin)
+ else:
+ self.context.send(u"JOIN %s" % self, origin=origin)
+
+ def part(self, partmsg=None, origin=None):
+ if not self.context:
+ raise ValueError, "No context defined."
+ if partmsg:
+ self.context.send(u"PART %s :%s" %
+ (",".join([channel.name for channel in self]), partmsg), origin=origin)
+ else:
+ self.context.send(u"PART %s" % self, origin=origin)
-class Pinger(Thread):
- def __init__(self, connection, lock=None):
- self.connection = connection
- self.lock = lock
- self.daemon = True
- Thread.__init__(self)
+ def msg(self, msg, origin=None):
+ if not self.context:
+ raise ValueError, "No context defined."
+ self.context.send(u"PRIVMSG %s :%s" % (self, msg), origin=origin)
- def run(self):
- pass
+ def __str__(self):
+ return ",".join([channel.name for channel in self])
+
+ def __getitem__(self, key):
+ if type(key) in (int, long):
+ return list.__getitem__(self, key)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ keylower = self.context.lower(key)
+ return self._dict[keylower]
+ else:
+ raise ValueError, "No dict available."
+
+ def __delitem__(self, key):
+ if type(key) in (int, long):
+ channel = self[key]
+ del self._dict[self.context.lower(channel.name)]
+ list.__delitem__(self, channel)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ keylower = self.context.lower(key)
+ list.__delitem__(self, self._dict[keylower])
+ del self._dict[keylower]
+ else:
+ raise ValueError, "No dict available."
+
+
+class UserList(list):
+ __doc__ = "Subclass of list, with builtin validation."
+
+ def __init__(self, iterable=None, context=None, withdict=False):
+ self._dict = {} if withdict else None
+ if context != None and type(context) != Connection:
+ raise TypeError, "context must be irc.Connection object or None"
+ self.context = context
+ if iterable:
+ userlist = []
+ for user in iterable:
+ if type(user) == User:
+ if context and user.context != context:
+ raise ValueError, "User object does not belong to context."
+ userlist.append(user)
+ elif type(user) in (str, unicode):
+ if context == None:
+ raise ValueError, "No context given for string object."
+ userlist.append(context.user(user))
+ list.__init__(self, userlist)
+ if self._dict is not None:
+ if self.context:
+ self._dict.update(
+ {self.context.lower(user.nick): user for user in userlist})
+ else:
+ self._dict.update(
+ {(user.context, user.context.lower(user.nick)): user for user in userlist})
+ else:
+ list.__init__(self)
+
+ def append(self, item):
+ """append(item)
+
+ Like list.append, but enforces that the appended item must be a User instance.
+ If item is a string, then a User instance will be appended in its place."""
+ if type(item) in (str, unicode):
+ if self.context:
+ user = self.context.user(item)
+ list.append(self, user)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = user
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != User:
+ raise TypeError, "Only user objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "User object does not belong to context."
+ list.append(self, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.nick)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.nick)] = item
+
+ def insert(self, index, item):
+ """insert(index, item)
+
+ Like list.insert."""
+ if type(item) in (str, unicode):
+ if self.context:
+ user = self.context.user(item)
+ list.insert(self, index, user)
+ if self._dict is not None:
+ self._dict[self.context.lower(item)] = user
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != User:
+ raise TypeError, "Only user objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "User object does not belong to context."
+ list.insert(self, index, item)
+ if self._dict is not None:
+ if self.context:
+ self._dict[self.context.lower(item.nick)] = item
+ else:
+ self._dict[item.context, item.context.lower(item.nick)] = item
+
+ def extend(self, iterable):
+ """extend(iterable)
+
+ Like list.extend."""
+ list.extend(self, UserList(iterable, context=self.context))
+
+ def msg(self, msg, origin=None):
+ """msg(msg[, origin])
+
+ Sends a PRIVMSG to all users on list."""
+ if not self.context:
+ raise ValueError, "No context defined."
+ self.context.send(u"PRIVMSG %s :%s" % (self, msg), origin=origin)
+
+ def __str__(self):
+ return ",".join([user.nick for user in self])
+
+ def __getitem__(self, index):
+ if type(index) in (int, long):
+ return list.__getitem__(self, index)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ return self._dict[self.context.lower(index)]
+ else:
+ raise ValueError, "No dict available."
+
+ def __delitem__(self, index):
+ if type(index) in (int, long):
+ user = self[index]
+ del self._dict[self.context.lower(user.name)]
+ list.__delitem__(self, user)
+ else:
+ if self._dict is not None:
+ if self.context == None:
+ raise ValueError, "No context given for string object."
+ index = self.context.lower(index)
+ list.__delitem__(self, self._dict[index])
+ del self._dict[index]
+ else:
+ raise ValueError, "No dict available."
+
+ def remove(self, item):
+ if type(item) == User:
+ list.remove(self, item)
+ if self._dict is not None:
+ if self.context:
+ del self._dict[self.context.lower(item.nick)]
+ else:
+ del self._dict[item.context, item.context.lower(item.nick)]
+ else:
+ self.remove(self[item])
+
+
+class Server(object):
+
+ def __init__(self, name, context):
+ self.name = name
+ self.context = context
+ self.lock = Lock()
+ self._init()
+
+ def _init(self):
+ self.stats = {}
+ self.users = UserList(context=self.context)
+ self.created = None
+ self.motdgreet = None
+ self.motd = []
+ self.motdend = None
+
+ def stats(self, query, origin=None):
+ self.context(query, self, origin=origin)
+
+ def __repr__(self):
+ return u"<Server: {self.name} on {self.context:uri}>".format(**vars())
+
+ def __str__(self):
+ return self.name
+
+
+class ServerList(list):
+ __doc__ = "Subclass of list, with builtin validation."
+
+ def __init__(self, iterable=None, context=None):
+ if context != None and type(context) != Connection:
+ raise TypeError, "context must be irc.Connection object or None"
+ self.context = context
+ if iterable:
+ serverlist = []
+ for server in iterable:
+ if type(server) == Server:
+ if self.context and server.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ serverlist.append(server)
+ elif type(server) in (str, unicode):
+ if context == None:
+ raise ValueError, "No context given for string object."
+ serverlist.append(context.getserver(server))
+ list.__init__(self, serverlist)
+ else:
+ list.__init__(self)
+
+ def append(self, item):
+ """append(item)
+
+ Like list.append, but enforces that the appended item must be a Server instance.
+ If item is a string, then a Server instance will be appended in its place."""
+ if type(item) in (str, unicode):
+ if self.context:
+ list.append(self, self.context.getserver(item))
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Server:
+ raise TypeError, "Only Server objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ list.append(self, item)
+
+ def insert(self, index, item):
+ """insert(index, item)
+
+ Like list.insert."""
+ if type(item) in (str, unicode):
+ if self.context:
+ list.insert(self, index, self.context.getserver(item))
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != Server:
+ raise TypeError, "Only Server objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ list.insert(self, index, item)
+
+ def extend(self, iterable):
+ """extend(iterable)
+
+ Like list.extend."""
+ serverlist = []
+ for item in iterable:
+ if type(item) in (str, unicode):
+ if self.context:
+ serverlist.append(self.context.getserver(item))
+ return
+ else:
+ raise ValueError, "No context given for string object."
+ if type(item) != User:
+ raise TypeError, "Only Server objects are permitted in list"
+ if self.context and item.context != self.context:
+ raise ValueError, "Server object does not belong to context."
+ serverlist.append(item)
+ list.extend(self, serverlist)
+
+ def __str__(self):
+ return ",".join([user.nick for user in self])