Updated to new 2.0.1 library interface.
authorEric S. Raymond <esr@thyrsus.com>
Mon, 27 Aug 2012 16:54:06 +0000 (12:54 -0400)
committerEric S. Raymond <esr@thyrsus.com>
Mon, 27 Aug 2012 16:54:06 +0000 (12:54 -0400)
.gitignore
irclib.py [deleted file]
irker.py

index 261c3f8a2079d9383c352e44a7d61907c529b504..50566040e8431c2bd57270a7b07e38935c9efa67 100644 (file)
@@ -1,4 +1,3 @@
 irker.pyc
-irclib.py
-irclib.pyc
-python-irclib*
+irc
+irc-*/
diff --git a/irclib.py b/irclib.py
deleted file mode 100644 (file)
index 724355c..0000000
--- a/irclib.py
+++ /dev/null
@@ -1,1723 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 1999-2002  Joel Rosdahl
-# Portions Copyright 2011 Jason R. Coombs
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
-#
-# keltus <keltus@users.sourceforge.net>
-
-"""
-irclib -- Internet Relay Chat (IRC) protocol client library.
-
-This library is intended to encapsulate the IRC protocol at a quite
-low level.  It provides an event-driven IRC client framework.  It has
-a fairly thorough support for the basic IRC protocol, CTCP, DCC chat,
-but DCC file transfers is not yet supported.
-
-In order to understand how to make an IRC client, I'm afraid you more
-or less must understand the IRC specifications.  They are available
-here: [IRC specifications].
-
-The main features of the IRC client framework are:
-
-  * Abstraction of the IRC protocol.
-  * Handles multiple simultaneous IRC server connections.
-  * Handles server PONGing transparently.
-  * Messages to the IRC server are done by calling methods on an IRC
-    connection object.
-  * Messages from an IRC server triggers events, which can be caught
-    by event handlers.
-  * Reading from and writing to IRC server sockets are normally done
-    by an internal select() loop, but the select()ing may be done by
-    an external main loop.
-  * Functions can be registered to execute at specified times by the
-    event-loop.
-  * Decodes CTCP tagging correctly (hopefully); I haven't seen any
-    other IRC client implementation that handles the CTCP
-    specification subtilties.
-  * A kind of simple, single-server, object-oriented IRC client class
-    that dispatches events to instance methods is included.
-
-Current limitations:
-
-  * The IRC protocol shines through the abstraction a bit too much.
-  * Data is not written asynchronously to the server, i.e. the write()
-    may block if the TCP buffers are stuffed.
-  * There are no support for DCC file transfers.
-  * The author haven't even read RFC 2810, 2811, 2812 and 2813.
-  * Like most projects, documentation is lacking...
-
-.. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
-"""
-
-import bisect
-import re
-import select
-import socket
-import string
-import time
-import types
-import ssl as ssl_mod
-import datetime
-import sys
-
-try:
-    import pkg_resources
-    _pkg = pkg_resources.require('python-irclib')[0]
-    VERSION = tuple(int(res) for res in re.findall('\d+', _pkg.version))
-except Exception:
-    VERSION = ()
-
-# TODO
-# ----
-# (maybe) thread safety
-# (maybe) color parser convenience functions
-# documentation (including all event types)
-# (maybe) add awareness of different types of ircds
-# send data asynchronously to the server (and DCC connections)
-# (maybe) automatically close unused, passive DCC connections after a while
-
-# NOTES
-# -----
-# connection.quit() only sends QUIT to the server.
-# ERROR from the server triggers the error event and the disconnect event.
-# dropping of the connection triggers the disconnect event.
-
-class IRCError(Exception):
-    """Represents an IRC exception."""
-    pass
-
-
-class IRC(object):
-    """Class that handles one or several IRC server connections.
-
-    When an IRC object has been instantiated, it can be used to create
-    Connection objects that represent the IRC connections.  The
-    responsibility of the IRC object is to provide an event-driven
-    framework for the connections and to keep the connections alive.
-    It runs a select loop to poll each connection's TCP socket and
-    hands over the sockets with incoming data for processing by the
-    corresponding connection.
-
-    The methods of most interest for an IRC client writer are server,
-    add_global_handler, remove_global_handler, execute_at,
-    execute_delayed, process_once and process_forever.
-
-    Here is an example:
-
-        irc = irclib.IRC()
-        server = irc.server()
-        server.connect("irc.some.where", 6667, "my_nickname")
-        server.privmsg("a_nickname", "Hi there!")
-        irc.process_forever()
-
-    This will connect to the IRC server irc.some.where on port 6667
-    using the nickname my_nickname and send the message "Hi there!"
-    to the nickname a_nickname.
-    """
-
-    def __init__(self, fn_to_add_socket=None,
-                 fn_to_remove_socket=None,
-                 fn_to_add_timeout=None,
-                 debuglevel=0):
-        """Constructor for IRC objects.
-
-        Optional arguments are fn_to_add_socket, fn_to_remove_socket
-        and fn_to_add_timeout.  The first two specify functions that
-        will be called with a socket object as argument when the IRC
-        object wants to be notified (or stop being notified) of data
-        coming on a new socket.  When new data arrives, the method
-        process_data should be called.  Similarly, fn_to_add_timeout
-        is called with a number of seconds (a floating point number)
-        as first argument when the IRC object wants to receive a
-        notification (by calling the process_timeout method).  So, if
-        e.g. the argument is 42.17, the object wants the
-        process_timeout method to be called after 42 seconds and 170
-        milliseconds.
-
-        The three arguments mainly exist to be able to use an external
-        main loop (for example Tkinter's or PyGTK's main app loop)
-        instead of calling the process_forever method.
-
-        An alternative is to just call ServerConnection.process_once()
-        once in a while.
-        """
-
-        if fn_to_add_socket and fn_to_remove_socket:
-            self.fn_to_add_socket = fn_to_add_socket
-            self.fn_to_remove_socket = fn_to_remove_socket
-        else:
-            self.fn_to_add_socket = None
-            self.fn_to_remove_socket = None
-
-        self.fn_to_add_timeout = fn_to_add_timeout
-        self.debuglevel = debuglevel
-        self.connections = []
-        self.handlers = {}
-        self.delayed_commands = []  # list of DelayedCommands
-
-        self.add_global_handler("ping", _ping_ponger, -42)
-
-    def server(self):
-        """Creates and returns a ServerConnection object."""
-
-        c = ServerConnection(self)
-        self.connections.append(c)
-        return c
-
-    def process_data(self, sockets):
-        """Called when there is more data to read on connection sockets.
-
-        Arguments:
-
-            sockets -- A list of socket objects.
-
-        See documentation for IRC.__init__.
-        """
-        self.debug(2, "process_data()")
-        for s in sockets:
-            for c in self.connections:
-                if s == c._get_socket():
-                    c.process_data()
-
-    def process_timeout(self):
-        """Called when a timeout notification is due.
-
-        See documentation for IRC.__init__.
-        """
-        while self.delayed_commands:
-            command = self.delayed_commands[0]
-            if not command.due():
-                break
-            command.function(*command.arguments)
-            if isinstance(command, PeriodicCommand):
-                self._schedule_command(command.next())
-            del self.delayed_commands[0]
-
-    def process_once(self, timeout=0):
-        """Process data from connections once.
-
-        Arguments:
-
-            timeout -- How long the select() call should wait if no
-                       data is available.
-
-        This method should be called periodically to check and process
-        incoming data, if there are any.  If that seems boring, look
-        at the process_forever method.
-        """
-        self.debug(2, "process_once()")
-        sockets = map(lambda x: x._get_socket(), self.connections)
-        sockets = filter(lambda x: x != None, sockets)
-        if sockets:
-            (i, o, e) = select.select(sockets, [], [], timeout)
-            self.process_data(i)
-        else:
-            time.sleep(timeout)
-        self.process_timeout()
-
-    def process_forever(self, timeout=0.2):
-        """Run an infinite loop, processing data from connections.
-
-        This method repeatedly calls process_once.
-
-        Arguments:
-
-            timeout -- Parameter to pass to process_once.
-        """
-        self.debug(1, "process_forever(timeout=%s)" % (timeout))
-        while 1:
-            self.process_once(timeout)
-
-    def disconnect_all(self, message=""):
-        """Disconnects all connections."""
-        for c in self.connections:
-            c.disconnect(message)
-
-    def add_global_handler(self, event, handler, priority=0):
-        """Adds a global handler function for a specific event type.
-
-        Arguments:
-
-            event -- Event type (a string).  Check the values of the
-            numeric_events dictionary in irclib.py for possible event
-            types.
-
-            handler -- Callback function.
-
-            priority -- A number (the lower number, the higher priority).
-
-        The handler function is called whenever the specified event is
-        triggered in any of the connections.  See documentation for
-        the Event class.
-
-        The handler functions are called in priority order (lowest
-        number is highest priority).  If a handler function returns
-        "NO MORE", no more handlers will be called.
-        """
-        if not event in self.handlers:
-            self.handlers[event] = []
-        bisect.insort(self.handlers[event], ((priority, handler)))
-
-    def remove_global_handler(self, event, handler):
-        """Removes a global handler function.
-
-        Arguments:
-
-            event -- Event type (a string).
-            handler -- Callback function.
-
-        Returns 1 on success, otherwise 0.
-        """
-        if not event in self.handlers:
-            return 0
-        for h in self.handlers[event]:
-            if handler == h[1]:
-                self.handlers[event].remove(h)
-        return 1
-
-    def execute_at(self, at, function, arguments=()):
-        """Execute a function at a specified time.
-
-        Arguments:
-
-            at -- Execute at this time (standard "time_t" time).
-            function -- Function to call.
-            arguments -- Arguments to give the function.
-        """
-        command = DelayedCommand.at_time(at, function, arguments)
-        self._schedule_command(command)
-
-    def execute_delayed(self, delay, function, arguments=()):
-        """
-        Execute a function after a specified time.
-
-        delay -- How many seconds to wait.
-        function -- Function to call.
-        arguments -- Arguments to give the function.
-        """
-        command = DelayedCommand(delay, function, arguments)
-        self._schedule_command(command)
-
-    def execute_every(self, period, function, arguments=()):
-        """
-        Execute a function every 'period' seconds.
-
-        period -- How often to run (always waits this long for first).
-        function -- Function to call.
-        arguments -- Arguments to give the function.
-        """
-        command = PeriodicCommand(period, function, arguments)
-        self._schedule_command(command)
-
-    def _schedule_command(self, command):
-        bisect.insort(self.delayed_commands, command)
-        if self.fn_to_add_timeout:
-            self.fn_to_add_timeout(total_seconds(command.delay))
-
-    def dcc(self, dcctype="chat"):
-        """Creates and returns a DCCConnection object.
-
-        Arguments:
-
-            dcctype -- "chat" for DCC CHAT connections or "raw" for
-                       DCC SEND (or other DCC types). If "chat",
-                       incoming data will be split in newline-separated
-                       chunks. If "raw", incoming data is not touched.
-        """
-        c = DCCConnection(self, dcctype)
-        self.connections.append(c)
-        return c
-
-    def _handle_event(self, connection, event):
-        """[Internal]"""
-        h = self.handlers
-        th = sorted(h.get("all_events", []) + h.get(event.eventtype(), []))
-        for handler in th:
-            if handler[1](connection, event) == "NO MORE":
-                return
-
-    def debug(self, level, errmsg):
-        """Display debugging information."""
-        if self.debuglevel >= level:
-            sys.stderr.write("irclib[%d]: %s\n" % (self.debuglevel, errmsg))
-
-    def _remove_connection(self, connection):
-        """[Internal]"""
-        self.connections.remove(connection)
-        if self.fn_to_remove_socket:
-            self.fn_to_remove_socket(connection._get_socket())
-
-class DelayedCommand(datetime.datetime):
-    """
-    A command to be executed after some delay (seconds or timedelta).
-    """
-    def __new__(cls, delay, function, arguments):
-        if not isinstance(delay, datetime.timedelta):
-            delay = datetime.timedelta(seconds=delay)
-        at = datetime.datetime.utcnow() + delay
-        cmd = datetime.datetime.__new__(DelayedCommand, at.year,
-            at.month, at.day, at.hour, at.minute, at.second,
-            at.microsecond, at.tzinfo)
-        cmd.delay = delay
-        cmd.function = function
-        cmd.arguments = arguments
-        return cmd
-
-    def at_time(cls, at, function, arguments):
-        """
-        Construct a DelayedCommand to come due at `at`, where `at` may be
-        a datetime or timestamp.
-        """
-        if isinstance(at, int):
-            at = datetime.datetime.utcfromtimestamp(at)
-        delay = at - datetime.datetime.utcnow()
-        return cls(delay, function, arguments)
-    at_time = classmethod(at_time)
-
-    def due(self):
-        return datetime.datetime.utcnow() >= self
-
-class PeriodicCommand(DelayedCommand):
-    """
-    Like a deferred command, but expect this command to run every delay
-    seconds.
-    """
-    def next(self):
-        return PeriodicCommand(self.delay, self.function,
-            self.arguments)
-
-_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
-
-class Connection(object):
-    """Base class for IRC connections.
-
-    Must be overridden.
-    """
-    def __init__(self, irclibobj):
-        self.irclibobj = irclibobj
-
-    def _get_socket():
-        raise IRCError("Not overridden")
-
-    ##############################
-    ### Convenience wrappers.
-
-    def execute_at(self, at, function, arguments=()):
-        self.irclibobj.execute_at(at, function, arguments)
-
-    def execute_delayed(self, delay, function, arguments=()):
-        self.irclibobj.execute_delayed(delay, function, arguments)
-
-    def execute_every(self, period, function, arguments=()):
-        self.irclibobj.execute_every(period, function, arguments)
-
-class ServerConnectionError(IRCError):
-    pass
-
-class ServerNotConnectedError(ServerConnectionError):
-    pass
-
-
-# Huh!?  Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
-# use \n as message separator!  :P
-_linesep_regexp = re.compile("\r?\n")
-
-class ServerConnection(Connection):
-    """This class represents an IRC server connection.
-
-    ServerConnection objects are instantiated by calling the server
-    method on an IRC object.
-    """
-
-    def __init__(self, irclibobj):
-        super(ServerConnection, self).__init__(irclibobj)
-        self.irclibobj = irclibobj
-        self.connected = 0  # Not connected yet.
-        self.socket = None
-        self.ssl = None
-
-    def connect(self, server, port, nickname, password=None, username=None,
-                ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
-        """Connect/reconnect to a server.
-
-        Arguments:
-
-            server -- Server name.
-
-            port -- Port number.
-
-            nickname -- The nickname.
-
-            password -- Password (if any).
-
-            username -- The username.
-
-            ircname -- The IRC name ("realname").
-
-            localaddress -- Bind the connection to a specific local IP address.
-
-            localport -- Bind the connection to a specific local port.
-
-            ssl -- Enable support for ssl.
-
-            ipv6 -- Enable support for ipv6.
-
-        This function can be called to reconnect a closed connection.
-
-        Returns the ServerConnection object.
-        """
-        if self.connected:
-            self.disconnect("Changing servers")
-
-        self.previous_buffer = ""
-        self.handlers = {}
-        self.real_server_name = ""
-        self.real_nickname = nickname
-        self.server = server
-        self.port = port
-        self.nickname = nickname
-        self.username = username or nickname
-        self.ircname = ircname or nickname
-        self.password = password
-        self.localaddress = localaddress
-        self.localport = localport
-
-        self.irclibobj.debug(1, "connect(server=%s, port=%s, nick=%s)" \
-                             % (self.server, self.port, self.nickname))
-
-        self.localhost = socket.gethostname()
-        if ipv6:
-            self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
-        else:
-            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        try:
-            self.socket.bind((self.localaddress, self.localport))
-            self.socket.connect((self.server, self.port))
-            if ssl:
-                self.ssl = ssl_mod.wrap_socket(self.socket)
-        except socket.error, x:
-            self.socket.close()
-            self.socket = None
-            raise ServerConnectionError("Couldn't connect to socket: %s" % x)
-        self.connected = 1
-        if self.irclibobj.fn_to_add_socket:
-            self.irclibobj.fn_to_add_socket(self.socket)
-
-        # Log on...
-        if self.password:
-            self.pass_(self.password)
-        self.nick(self.nickname)
-        self.user(self.username, self.ircname)
-        return self
-
-    def close(self):
-        """Close the connection.
-
-        This method closes the connection permanently; after it has
-        been called, the object is unusable.
-        """
-
-        self.disconnect("Closing object")
-        self.irclibobj._remove_connection(self)
-
-    def _get_socket(self):
-        """[Internal]"""
-        return self.socket
-
-    def get_server_name(self):
-        """Get the (real) server name.
-
-        This method returns the (real) server name, or, more
-        specifically, what the server calls itself.
-        """
-
-        if self.real_server_name:
-            return self.real_server_name
-        else:
-            return ""
-
-    def get_nickname(self):
-        """Get the (real) nick name.
-
-        This method returns the (real) nickname.  The library keeps
-        track of nick changes, so it might not be the nick name that
-        was passed to the connect() method.  """
-
-        return self.real_nickname
-
-    def process_data(self):
-        """[Internal]"""
-
-        try:
-            if self.ssl:
-                new_data = self.ssl.read(2 ** 14)
-            else:
-                new_data = self.socket.recv(2 ** 14)
-        except socket.error:
-            # The server hung up.
-            self.disconnect("Connection reset by peer")
-            return
-        if not new_data:
-            # Read nothing: connection must be down.
-            self.disconnect("Connection reset by peer")
-            return
-
-        lines = _linesep_regexp.split(self.previous_buffer + new_data)
-
-        # Save the last, unfinished line.
-        self.previous_buffer = lines.pop()
-
-        for line in lines:
-            self.irclibobj.debug(1, "FROM SERVER: %s" % repr(line))
-
-            if not line:
-                continue
-
-            prefix = None
-            command = None
-            arguments = None
-            self._handle_event(Event("all_raw_messages",
-                                     self.get_server_name(),
-                                     None,
-                                     [line]))
-
-            m = _rfc_1459_command_regexp.match(line)
-            if m.group("prefix"):
-                prefix = m.group("prefix")
-                if not self.real_server_name:
-                    self.real_server_name = prefix
-
-            if m.group("command"):
-                command = m.group("command").lower()
-
-            if m.group("argument"):
-                a = m.group("argument").split(" :", 1)
-                arguments = a[0].split()
-                if len(a) == 2:
-                    arguments.append(a[1])
-
-            # Translate numerics into more readable strings.
-            if command in numeric_events:
-                command = numeric_events[command]
-
-            if command == "nick":
-                if nm_to_n(prefix) == self.real_nickname:
-                    self.real_nickname = arguments[0]
-            elif command == "welcome":
-                # Record the nickname in case the client changed nick
-                # in a nicknameinuse callback.
-                self.real_nickname = arguments[0]
-
-            if command in ["privmsg", "notice"]:
-                target, message = arguments[0], arguments[1]
-                messages = _ctcp_dequote(message)
-
-                if command == "privmsg":
-                    if is_channel(target):
-                        command = "pubmsg"
-                else:
-                    if is_channel(target):
-                        command = "pubnotice"
-                    else:
-                        command = "privnotice"
-
-                for m in messages:
-                    if type(m) is types.TupleType:
-                        if command in ["privmsg", "pubmsg"]:
-                            command = "ctcp"
-                        else:
-                            command = "ctcpreply"
-
-                        m = list(m)
-                        self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (command, prefix, target, m))
-                        self._handle_event(Event(command, prefix, target, m))
-                        if command == "ctcp" and m[0] == "ACTION":
-                            self._handle_event(Event("action", prefix, target, m[1:]))
-                    else:
-                        self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (command, prefix, target, [m]))
-                        self._handle_event(Event(command, prefix, target, [m]))
-            else:
-                target = None
-
-                if command == "quit":
-                    arguments = [arguments[0]]
-                elif command == "ping":
-                    target = arguments[0]
-                else:
-                    target = arguments[0]
-                    arguments = arguments[1:]
-
-                if command == "mode":
-                    if not is_channel(target):
-                        command = "umode"
-
-                self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (command, prefix, target, m))
-                self._handle_event(Event(command, prefix, target, arguments))
-
-    def _handle_event(self, event):
-        """[Internal]"""
-        self.irclibobj._handle_event(self, event)
-        if event.eventtype() in self.handlers:
-            for fn in self.handlers[event.eventtype()]:
-                fn(self, event)
-
-    def is_connected(self):
-        """Return connection status.
-
-        Returns true if connected, otherwise false.
-        """
-        return self.connected
-
-    def add_global_handler(self, *args):
-        """Add global handler.
-
-        See documentation for IRC.add_global_handler.
-        """
-        self.irclibobj.add_global_handler(*args)
-
-    def remove_global_handler(self, *args):
-        """Remove global handler.
-
-        See documentation for IRC.remove_global_handler.
-        """
-        self.irclibobj.remove_global_handler(*args)
-
-    def action(self, target, action):
-        """Send a CTCP ACTION command."""
-        self.ctcp("ACTION", target, action)
-
-    def admin(self, server=""):
-        """Send an ADMIN command."""
-        self.send_raw(" ".join(["ADMIN", server]).strip())
-
-    def ctcp(self, ctcptype, target, parameter=""):
-        """Send a CTCP command."""
-        ctcptype = ctcptype.upper()
-        self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
-
-    def ctcp_reply(self, target, parameter):
-        """Send a CTCP REPLY command."""
-        self.notice(target, "\001%s\001" % parameter)
-
-    def disconnect(self, message=""):
-        """Hang up the connection.
-
-        Arguments:
-
-            message -- Quit message.
-        """
-        if not self.connected:
-            return
-
-        self.connected = 0
-
-        self.quit(message)
-
-        try:
-            self.socket.shutdown()
-            self.socket.close()
-        except socket.error:
-            pass
-        self.socket = None
-        self._handle_event(Event("disconnect", self.server, "", [message]))
-
-    def globops(self, text):
-        """Send a GLOBOPS command."""
-        self.send_raw("GLOBOPS :" + text)
-
-    def info(self, server=""):
-        """Send an INFO command."""
-        self.send_raw(" ".join(["INFO", server]).strip())
-
-    def invite(self, nick, channel):
-        """Send an INVITE command."""
-        self.send_raw(" ".join(["INVITE", nick, channel]).strip())
-
-    def ison(self, nicks):
-        """Send an ISON command.
-
-        Arguments:
-
-            nicks -- List of nicks.
-        """
-        self.send_raw("ISON " + " ".join(nicks))
-
-    def join(self, channel, key=""):
-        """Send a JOIN command."""
-        self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
-
-    def kick(self, channel, nick, comment=""):
-        """Send a KICK command."""
-        self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
-
-    def links(self, remote_server="", server_mask=""):
-        """Send a LINKS command."""
-        command = "LINKS"
-        if remote_server:
-            command = command + " " + remote_server
-        if server_mask:
-            command = command + " " + server_mask
-        self.send_raw(command)
-
-    def list(self, channels=None, server=""):
-        """Send a LIST command."""
-        command = "LIST"
-        if channels:
-            command = command + " " + ",".join(channels)
-        if server:
-            command = command + " " + server
-        self.send_raw(command)
-
-    def lusers(self, server=""):
-        """Send a LUSERS command."""
-        self.send_raw("LUSERS" + (server and (" " + server)))
-
-    def mode(self, target, command):
-        """Send a MODE command."""
-        self.send_raw("MODE %s %s" % (target, command))
-
-    def motd(self, server=""):
-        """Send an MOTD command."""
-        self.send_raw("MOTD" + (server and (" " + server)))
-
-    def names(self, channels=None):
-        """Send a NAMES command."""
-        self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or ""))
-
-    def nick(self, newnick):
-        """Send a NICK command."""
-        self.send_raw("NICK " + newnick)
-
-    def notice(self, target, text):
-        """Send a NOTICE command."""
-        # Should limit len(text) here!
-        self.send_raw("NOTICE %s :%s" % (target, text))
-
-    def oper(self, nick, password):
-        """Send an OPER command."""
-        self.send_raw("OPER %s %s" % (nick, password))
-
-    def part(self, channels, message=""):
-        """Send a PART command."""
-        channels = always_iterable(channels)
-        cmd_parts = [
-            'PART',
-            ','.join(channels),
-        ]
-        if message: cmd_parts.append(message)
-        self.send_raw(' '.join(cmd_parts))
-
-    def pass_(self, password):
-        """Send a PASS command."""
-        self.send_raw("PASS " + password)
-
-    def ping(self, target, target2=""):
-        """Send a PING command."""
-        self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
-
-    def pong(self, target, target2=""):
-        """Send a PONG command."""
-        self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
-
-    def privmsg(self, target, text):
-        """Send a PRIVMSG command."""
-        # Should limit len(text) here!
-        self.send_raw("PRIVMSG %s :%s" % (target, text))
-
-    def privmsg_many(self, targets, text):
-        """Send a PRIVMSG command to multiple targets."""
-        # Should limit len(text) here!
-        self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text))
-
-    def quit(self, message=""):
-        """Send a QUIT command."""
-        # Note that many IRC servers don't use your QUIT message
-        # unless you've been connected for at least 5 minutes!
-        self.send_raw("QUIT" + (message and (" :" + message)))
-
-    def send_raw(self, string):
-        """Send raw string to the server.
-
-        The string will be padded with appropriate CR LF.
-        """
-        if self.socket is None:
-            raise ServerNotConnectedError("Not connected.")
-        try:
-            if self.ssl:
-                self.ssl.write(string + "\r\n")
-            else:
-                self.socket.send(string + "\r\n")
-            self.irclibobj.debug(1, "TO SERVER: " + repr(string))
-        except socket.error:
-            # Ouch!
-            self.disconnect("Connection reset by peer.")
-
-    def squit(self, server, comment=""):
-        """Send an SQUIT command."""
-        self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
-
-    def stats(self, statstype, server=""):
-        """Send a STATS command."""
-        self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
-
-    def time(self, server=""):
-        """Send a TIME command."""
-        self.send_raw("TIME" + (server and (" " + server)))
-
-    def topic(self, channel, new_topic=None):
-        """Send a TOPIC command."""
-        if new_topic is None:
-            self.send_raw("TOPIC " + channel)
-        else:
-            self.send_raw("TOPIC %s :%s" % (channel, new_topic))
-
-    def trace(self, target=""):
-        """Send a TRACE command."""
-        self.send_raw("TRACE" + (target and (" " + target)))
-
-    def user(self, username, realname):
-        """Send a USER command."""
-        self.send_raw("USER %s 0 * :%s" % (username, realname))
-
-    def userhost(self, nicks):
-        """Send a USERHOST command."""
-        self.send_raw("USERHOST " + ",".join(nicks))
-
-    def users(self, server=""):
-        """Send a USERS command."""
-        self.send_raw("USERS" + (server and (" " + server)))
-
-    def version(self, server=""):
-        """Send a VERSION command."""
-        self.send_raw("VERSION" + (server and (" " + server)))
-
-    def wallops(self, text):
-        """Send a WALLOPS command."""
-        self.send_raw("WALLOPS :" + text)
-
-    def who(self, target="", op=""):
-        """Send a WHO command."""
-        self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
-
-    def whois(self, targets):
-        """Send a WHOIS command."""
-        self.send_raw("WHOIS " + ",".join(targets))
-
-    def whowas(self, nick, max="", server=""):
-        """Send a WHOWAS command."""
-        self.send_raw("WHOWAS %s%s%s" % (nick,
-                                         max and (" " + max),
-                                         server and (" " + server)))
-
-class DCCConnectionError(IRCError):
-    pass
-
-
-class DCCConnection(Connection):
-    """This class represents a DCC connection.
-
-    DCCConnection objects are instantiated by calling the dcc
-    method on an IRC object.
-    """
-    def __init__(self, irclibobj, dcctype):
-        super(DCCConnection, self).__init__(irclibobj)
-        self.irclibobj = irclibobj
-        self.connected = 0
-        self.passive = 0
-        self.dcctype = dcctype
-        self.peeraddress = None
-        self.peerport = None
-
-    def connect(self, address, port):
-        """Connect/reconnect to a DCC peer.
-
-        Arguments:
-            address -- Host/IP address of the peer.
-
-            port -- The port number to connect to.
-
-        Returns the DCCConnection object.
-        """
-        self.peeraddress = socket.gethostbyname(address)
-        self.peerport = port
-        self.socket = None
-        self.previous_buffer = ""
-        self.handlers = {}
-        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.passive = 0
-        try:
-            self.socket.connect((self.peeraddress, self.peerport))
-        except socket.error, x:
-            raise DCCConnectionError("Couldn't connect to socket: %s" % x)
-        self.connected = 1
-        if self.irclibobj.fn_to_add_socket:
-            self.irclibobj.fn_to_add_socket(self.socket)
-        return self
-
-    def listen(self):
-        """Wait for a connection/reconnection from a DCC peer.
-
-        Returns the DCCConnection object.
-
-        The local IP address and port are available as
-        self.localaddress and self.localport.  After connection from a
-        peer, the peer address and port are available as
-        self.peeraddress and self.peerport.
-        """
-        self.previous_buffer = ""
-        self.handlers = {}
-        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-        self.passive = 1
-        try:
-            self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
-            self.localaddress, self.localport = self.socket.getsockname()
-            self.socket.listen(10)
-        except socket.error, x:
-            raise DCCConnectionError("Couldn't bind socket: %s" % x)
-        return self
-
-    def disconnect(self, message=""):
-        """Hang up the connection and close the object.
-
-        Arguments:
-
-            message -- Quit message.
-        """
-        if not self.connected:
-            return
-
-        self.connected = 0
-        try:
-            self.socket.shutdown()
-            self.socket.close()
-        except socket.error:
-            pass
-        self.socket = None
-        self.irclibobj._handle_event(
-            self,
-            Event("dcc_disconnect", self.peeraddress, "", [message]))
-        self.irclibobj._remove_connection(self)
-
-    def process_data(self):
-        """[Internal]"""
-
-        if self.passive and not self.connected:
-            conn, (self.peeraddress, self.peerport) = self.socket.accept()
-            self.socket.close()
-            self.socket = conn
-            self.connected = 1
-            self.irclibobj.debug(1, "DCC connection from %s:%d" % (
-                self.peeraddress, self.peerport))
-            self.irclibobj._handle_event(
-                self,
-                Event("dcc_connect", self.peeraddress, None, None))
-            return
-
-        try:
-            new_data = self.socket.recv(2 ** 14)
-        except socket.error:
-            # The server hung up.
-            self.disconnect("Connection reset by peer")
-            return
-        if not new_data:
-            # Read nothing: connection must be down.
-            self.disconnect("Connection reset by peer")
-            return
-
-        if self.dcctype == "chat":
-            # The specification says lines are terminated with LF, but
-            # it seems safer to handle CR LF terminations too.
-            chunks = _linesep_regexp.split(self.previous_buffer + new_data)
-
-            # Save the last, unfinished line.
-            self.previous_buffer = chunks[-1]
-            if len(self.previous_buffer) > 2 ** 14:
-                # Bad peer! Naughty peer!
-                self.disconnect()
-                return
-            chunks = chunks[:-1]
-        else:
-            chunks = [new_data]
-
-        command = "dccmsg"
-        prefix = self.peeraddress
-        target = None
-        for chunk in chunks:
-            self.irclibobj.debug(1, "FROM PEER: " + repr(chunk))
-            arguments = [chunk]
-            self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (
-                    command, prefix, target, arguments))
-            self.irclibobj._handle_event(
-                self,
-                Event(command, prefix, target, arguments))
-
-    def _get_socket(self):
-        """[Internal]"""
-        return self.socket
-
-    def privmsg(self, string):
-        """Send data to DCC peer.
-
-        The string will be padded with appropriate LF if it's a DCC
-        CHAT session.
-        """
-        try:
-            self.socket.send(string)
-            if self.dcctype == "chat":
-                self.socket.send("\n")
-            self.irclibobj.debug("TO PEER: %s\n" % repr(string))
-        except socket.error:
-            # Ouch!
-            self.disconnect("Connection reset by peer.")
-
-class SimpleIRCClient(object):
-    """A simple single-server IRC client class.
-
-    This is an example of an object-oriented wrapper of the IRC
-    framework.  A real IRC client can be made by subclassing this
-    class and adding appropriate methods.
-
-    The method on_join will be called when a "join" event is created
-    (which is done when the server sends a JOIN messsage/command),
-    on_privmsg will be called for "privmsg" events, and so on.  The
-    handler methods get two arguments: the connection object (same as
-    self.connection) and the event object.
-
-    Instance attributes that can be used by sub classes:
-
-        ircobj -- The IRC instance.
-
-        connection -- The ServerConnection instance.
-
-        dcc_connections -- A list of DCCConnection instances.
-    """
-    def __init__(self):
-        self.ircobj = IRC()
-        self.connection = self.ircobj.server()
-        self.dcc_connections = []
-        self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
-        self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
-
-    def _dispatcher(self, c, e):
-        """[Internal]"""
-        self.irclibobj.debug("dispatcher:%s" % e.eventtype())
-
-        m = "on_" + e.eventtype()
-        if hasattr(self, m):
-            getattr(self, m)(c, e)
-
-    def _dcc_disconnect(self, c, e):
-        self.dcc_connections.remove(c)
-
-    def connect(self, server, port, nickname, password=None, username=None,
-                ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
-        """Connect/reconnect to a server.
-
-        Arguments:
-
-            server -- Server name.
-
-            port -- Port number.
-
-            nickname -- The nickname.
-
-            password -- Password (if any).
-
-            username -- The username.
-
-            ircname -- The IRC name.
-
-            localaddress -- Bind the connection to a specific local IP address.
-
-            localport -- Bind the connection to a specific local port.
-
-            ssl -- Enable support for ssl.
-
-            ipv6 -- Enable support for ipv6.
-
-        This function can be called to reconnect a closed connection.
-        """
-        self.connection.connect(server, port, nickname,
-                                password, username, ircname,
-                                localaddress, localport, ssl, ipv6)
-
-    def dcc_connect(self, address, port, dcctype="chat"):
-        """Connect to a DCC peer.
-
-        Arguments:
-
-            address -- IP address of the peer.
-
-            port -- Port to connect to.
-
-        Returns a DCCConnection instance.
-        """
-        dcc = self.ircobj.dcc(dcctype)
-        self.dcc_connections.append(dcc)
-        dcc.connect(address, port)
-        return dcc
-
-    def dcc_listen(self, dcctype="chat"):
-        """Listen for connections from a DCC peer.
-
-        Returns a DCCConnection instance.
-        """
-        dcc = self.ircobj.dcc(dcctype)
-        self.dcc_connections.append(dcc)
-        dcc.listen()
-        return dcc
-
-    def start(self):
-        """Start the IRC client."""
-        self.ircobj.process_forever()
-
-
-class Event(object):
-    """Class representing an IRC event."""
-    def __init__(self, eventtype, source, target, arguments=None):
-        """Constructor of Event objects.
-
-        Arguments:
-
-            eventtype -- A string describing the event.
-
-            source -- The originator of the event (a nick mask or a server).
-
-            target -- The target of the event (a nick or a channel).
-
-            arguments -- Any event specific arguments.
-        """
-        self._eventtype = eventtype
-        self._source = source
-        self._target = target
-        if arguments:
-            self._arguments = arguments
-        else:
-            self._arguments = []
-
-    def eventtype(self):
-        """Get the event type."""
-        return self._eventtype
-
-    def source(self):
-        """Get the event source."""
-        return self._source
-
-    def target(self):
-        """Get the event target."""
-        return self._target
-
-    def arguments(self):
-        """Get the event arguments."""
-        return self._arguments
-
-_LOW_LEVEL_QUOTE = "\020"
-_CTCP_LEVEL_QUOTE = "\134"
-_CTCP_DELIMITER = "\001"
-
-_low_level_mapping = {
-    "0": "\000",
-    "n": "\n",
-    "r": "\r",
-    _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
-}
-
-_low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
-
-def mask_matches(nick, mask):
-    """Check if a nick matches a mask.
-
-    Returns true if the nick matches, otherwise false.
-    """
-    nick = irc_lower(nick)
-    mask = irc_lower(mask)
-    mask = mask.replace("\\", "\\\\")
-    for ch in ".$|[](){}+":
-        mask = mask.replace(ch, "\\" + ch)
-    mask = mask.replace("?", ".")
-    mask = mask.replace("*", ".*")
-    r = re.compile(mask, re.IGNORECASE)
-    return r.match(nick)
-
-_special = "-[]\\`^{}"
-nick_characters = string.ascii_letters + string.digits + _special
-
-def _ctcp_dequote(message):
-    """[Internal] Dequote a message according to CTCP specifications.
-
-    The function returns a list where each element can be either a
-    string (normal message) or a tuple of one or two strings (tagged
-    messages).  If a tuple has only one element (ie is a singleton),
-    that element is the tag; otherwise the tuple has two elements: the
-    tag and the data.
-
-    Arguments:
-
-        message -- The message to be decoded.
-    """
-
-    def _low_level_replace(match_obj):
-        ch = match_obj.group(1)
-
-        # If low_level_mapping doesn't have the character as key, we
-        # should just return the character.
-        return _low_level_mapping.get(ch, ch)
-
-    if _LOW_LEVEL_QUOTE in message:
-        # Yup, there was a quote.  Release the dequoter, man!
-        message = _low_level_regexp.sub(_low_level_replace, message)
-
-    if _CTCP_DELIMITER not in message:
-        return [message]
-    else:
-        # Split it into parts.  (Does any IRC client actually *use*
-        # CTCP stacking like this?)
-        chunks = message.split(_CTCP_DELIMITER)
-
-        messages = []
-        i = 0
-        while i < len(chunks) - 1:
-            # Add message if it's non-empty.
-            if len(chunks[i]) > 0:
-                messages.append(chunks[i])
-
-            if i < len(chunks) - 2:
-                # Aye!  CTCP tagged data ahead!
-                messages.append(tuple(chunks[i + 1].split(" ", 1)))
-
-            i = i + 2
-
-        if len(chunks) % 2 == 0:
-            # Hey, a lonely _CTCP_DELIMITER at the end!  This means
-            # that the last chunk, including the delimiter, is a
-            # normal message!  (This is according to the CTCP
-            # specification.)
-            messages.append(_CTCP_DELIMITER + chunks[-1])
-
-        return messages
-
-def is_channel(string):
-    """Check if a string is a channel name.
-
-    Returns true if the argument is a channel name, otherwise false.
-    """
-    return string and string[0] in "#&+!"
-
-def ip_numstr_to_quad(num):
-    """Convert an IP number as an integer given in ASCII
-    representation (e.g. '3232235521') to an IP address string
-    (e.g. '192.168.0.1')."""
-    n = long(num)
-    p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
-                           n >> 8 & 0xFF, n & 0xFF]))
-    return ".".join(p)
-
-def ip_quad_to_numstr(quad):
-    """Convert an IP address string (e.g. '192.168.0.1') to an IP
-    number as an integer given in ASCII representation
-    (e.g. '3232235521')."""
-    p = map(long, quad.split("."))
-    s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
-    if s[-1] == "L":
-        s = s[:-1]
-    return s
-
-def nm_to_n(s):
-    """Get the nick part of a nickmask.
-
-    (The source of an Event is a nickmask.)
-    """
-    return s.split("!")[0]
-
-def nm_to_uh(s):
-    """Get the userhost part of a nickmask.
-
-    (The source of an Event is a nickmask.)
-    """
-    return s.split("!")[1]
-
-def nm_to_h(s):
-    """Get the host part of a nickmask.
-
-    (The source of an Event is a nickmask.)
-    """
-    return s.split("@")[1]
-
-def nm_to_u(s):
-    """Get the user part of a nickmask.
-
-    (The source of an Event is a nickmask.)
-    """
-    s = s.split("!")[1]
-    return s.split("@")[0]
-
-def parse_nick_modes(mode_string):
-    """Parse a nick mode string.
-
-    The function returns a list of lists with three members: sign,
-    mode and argument.  The sign is "+" or "-".  The argument is
-    always None.
-
-    Example:
-
-    >>> parse_nick_modes("+ab-c")
-    [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
-    """
-
-    return _parse_modes(mode_string, "")
-
-def parse_channel_modes(mode_string):
-    """Parse a channel mode string.
-
-    The function returns a list of lists with three members: sign,
-    mode and argument.  The sign is "+" or "-".  The argument is
-    None if mode isn't one of "b", "k", "l", "v" or "o".
-
-    Example:
-
-    >>> parse_channel_modes("+ab-c foo")
-    [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
-    """
-
-    return _parse_modes(mode_string, "bklvo")
-
-def _parse_modes(mode_string, unary_modes=""):
-    """[Internal]"""
-    modes = []
-    arg_count = 0
-
-    # State variable.
-    sign = ""
-
-    a = mode_string.split()
-    if len(a) == 0:
-        return []
-    else:
-        mode_part, args = a[0], a[1:]
-
-    if mode_part[0] not in "+-":
-        return []
-    for ch in mode_part:
-        if ch in "+-":
-            sign = ch
-        elif ch == " ":
-            pass
-        elif ch in unary_modes:
-            if len(args) >= arg_count + 1:
-                modes.append([sign, ch, args[arg_count]])
-                arg_count = arg_count + 1
-            else:
-                modes.append([sign, ch, None])
-        else:
-            modes.append([sign, ch, None])
-    return modes
-
-def _ping_ponger(connection, event):
-    """[Internal]"""
-    connection.pong(event.target())
-
-# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
-numeric_events = {
-    "001": "welcome",
-    "002": "yourhost",
-    "003": "created",
-    "004": "myinfo",
-    "005": "featurelist",  # XXX
-    "200": "tracelink",
-    "201": "traceconnecting",
-    "202": "tracehandshake",
-    "203": "traceunknown",
-    "204": "traceoperator",
-    "205": "traceuser",
-    "206": "traceserver",
-    "207": "traceservice",
-    "208": "tracenewtype",
-    "209": "traceclass",
-    "210": "tracereconnect",
-    "211": "statslinkinfo",
-    "212": "statscommands",
-    "213": "statscline",
-    "214": "statsnline",
-    "215": "statsiline",
-    "216": "statskline",
-    "217": "statsqline",
-    "218": "statsyline",
-    "219": "endofstats",
-    "221": "umodeis",
-    "231": "serviceinfo",
-    "232": "endofservices",
-    "233": "service",
-    "234": "servlist",
-    "235": "servlistend",
-    "241": "statslline",
-    "242": "statsuptime",
-    "243": "statsoline",
-    "244": "statshline",
-    "250": "luserconns",
-    "251": "luserclient",
-    "252": "luserop",
-    "253": "luserunknown",
-    "254": "luserchannels",
-    "255": "luserme",
-    "256": "adminme",
-    "257": "adminloc1",
-    "258": "adminloc2",
-    "259": "adminemail",
-    "261": "tracelog",
-    "262": "endoftrace",
-    "263": "tryagain",
-    "265": "n_local",
-    "266": "n_global",
-    "300": "none",
-    "301": "away",
-    "302": "userhost",
-    "303": "ison",
-    "305": "unaway",
-    "306": "nowaway",
-    "311": "whoisuser",
-    "312": "whoisserver",
-    "313": "whoisoperator",
-    "314": "whowasuser",
-    "315": "endofwho",
-    "316": "whoischanop",
-    "317": "whoisidle",
-    "318": "endofwhois",
-    "319": "whoischannels",
-    "321": "liststart",
-    "322": "list",
-    "323": "listend",
-    "324": "channelmodeis",
-    "329": "channelcreate",
-    "331": "notopic",
-    "332": "currenttopic",
-    "333": "topicinfo",
-    "341": "inviting",
-    "342": "summoning",
-    "346": "invitelist",
-    "347": "endofinvitelist",
-    "348": "exceptlist",
-    "349": "endofexceptlist",
-    "351": "version",
-    "352": "whoreply",
-    "353": "namreply",
-    "361": "killdone",
-    "362": "closing",
-    "363": "closeend",
-    "364": "links",
-    "365": "endoflinks",
-    "366": "endofnames",
-    "367": "banlist",
-    "368": "endofbanlist",
-    "369": "endofwhowas",
-    "371": "info",
-    "372": "motd",
-    "373": "infostart",
-    "374": "endofinfo",
-    "375": "motdstart",
-    "376": "endofmotd",
-    "377": "motd2",        # 1997-10-16 -- tkil
-    "381": "youreoper",
-    "382": "rehashing",
-    "384": "myportis",
-    "391": "time",
-    "392": "usersstart",
-    "393": "users",
-    "394": "endofusers",
-    "395": "nousers",
-    "401": "nosuchnick",
-    "402": "nosuchserver",
-    "403": "nosuchchannel",
-    "404": "cannotsendtochan",
-    "405": "toomanychannels",
-    "406": "wasnosuchnick",
-    "407": "toomanytargets",
-    "409": "noorigin",
-    "411": "norecipient",
-    "412": "notexttosend",
-    "413": "notoplevel",
-    "414": "wildtoplevel",
-    "421": "unknowncommand",
-    "422": "nomotd",
-    "423": "noadmininfo",
-    "424": "fileerror",
-    "431": "nonicknamegiven",
-    "432": "erroneusnickname",  # Thiss iz how its speld in thee RFC.
-    "433": "nicknameinuse",
-    "436": "nickcollision",
-    "437": "unavailresource",  # "Nick temporally unavailable"
-    "441": "usernotinchannel",
-    "442": "notonchannel",
-    "443": "useronchannel",
-    "444": "nologin",
-    "445": "summondisabled",
-    "446": "usersdisabled",
-    "451": "notregistered",
-    "461": "needmoreparams",
-    "462": "alreadyregistered",
-    "463": "nopermforhost",
-    "464": "passwdmismatch",
-    "465": "yourebannedcreep",  # I love this one...
-    "466": "youwillbebanned",
-    "467": "keyset",
-    "471": "channelisfull",
-    "472": "unknownmode",
-    "473": "inviteonlychan",
-    "474": "bannedfromchan",
-    "475": "badchannelkey",
-    "476": "badchanmask",
-    "477": "nochanmodes",  # "Channel doesn't support modes"
-    "478": "banlistfull",
-    "481": "noprivileges",
-    "482": "chanoprivsneeded",
-    "483": "cantkillserver",
-    "484": "restricted",   # Connection is restricted
-    "485": "uniqopprivsneeded",
-    "491": "nooperhost",
-    "492": "noservicehost",
-    "501": "umodeunknownflag",
-    "502": "usersdontmatch",
-}
-
-generated_events = [
-    # Generated events
-    "dcc_connect",
-    "dcc_disconnect",
-    "dccmsg",
-    "disconnect",
-    "ctcp",
-    "ctcpreply",
-]
-
-protocol_events = [
-    # IRC protocol events
-    "error",
-    "join",
-    "kick",
-    "mode",
-    "part",
-    "ping",
-    "privmsg",
-    "privnotice",
-    "pubmsg",
-    "pubnotice",
-    "quit",
-    "invite",
-    "pong",
-]
-
-all_events = generated_events + protocol_events + numeric_events.values()
-
-# from jaraco.util.itertools
-def always_iterable(item):
-    """
-    Given an object, always return an iterable. If the item is not
-    already iterable, return a tuple containing only the item.
-
-    >>> always_iterable([1,2,3])
-    [1, 2, 3]
-    >>> always_iterable('foo')
-    ('foo',)
-    >>> always_iterable(None)
-    (None,)
-    >>> always_iterable(xrange(10))
-    xrange(10)
-    """
-    if isinstance(item, basestring) or not hasattr(item, '__iter__'):
-        item = item,
-    return item
-
-# from jaraco.util.string
-class FoldedCase(str):
-    """
-    A case insensitive string class; behaves just like str
-    except compares equal when the only variation is case.
-    >>> s = FoldedCase('hello world')
-
-    >>> s == 'Hello World'
-    True
-
-    >>> 'Hello World' == s
-    True
-
-    >>> s.index('O')
-    4
-
-    >>> s.split('O')
-    ['hell', ' w', 'rld']
-
-    >>> names = map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])
-    >>> names.sort()
-    >>> names
-    ['alpha', 'Beta', 'GAMMA']
-    """
-    def __lt__(self, other):
-        return self.lower() < other.lower()
-
-    def __gt__(self, other):
-        return self.lower() > other.lower()
-
-    def __eq__(self, other):
-        return self.lower() == other.lower()
-
-    def __hash__(self):
-        return hash(self.lower())
-
-    # cache lower since it's likely to be called frequently.
-    def lower(self):
-        self._lower = super(FoldedCase, self).lower()
-        self.lower = lambda: self._lower
-        return self._lower
-
-    def index(self, sub):
-        return self.lower().index(sub.lower())
-
-    def split(self, splitter=' ', maxsplit=0):
-        pattern = re.compile(re.escape(splitter), re.I)
-        return pattern.split(self, maxsplit)
-
-class IRCFoldedCase(FoldedCase):
-    """
-    A version of FoldedCase that honors the IRC specification for lowercased
-    strings (RFC 1459).
-
-    >>> IRCFoldedCase('Foo^').lower()
-    'foo~'
-    >>> IRCFoldedCase('[this]') == IRCFoldedCase('{THIS}')
-    True
-    """
-    translation = string.maketrans(
-        string.ascii_uppercase + r"[]\^",
-        string.ascii_lowercase + r"{}|~",
-    )
-
-    def lower(self):
-        return self.translate(self.translation)
-
-# for compatibility
-def irc_lower(str):
-    return IRCFoldedCase(str).lower()
-
-def total_seconds(td):
-    """
-    Python 2.7 adds a total_seconds method to timedelta objects.
-    See http://docs.python.org/library/datetime.html#datetime.timedelta.total_seconds
-    """
-    try:
-        result = td.total_seconds()
-    except AttributeError:
-        result = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
-    return result
index 0b2373b6100832bdfee2c9a040addd7d64f40873..d486a21c23b5873cb82898013711166fbc40e652 100755 (executable)
--- a/irker.py
+++ b/irker.py
@@ -10,7 +10,9 @@ Run this as a daemon in order to maintain stateful connections to IRC
 servers; this will allow it to respond to server pings and minimize
 join/leave traffic.
 
