Home | History | Annotate | Download | only in irc
      1 # Copyright (C) 1999--2002  Joel Rosdahl
      2 #
      3 # This library is free software; you can redistribute it and/or
      4 # modify it under the terms of the GNU Lesser General Public
      5 # License as published by the Free Software Foundation; either
      6 # version 2.1 of the License, or (at your option) any later version.
      7 #
      8 # This library is distributed in the hope that it will be useful,
      9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
     10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     11 # Lesser General Public License for more details.
     12 #
     13 # You should have received a copy of the GNU Lesser General Public
     14 # License along with this library; if not, write to the Free Software
     15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
     16 #
     17 # keltus <keltus (at] users.sourceforge.net>
     18 #
     19 # $Id: irclib.py,v 1.47 2008/09/25 22:00:59 keltus Exp $
     20 
     21 """irclib -- Internet Relay Chat (IRC) protocol client library.
     22 
     23 This library is intended to encapsulate the IRC protocol at a quite
     24 low level.  It provides an event-driven IRC client framework.  It has
     25 a fairly thorough support for the basic IRC protocol, CTCP, DCC chat,
     26 but DCC file transfers is not yet supported.
     27 
     28 In order to understand how to make an IRC client, I'm afraid you more
     29 or less must understand the IRC specifications.  They are available
     30 here: [IRC specifications].
     31 
     32 The main features of the IRC client framework are:
     33 
     34   * Abstraction of the IRC protocol.
     35   * Handles multiple simultaneous IRC server connections.
     36   * Handles server PONGing transparently.
     37   * Messages to the IRC server are done by calling methods on an IRC
     38     connection object.
     39   * Messages from an IRC server triggers events, which can be caught
     40     by event handlers.
     41   * Reading from and writing to IRC server sockets are normally done
     42     by an internal select() loop, but the select()ing may be done by
     43     an external main loop.
     44   * Functions can be registered to execute at specified times by the
     45     event-loop.
     46   * Decodes CTCP tagging correctly (hopefully); I haven't seen any
     47     other IRC client implementation that handles the CTCP
     48     specification subtilties.
     49   * A kind of simple, single-server, object-oriented IRC client class
     50     that dispatches events to instance methods is included.
     51 
     52 Current limitations:
     53 
     54   * The IRC protocol shines through the abstraction a bit too much.
     55   * Data is not written asynchronously to the server, i.e. the write()
     56     may block if the TCP buffers are stuffed.
     57   * There are no support for DCC file transfers.
     58   * The author haven't even read RFC 2810, 2811, 2812 and 2813.
     59   * Like most projects, documentation is lacking...
     60 
     61 .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
     62 """
     63 
     64 import bisect
     65 import re
     66 import select
     67 import socket
     68 import string
     69 import sys
     70 import time
     71 import types
     72 
     73 VERSION = 0, 4, 8
     74 DEBUG = 0
     75 
     76 # TODO
     77 # ----
     78 # (maybe) thread safety
     79 # (maybe) color parser convenience functions
     80 # documentation (including all event types)
     81 # (maybe) add awareness of different types of ircds
     82 # send data asynchronously to the server (and DCC connections)
     83 # (maybe) automatically close unused, passive DCC connections after a while
     84 
     85 # NOTES
     86 # -----
     87 # connection.quit() only sends QUIT to the server.
     88 # ERROR from the server triggers the error event and the disconnect event.
     89 # dropping of the connection triggers the disconnect event.
     90 
     91 class IRCError(Exception):
     92     """Represents an IRC exception."""
     93     pass
     94 
     95 
     96 class IRC:
     97     """Class that handles one or several IRC server connections.
     98 
     99     When an IRC object has been instantiated, it can be used to create
    100     Connection objects that represent the IRC connections.  The
    101     responsibility of the IRC object is to provide an event-driven
    102     framework for the connections and to keep the connections alive.
    103     It runs a select loop to poll each connection's TCP socket and
    104     hands over the sockets with incoming data for processing by the
    105     corresponding connection.
    106 
    107     The methods of most interest for an IRC client writer are server,
    108     add_global_handler, remove_global_handler, execute_at,
    109     execute_delayed, process_once and process_forever.
    110 
    111     Here is an example:
    112 
    113         irc = irclib.IRC()
    114         server = irc.server()
    115         server.connect(\"irc.some.where\", 6667, \"my_nickname\")
    116         server.privmsg(\"a_nickname\", \"Hi there!\")
    117         irc.process_forever()
    118 
    119     This will connect to the IRC server irc.some.where on port 6667
    120     using the nickname my_nickname and send the message \"Hi there!\"
    121     to the nickname a_nickname.
    122     """
    123 
    124     def __init__(self, fn_to_add_socket=None,
    125                  fn_to_remove_socket=None,
    126                  fn_to_add_timeout=None):
    127         """Constructor for IRC objects.
    128 
    129         Optional arguments are fn_to_add_socket, fn_to_remove_socket
    130         and fn_to_add_timeout.  The first two specify functions that
    131         will be called with a socket object as argument when the IRC
    132         object wants to be notified (or stop being notified) of data
    133         coming on a new socket.  When new data arrives, the method
    134         process_data should be called.  Similarly, fn_to_add_timeout
    135         is called with a number of seconds (a floating point number)
    136         as first argument when the IRC object wants to receive a
    137         notification (by calling the process_timeout method).  So, if
    138         e.g. the argument is 42.17, the object wants the
    139         process_timeout method to be called after 42 seconds and 170
    140         milliseconds.
    141 
    142         The three arguments mainly exist to be able to use an external
    143         main loop (for example Tkinter's or PyGTK's main app loop)
    144         instead of calling the process_forever method.
    145 
    146         An alternative is to just call ServerConnection.process_once()
    147         once in a while.
    148         """
    149 
    150         if fn_to_add_socket and fn_to_remove_socket:
    151             self.fn_to_add_socket = fn_to_add_socket
    152             self.fn_to_remove_socket = fn_to_remove_socket
    153         else:
    154             self.fn_to_add_socket = None
    155             self.fn_to_remove_socket = None
    156 
    157         self.fn_to_add_timeout = fn_to_add_timeout
    158         self.connections = []
    159         self.handlers = {}
    160         self.delayed_commands = [] # list of tuples in the format (time, function, arguments)
    161 
    162         self.add_global_handler("ping", _ping_ponger, -42)
    163 
    164     def server(self):
    165         """Creates and returns a ServerConnection object."""
    166 
    167         c = ServerConnection(self)
    168         self.connections.append(c)
    169         return c
    170 
    171     def process_data(self, sockets):
    172         """Called when there is more data to read on connection sockets.
    173 
    174         Arguments:
    175 
    176             sockets -- A list of socket objects.
    177 
    178         See documentation for IRC.__init__.
    179         """
    180         for s in sockets:
    181             for c in self.connections:
    182                 if s == c._get_socket():
    183                     c.process_data()
    184 
    185     def process_timeout(self):
    186         """Called when a timeout notification is due.
    187 
    188         See documentation for IRC.__init__.
    189         """
    190         t = time.time()
    191         while self.delayed_commands:
    192             if t >= self.delayed_commands[0][0]:
    193                 self.delayed_commands[0][1](*self.delayed_commands[0][2])
    194                 del self.delayed_commands[0]
    195             else:
    196                 break
    197 
    198     def process_once(self, timeout=0):
    199         """Process data from connections once.
    200 
    201         Arguments:
    202 
    203             timeout -- How long the select() call should wait if no
    204                        data is available.
    205 
    206         This method should be called periodically to check and process
    207         incoming data, if there are any.  If that seems boring, look
    208         at the process_forever method.
    209         """
    210         sockets = map(lambda x: x._get_socket(), self.connections)
    211         sockets = filter(lambda x: x != None, sockets)
    212         if sockets:
    213             (i, o, e) = select.select(sockets, [], [], timeout)
    214             self.process_data(i)
    215         else:
    216             time.sleep(timeout)
    217         self.process_timeout()
    218 
    219     def process_forever(self, timeout=0.2):
    220         """Run an infinite loop, processing data from connections.
    221 
    222         This method repeatedly calls process_once.
    223 
    224         Arguments:
    225 
    226             timeout -- Parameter to pass to process_once.
    227         """
    228         while 1:
    229             self.process_once(timeout)
    230 
    231     def disconnect_all(self, message=""):
    232         """Disconnects all connections."""
    233         for c in self.connections:
    234             c.disconnect(message)
    235 
    236     def add_global_handler(self, event, handler, priority=0):
    237         """Adds a global handler function for a specific event type.
    238 
    239         Arguments:
    240 
    241             event -- Event type (a string).  Check the values of the
    242             numeric_events dictionary in irclib.py for possible event
    243             types.
    244 
    245             handler -- Callback function.
    246 
    247             priority -- A number (the lower number, the higher priority).
    248 
    249         The handler function is called whenever the specified event is
    250         triggered in any of the connections.  See documentation for
    251         the Event class.
    252 
    253         The handler functions are called in priority order (lowest
    254         number is highest priority).  If a handler function returns
    255         \"NO MORE\", no more handlers will be called.
    256         """
    257         if not event in self.handlers:
    258             self.handlers[event] = []
    259         bisect.insort(self.handlers[event], ((priority, handler)))
    260 
    261     def remove_global_handler(self, event, handler):
    262         """Removes a global handler function.
    263 
    264         Arguments:
    265 
    266             event -- Event type (a string).
    267 
    268             handler -- Callback function.
    269 
    270         Returns 1 on success, otherwise 0.
    271         """
    272         if not event in self.handlers:
    273             return 0
    274         for h in self.handlers[event]:
    275             if handler == h[1]:
    276                 self.handlers[event].remove(h)
    277         return 1
    278 
    279     def execute_at(self, at, function, arguments=()):
    280         """Execute a function at a specified time.
    281 
    282         Arguments:
    283 
    284             at -- Execute at this time (standard \"time_t\" time).
    285 
    286             function -- Function to call.
    287 
    288             arguments -- Arguments to give the function.
    289         """
    290         self.execute_delayed(at-time.time(), function, arguments)
    291 
    292     def execute_delayed(self, delay, function, arguments=()):
    293         """Execute a function after a specified time.
    294 
    295         Arguments:
    296 
    297             delay -- How many seconds to wait.
    298 
    299             function -- Function to call.
    300 
    301             arguments -- Arguments to give the function.
    302         """
    303         bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
    304         if self.fn_to_add_timeout:
    305             self.fn_to_add_timeout(delay)
    306 
    307     def dcc(self, dcctype="chat"):
    308         """Creates and returns a DCCConnection object.
    309 
    310         Arguments:
    311 
    312             dcctype -- "chat" for DCC CHAT connections or "raw" for
    313                        DCC SEND (or other DCC types). If "chat",
    314                        incoming data will be split in newline-separated
    315                        chunks. If "raw", incoming data is not touched.
    316         """
    317         c = DCCConnection(self, dcctype)
    318         self.connections.append(c)
    319         return c
    320 
    321     def _handle_event(self, connection, event):
    322         """[Internal]"""
    323         h = self.handlers
    324         for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
    325             if handler[1](connection, event) == "NO MORE":
    326                 return
    327 
    328     def _remove_connection(self, connection):
    329         """[Internal]"""
    330         self.connections.remove(connection)
    331         if self.fn_to_remove_socket:
    332             self.fn_to_remove_socket(connection._get_socket())
    333 
    334 _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
    335 
    336 class Connection:
    337     """Base class for IRC connections.
    338 
    339     Must be overridden.
    340     """
    341     def __init__(self, irclibobj):
    342         self.irclibobj = irclibobj
    343 
    344     def _get_socket():
    345         raise IRCError, "Not overridden"
    346 
    347     ##############################
    348     ### Convenience wrappers.
    349 
    350     def execute_at(self, at, function, arguments=()):
    351         self.irclibobj.execute_at(at, function, arguments)
    352 
    353     def execute_delayed(self, delay, function, arguments=()):
    354         self.irclibobj.execute_delayed(delay, function, arguments)
    355 
    356 
    357 class ServerConnectionError(IRCError):
    358     pass
    359 
    360 class ServerNotConnectedError(ServerConnectionError):
    361     pass
    362 
    363 
    364 # Huh!?  Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
    365 # use \n as message separator!  :P
    366 _linesep_regexp = re.compile("\r?\n")
    367 
    368 class ServerConnection(Connection):
    369     """This class represents an IRC server connection.
    370 
    371     ServerConnection objects are instantiated by calling the server
    372     method on an IRC object.
    373     """
    374 
    375     def __init__(self, irclibobj):
    376         Connection.__init__(self, irclibobj)
    377         self.connected = 0  # Not connected yet.
    378         self.socket = None
    379         self.ssl = None
    380 
    381     def connect(self, server, port, nickname, password=None, username=None,
    382                 ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
    383         """Connect/reconnect to a server.
    384 
    385         Arguments:
    386 
    387             server -- Server name.
    388 
    389             port -- Port number.
    390 
    391             nickname -- The nickname.
    392 
    393             password -- Password (if any).
    394 
    395             username -- The username.
    396 
    397             ircname -- The IRC name ("realname").
    398 
    399             localaddress -- Bind the connection to a specific local IP address.
    400 
    401             localport -- Bind the connection to a specific local port.
    402 
    403             ssl -- Enable support for ssl.
    404 
    405             ipv6 -- Enable support for ipv6.
    406 
    407         This function can be called to reconnect a closed connection.
    408 
    409         Returns the ServerConnection object.
    410         """
    411         if self.connected:
    412             self.disconnect("Changing servers")
    413 
    414         self.previous_buffer = ""
    415         self.handlers = {}
    416         self.real_server_name = ""
    417         self.real_nickname = nickname
    418         self.server = server
    419         self.port = port
    420         self.nickname = nickname
    421         self.username = username or nickname
    422         self.ircname = ircname or nickname
    423         self.password = password
    424         self.localaddress = localaddress
    425         self.localport = localport
    426         self.localhost = socket.gethostname()
    427         if ipv6:
    428             self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
    429         else:
    430             self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    431         try:
    432             self.socket.bind((self.localaddress, self.localport))
    433             self.socket.connect((self.server, self.port))
    434             if ssl:
    435                 self.ssl = socket.ssl(self.socket)
    436         except socket.error, x:
    437             self.socket.close()
    438             self.socket = None
    439             raise ServerConnectionError, "Couldn't connect to socket: %s" % x
    440         self.connected = 1
    441         if self.irclibobj.fn_to_add_socket:
    442             self.irclibobj.fn_to_add_socket(self.socket)
    443 
    444         # Log on...
    445         if self.password:
    446             self.pass_(self.password)
    447         self.nick(self.nickname)
    448         self.user(self.username, self.ircname)
    449         return self
    450 
    451     def close(self):
    452         """Close the connection.
    453 
    454         This method closes the connection permanently; after it has
    455         been called, the object is unusable.
    456         """
    457 
    458         self.disconnect("Closing object")
    459         self.irclibobj._remove_connection(self)
    460 
    461     def _get_socket(self):
    462         """[Internal]"""
    463         return self.socket
    464 
    465     def get_server_name(self):
    466         """Get the (real) server name.
    467 
    468         This method returns the (real) server name, or, more
    469         specifically, what the server calls itself.
    470         """
    471 
    472         if self.real_server_name:
    473             return self.real_server_name
    474         else:
    475             return ""
    476 
    477     def get_nickname(self):
    478         """Get the (real) nick name.
    479 
    480         This method returns the (real) nickname.  The library keeps
    481         track of nick changes, so it might not be the nick name that
    482         was passed to the connect() method.  """
    483 
    484         return self.real_nickname
    485 
    486     def process_data(self):
    487         """[Internal]"""
    488 
    489         try:
    490             if self.ssl:
    491                 new_data = self.ssl.read(2**14)
    492             else:
    493                 new_data = self.socket.recv(2**14)
    494         except socket.error, x:
    495             # The server hung up.
    496             self.disconnect("Connection reset by peer")
    497             return
    498         if not new_data:
    499             # Read nothing: connection must be down.
    500             self.disconnect("Connection reset by peer")
    501             return
    502 
    503         lines = _linesep_regexp.split(self.previous_buffer + new_data)
    504 
    505         # Save the last, unfinished line.
    506         self.previous_buffer = lines.pop()
    507 
    508         for line in lines:
    509             if DEBUG:
    510                 print "FROM SERVER:", line
    511 
    512             if not line:
    513                 continue
    514 
    515             prefix = None
    516             command = None
    517             arguments = None
    518             self._handle_event(Event("all_raw_messages",
    519                                      self.get_server_name(),
    520                                      None,
    521                                      [line]))
    522 
    523             m = _rfc_1459_command_regexp.match(line)
    524             if m.group("prefix"):
    525                 prefix = m.group("prefix")
    526                 if not self.real_server_name:
    527                     self.real_server_name = prefix
    528 
    529             if m.group("command"):
    530                 command = m.group("command").lower()
    531 
    532             if m.group("argument"):
    533                 a = m.group("argument").split(" :", 1)
    534                 arguments = a[0].split()
    535                 if len(a) == 2:
    536                     arguments.append(a[1])
    537 
    538             # Translate numerics into more readable strings.
    539             if command in numeric_events:
    540                 command = numeric_events[command]
    541 
    542             if command == "nick":
    543                 if nm_to_n(prefix) == self.real_nickname:
    544                     self.real_nickname = arguments[0]
    545             elif command == "welcome":
    546                 # Record the nickname in case the client changed nick
    547                 # in a nicknameinuse callback.
    548                 self.real_nickname = arguments[0]
    549 
    550             if command in ["privmsg", "notice"]:
    551                 target, message = arguments[0], arguments[1]
    552                 messages = _ctcp_dequote(message)
    553 
    554                 if command == "privmsg":
    555                     if is_channel(target):
    556                         command = "pubmsg"
    557                 else:
    558                     if is_channel(target):
    559                         command = "pubnotice"
    560                     else:
    561                         command = "privnotice"
    562 
    563                 for m in messages:
    564                     if type(m) is types.TupleType:
    565                         if command in ["privmsg", "pubmsg"]:
    566                             command = "ctcp"
    567                         else:
    568                             command = "ctcpreply"
    569 
    570                         m = list(m)
    571                         if DEBUG:
    572                             print "command: %s, source: %s, target: %s, arguments: %s" % (
    573                                 command, prefix, target, m)
    574                         self._handle_event(Event(command, prefix, target, m))
    575                         if command == "ctcp" and m[0] == "ACTION":
    576                             self._handle_event(Event("action", prefix, target, m[1:]))
    577                     else:
    578                         if DEBUG:
    579                             print "command: %s, source: %s, target: %s, arguments: %s" % (
    580                                 command, prefix, target, [m])
    581                         self._handle_event(Event(command, prefix, target, [m]))
    582             else:
    583                 target = None
    584 
    585                 if command == "quit":
    586                     arguments = [arguments[0]]
    587                 elif command == "ping":
    588                     target = arguments[0]
    589                 else:
    590                     target = arguments[0]
    591                     arguments = arguments[1:]
    592 
    593                 if command == "mode":
    594                     if not is_channel(target):
    595                         command = "umode"
    596 
    597                 if DEBUG:
    598                     print "command: %s, source: %s, target: %s, arguments: %s" % (
    599                         command, prefix, target, arguments)
    600                 self._handle_event(Event(command, prefix, target, arguments))
    601 
    602     def _handle_event(self, event):
    603         """[Internal]"""
    604         self.irclibobj._handle_event(self, event)
    605         if event.eventtype() in self.handlers:
    606             for fn in self.handlers[event.eventtype()]:
    607                 fn(self, event)
    608 
    609     def is_connected(self):
    610         """Return connection status.
    611 
    612         Returns true if connected, otherwise false.
    613         """
    614         return self.connected
    615 
    616     def add_global_handler(self, *args):
    617         """Add global handler.
    618 
    619         See documentation for IRC.add_global_handler.
    620         """
    621         self.irclibobj.add_global_handler(*args)
    622 
    623     def remove_global_handler(self, *args):
    624         """Remove global handler.
    625 
    626         See documentation for IRC.remove_global_handler.
    627         """
    628         self.irclibobj.remove_global_handler(*args)
    629 
    630     def action(self, target, action):
    631         """Send a CTCP ACTION command."""
    632         self.ctcp("ACTION", target, action)
    633 
    634     def admin(self, server=""):
    635         """Send an ADMIN command."""
    636         self.send_raw(" ".join(["ADMIN", server]).strip())
    637 
    638     def ctcp(self, ctcptype, target, parameter=""):
    639         """Send a CTCP command."""
    640         ctcptype = ctcptype.upper()
    641         self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
    642 
    643     def ctcp_reply(self, target, parameter):
    644         """Send a CTCP REPLY command."""
    645         self.notice(target, "\001%s\001" % parameter)
    646 
    647     def disconnect(self, message=""):
    648         """Hang up the connection.
    649 
    650         Arguments:
    651 
    652             message -- Quit message.
    653         """
    654         if not self.connected:
    655             return
    656 
    657         self.connected = 0
    658 
    659         self.quit(message)
    660 
    661         try:
    662             self.socket.close()
    663         except socket.error, x:
    664             pass
    665         self.socket = None
    666         self._handle_event(Event("disconnect", self.server, "", [message]))
    667 
    668     def globops(self, text):
    669         """Send a GLOBOPS command."""
    670         self.send_raw("GLOBOPS :" + text)
    671 
    672     def info(self, server=""):
    673         """Send an INFO command."""
    674         self.send_raw(" ".join(["INFO", server]).strip())
    675 
    676     def invite(self, nick, channel):
    677         """Send an INVITE command."""
    678         self.send_raw(" ".join(["INVITE", nick, channel]).strip())
    679 
    680     def ison(self, nicks):
    681         """Send an ISON command.
    682 
    683         Arguments:
    684 
    685             nicks -- List of nicks.
    686         """
    687         self.send_raw("ISON " + " ".join(nicks))
    688 
    689     def join(self, channel, key=""):
    690         """Send a JOIN command."""
    691         self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
    692 
    693     def kick(self, channel, nick, comment=""):
    694         """Send a KICK command."""
    695         self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
    696 
    697     def links(self, remote_server="", server_mask=""):
    698         """Send a LINKS command."""
    699         command = "LINKS"
    700         if remote_server:
    701             command = command + " " + remote_server
    702         if server_mask:
    703             command = command + " " + server_mask
    704         self.send_raw(command)
    705 
    706     def list(self, channels=None, server=""):
    707         """Send a LIST command."""
    708         command = "LIST"
    709         if channels:
    710             command = command + " " + ",".join(channels)
    711         if server:
    712             command = command + " " + server
    713         self.send_raw(command)
    714 
    715     def lusers(self, server=""):
    716         """Send a LUSERS command."""
    717         self.send_raw("LUSERS" + (server and (" " + server)))
    718 
    719     def mode(self, target, command):
    720         """Send a MODE command."""
    721         self.send_raw("MODE %s %s" % (target, command))
    722 
    723     def motd(self, server=""):
    724         """Send an MOTD command."""
    725         self.send_raw("MOTD" + (server and (" " + server)))
    726 
    727     def names(self, channels=None):
    728         """Send a NAMES command."""
    729         self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or ""))
    730 
    731     def nick(self, newnick):
    732         """Send a NICK command."""
    733         self.send_raw("NICK " + newnick)
    734 
    735     def notice(self, target, text):
    736         """Send a NOTICE command."""
    737         # Should limit len(text) here!
    738         self.send_raw("NOTICE %s :%s" % (target, text))
    739 
    740     def oper(self, nick, password):
    741         """Send an OPER command."""
    742         self.send_raw("OPER %s %s" % (nick, password))
    743 
    744     def part(self, channels, message=""):
    745         """Send a PART command."""
    746         if type(channels) == types.StringType:
    747             self.send_raw("PART " + channels + (message and (" " + message)))
    748         else:
    749             self.send_raw("PART " + ",".join(channels) + (message and (" " + message)))
    750 
    751     def pass_(self, password):
    752         """Send a PASS command."""
    753         self.send_raw("PASS " + password)
    754 
    755     def ping(self, target, target2=""):
    756         """Send a PING command."""
    757         self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
    758 
    759     def pong(self, target, target2=""):
    760         """Send a PONG command."""
    761         self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
    762 
    763     def privmsg(self, target, text):
    764         """Send a PRIVMSG command."""
    765         # Should limit len(text) here!
    766         self.send_raw("PRIVMSG %s :%s" % (target, text))
    767 
    768     def privmsg_many(self, targets, text):
    769         """Send a PRIVMSG command to multiple targets."""
    770         # Should limit len(text) here!
    771         self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text))
    772 
    773     def quit(self, message=""):
    774         """Send a QUIT command."""
    775         # Note that many IRC servers don't use your QUIT message
    776         # unless you've been connected for at least 5 minutes!
    777         self.send_raw("QUIT" + (message and (" :" + message)))
    778 
    779     def send_raw(self, string):
    780         """Send raw string to the server.
    781 
    782         The string will be padded with appropriate CR LF.
    783         """
    784         if self.socket is None:
    785             raise ServerNotConnectedError, "Not connected."
    786         try:
    787             if self.ssl:
    788                 self.ssl.write(string + "\r\n")
    789             else:
    790                 self.socket.send(string + "\r\n")
    791             if DEBUG:
    792                 print "TO SERVER:", string
    793         except socket.error, x:
    794             # Ouch!
    795             self.disconnect("Connection reset by peer.")
    796 
    797     def squit(self, server, comment=""):
    798         """Send an SQUIT command."""
    799         self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
    800 
    801     def stats(self, statstype, server=""):
    802         """Send a STATS command."""
    803         self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
    804 
    805     def time(self, server=""):
    806         """Send a TIME command."""
    807         self.send_raw("TIME" + (server and (" " + server)))
    808 
    809     def topic(self, channel, new_topic=None):
    810         """Send a TOPIC command."""
    811         if new_topic is None:
    812             self.send_raw("TOPIC " + channel)
    813         else:
    814             self.send_raw("TOPIC %s :%s" % (channel, new_topic))
    815 
    816     def trace(self, target=""):
    817         """Send a TRACE command."""
    818         self.send_raw("TRACE" + (target and (" " + target)))
    819 
    820     def user(self, username, realname):
    821         """Send a USER command."""
    822         self.send_raw("USER %s 0 * :%s" % (username, realname))
    823 
    824     def userhost(self, nicks):
    825         """Send a USERHOST command."""
    826         self.send_raw("USERHOST " + ",".join(nicks))
    827 
    828     def users(self, server=""):
    829         """Send a USERS command."""
    830         self.send_raw("USERS" + (server and (" " + server)))
    831 
    832     def version(self, server=""):
    833         """Send a VERSION command."""
    834         self.send_raw("VERSION" + (server and (" " + server)))
    835 
    836     def wallops(self, text):
    837         """Send a WALLOPS command."""
    838         self.send_raw("WALLOPS :" + text)
    839 
    840     def who(self, target="", op=""):
    841         """Send a WHO command."""
    842         self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
    843 
    844     def whois(self, targets):
    845         """Send a WHOIS command."""
    846         self.send_raw("WHOIS " + ",".join(targets))
    847 
    848     def whowas(self, nick, max="", server=""):
    849         """Send a WHOWAS command."""
    850         self.send_raw("WHOWAS %s%s%s" % (nick,
    851                                          max and (" " + max),
    852                                          server and (" " + server)))
    853 
    854 class DCCConnectionError(IRCError):
    855     pass
    856 
    857 
    858 class DCCConnection(Connection):
    859     """This class represents a DCC connection.
    860 
    861     DCCConnection objects are instantiated by calling the dcc
    862     method on an IRC object.
    863     """
    864     def __init__(self, irclibobj, dcctype):
    865         Connection.__init__(self, irclibobj)
    866         self.connected = 0
    867         self.passive = 0
    868         self.dcctype = dcctype
    869         self.peeraddress = None
    870         self.peerport = None
    871 
    872     def connect(self, address, port):
    873         """Connect/reconnect to a DCC peer.
    874 
    875         Arguments:
    876             address -- Host/IP address of the peer.
    877 
    878             port -- The port number to connect to.
    879 
    880         Returns the DCCConnection object.
    881         """
    882         self.peeraddress = socket.gethostbyname(address)
    883         self.peerport = port
    884         self.socket = None
    885         self.previous_buffer = ""
    886         self.handlers = {}
    887         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    888         self.passive = 0
    889         try:
    890             self.socket.connect((self.peeraddress, self.peerport))
    891         except socket.error, x:
    892             raise DCCConnectionError, "Couldn't connect to socket: %s" % x
    893         self.connected = 1
    894         if self.irclibobj.fn_to_add_socket:
    895             self.irclibobj.fn_to_add_socket(self.socket)
    896         return self
    897 
    898     def listen(self):
    899         """Wait for a connection/reconnection from a DCC peer.
    900 
    901         Returns the DCCConnection object.
    902 
    903         The local IP address and port are available as
    904         self.localaddress and self.localport.  After connection from a
    905         peer, the peer address and port are available as
    906         self.peeraddress and self.peerport.
    907         """
    908         self.previous_buffer = ""
    909         self.handlers = {}
    910         self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    911         self.passive = 1
    912         try:
    913             self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
    914             self.localaddress, self.localport = self.socket.getsockname()
    915             self.socket.listen(10)
    916         except socket.error, x:
    917             raise DCCConnectionError, "Couldn't bind socket: %s" % x
    918         return self
    919 
    920     def disconnect(self, message=""):
    921         """Hang up the connection and close the object.
    922 
    923         Arguments:
    924 
    925             message -- Quit message.
    926         """
    927         if not self.connected:
    928             return
    929 
    930         self.connected = 0
    931         try:
    932             self.socket.close()
    933         except socket.error, x:
    934             pass
    935         self.socket = None
    936         self.irclibobj._handle_event(
    937             self,
    938             Event("dcc_disconnect", self.peeraddress, "", [message]))
    939         self.irclibobj._remove_connection(self)
    940 
    941     def process_data(self):
    942         """[Internal]"""
    943 
    944         if self.passive and not self.connected:
    945             conn, (self.peeraddress, self.peerport) = self.socket.accept()
    946             self.socket.close()
    947             self.socket = conn
    948             self.connected = 1
    949             if DEBUG:
    950                 print "DCC connection from %s:%d" % (
    951                     self.peeraddress, self.peerport)
    952             self.irclibobj._handle_event(
    953                 self,
    954                 Event("dcc_connect", self.peeraddress, None, None))
    955             return
    956 
    957         try:
    958             new_data = self.socket.recv(2**14)
    959         except socket.error, x:
    960             # The server hung up.
    961             self.disconnect("Connection reset by peer")
    962             return
    963         if not new_data:
    964             # Read nothing: connection must be down.
    965             self.disconnect("Connection reset by peer")
    966             return
    967 
    968         if self.dcctype == "chat":
    969             # The specification says lines are terminated with LF, but
    970             # it seems safer to handle CR LF terminations too.
    971             chunks = _linesep_regexp.split(self.previous_buffer + new_data)
    972 
    973             # Save the last, unfinished line.
    974             self.previous_buffer = chunks[-1]
    975             if len(self.previous_buffer) > 2**14:
    976                 # Bad peer! Naughty peer!
    977                 self.disconnect()
    978                 return
    979             chunks = chunks[:-1]
    980         else:
    981             chunks = [new_data]
    982 
    983         command = "dccmsg"
    984         prefix = self.peeraddress
    985         target = None
    986         for chunk in chunks:
    987             if DEBUG:
    988                 print "FROM PEER:", chunk
    989             arguments = [chunk]
    990             if DEBUG:
    991                 print "command: %s, source: %s, target: %s, arguments: %s" % (
    992                     command, prefix, target, arguments)
    993             self.irclibobj._handle_event(
    994                 self,
    995                 Event(command, prefix, target, arguments))
    996 
    997     def _get_socket(self):
    998         """[Internal]"""
    999         return self.socket
   1000 
   1001     def privmsg(self, string):
   1002         """Send data to DCC peer.
   1003 
   1004         The string will be padded with appropriate LF if it's a DCC
   1005         CHAT session.
   1006         """
   1007         try:
   1008             self.socket.send(string)
   1009             if self.dcctype == "chat":
   1010                 self.socket.send("\n")
   1011             if DEBUG:
   1012                 print "TO PEER: %s\n" % string
   1013         except socket.error, x:
   1014             # Ouch!
   1015             self.disconnect("Connection reset by peer.")
   1016 
   1017 class SimpleIRCClient:
   1018     """A simple single-server IRC client class.
   1019 
   1020     This is an example of an object-oriented wrapper of the IRC
   1021     framework.  A real IRC client can be made by subclassing this
   1022     class and adding appropriate methods.
   1023 
   1024     The method on_join will be called when a "join" event is created
   1025     (which is done when the server sends a JOIN messsage/command),
   1026     on_privmsg will be called for "privmsg" events, and so on.  The
   1027     handler methods get two arguments: the connection object (same as
   1028     self.connection) and the event object.
   1029 
   1030     Instance attributes that can be used by sub classes:
   1031 
   1032         ircobj -- The IRC instance.
   1033 
   1034         connection -- The ServerConnection instance.
   1035 
   1036         dcc_connections -- A list of DCCConnection instances.
   1037     """
   1038     def __init__(self):
   1039         self.ircobj = IRC()
   1040         self.connection = self.ircobj.server()
   1041         self.dcc_connections = []
   1042         self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
   1043         self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
   1044 
   1045     def _dispatcher(self, c, e):
   1046         """[Internal]"""
   1047         m = "on_" + e.eventtype()
   1048         if hasattr(self, m):
   1049             getattr(self, m)(c, e)
   1050 
   1051     def _dcc_disconnect(self, c, e):
   1052         self.dcc_connections.remove(c)
   1053 
   1054     def connect(self, server, port, nickname, password=None, username=None,
   1055                 ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
   1056         """Connect/reconnect to a server.
   1057 
   1058         Arguments:
   1059 
   1060             server -- Server name.
   1061 
   1062             port -- Port number.
   1063 
   1064             nickname -- The nickname.
   1065 
   1066             password -- Password (if any).
   1067 
   1068             username -- The username.
   1069 
   1070             ircname -- The IRC name.
   1071 
   1072             localaddress -- Bind the connection to a specific local IP address.
   1073 
   1074             localport -- Bind the connection to a specific local port.
   1075 
   1076             ssl -- Enable support for ssl.
   1077 
   1078             ipv6 -- Enable support for ipv6.
   1079 
   1080         This function can be called to reconnect a closed connection.
   1081         """
   1082         self.connection.connect(server, port, nickname,
   1083                                 password, username, ircname,
   1084                                 localaddress, localport, ssl, ipv6)
   1085 
   1086     def dcc_connect(self, address, port, dcctype="chat"):
   1087         """Connect to a DCC peer.
   1088 
   1089         Arguments:
   1090 
   1091             address -- IP address of the peer.
   1092 
   1093             port -- Port to connect to.
   1094 
   1095         Returns a DCCConnection instance.
   1096         """
   1097         dcc = self.ircobj.dcc(dcctype)
   1098         self.dcc_connections.append(dcc)
   1099         dcc.connect(address, port)
   1100         return dcc
   1101 
   1102     def dcc_listen(self, dcctype="chat"):
   1103         """Listen for connections from a DCC peer.
   1104 
   1105         Returns a DCCConnection instance.
   1106         """
   1107         dcc = self.ircobj.dcc(dcctype)
   1108         self.dcc_connections.append(dcc)
   1109         dcc.listen()
   1110         return dcc
   1111 
   1112     def start(self):
   1113         """Start the IRC client."""
   1114         self.ircobj.process_forever()
   1115 
   1116 
   1117 class Event:
   1118     """Class representing an IRC event."""
   1119     def __init__(self, eventtype, source, target, arguments=None):
   1120         """Constructor of Event objects.
   1121 
   1122         Arguments:
   1123 
   1124             eventtype -- A string describing the event.
   1125 
   1126             source -- The originator of the event (a nick mask or a server).
   1127 
   1128             target -- The target of the event (a nick or a channel).
   1129 
   1130             arguments -- Any event specific arguments.
   1131         """
   1132         self._eventtype = eventtype
   1133         self._source = source
   1134         self._target = target
   1135         if arguments:
   1136             self._arguments = arguments
   1137         else:
   1138             self._arguments = []
   1139 
   1140     def eventtype(self):
   1141         """Get the event type."""
   1142         return self._eventtype
   1143 
   1144     def source(self):
   1145         """Get the event source."""
   1146         return self._source
   1147 
   1148     def target(self):
   1149         """Get the event target."""
   1150         return self._target
   1151 
   1152     def arguments(self):
   1153         """Get the event arguments."""
   1154         return self._arguments
   1155 
   1156 _LOW_LEVEL_QUOTE = "\020"
   1157 _CTCP_LEVEL_QUOTE = "\134"
   1158 _CTCP_DELIMITER = "\001"
   1159 
   1160 _low_level_mapping = {
   1161     "0": "\000",
   1162     "n": "\n",
   1163     "r": "\r",
   1164     _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
   1165 }
   1166 
   1167 _low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
   1168 
   1169 def mask_matches(nick, mask):
   1170     """Check if a nick matches a mask.
   1171 
   1172     Returns true if the nick matches, otherwise false.
   1173     """
   1174     nick = irc_lower(nick)
   1175     mask = irc_lower(mask)
   1176     mask = mask.replace("\\", "\\\\")
   1177     for ch in ".$|[](){}+":
   1178         mask = mask.replace(ch, "\\" + ch)
   1179     mask = mask.replace("?", ".")
   1180     mask = mask.replace("*", ".*")
   1181     r = re.compile(mask, re.IGNORECASE)
   1182     return r.match(nick)
   1183 
   1184 _special = "-[]\\`^{}"
   1185 nick_characters = string.ascii_letters + string.digits + _special
   1186 _ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
   1187                                           string.ascii_lowercase + "{}|~")
   1188 
   1189 def irc_lower(s):
   1190     """Returns a lowercased string.
   1191 
   1192     The definition of lowercased comes from the IRC specification (RFC
   1193     1459).
   1194     """
   1195     return s.translate(_ircstring_translation)
   1196 
   1197 def _ctcp_dequote(message):
   1198     """[Internal] Dequote a message according to CTCP specifications.
   1199 
   1200     The function returns a list where each element can be either a
   1201     string (normal message) or a tuple of one or two strings (tagged
   1202     messages).  If a tuple has only one element (ie is a singleton),
   1203     that element is the tag; otherwise the tuple has two elements: the
   1204     tag and the data.
   1205 
   1206     Arguments:
   1207 
   1208         message -- The message to be decoded.
   1209     """
   1210 
   1211     def _low_level_replace(match_obj):
   1212         ch = match_obj.group(1)
   1213 
   1214         # If low_level_mapping doesn't have the character as key, we
   1215         # should just return the character.
   1216         return _low_level_mapping.get(ch, ch)
   1217 
   1218     if _LOW_LEVEL_QUOTE in message:
   1219         # Yup, there was a quote.  Release the dequoter, man!
   1220         message = _low_level_regexp.sub(_low_level_replace, message)
   1221 
   1222     if _CTCP_DELIMITER not in message:
   1223         return [message]
   1224     else:
   1225         # Split it into parts.  (Does any IRC client actually *use*
   1226         # CTCP stacking like this?)
   1227         chunks = message.split(_CTCP_DELIMITER)
   1228 
   1229         messages = []
   1230         i = 0
   1231         while i < len(chunks)-1:
   1232             # Add message if it's non-empty.
   1233             if len(chunks[i]) > 0:
   1234                 messages.append(chunks[i])
   1235 
   1236             if i < len(chunks)-2:
   1237                 # Aye!  CTCP tagged data ahead!
   1238                 messages.append(tuple(chunks[i+1].split(" ", 1)))
   1239 
   1240             i = i + 2
   1241 
   1242         if len(chunks) % 2 == 0:
   1243             # Hey, a lonely _CTCP_DELIMITER at the end!  This means
   1244             # that the last chunk, including the delimiter, is a
   1245             # normal message!  (This is according to the CTCP
   1246             # specification.)
   1247             messages.append(_CTCP_DELIMITER + chunks[-1])
   1248 
   1249         return messages
   1250 
   1251 def is_channel(string):
   1252     """Check if a string is a channel name.
   1253 
   1254     Returns true if the argument is a channel name, otherwise false.
   1255     """
   1256     return string and string[0] in "#&+!"
   1257 
   1258 def ip_numstr_to_quad(num):
   1259     """Convert an IP number as an integer given in ASCII
   1260     representation (e.g. '3232235521') to an IP address string
   1261     (e.g. '192.168.0.1')."""
   1262     n = long(num)
   1263     p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
   1264                            n >> 8 & 0xFF, n & 0xFF]))
   1265     return ".".join(p)
   1266 
   1267 def ip_quad_to_numstr(quad):
   1268     """Convert an IP address string (e.g. '192.168.0.1') to an IP
   1269     number as an integer given in ASCII representation
   1270     (e.g. '3232235521')."""
   1271     p = map(long, quad.split("."))
   1272     s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
   1273     if s[-1] == "L":
   1274         s = s[:-1]
   1275     return s
   1276 
   1277 def nm_to_n(s):
   1278     """Get the nick part of a nickmask.
   1279 
   1280     (The source of an Event is a nickmask.)
   1281     """
   1282     return s.split("!")[0]
   1283 
   1284 def nm_to_uh(s):
   1285     """Get the userhost part of a nickmask.
   1286 
   1287     (The source of an Event is a nickmask.)
   1288     """
   1289     return s.split("!")[1]
   1290 
   1291 def nm_to_h(s):
   1292     """Get the host part of a nickmask.
   1293 
   1294     (The source of an Event is a nickmask.)
   1295     """
   1296     return s.split("@")[1]
   1297 
   1298 def nm_to_u(s):
   1299     """Get the user part of a nickmask.
   1300 
   1301     (The source of an Event is a nickmask.)
   1302     """
   1303     s = s.split("!")[1]
   1304     return s.split("@")[0]
   1305 
   1306 def parse_nick_modes(mode_string):
   1307     """Parse a nick mode string.
   1308 
   1309     The function returns a list of lists with three members: sign,
   1310     mode and argument.  The sign is \"+\" or \"-\".  The argument is
   1311     always None.
   1312 
   1313     Example:
   1314 
   1315     >>> irclib.parse_nick_modes(\"+ab-c\")
   1316     [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
   1317     """
   1318 
   1319     return _parse_modes(mode_string, "")
   1320 
   1321 def parse_channel_modes(mode_string):
   1322     """Parse a channel mode string.
   1323 
   1324     The function returns a list of lists with three members: sign,
   1325     mode and argument.  The sign is \"+\" or \"-\".  The argument is
   1326     None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
   1327 
   1328     Example:
   1329 
   1330     >>> irclib.parse_channel_modes(\"+ab-c foo\")
   1331     [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
   1332     """
   1333 
   1334     return _parse_modes(mode_string, "bklvo")
   1335 
   1336 def _parse_modes(mode_string, unary_modes=""):
   1337     """[Internal]"""
   1338     modes = []
   1339     arg_count = 0
   1340 
   1341     # State variable.
   1342     sign = ""
   1343 
   1344     a = mode_string.split()
   1345     if len(a) == 0:
   1346         return []
   1347     else:
   1348         mode_part, args = a[0], a[1:]
   1349 
   1350     if mode_part[0] not in "+-":
   1351         return []
   1352     for ch in mode_part:
   1353         if ch in "+-":
   1354             sign = ch
   1355         elif ch == " ":
   1356             collecting_arguments = 1
   1357         elif ch in unary_modes:
   1358             if len(args) >= arg_count + 1:
   1359                 modes.append([sign, ch, args[arg_count]])
   1360                 arg_count = arg_count + 1
   1361             else:
   1362                 modes.append([sign, ch, None])
   1363         else:
   1364             modes.append([sign, ch, None])
   1365     return modes
   1366 
   1367 def _ping_ponger(connection, event):
   1368     """[Internal]"""
   1369     connection.pong(event.target())
   1370 
   1371 # Numeric table mostly stolen from the Perl IRC module (Net::IRC).
   1372 numeric_events = {
   1373     "001": "welcome",
   1374     "002": "yourhost",
   1375     "003": "created",
   1376     "004": "myinfo",
   1377     "005": "featurelist",  # XXX
   1378     "200": "tracelink",
   1379     "201": "traceconnecting",
   1380     "202": "tracehandshake",
   1381     "203": "traceunknown",
   1382     "204": "traceoperator",
   1383     "205": "traceuser",
   1384     "206": "traceserver",
   1385     "207": "traceservice",
   1386     "208": "tracenewtype",
   1387     "209": "traceclass",
   1388     "210": "tracereconnect",
   1389     "211": "statslinkinfo",
   1390     "212": "statscommands",
   1391     "213": "statscline",
   1392     "214": "statsnline",
   1393     "215": "statsiline",
   1394     "216": "statskline",
   1395     "217": "statsqline",
   1396     "218": "statsyline",
   1397     "219": "endofstats",
   1398     "221": "umodeis",
   1399     "231": "serviceinfo",
   1400     "232": "endofservices",
   1401     "233": "service",
   1402     "234": "servlist",
   1403     "235": "servlistend",
   1404     "241": "statslline",
   1405     "242": "statsuptime",
   1406     "243": "statsoline",
   1407     "244": "statshline",
   1408     "250": "luserconns",
   1409     "251": "luserclient",
   1410     "252": "luserop",
   1411     "253": "luserunknown",
   1412     "254": "luserchannels",
   1413     "255": "luserme",
   1414     "256": "adminme",
   1415     "257": "adminloc1",
   1416     "258": "adminloc2",
   1417     "259": "adminemail",
   1418     "261": "tracelog",
   1419     "262": "endoftrace",
   1420     "263": "tryagain",
   1421     "265": "n_local",
   1422     "266": "n_global",
   1423     "300": "none",
   1424     "301": "away",
   1425     "302": "userhost",
   1426     "303": "ison",
   1427     "305": "unaway",
   1428     "306": "nowaway",
   1429     "311": "whoisuser",
   1430     "312": "whoisserver",
   1431     "313": "whoisoperator",
   1432     "314": "whowasuser",
   1433     "315": "endofwho",
   1434     "316": "whoischanop",
   1435     "317": "whoisidle",
   1436     "318": "endofwhois",
   1437     "319": "whoischannels",
   1438     "321": "liststart",
   1439     "322": "list",
   1440     "323": "listend",
   1441     "324": "channelmodeis",
   1442     "329": "channelcreate",
   1443     "331": "notopic",
   1444     "332": "currenttopic",
   1445     "333": "topicinfo",
   1446     "341": "inviting",
   1447     "342": "summoning",
   1448     "346": "invitelist",
   1449     "347": "endofinvitelist",
   1450     "348": "exceptlist",
   1451     "349": "endofexceptlist",
   1452     "351": "version",
   1453     "352": "whoreply",
   1454     "353": "namreply",
   1455     "361": "killdone",
   1456     "362": "closing",
   1457     "363": "closeend",
   1458     "364": "links",
   1459     "365": "endoflinks",
   1460     "366": "endofnames",
   1461     "367": "banlist",
   1462     "368": "endofbanlist",
   1463     "369": "endofwhowas",
   1464     "371": "info",
   1465     "372": "motd",
   1466     "373": "infostart",
   1467     "374": "endofinfo",
   1468     "375": "motdstart",
   1469     "376": "endofmotd",
   1470     "377": "motd2",        # 1997-10-16 -- tkil
   1471     "381": "youreoper",
   1472     "382": "rehashing",
   1473     "384": "myportis",
   1474     "391": "time",
   1475     "392": "usersstart",
   1476     "393": "users",
   1477     "394": "endofusers",
   1478     "395": "nousers",
   1479     "401": "nosuchnick",
   1480     "402": "nosuchserver",
   1481     "403": "nosuchchannel",
   1482     "404": "cannotsendtochan",
   1483     "405": "toomanychannels",
   1484     "406": "wasnosuchnick",
   1485     "407": "toomanytargets",
   1486     "409": "noorigin",
   1487     "411": "norecipient",
   1488     "412": "notexttosend",
   1489     "413": "notoplevel",
   1490     "414": "wildtoplevel",
   1491     "421": "unknowncommand",
   1492     "422": "nomotd",
   1493     "423": "noadmininfo",
   1494     "424": "fileerror",
   1495     "431": "nonicknamegiven",
   1496     "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
   1497     "433": "nicknameinuse",
   1498     "436": "nickcollision",
   1499     "437": "unavailresource",  # "Nick temporally unavailable"
   1500     "441": "usernotinchannel",
   1501     "442": "notonchannel",
   1502     "443": "useronchannel",
   1503     "444": "nologin",
   1504     "445": "summondisabled",
   1505     "446": "usersdisabled",
   1506     "451": "notregistered",
   1507     "461": "needmoreparams",
   1508     "462": "alreadyregistered",
   1509     "463": "nopermforhost",
   1510     "464": "passwdmismatch",
   1511     "465": "yourebannedcreep", # I love this one...
   1512     "466": "youwillbebanned",
   1513     "467": "keyset",
   1514     "471": "channelisfull",
   1515     "472": "unknownmode",
   1516     "473": "inviteonlychan",
   1517     "474": "bannedfromchan",
   1518     "475": "badchannelkey",
   1519     "476": "badchanmask",
   1520     "477": "nochanmodes",  # "Channel doesn't support modes"
   1521     "478": "banlistfull",
   1522     "481": "noprivileges",
   1523     "482": "chanoprivsneeded",
   1524     "483": "cantkillserver",
   1525     "484": "restricted",   # Connection is restricted
   1526     "485": "uniqopprivsneeded",
   1527     "491": "nooperhost",
   1528     "492": "noservicehost",
   1529     "501": "umodeunknownflag",
   1530     "502": "usersdontmatch",
   1531 }
   1532 
   1533 generated_events = [
   1534     # Generated events
   1535     "dcc_connect",
   1536     "dcc_disconnect",
   1537     "dccmsg",
   1538     "disconnect",
   1539     "ctcp",
   1540     "ctcpreply",
   1541 ]
   1542 
   1543 protocol_events = [
   1544     # IRC protocol events
   1545     "error",
   1546     "join",
   1547     "kick",
   1548     "mode",
   1549     "part",
   1550     "ping",
   1551     "privmsg",
   1552     "privnotice",
   1553     "pubmsg",
   1554     "pubnotice",
   1555     "quit",
   1556     "invite",
   1557     "pong",
   1558 ]
   1559 
   1560 all_events = generated_events + protocol_events + numeric_events.values()
   1561