17757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Copyright (C) 1999--2002  Joel Rosdahl
27757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch#
37757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# This library is free software; you can redistribute it and/or
47757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# modify it under the terms of the GNU Lesser General Public
57757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# License as published by the Free Software Foundation; either
67757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# version 2.1 of the License, or (at your option) any later version.
77757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch#
87757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# This library is distributed in the hope that it will be useful,
97757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# but WITHOUT ANY WARRANTY; without even the implied warranty of
107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Lesser General Public License for more details.
127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch#
137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# You should have received a copy of the GNU Lesser General Public
147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# License along with this library; if not, write to the Free Software
157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch#
177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# keltus <keltus@users.sourceforge.net>
187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch#
197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# $Id: irclib.py,v 1.47 2008/09/25 22:00:59 keltus Exp $
207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch"""irclib -- Internet Relay Chat (IRC) protocol client library.
227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen MurdochThis library is intended to encapsulate the IRC protocol at a quite
247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochlow level.  It provides an event-driven IRC client framework.  It has
257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdocha fairly thorough support for the basic IRC protocol, CTCP, DCC chat,
267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochbut DCC file transfers is not yet supported.
277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen MurdochIn order to understand how to make an IRC client, I'm afraid you more
297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochor less must understand the IRC specifications.  They are available
307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochhere: [IRC specifications].
317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen MurdochThe main features of the IRC client framework are:
337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Abstraction of the IRC protocol.
357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Handles multiple simultaneous IRC server connections.
367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Handles server PONGing transparently.
377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Messages to the IRC server are done by calling methods on an IRC
387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    connection object.
397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Messages from an IRC server triggers events, which can be caught
407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    by event handlers.
417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Reading from and writing to IRC server sockets are normally done
427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    by an internal select() loop, but the select()ing may be done by
437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    an external main loop.
447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Functions can be registered to execute at specified times by the
457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    event-loop.
467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Decodes CTCP tagging correctly (hopefully); I haven't seen any
477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    other IRC client implementation that handles the CTCP
487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    specification subtilties.
497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * A kind of simple, single-server, object-oriented IRC client class
507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    that dispatches events to instance methods is included.
517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen MurdochCurrent limitations:
537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * The IRC protocol shines through the abstraction a bit too much.
557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Data is not written asynchronously to the server, i.e. the write()
567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    may block if the TCP buffers are stuffed.
577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * There are no support for DCC file transfers.
587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * The author haven't even read RFC 2810, 2811, 2812 and 2813.
597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch  * Like most projects, documentation is lacking...
607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch.. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch"""
637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport bisect
657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport re
667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport select
677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport socket
687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport string
697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport sys
707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport time
717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochimport types
727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen MurdochVERSION = 0, 4, 8
747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen MurdochDEBUG = 0
757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# TODO
777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# ----
787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# (maybe) thread safety
797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# (maybe) color parser convenience functions
807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# documentation (including all event types)
817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# (maybe) add awareness of different types of ircds
827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# send data asynchronously to the server (and DCC connections)
837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# (maybe) automatically close unused, passive DCC connections after a while
847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# NOTES
867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# -----
877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# connection.quit() only sends QUIT to the server.
887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# ERROR from the server triggers the error event and the disconnect event.
897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# dropping of the connection triggers the disconnect event.
907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass IRCError(Exception):
927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Represents an IRC exception."""
937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pass
947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass IRC:
977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Class that handles one or several IRC server connections.
987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    When an IRC object has been instantiated, it can be used to create
1007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Connection objects that represent the IRC connections.  The
1017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    responsibility of the IRC object is to provide an event-driven
1027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    framework for the connections and to keep the connections alive.
1037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    It runs a select loop to poll each connection's TCP socket and
1047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    hands over the sockets with incoming data for processing by the
1057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    corresponding connection.
1067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The methods of most interest for an IRC client writer are server,
1087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    add_global_handler, remove_global_handler, execute_at,
1097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    execute_delayed, process_once and process_forever.
1107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Here is an example:
1127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        irc = irclib.IRC()
1147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        server = irc.server()
1157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        server.connect(\"irc.some.where\", 6667, \"my_nickname\")
1167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        server.privmsg(\"a_nickname\", \"Hi there!\")
1177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        irc.process_forever()
1187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    This will connect to the IRC server irc.some.where on port 6667
1207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    using the nickname my_nickname and send the message \"Hi there!\"
1217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    to the nickname a_nickname.
1227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
1237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, fn_to_add_socket=None,
1257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                 fn_to_remove_socket=None,
1267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                 fn_to_add_timeout=None):
1277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Constructor for IRC objects.
1287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Optional arguments are fn_to_add_socket, fn_to_remove_socket
1307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        and fn_to_add_timeout.  The first two specify functions that
1317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        will be called with a socket object as argument when the IRC
1327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        object wants to be notified (or stop being notified) of data
1337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        coming on a new socket.  When new data arrives, the method
1347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        process_data should be called.  Similarly, fn_to_add_timeout
1357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        is called with a number of seconds (a floating point number)
1367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        as first argument when the IRC object wants to receive a
1377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        notification (by calling the process_timeout method).  So, if
1387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        e.g. the argument is 42.17, the object wants the
1397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        process_timeout method to be called after 42 seconds and 170
1407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        milliseconds.
1417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        The three arguments mainly exist to be able to use an external
1437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        main loop (for example Tkinter's or PyGTK's main app loop)
1447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        instead of calling the process_forever method.
1457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        An alternative is to just call ServerConnection.process_once()
1477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        once in a while.
1487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if fn_to_add_socket and fn_to_remove_socket:
1517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.fn_to_add_socket = fn_to_add_socket
1527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.fn_to_remove_socket = fn_to_remove_socket
1537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
1547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.fn_to_add_socket = None
1557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.fn_to_remove_socket = None
1567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.fn_to_add_timeout = fn_to_add_timeout
1587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connections = []
1597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.handlers = {}
1607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.delayed_commands = [] # list of tuples in the format (time, function, arguments)
1617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.add_global_handler("ping", _ping_ponger, -42)
1637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def server(self):
1657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Creates and returns a ServerConnection object."""
1667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        c = ServerConnection(self)
1687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connections.append(c)
1697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return c
1707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def process_data(self, sockets):
1727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Called when there is more data to read on connection sockets.
1737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
1757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            sockets -- A list of socket objects.
1777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        See documentation for IRC.__init__.
1797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for s in sockets:
1817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            for c in self.connections:
1827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if s == c._get_socket():
1837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    c.process_data()
1847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def process_timeout(self):
1867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Called when a timeout notification is due.
1877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        See documentation for IRC.__init__.
1897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
1907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        t = time.time()
1917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        while self.delayed_commands:
1927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if t >= self.delayed_commands[0][0]:
1937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.delayed_commands[0][1](*self.delayed_commands[0][2])
1947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                del self.delayed_commands[0]
1957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            else:
1967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                break
1977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
1987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def process_once(self, timeout=0):
1997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Process data from connections once.
2007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
2027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            timeout -- How long the select() call should wait if no
2047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                       data is available.
2057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This method should be called periodically to check and process
2077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        incoming data, if there are any.  If that seems boring, look
2087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        at the process_forever method.
2097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        sockets = map(lambda x: x._get_socket(), self.connections)
2117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        sockets = filter(lambda x: x != None, sockets)
2127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if sockets:
2137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            (i, o, e) = select.select(sockets, [], [], timeout)
2147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.process_data(i)
2157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
2167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            time.sleep(timeout)
2177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.process_timeout()
2187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def process_forever(self, timeout=0.2):
2207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Run an infinite loop, processing data from connections.
2217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This method repeatedly calls process_once.
2237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
2257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            timeout -- Parameter to pass to process_once.
2277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        while 1:
2297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.process_once(timeout)
2307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def disconnect_all(self, message=""):
2327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Disconnects all connections."""
2337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for c in self.connections:
2347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            c.disconnect(message)
2357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def add_global_handler(self, event, handler, priority=0):
2377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Adds a global handler function for a specific event type.
2387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
2407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            event -- Event type (a string).  Check the values of the
2427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            numeric_events dictionary in irclib.py for possible event
2437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            types.
2447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            handler -- Callback function.
2467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            priority -- A number (the lower number, the higher priority).
2487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        The handler function is called whenever the specified event is
2507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        triggered in any of the connections.  See documentation for
2517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        the Event class.
2527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        The handler functions are called in priority order (lowest
2547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        number is highest priority).  If a handler function returns
2557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        \"NO MORE\", no more handlers will be called.
2567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if not event in self.handlers:
2587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.handlers[event] = []
2597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        bisect.insort(self.handlers[event], ((priority, handler)))
2607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def remove_global_handler(self, event, handler):
2627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Removes a global handler function.
2637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
2657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            event -- Event type (a string).
2677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            handler -- Callback function.
2697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns 1 on success, otherwise 0.
2717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if not event in self.handlers:
2737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return 0
2747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for h in self.handlers[event]:
2757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if handler == h[1]:
2767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.handlers[event].remove(h)
2777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return 1
2787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def execute_at(self, at, function, arguments=()):
2807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Execute a function at a specified time.
2817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
2837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            at -- Execute at this time (standard \"time_t\" time).
2857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            function -- Function to call.
2877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            arguments -- Arguments to give the function.
2897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
2907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.execute_delayed(at-time.time(), function, arguments)
2917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def execute_delayed(self, delay, function, arguments=()):
2937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Execute a function after a specified time.
2947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
2967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            delay -- How many seconds to wait.
2987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
2997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            function -- Function to call.
3007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            arguments -- Arguments to give the function.
3027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
3037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
3047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.fn_to_add_timeout:
3057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.fn_to_add_timeout(delay)
3067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def dcc(self, dcctype="chat"):
3087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Creates and returns a DCCConnection object.
3097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
3117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            dcctype -- "chat" for DCC CHAT connections or "raw" for
3137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                       DCC SEND (or other DCC types). If "chat",
3147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                       incoming data will be split in newline-separated
3157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                       chunks. If "raw", incoming data is not touched.
3167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
3177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        c = DCCConnection(self, dcctype)
3187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connections.append(c)
3197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return c
3207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _handle_event(self, connection, event):
3227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
3237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        h = self.handlers
3247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
3257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if handler[1](connection, event) == "NO MORE":
3267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                return
3277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _remove_connection(self, connection):
3297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
3307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connections.remove(connection)
3317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.fn_to_remove_socket:
3327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.fn_to_remove_socket(connection._get_socket())
3337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
3357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass Connection:
3377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Base class for IRC connections.
3387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Must be overridden.
3407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
3417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, irclibobj):
3427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj = irclibobj
3437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_socket():
3457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        raise IRCError, "Not overridden"
3467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    ##############################
3487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    ### Convenience wrappers.
3497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def execute_at(self, at, function, arguments=()):
3517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj.execute_at(at, function, arguments)
3527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def execute_delayed(self, delay, function, arguments=()):
3547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj.execute_delayed(delay, function, arguments)
3557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass ServerConnectionError(IRCError):
3587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pass
3597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass ServerNotConnectedError(ServerConnectionError):
3617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pass
3627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Huh!?  Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
3657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# use \n as message separator!  :P
3667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_linesep_regexp = re.compile("\r?\n")
3677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass ServerConnection(Connection):
3697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """This class represents an IRC server connection.
3707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    ServerConnection objects are instantiated by calling the server
3727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    method on an IRC object.
3737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
3747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, irclibobj):
3767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Connection.__init__(self, irclibobj)
3777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connected = 0  # Not connected yet.
3787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.socket = None
3797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ssl = None
3807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def connect(self, server, port, nickname, password=None, username=None,
3827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
3837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Connect/reconnect to a server.
3847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
3867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            server -- Server name.
3887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            port -- Port number.
3907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            nickname -- The nickname.
3927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            password -- Password (if any).
3947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            username -- The username.
3967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ircname -- The IRC name ("realname").
3987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
3997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            localaddress -- Bind the connection to a specific local IP address.
4007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            localport -- Bind the connection to a specific local port.
4027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ssl -- Enable support for ssl.
4047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ipv6 -- Enable support for ipv6.
4067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This function can be called to reconnect a closed connection.
4087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns the ServerConnection object.
4107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
4117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.connected:
4127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Changing servers")
4137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.previous_buffer = ""
4157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.handlers = {}
4167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.real_server_name = ""
4177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.real_nickname = nickname
4187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.server = server
4197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.port = port
4207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.nickname = nickname
4217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.username = username or nickname
4227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ircname = ircname or nickname
4237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.password = password
4247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.localaddress = localaddress
4257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.localport = localport
4267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.localhost = socket.gethostname()
4277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if ipv6:
4287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
4297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
4307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
4317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
4327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.bind((self.localaddress, self.localport))
4337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.connect((self.server, self.port))
4347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if ssl:
4357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.ssl = socket.ssl(self.socket)
4367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
4377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.close()
4387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket = None
4397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            raise ServerConnectionError, "Couldn't connect to socket: %s" % x
4407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connected = 1
4417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.irclibobj.fn_to_add_socket:
4427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.irclibobj.fn_to_add_socket(self.socket)
4437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Log on...
4457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.password:
4467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.pass_(self.password)
4477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.nick(self.nickname)
4487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.user(self.username, self.ircname)
4497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self
4507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def close(self):
4527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Close the connection.
4537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This method closes the connection permanently; after it has
4557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        been called, the object is unusable.
4567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
4577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.disconnect("Closing object")
4597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj._remove_connection(self)
4607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_socket(self):
4627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
4637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.socket
4647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def get_server_name(self):
4667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get the (real) server name.
4677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This method returns the (real) server name, or, more
4697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        specifically, what the server calls itself.
4707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
4717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.real_server_name:
4737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return self.real_server_name
4747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
4757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return ""
4767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def get_nickname(self):
4787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get the (real) nick name.
4797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This method returns the (real) nickname.  The library keeps
4817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        track of nick changes, so it might not be the nick name that
4827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        was passed to the connect() method.  """
4837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.real_nickname
4857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def process_data(self):
4877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
4887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
4897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
4907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if self.ssl:
4917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                new_data = self.ssl.read(2**14)
4927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            else:
4937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                new_data = self.socket.recv(2**14)
4947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
4957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # The server hung up.
4967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Connection reset by peer")
4977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
4987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if not new_data:
4997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Read nothing: connection must be down.
5007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Connection reset by peer")
5017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
5027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        lines = _linesep_regexp.split(self.previous_buffer + new_data)
5047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Save the last, unfinished line.
5067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.previous_buffer = lines.pop()
5077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for line in lines:
5097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if DEBUG:
5107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                print "FROM SERVER:", line
5117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if not line:
5137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                continue
5147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            prefix = None
5167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            command = None
5177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            arguments = None
5187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self._handle_event(Event("all_raw_messages",
5197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                     self.get_server_name(),
5207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                     None,
5217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                     [line]))
5227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            m = _rfc_1459_command_regexp.match(line)
5247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if m.group("prefix"):
5257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                prefix = m.group("prefix")
5267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if not self.real_server_name:
5277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    self.real_server_name = prefix
5287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if m.group("command"):
5307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                command = m.group("command").lower()
5317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if m.group("argument"):
5337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                a = m.group("argument").split(" :", 1)
5347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                arguments = a[0].split()
5357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if len(a) == 2:
5367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    arguments.append(a[1])
5377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Translate numerics into more readable strings.
5397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if command in numeric_events:
5407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                command = numeric_events[command]
5417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if command == "nick":
5437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if nm_to_n(prefix) == self.real_nickname:
5447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    self.real_nickname = arguments[0]
5457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            elif command == "welcome":
5467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                # Record the nickname in case the client changed nick
5477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                # in a nicknameinuse callback.
5487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.real_nickname = arguments[0]
5497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if command in ["privmsg", "notice"]:
5517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                target, message = arguments[0], arguments[1]
5527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                messages = _ctcp_dequote(message)
5537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if command == "privmsg":
5557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    if is_channel(target):
5567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        command = "pubmsg"
5577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                else:
5587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    if is_channel(target):
5597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        command = "pubnotice"
5607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    else:
5617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        command = "privnotice"
5627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                for m in messages:
5647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    if type(m) is types.TupleType:
5657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        if command in ["privmsg", "pubmsg"]:
5667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                            command = "ctcp"
5677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        else:
5687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                            command = "ctcpreply"
5697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        m = list(m)
5717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        if DEBUG:
5727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                            print "command: %s, source: %s, target: %s, arguments: %s" % (
5737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                command, prefix, target, m)
5747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        self._handle_event(Event(command, prefix, target, m))
5757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        if command == "ctcp" and m[0] == "ACTION":
5767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                            self._handle_event(Event("action", prefix, target, m[1:]))
5777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    else:
5787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        if DEBUG:
5797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                            print "command: %s, source: %s, target: %s, arguments: %s" % (
5807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                command, prefix, target, [m])
5817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        self._handle_event(Event(command, prefix, target, [m]))
5827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            else:
5837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                target = None
5847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if command == "quit":
5867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    arguments = [arguments[0]]
5877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                elif command == "ping":
5887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    target = arguments[0]
5897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                else:
5907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    target = arguments[0]
5917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    arguments = arguments[1:]
5927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if command == "mode":
5947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    if not is_channel(target):
5957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        command = "umode"
5967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
5977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                if DEBUG:
5987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    print "command: %s, source: %s, target: %s, arguments: %s" % (
5997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                        command, prefix, target, arguments)
6007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self._handle_event(Event(command, prefix, target, arguments))
6017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _handle_event(self, event):
6037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
6047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj._handle_event(self, event)
6057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if event.eventtype() in self.handlers:
6067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            for fn in self.handlers[event.eventtype()]:
6077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                fn(self, event)
6087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def is_connected(self):
6107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Return connection status.
6117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns true if connected, otherwise false.
6137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
6147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.connected
6157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def add_global_handler(self, *args):
6177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Add global handler.
6187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        See documentation for IRC.add_global_handler.
6207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
6217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj.add_global_handler(*args)
6227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def remove_global_handler(self, *args):
6247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Remove global handler.
6257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        See documentation for IRC.remove_global_handler.
6277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
6287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj.remove_global_handler(*args)
6297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def action(self, target, action):
6317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a CTCP ACTION command."""
6327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ctcp("ACTION", target, action)
6337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def admin(self, server=""):
6357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an ADMIN command."""
6367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw(" ".join(["ADMIN", server]).strip())
6377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def ctcp(self, ctcptype, target, parameter=""):
6397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a CTCP command."""
6407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        ctcptype = ctcptype.upper()
6417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
6427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def ctcp_reply(self, target, parameter):
6447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a CTCP REPLY command."""
6457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.notice(target, "\001%s\001" % parameter)
6467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def disconnect(self, message=""):
6487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Hang up the connection.
6497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
6517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            message -- Quit message.
6537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
6547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if not self.connected:
6557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
6567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connected = 0
6587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.quit(message)
6607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
6627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.close()
6637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
6647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pass
6657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.socket = None
6667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self._handle_event(Event("disconnect", self.server, "", [message]))
6677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def globops(self, text):
6697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a GLOBOPS command."""
6707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("GLOBOPS :" + text)
6717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def info(self, server=""):
6737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an INFO command."""
6747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw(" ".join(["INFO", server]).strip())
6757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def invite(self, nick, channel):
6777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an INVITE command."""
6787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw(" ".join(["INVITE", nick, channel]).strip())
6797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def ison(self, nicks):
6817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an ISON command.
6827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
6847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            nicks -- List of nicks.
6867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
6877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("ISON " + " ".join(nicks))
6887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def join(self, channel, key=""):
6907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a JOIN command."""
6917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
6927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def kick(self, channel, nick, comment=""):
6947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a KICK command."""
6957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
6967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
6977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def links(self, remote_server="", server_mask=""):
6987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a LINKS command."""
6997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        command = "LINKS"
7007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if remote_server:
7017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            command = command + " " + remote_server
7027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if server_mask:
7037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            command = command + " " + server_mask
7047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw(command)
7057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def list(self, channels=None, server=""):
7077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a LIST command."""
7087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        command = "LIST"
7097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if channels:
7107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            command = command + " " + ",".join(channels)
7117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if server:
7127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            command = command + " " + server
7137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw(command)
7147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def lusers(self, server=""):
7167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a LUSERS command."""
7177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("LUSERS" + (server and (" " + server)))
7187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def mode(self, target, command):
7207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a MODE command."""
7217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("MODE %s %s" % (target, command))
7227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def motd(self, server=""):
7247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an MOTD command."""
7257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("MOTD" + (server and (" " + server)))
7267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def names(self, channels=None):
7287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a NAMES command."""
7297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or ""))
7307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def nick(self, newnick):
7327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a NICK command."""
7337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("NICK " + newnick)
7347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def notice(self, target, text):
7367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a NOTICE command."""
7377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Should limit len(text) here!
7387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("NOTICE %s :%s" % (target, text))
7397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def oper(self, nick, password):
7417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an OPER command."""
7427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("OPER %s %s" % (nick, password))
7437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def part(self, channels, message=""):
7457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a PART command."""
7467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if type(channels) == types.StringType:
7477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.send_raw("PART " + channels + (message and (" " + message)))
7487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
7497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.send_raw("PART " + ",".join(channels) + (message and (" " + message)))
7507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def pass_(self, password):
7527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a PASS command."""
7537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("PASS " + password)
7547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def ping(self, target, target2=""):
7567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a PING command."""
7577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
7587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def pong(self, target, target2=""):
7607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a PONG command."""
7617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
7627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def privmsg(self, target, text):
7647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a PRIVMSG command."""
7657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Should limit len(text) here!
7667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("PRIVMSG %s :%s" % (target, text))
7677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def privmsg_many(self, targets, text):
7697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a PRIVMSG command to multiple targets."""
7707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Should limit len(text) here!
7717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text))
7727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def quit(self, message=""):
7747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a QUIT command."""
7757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Note that many IRC servers don't use your QUIT message
7767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # unless you've been connected for at least 5 minutes!
7777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("QUIT" + (message and (" :" + message)))
7787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def send_raw(self, string):
7807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send raw string to the server.
7817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        The string will be padded with appropriate CR LF.
7837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
7847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.socket is None:
7857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            raise ServerNotConnectedError, "Not connected."
7867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
7877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if self.ssl:
7887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.ssl.write(string + "\r\n")
7897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            else:
7907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.socket.send(string + "\r\n")
7917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if DEBUG:
7927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                print "TO SERVER:", string
7937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
7947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Ouch!
7957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Connection reset by peer.")
7967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
7977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def squit(self, server, comment=""):
7987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send an SQUIT command."""
7997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
8007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def stats(self, statstype, server=""):
8027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a STATS command."""
8037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
8047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def time(self, server=""):
8067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a TIME command."""
8077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("TIME" + (server and (" " + server)))
8087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def topic(self, channel, new_topic=None):
8107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a TOPIC command."""
8117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if new_topic is None:
8127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.send_raw("TOPIC " + channel)
8137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
8147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.send_raw("TOPIC %s :%s" % (channel, new_topic))
8157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def trace(self, target=""):
8177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a TRACE command."""
8187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("TRACE" + (target and (" " + target)))
8197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def user(self, username, realname):
8217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a USER command."""
8227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("USER %s 0 * :%s" % (username, realname))
8237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def userhost(self, nicks):
8257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a USERHOST command."""
8267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("USERHOST " + ",".join(nicks))
8277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def users(self, server=""):
8297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a USERS command."""
8307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("USERS" + (server and (" " + server)))
8317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def version(self, server=""):
8337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a VERSION command."""
8347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("VERSION" + (server and (" " + server)))
8357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def wallops(self, text):
8377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a WALLOPS command."""
8387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("WALLOPS :" + text)
8397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def who(self, target="", op=""):
8417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a WHO command."""
8427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
8437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def whois(self, targets):
8457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a WHOIS command."""
8467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("WHOIS " + ",".join(targets))
8477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def whowas(self, nick, max="", server=""):
8497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send a WHOWAS command."""
8507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.send_raw("WHOWAS %s%s%s" % (nick,
8517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                         max and (" " + max),
8527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                         server and (" " + server)))
8537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass DCCConnectionError(IRCError):
8557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    pass
8567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass DCCConnection(Connection):
8597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """This class represents a DCC connection.
8607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    DCCConnection objects are instantiated by calling the dcc
8627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    method on an IRC object.
8637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
8647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, irclibobj, dcctype):
8657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Connection.__init__(self, irclibobj)
8667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connected = 0
8677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.passive = 0
8687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.dcctype = dcctype
8697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.peeraddress = None
8707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.peerport = None
8717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def connect(self, address, port):
8737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Connect/reconnect to a DCC peer.
8747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
8767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            address -- Host/IP address of the peer.
8777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            port -- The port number to connect to.
8797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns the DCCConnection object.
8817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
8827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.peeraddress = socket.gethostbyname(address)
8837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.peerport = port
8847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.socket = None
8857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.previous_buffer = ""
8867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.handlers = {}
8877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.passive = 0
8897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
8907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.connect((self.peeraddress, self.peerport))
8917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
8927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            raise DCCConnectionError, "Couldn't connect to socket: %s" % x
8937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connected = 1
8947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.irclibobj.fn_to_add_socket:
8957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.irclibobj.fn_to_add_socket(self.socket)
8967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self
8977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
8987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def listen(self):
8997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Wait for a connection/reconnection from a DCC peer.
9007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns the DCCConnection object.
9027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        The local IP address and port are available as
9047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.localaddress and self.localport.  After connection from a
9057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        peer, the peer address and port are available as
9067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.peeraddress and self.peerport.
9077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
9087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.previous_buffer = ""
9097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.handlers = {}
9107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
9117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.passive = 1
9127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
9137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
9147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.localaddress, self.localport = self.socket.getsockname()
9157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.listen(10)
9167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
9177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            raise DCCConnectionError, "Couldn't bind socket: %s" % x
9187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self
9197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def disconnect(self, message=""):
9217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Hang up the connection and close the object.
9227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
9247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            message -- Quit message.
9267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
9277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if not self.connected:
9287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
9297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connected = 0
9317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
9327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.close()
9337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
9347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            pass
9357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.socket = None
9367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj._handle_event(
9377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self,
9387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            Event("dcc_disconnect", self.peeraddress, "", [message]))
9397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.irclibobj._remove_connection(self)
9407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def process_data(self):
9427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
9437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.passive and not self.connected:
9457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            conn, (self.peeraddress, self.peerport) = self.socket.accept()
9467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.close()
9477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket = conn
9487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.connected = 1
9497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if DEBUG:
9507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                print "DCC connection from %s:%d" % (
9517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    self.peeraddress, self.peerport)
9527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.irclibobj._handle_event(
9537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self,
9547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                Event("dcc_connect", self.peeraddress, None, None))
9557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
9567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
9587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            new_data = self.socket.recv(2**14)
9597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
9607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # The server hung up.
9617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Connection reset by peer")
9627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
9637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if not new_data:
9647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Read nothing: connection must be down.
9657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Connection reset by peer")
9667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            return
9677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if self.dcctype == "chat":
9697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # The specification says lines are terminated with LF, but
9707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # it seems safer to handle CR LF terminations too.
9717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            chunks = _linesep_regexp.split(self.previous_buffer + new_data)
9727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Save the last, unfinished line.
9747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.previous_buffer = chunks[-1]
9757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if len(self.previous_buffer) > 2**14:
9767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                # Bad peer! Naughty peer!
9777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.disconnect()
9787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                return
9797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            chunks = chunks[:-1]
9807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
9817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            chunks = [new_data]
9827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        command = "dccmsg"
9847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        prefix = self.peeraddress
9857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        target = None
9867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        for chunk in chunks:
9877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if DEBUG:
9887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                print "FROM PEER:", chunk
9897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            arguments = [chunk]
9907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if DEBUG:
9917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                print "command: %s, source: %s, target: %s, arguments: %s" % (
9927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                    command, prefix, target, arguments)
9937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.irclibobj._handle_event(
9947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self,
9957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                Event(command, prefix, target, arguments))
9967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
9977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _get_socket(self):
9987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
9997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self.socket
10007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def privmsg(self, string):
10027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Send data to DCC peer.
10037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        The string will be padded with appropriate LF if it's a DCC
10057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        CHAT session.
10067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
10077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        try:
10087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.socket.send(string)
10097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if self.dcctype == "chat":
10107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                self.socket.send("\n")
10117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if DEBUG:
10127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                print "TO PEER: %s\n" % string
10137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        except socket.error, x:
10147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Ouch!
10157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self.disconnect("Connection reset by peer.")
10167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass SimpleIRCClient:
10187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """A simple single-server IRC client class.
10197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    This is an example of an object-oriented wrapper of the IRC
10217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    framework.  A real IRC client can be made by subclassing this
10227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    class and adding appropriate methods.
10237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The method on_join will be called when a "join" event is created
10257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (which is done when the server sends a JOIN messsage/command),
10267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    on_privmsg will be called for "privmsg" events, and so on.  The
10277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    handler methods get two arguments: the connection object (same as
10287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    self.connection) and the event object.
10297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Instance attributes that can be used by sub classes:
10317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        ircobj -- The IRC instance.
10337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        connection -- The ServerConnection instance.
10357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        dcc_connections -- A list of DCCConnection instances.
10377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
10387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self):
10397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ircobj = IRC()
10407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connection = self.ircobj.server()
10417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.dcc_connections = []
10427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
10437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
10447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _dispatcher(self, c, e):
10467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """[Internal]"""
10477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        m = "on_" + e.eventtype()
10487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if hasattr(self, m):
10497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            getattr(self, m)(c, e)
10507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _dcc_disconnect(self, c, e):
10527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.dcc_connections.remove(c)
10537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def connect(self, server, port, nickname, password=None, username=None,
10557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
10567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Connect/reconnect to a server.
10577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
10597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            server -- Server name.
10617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            port -- Port number.
10637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            nickname -- The nickname.
10657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            password -- Password (if any).
10677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            username -- The username.
10697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ircname -- The IRC name.
10717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            localaddress -- Bind the connection to a specific local IP address.
10737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            localport -- Bind the connection to a specific local port.
10757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ssl -- Enable support for ssl.
10777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            ipv6 -- Enable support for ipv6.
10797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        This function can be called to reconnect a closed connection.
10817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
10827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.connection.connect(server, port, nickname,
10837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                password, username, ircname,
10847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                localaddress, localport, ssl, ipv6)
10857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def dcc_connect(self, address, port, dcctype="chat"):
10877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Connect to a DCC peer.
10887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
10907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            address -- IP address of the peer.
10927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            port -- Port to connect to.
10947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
10957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns a DCCConnection instance.
10967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
10977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        dcc = self.ircobj.dcc(dcctype)
10987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.dcc_connections.append(dcc)
10997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        dcc.connect(address, port)
11007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return dcc
11017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def dcc_listen(self, dcctype="chat"):
11037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Listen for connections from a DCC peer.
11047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Returns a DCCConnection instance.
11067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
11077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        dcc = self.ircobj.dcc(dcctype)
11087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.dcc_connections.append(dcc)
11097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        dcc.listen()
11107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return dcc
11117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def start(self):
11137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Start the IRC client."""
11147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self.ircobj.process_forever()
11157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochclass Event:
11187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Class representing an IRC event."""
11197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def __init__(self, eventtype, source, target, arguments=None):
11207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Constructor of Event objects.
11217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        Arguments:
11237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            eventtype -- A string describing the event.
11257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            source -- The originator of the event (a nick mask or a server).
11277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            target -- The target of the event (a nick or a channel).
11297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            arguments -- Any event specific arguments.
11317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """
11327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self._eventtype = eventtype
11337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self._source = source
11347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        self._target = target
11357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if arguments:
11367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self._arguments = arguments
11377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
11387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            self._arguments = []
11397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def eventtype(self):
11417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get the event type."""
11427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self._eventtype
11437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def source(self):
11457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get the event source."""
11467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self._source
11477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def target(self):
11497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get the event target."""
11507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self._target
11517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def arguments(self):
11537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        """Get the event arguments."""
11547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return self._arguments
11557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_LOW_LEVEL_QUOTE = "\020"
11577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_CTCP_LEVEL_QUOTE = "\134"
11587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_CTCP_DELIMITER = "\001"
11597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_low_level_mapping = {
11617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "0": "\000",
11627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "n": "\n",
11637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "r": "\r",
11647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
11657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch}
11667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
11687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef mask_matches(nick, mask):
11707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Check if a nick matches a mask.
11717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Returns true if the nick matches, otherwise false.
11737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
11747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    nick = irc_lower(nick)
11757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    mask = irc_lower(mask)
11767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    mask = mask.replace("\\", "\\\\")
11777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    for ch in ".$|[](){}+":
11787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        mask = mask.replace(ch, "\\" + ch)
11797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    mask = mask.replace("?", ".")
11807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    mask = mask.replace("*", ".*")
11817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    r = re.compile(mask, re.IGNORECASE)
11827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return r.match(nick)
11837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_special = "-[]\\`^{}"
11857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochnick_characters = string.ascii_letters + string.digits + _special
11867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
11877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                                          string.ascii_lowercase + "{}|~")
11887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef irc_lower(s):
11907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Returns a lowercased string.
11917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The definition of lowercased comes from the IRC specification (RFC
11937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    1459).
11947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
11957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return s.translate(_ircstring_translation)
11967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
11977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef _ctcp_dequote(message):
11987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """[Internal] Dequote a message according to CTCP specifications.
11997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The function returns a list where each element can be either a
12017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    string (normal message) or a tuple of one or two strings (tagged
12027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    messages).  If a tuple has only one element (ie is a singleton),
12037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    that element is the tag; otherwise the tuple has two elements: the
12047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    tag and the data.
12057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Arguments:
12077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        message -- The message to be decoded.
12097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
12107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    def _low_level_replace(match_obj):
12127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        ch = match_obj.group(1)
12137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # If low_level_mapping doesn't have the character as key, we
12157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # should just return the character.
12167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return _low_level_mapping.get(ch, ch)
12177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    if _LOW_LEVEL_QUOTE in message:
12197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Yup, there was a quote.  Release the dequoter, man!
12207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        message = _low_level_regexp.sub(_low_level_replace, message)
12217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    if _CTCP_DELIMITER not in message:
12237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return [message]
12247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    else:
12257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # Split it into parts.  (Does any IRC client actually *use*
12267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        # CTCP stacking like this?)
12277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        chunks = message.split(_CTCP_DELIMITER)
12287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        messages = []
12307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        i = 0
12317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        while i < len(chunks)-1:
12327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Add message if it's non-empty.
12337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if len(chunks[i]) > 0:
12347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                messages.append(chunks[i])
12357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if i < len(chunks)-2:
12377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                # Aye!  CTCP tagged data ahead!
12387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                messages.append(tuple(chunks[i+1].split(" ", 1)))
12397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            i = i + 2
12417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if len(chunks) % 2 == 0:
12437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # Hey, a lonely _CTCP_DELIMITER at the end!  This means
12447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # that the last chunk, including the delimiter, is a
12457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # normal message!  (This is according to the CTCP
12467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            # specification.)
12477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            messages.append(_CTCP_DELIMITER + chunks[-1])
12487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return messages
12507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef is_channel(string):
12527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Check if a string is a channel name.
12537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Returns true if the argument is a channel name, otherwise false.
12557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
12567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return string and string[0] in "#&+!"
12577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef ip_numstr_to_quad(num):
12597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Convert an IP number as an integer given in ASCII
12607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    representation (e.g. '3232235521') to an IP address string
12617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (e.g. '192.168.0.1')."""
12627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    n = long(num)
12637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
12647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                           n >> 8 & 0xFF, n & 0xFF]))
12657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return ".".join(p)
12667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef ip_quad_to_numstr(quad):
12687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Convert an IP address string (e.g. '192.168.0.1') to an IP
12697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    number as an integer given in ASCII representation
12707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (e.g. '3232235521')."""
12717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    p = map(long, quad.split("."))
12727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
12737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    if s[-1] == "L":
12747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        s = s[:-1]
12757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return s
12767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef nm_to_n(s):
12787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Get the nick part of a nickmask.
12797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (The source of an Event is a nickmask.)
12817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
12827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return s.split("!")[0]
12837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef nm_to_uh(s):
12857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Get the userhost part of a nickmask.
12867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (The source of an Event is a nickmask.)
12887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
12897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return s.split("!")[1]
12907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef nm_to_h(s):
12927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Get the host part of a nickmask.
12937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (The source of an Event is a nickmask.)
12957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
12967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return s.split("@")[1]
12977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
12987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef nm_to_u(s):
12997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Get the user part of a nickmask.
13007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    (The source of an Event is a nickmask.)
13027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
13037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    s = s.split("!")[1]
13047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return s.split("@")[0]
13057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef parse_nick_modes(mode_string):
13077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Parse a nick mode string.
13087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The function returns a list of lists with three members: sign,
13107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    mode and argument.  The sign is \"+\" or \"-\".  The argument is
13117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    always None.
13127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Example:
13147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    >>> irclib.parse_nick_modes(\"+ab-c\")
13167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
13177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
13187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return _parse_modes(mode_string, "")
13207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef parse_channel_modes(mode_string):
13227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """Parse a channel mode string.
13237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    The function returns a list of lists with three members: sign,
13257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    mode and argument.  The sign is \"+\" or \"-\".  The argument is
13267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
13277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    Example:
13297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    >>> irclib.parse_channel_modes(\"+ab-c foo\")
13317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
13327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """
13337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return _parse_modes(mode_string, "bklvo")
13357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef _parse_modes(mode_string, unary_modes=""):
13377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """[Internal]"""
13387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    modes = []
13397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    arg_count = 0
13407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    # State variable.
13427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    sign = ""
13437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    a = mode_string.split()
13457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    if len(a) == 0:
13467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return []
13477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    else:
13487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        mode_part, args = a[0], a[1:]
13497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    if mode_part[0] not in "+-":
13517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        return []
13527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    for ch in mode_part:
13537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        if ch in "+-":
13547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            sign = ch
13557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        elif ch == " ":
13567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            collecting_arguments = 1
13577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        elif ch in unary_modes:
13587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            if len(args) >= arg_count + 1:
13597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                modes.append([sign, ch, args[arg_count]])
13607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                arg_count = arg_count + 1
13617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            else:
13627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch                modes.append([sign, ch, None])
13637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch        else:
13647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch            modes.append([sign, ch, None])
13657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    return modes
13667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochdef _ping_ponger(connection, event):
13687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    """[Internal]"""
13697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    connection.pong(event.target())
13707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
13717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
13727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochnumeric_events = {
13737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "001": "welcome",
13747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "002": "yourhost",
13757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "003": "created",
13767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "004": "myinfo",
13777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "005": "featurelist",  # XXX
13787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "200": "tracelink",
13797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "201": "traceconnecting",
13807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "202": "tracehandshake",
13817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "203": "traceunknown",
13827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "204": "traceoperator",
13837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "205": "traceuser",
13847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "206": "traceserver",
13857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "207": "traceservice",
13867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "208": "tracenewtype",
13877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "209": "traceclass",
13887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "210": "tracereconnect",
13897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "211": "statslinkinfo",
13907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "212": "statscommands",
13917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "213": "statscline",
13927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "214": "statsnline",
13937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "215": "statsiline",
13947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "216": "statskline",
13957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "217": "statsqline",
13967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "218": "statsyline",
13977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "219": "endofstats",
13987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "221": "umodeis",
13997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "231": "serviceinfo",
14007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "232": "endofservices",
14017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "233": "service",
14027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "234": "servlist",
14037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "235": "servlistend",
14047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "241": "statslline",
14057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "242": "statsuptime",
14067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "243": "statsoline",
14077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "244": "statshline",
14087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "250": "luserconns",
14097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "251": "luserclient",
14107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "252": "luserop",
14117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "253": "luserunknown",
14127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "254": "luserchannels",
14137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "255": "luserme",
14147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "256": "adminme",
14157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "257": "adminloc1",
14167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "258": "adminloc2",
14177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "259": "adminemail",
14187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "261": "tracelog",
14197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "262": "endoftrace",
14207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "263": "tryagain",
14217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "265": "n_local",
14227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "266": "n_global",
14237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "300": "none",
14247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "301": "away",
14257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "302": "userhost",
14267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "303": "ison",
14277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "305": "unaway",
14287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "306": "nowaway",
14297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "311": "whoisuser",
14307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "312": "whoisserver",
14317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "313": "whoisoperator",
14327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "314": "whowasuser",
14337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "315": "endofwho",
14347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "316": "whoischanop",
14357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "317": "whoisidle",
14367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "318": "endofwhois",
14377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "319": "whoischannels",
14387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "321": "liststart",
14397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "322": "list",
14407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "323": "listend",
14417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "324": "channelmodeis",
14427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "329": "channelcreate",
14437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "331": "notopic",
14447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "332": "currenttopic",
14457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "333": "topicinfo",
14467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "341": "inviting",
14477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "342": "summoning",
14487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "346": "invitelist",
14497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "347": "endofinvitelist",
14507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "348": "exceptlist",
14517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "349": "endofexceptlist",
14527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "351": "version",
14537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "352": "whoreply",
14547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "353": "namreply",
14557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "361": "killdone",
14567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "362": "closing",
14577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "363": "closeend",
14587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "364": "links",
14597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "365": "endoflinks",
14607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "366": "endofnames",
14617757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "367": "banlist",
14627757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "368": "endofbanlist",
14637757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "369": "endofwhowas",
14647757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "371": "info",
14657757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "372": "motd",
14667757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "373": "infostart",
14677757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "374": "endofinfo",
14687757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "375": "motdstart",
14697757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "376": "endofmotd",
14707757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "377": "motd2",        # 1997-10-16 -- tkil
14717757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "381": "youreoper",
14727757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "382": "rehashing",
14737757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "384": "myportis",
14747757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "391": "time",
14757757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "392": "usersstart",
14767757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "393": "users",
14777757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "394": "endofusers",
14787757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "395": "nousers",
14797757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "401": "nosuchnick",
14807757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "402": "nosuchserver",
14817757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "403": "nosuchchannel",
14827757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "404": "cannotsendtochan",
14837757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "405": "toomanychannels",
14847757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "406": "wasnosuchnick",
14857757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "407": "toomanytargets",
14867757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "409": "noorigin",
14877757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "411": "norecipient",
14887757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "412": "notexttosend",
14897757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "413": "notoplevel",
14907757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "414": "wildtoplevel",
14917757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "421": "unknowncommand",
14927757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "422": "nomotd",
14937757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "423": "noadmininfo",
14947757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "424": "fileerror",
14957757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "431": "nonicknamegiven",
14967757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
14977757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "433": "nicknameinuse",
14987757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "436": "nickcollision",
14997757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "437": "unavailresource",  # "Nick temporally unavailable"
15007757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "441": "usernotinchannel",
15017757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "442": "notonchannel",
15027757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "443": "useronchannel",
15037757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "444": "nologin",
15047757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "445": "summondisabled",
15057757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "446": "usersdisabled",
15067757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "451": "notregistered",
15077757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "461": "needmoreparams",
15087757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "462": "alreadyregistered",
15097757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "463": "nopermforhost",
15107757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "464": "passwdmismatch",
15117757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "465": "yourebannedcreep", # I love this one...
15127757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "466": "youwillbebanned",
15137757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "467": "keyset",
15147757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "471": "channelisfull",
15157757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "472": "unknownmode",
15167757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "473": "inviteonlychan",
15177757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "474": "bannedfromchan",
15187757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "475": "badchannelkey",
15197757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "476": "badchanmask",
15207757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "477": "nochanmodes",  # "Channel doesn't support modes"
15217757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "478": "banlistfull",
15227757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "481": "noprivileges",
15237757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "482": "chanoprivsneeded",
15247757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "483": "cantkillserver",
15257757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "484": "restricted",   # Connection is restricted
15267757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "485": "uniqopprivsneeded",
15277757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "491": "nooperhost",
15287757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "492": "noservicehost",
15297757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "501": "umodeunknownflag",
15307757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "502": "usersdontmatch",
15317757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch}
15327757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
15337757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochgenerated_events = [
15347757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    # Generated events
15357757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "dcc_connect",
15367757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "dcc_disconnect",
15377757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "dccmsg",
15387757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "disconnect",
15397757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "ctcp",
15407757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "ctcpreply",
15417757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch]
15427757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
15437757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochprotocol_events = [
15447757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    # IRC protocol events
15457757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "error",
15467757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "join",
15477757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "kick",
15487757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "mode",
15497757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "part",
15507757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "ping",
15517757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "privmsg",
15527757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "privnotice",
15537757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "pubmsg",
15547757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "pubnotice",
15557757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "quit",
15567757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "invite",
15577757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch    "pong",
15587757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch]
15597757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdoch
15607757ec2eadfa2dd8ac2aeed0a4399e9b07ec38cbBen Murdochall_events = generated_events + protocol_events + numeric_events.values()
1561