-Requires Python 2.6.
+Requires Python 2.6 and the irc.client library: see.
+
+http://sourceforge.net/projects/python-irclib
 
 TO-DO: Register the port?
 """
@@ -28,7 +30,7 @@ CONNECT_MAX = 18              # Maximum connections per bot (freenet limit)
 
 import sys, json, exceptions, getopt, urlparse, time, socket
 import threading, Queue, SocketServer
-import irclib
+import irc.client, logging
 
 class SessionException(exceptions.Exception):
     def __init__(self, message):
@@ -102,7 +104,7 @@ class Irker:
     def __init__(self, debuglevel=0, namesuffix=None):
         self.debuglevel = debuglevel
         self.namesuffix = namesuffix or socket.getfqdn().replace(".", "-")
-        self.irc = irclib.IRC(debuglevel=self.debuglevel-1)
+        self.irc = irc.client.IRC()
         self.irc.add_global_handler("ping", lambda c, e: self._handle_ping(c,e))
         thread = threading.Thread(target=self.irc.process_forever)
         self.irc._thread = thread
@@ -204,6 +206,8 @@ if __name__ == '__main__':
     for (opt, val) in options:
         if opt == '-d':
             debuglevel = int(val)
+            if debuglevel > 1:
+                logging.basicConfig(level=DEBUG)
         elif opt == '-p':
             port = int(val)
         elif opt == '-n':