PageRenderTime 550ms CodeModel.GetById 100ms app.highlight 334ms RepoModel.GetById 99ms app.codeStats 1ms

/IRCXBMC/irclib.py

http://xbmc-scripting.googlecode.com/
Python | 1556 lines | 1418 code | 32 blank | 106 comment | 39 complexity | e61a15a034009a8736f6934731c7951a MD5 | raw file
   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@users.sourceforge.net>
  18#
  19# $Id: irclib.py,v 1.43 2005/12/24 22:12:40 keltus Exp $
  20
  21"""irclib -- Internet Relay Chat (IRC) protocol client library.
  22
  23This library is intended to encapsulate the IRC protocol at a quite
  24low level.  It provides an event-driven IRC client framework.  It has
  25a fairly thorough support for the basic IRC protocol, CTCP, DCC chat,
  26but DCC file transfers is not yet supported.
  27
  28In order to understand how to make an IRC client, I'm afraid you more
  29or less must understand the IRC specifications.  They are available
  30here: [IRC specifications].
  31
  32The 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
  52Current 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
  64import bisect
  65import re
  66import select
  67import socket
  68import string
  69import sys
  70import time
  71import types
  72
  73VERSION = 0, 4, 6
  74DEBUG = 1
  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
  91class IRCError(Exception):
  92    """Represents an IRC exception."""
  93    pass
  94
  95
  96class 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
 258        if not event in self.handlers:
 259            self.handlers[event] = []
 260        bisect.insort(self.handlers[event], ((priority, handler)))
 261
 262    def remove_global_handler(self, event, handler):
 263        """Removes a global handler function.
 264
 265        Arguments:
 266
 267            event -- Event type (a string).
 268
 269            handler -- Callback function.
 270
 271        Returns 1 on success, otherwise 0.
 272        """
 273        if not event in self.handlers:
 274            return 0
 275        for h in self.handlers[event]:
 276            if handler == h[1]:
 277                self.handlers[event].remove(h)
 278        return 1
 279
 280    def execute_at(self, at, function, arguments=()):
 281        """Execute a function at a specified time.
 282
 283        Arguments:
 284
 285            at -- Execute at this time (standard \"time_t\" time).
 286
 287            function -- Function to call.
 288
 289            arguments -- Arguments to give the function.
 290        """
 291        self.execute_delayed(at-time.time(), function, arguments)
 292
 293    def execute_delayed(self, delay, function, arguments=()):
 294        """Execute a function after a specified time.
 295
 296        Arguments:
 297
 298            delay -- How many seconds to wait.
 299
 300            function -- Function to call.
 301
 302            arguments -- Arguments to give the function.
 303        """
 304        bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
 305        if self.fn_to_add_timeout:
 306            self.fn_to_add_timeout(delay)
 307
 308    def dcc(self, dcctype="chat"):
 309        """Creates and returns a DCCConnection object.
 310
 311        Arguments:
 312
 313            dcctype -- "chat" for DCC CHAT connections or "raw" for
 314                       DCC SEND (or other DCC types). If "chat",
 315                       incoming data will be split in newline-separated
 316                       chunks. If "raw", incoming data is not touched.
 317        """
 318        c = DCCConnection(self, dcctype)
 319        self.connections.append(c)
 320        return c
 321
 322    def _handle_event(self, connection, event):
 323        """[Internal]"""
 324        h = self.handlers
 325        for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
 326            if handler[1](connection, event) == "NO MORE":
 327                return
 328
 329    def _remove_connection(self, connection):
 330        """[Internal]"""
 331        self.connections.remove(connection)
 332        if self.fn_to_remove_socket:
 333            self.fn_to_remove_socket(connection._get_socket())
 334
 335_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
 336
 337
 338class Connection:
 339    """Base class for IRC connections.
 340
 341    Must be overridden.
 342    """
 343    def __init__(self, irclibobj):
 344        self.irclibobj = irclibobj
 345
 346    def _get_socket():
 347        raise IRCError, "Not overridden"
 348
 349    ##############################
 350    ### Convenience wrappers.
 351
 352    def execute_at(self, at, function, arguments=()):
 353        self.irclibobj.execute_at(at, function, arguments)
 354
 355    def execute_delayed(self, delay, function, arguments=()):
 356        self.irclibobj.execute_delayed(delay, function, arguments)
 357
 358
 359class ServerConnectionError(IRCError):
 360    pass
 361
 362class ServerNotConnectedError(ServerConnectionError):
 363    pass
 364
 365
 366# Huh!?  Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
 367# use \n as message separator!  :P
 368_linesep_regexp = re.compile("\r?\n")
 369
 370class ServerConnection(Connection):
 371    """This class represents an IRC server connection.
 372
 373    ServerConnection objects are instantiated by calling the server
 374    method on an IRC object.
 375    """
 376
 377    def __init__(self, irclibobj):
 378        Connection.__init__(self, irclibobj)
 379        self.connected = 0  # Not connected yet.
 380        self.socket = None
 381
 382    def connect(self, server, port, nickname, password=None, username=None,
 383                ircname=None, localaddress="", localport=0):
 384        """Connect/reconnect to a server.
 385
 386        Arguments:
 387
 388            server -- Server name.
 389
 390            port -- Port number.
 391
 392            nickname -- The nickname.
 393
 394            password -- Password (if any).
 395
 396            username -- The username.
 397
 398            ircname -- The IRC name ("realname").
 399
 400            localaddress -- Bind the connection to a specific local IP address.
 401
 402            localport -- Bind the connection to a specific local port.
 403
 404        This function can be called to reconnect a closed connection.
 405
 406        Returns the ServerConnection object.
 407        """
 408        if self.connected:
 409            self.disconnect("Changing servers")
 410
 411        self.previous_buffer = ""
 412        self.handlers = {}
 413        self.real_server_name = ""
 414        self.real_nickname = nickname
 415        self.server = server
 416        self.port = port
 417        self.nickname = nickname
 418        self.username = username or nickname
 419        self.ircname = ircname or nickname
 420        self.password = password
 421        self.localaddress = localaddress
 422        self.localport = localport
 423        self.localhost = socket.gethostname()
 424        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 425        try:
 426            self.socket.bind((self.localaddress, self.localport))
 427            self.socket.connect((self.server, self.port))
 428        except socket.error, x:
 429            self.socket.close()
 430            self.socket = None
 431            raise ServerConnectionError, "Couldn't connect to socket: %s" % x
 432        self.connected = 1
 433        if self.irclibobj.fn_to_add_socket:
 434            self.irclibobj.fn_to_add_socket(self.socket)
 435
 436        # Log on...
 437        if self.password:
 438            self.pass_(self.password)
 439        self.nick(self.nickname)
 440        self.user(self.username, self.ircname)
 441        return self
 442
 443    def close(self):
 444        """Close the connection.
 445
 446        This method closes the connection permanently; after it has
 447        been called, the object is unusable.
 448        """
 449
 450        self.disconnect("Closing object")
 451        self.irclibobj._remove_connection(self)
 452
 453    def _get_socket(self):
 454        """[Internal]"""
 455        return self.socket
 456
 457    def get_server_name(self):
 458        """Get the (real) server name.
 459
 460        This method returns the (real) server name, or, more
 461        specifically, what the server calls itself.
 462        """
 463
 464        if self.real_server_name:
 465            return self.real_server_name
 466        else:
 467            return ""
 468
 469    def get_nickname(self):
 470        """Get the (real) nick name.
 471
 472        This method returns the (real) nickname.  The library keeps
 473        track of nick changes, so it might not be the nick name that
 474        was passed to the connect() method.  """
 475
 476        return self.real_nickname
 477
 478    def process_data(self):
 479        """[Internal]"""
 480
 481        try:
 482            new_data = self.socket.recv(2**14)
 483        except socket.error, x:
 484            # The server hung up.
 485            self.disconnect("Connection reset by peer")
 486            return
 487        if not new_data:
 488            # Read nothing: connection must be down.
 489            self.disconnect("Connection reset by peer")
 490            return
 491
 492        lines = _linesep_regexp.split(self.previous_buffer + new_data)
 493
 494        # Save the last, unfinished line.
 495        self.previous_buffer = lines[-1]
 496        lines = lines[:-1]
 497
 498        for line in lines:
 499            if DEBUG:
 500                print "FROM SERVER:", line
 501
 502            if not line:
 503                continue
 504
 505            prefix = None
 506            command = None
 507            arguments = None
 508            self._handle_event(Event("all_raw_messages",
 509                                     self.get_server_name(),
 510                                     None,
 511                                     [line]))
 512
 513            m = _rfc_1459_command_regexp.match(line)
 514            if m.group("prefix"):
 515                prefix = m.group("prefix")
 516                if not self.real_server_name:
 517                    self.real_server_name = prefix
 518
 519            if m.group("command"):
 520                command = m.group("command").lower()
 521
 522            if m.group("argument"):
 523                a = m.group("argument").split(" :", 1)
 524                arguments = a[0].split()
 525                if len(a) == 2:
 526                    arguments.append(a[1])
 527
 528            # Translate numerics into more readable strings.
 529            if command in numeric_events:
 530                command = numeric_events[command]
 531
 532            if command == "nick":
 533                if nm_to_n(prefix) == self.real_nickname:
 534                    self.real_nickname = arguments[0]
 535            elif command == "welcome":
 536                # Record the nickname in case the client changed nick
 537                # in a nicknameinuse callback.
 538                self.real_nickname = arguments[0]
 539            if command == "namreply":
 540                #DONNO HACK: Auto handle this now coz can't get it working on main page

 541                pass
 542            if command == "nicknameinuse":
 543                #DONNO HACK: Auto handle this now coz it will slow stuff down if handels else wherer

 544                self.nickname = self.nickname + "_"
 545                self.nick(self.nickname)
 546            if command in ["privmsg", "notice"]:
 547                target, message = arguments[0], arguments[1]
 548                messages = _ctcp_dequote(message)
 549
 550                if command == "privmsg":
 551                    if is_channel(target):
 552                        command = "pubmsg"
 553                else:
 554                    if is_channel(target):
 555                        command = "pubnotice"
 556                    else:
 557                        command = "privnotice"
 558
 559                for m in messages:
 560                    if type(m) is types.TupleType:
 561                        if command in ["privmsg", "pubmsg"]:
 562                            command = "ctcp"
 563                        else:
 564                            command = "ctcpreply"
 565
 566                        m = list(m)
 567                        if DEBUG:
 568                            print "command: %s, source: %s, target: %s, arguments: %s" % (
 569                                command, prefix, target, m)
 570                        self._handle_event(Event(command, prefix, target, m))
 571                        if command == "ctcp" and m[0] == "ACTION":
 572                            self._handle_event(Event("action", prefix, target, m[1:]))
 573                    else:
 574                        if DEBUG:
 575                            print "command: %s, source: %s, target: %s, arguments: %s" % (
 576                                command, prefix, target, [m])
 577                        self._handle_event(Event(command, prefix, target, [m]))
 578            else:
 579                target = None
 580
 581                if command == "quit":
 582                    arguments = [arguments[0]]
 583                elif command == "ping":
 584                    target = arguments[0]
 585                else:
 586                    target = arguments[0]
 587                    arguments = arguments[1:]
 588
 589                if command == "mode":
 590                    if not is_channel(target):
 591                        command = "umode"
 592
 593                if DEBUG:
 594                    print "command: %s, source: %s, target: %s, arguments: %s" % (
 595                        command, prefix, target, arguments)
 596                self._handle_event(Event(command, prefix, target, arguments))
 597
 598    def _handle_event(self, event):
 599        """[Internal]"""
 600        self.irclibobj._handle_event(self, event)
 601        if event.eventtype() in self.handlers:
 602            for fn in self.handlers[event.eventtype()]:
 603                fn(self, event)
 604
 605    def is_connected(self):
 606        """Return connection status.
 607
 608        Returns true if connected, otherwise false.
 609        """
 610        return self.connected
 611
 612    def add_global_handler(self, *args):
 613        """Add global handler.
 614
 615        See documentation for IRC.add_global_handler.
 616        """
 617        self.irclibobj.add_global_handler(*args)
 618
 619    def remove_global_handler(self, *args):
 620        """Remove global handler.
 621
 622        See documentation for IRC.remove_global_handler.
 623        """
 624        self.irclibobj.remove_global_handler(*args)
 625
 626    def action(self, target, action):
 627        """Send a CTCP ACTION command."""
 628        self.ctcp("ACTION", target, action)
 629
 630    def admin(self, server=""):
 631        """Send an ADMIN command."""
 632        self.send_raw(" ".join(["ADMIN", server]).strip())
 633
 634    def ctcp(self, ctcptype, target, parameter=""):
 635        """Send a CTCP command."""
 636        ctcptype = ctcptype.upper()
 637        self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
 638
 639    def ctcp_reply(self, target, parameter):
 640        """Send a CTCP REPLY command."""
 641        self.notice(target, "\001%s\001" % parameter)
 642
 643    def disconnect(self, message=""):
 644        """Hang up the connection.
 645
 646        Arguments:
 647
 648            message -- Quit message.
 649        """
 650        if not self.connected:
 651            return
 652
 653        self.connected = 0
 654
 655        self.quit(message)
 656
 657        try:
 658            self.socket.close()
 659        except socket.error, x:
 660            pass
 661        self.socket = None
 662        self._handle_event(Event("disconnect", self.server, "", [message]))
 663
 664    def globops(self, text):
 665        """Send a GLOBOPS command."""
 666        self.send_raw("GLOBOPS :" + text)
 667
 668    def info(self, server=""):
 669        """Send an INFO command."""
 670        self.send_raw(" ".join(["INFO", server]).strip())
 671
 672    def invite(self, nick, channel):
 673        """Send an INVITE command."""
 674        self.send_raw(" ".join(["INVITE", nick, channel]).strip())
 675
 676    def ison(self, nicks):
 677        """Send an ISON command.
 678
 679        Arguments:
 680
 681            nicks -- List of nicks.
 682        """
 683        self.send_raw("ISON " + " ".join(nicks))
 684
 685    def join(self, channel, key=""):
 686        """Send a JOIN command."""
 687        self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
 688
 689    def kick(self, channel, nick, comment=""):
 690        """Send a KICK command."""
 691        self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
 692
 693    def links(self, remote_server="", server_mask=""):
 694        """Send a LINKS command."""
 695        command = "LINKS"
 696        if remote_server:
 697            command = command + " " + remote_server
 698        if server_mask:
 699            command = command + " " + server_mask
 700        self.send_raw(command)
 701
 702    def list(self, channels=None, server=""):
 703        """Send a LIST command."""
 704        command = "LIST"
 705        if channels:
 706            command = command + " " + ",".join(channels)
 707        if server:
 708            command = command + " " + server
 709        self.send_raw(command)
 710
 711    def lusers(self, server=""):
 712        """Send a LUSERS command."""
 713        self.send_raw("LUSERS" + (server and (" " + server)))
 714
 715    def mode(self, target, command):
 716        """Send a MODE command."""
 717        self.send_raw("MODE %s %s" % (target, command))
 718
 719    def motd(self, server=""):
 720        """Send an MOTD command."""
 721        self.send_raw("MOTD" + (server and (" " + server)))
 722
 723    def names(self, channels=None):
 724        """Send a NAMES command."""
 725        self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or ""))
 726
 727    def nick(self, newnick):
 728        """Send a NICK command."""
 729        self.send_raw("NICK " + newnick)
 730
 731    def notice(self, target, text):
 732        """Send a NOTICE command."""
 733        # Should limit len(text) here!
 734        self.send_raw("NOTICE %s :%s" % (target, text))
 735
 736    def oper(self, nick, password):
 737        """Send an OPER command."""
 738        self.send_raw("OPER %s %s" % (nick, password))
 739
 740    def part(self, channels, message=""):
 741        """Send a PART command."""
 742        if type(channels) == types.StringType:
 743            self.send_raw("PART " + channels + (message and (" " + message)))
 744        else:
 745            self.send_raw("PART " + ",".join(channels) + (message and (" " + message)))
 746
 747    def pass_(self, password):
 748        """Send a PASS command."""
 749        self.send_raw("PASS " + password)
 750
 751    def ping(self, target, target2=""):
 752        """Send a PING command."""
 753        self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
 754
 755    def pong(self, target, target2=""):
 756        """Send a PONG command."""
 757        self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
 758
 759    def privmsg(self, target, text):
 760        """Send a PRIVMSG command."""
 761        # Should limit len(text) here!
 762        self.send_raw("PRIVMSG %s :%s" % (target, text))
 763
 764    def privmsg_many(self, targets, text):
 765        """Send a PRIVMSG command to multiple targets."""
 766        # Should limit len(text) here!
 767        self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text))
 768
 769    def quit(self, message=""):
 770        """Send a QUIT command."""
 771        # Note that many IRC servers don't use your QUIT message
 772        # unless you've been connected for at least 5 minutes!
 773        self.send_raw("QUIT" + (message and (" :" + message)))
 774
 775    def sconnect(self, target, port="", server=""):
 776        """Send an SCONNECT command."""
 777        self.send_raw("CONNECT %s%s%s" % (target,
 778                                          port and (" " + port),
 779                                          server and (" " + server)))
 780
 781    def send_raw(self, string):
 782        """Send raw string to the server.
 783
 784        The string will be padded with appropriate CR LF.
 785        """
 786        if self.socket is None:
 787            raise ServerNotConnectedError, "Not connected."
 788        try:
 789            self.socket.send(string + "\r\n")
 790            if DEBUG:
 791                print "TO SERVER:", string
 792        except socket.error, x:
 793            # Ouch!
 794            self.disconnect("Connection reset by peer.")
 795
 796    def squit(self, server, comment=""):
 797        """Send an SQUIT command."""
 798        self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
 799
 800    def stats(self, statstype, server=""):
 801        """Send a STATS command."""
 802        self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
 803
 804    def time(self, server=""):
 805        """Send a TIME command."""
 806        self.send_raw("TIME" + (server and (" " + server)))
 807
 808    def topic(self, channel, new_topic=None):
 809        """Send a TOPIC command."""
 810        if new_topic is None:
 811            self.send_raw("TOPIC " + channel)
 812        else:
 813            self.send_raw("TOPIC %s :%s" % (channel, new_topic))
 814
 815    def trace(self, target=""):
 816        """Send a TRACE command."""
 817        self.send_raw("TRACE" + (target and (" " + target)))
 818
 819    def user(self, username, realname):
 820        """Send a USER command."""
 821        self.send_raw("USER %s 0 * :%s" % (username, realname))
 822
 823    def userhost(self, nicks):
 824        """Send a USERHOST command."""
 825        self.send_raw("USERHOST " + ",".join(nicks))
 826
 827    def users(self, server=""):
 828        """Send a USERS command."""
 829        self.send_raw("USERS" + (server and (" " + server)))
 830
 831    def version(self, server=""):
 832        """Send a VERSION command."""
 833        self.send_raw("VERSION" + (server and (" " + server)))
 834
 835    def wallops(self, text):
 836        """Send a WALLOPS command."""
 837        self.send_raw("WALLOPS :" + text)
 838
 839    def who(self, target="", op=""):
 840        """Send a WHO command."""
 841        self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
 842
 843    def whois(self, targets):
 844        """Send a WHOIS command."""
 845        self.send_raw("WHOIS " + ",".join(targets))
 846
 847    def whowas(self, nick, max="", server=""):
 848        """Send a WHOWAS command."""
 849        self.send_raw("WHOWAS %s%s%s" % (nick,
 850                                         max and (" " + max),
 851                                         server and (" " + server)))
 852
 853
 854class DCCConnectionError(IRCError):
 855    pass
 856
 857
 858class 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
1017class 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):
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        This function can be called to reconnect a closed connection.
1077        """
1078        self.connection.connect(server, port, nickname,
1079                                password, username, ircname,
1080                                localaddress, localport)
1081
1082    def dcc_connect(self, address, port, dcctype="chat"):
1083        """Connect to a DCC peer.
1084
1085        Arguments:
1086
1087            address -- IP address of the peer.
1088
1089            port -- Port to connect to.
1090
1091        Returns a DCCConnection instance.
1092        """
1093        dcc = self.ircobj.dcc(dcctype)
1094        self.dcc_connections.append(dcc)
1095        dcc.connect(address, port)
1096        return dcc
1097
1098    def dcc_listen(self, dcctype="chat"):
1099        """Listen for connections from a DCC peer.
1100
1101        Returns a DCCConnection instance.
1102        """
1103        dcc = self.ircobj.dcc(dcctype)
1104        self.dcc_connections.append(dcc)
1105        dcc.listen()
1106        return dcc
1107
1108    def start(self):
1109        """Start the IRC client."""
1110        self.ircobj.process_forever()
1111
1112
1113class Event:
1114    """Class representing an IRC event."""
1115    def __init__(self, eventtype, source, target, arguments=None):
1116        """Constructor of Event objects.
1117
1118        Arguments:
1119
1120            eventtype -- A string describing the event.
1121
1122            source -- The originator of the event (a nick mask or a server).
1123
1124            target -- The target of the event (a nick or a channel).
1125
1126            arguments -- Any event specific arguments.
1127        """
1128        self._eventtype = eventtype
1129        self._source = source
1130        self._target = target
1131        if arguments:
1132            self._arguments = arguments
1133        else:
1134            self._arguments = []
1135
1136    def eventtype(self):
1137        """Get the event type."""
1138        return self._eventtype
1139
1140    def source(self):
1141        """Get the event source."""
1142        return self._source
1143
1144    def target(self):
1145        """Get the event target."""
1146        return self._target
1147
1148    def arguments(self):
1149        """Get the event arguments."""
1150        return self._arguments
1151
1152_LOW_LEVEL_QUOTE = "\020"
1153_CTCP_LEVEL_QUOTE = "\134"
1154_CTCP_DELIMITER = "\001"
1155
1156_low_level_mapping = {
1157    "0": "\000",
1158    "n": "\n",
1159    "r": "\r",
1160    _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
1161}
1162
1163_low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
1164
1165def mask_matches(nick, mask):
1166    """Check if a nick matches a mask.
1167
1168    Returns true if the nick matches, otherwise false.
1169    """
1170    nick = irc_lower(nick)
1171    mask = irc_lower(mask)
1172    mask = mask.replace("\\", "\\\\")
1173    for ch in ".$|[](){}+":
1174        mask = mask.replace(ch, "\\" + ch)
1175    mask = mask.replace("?", ".")
1176    mask = mask.replace("*", ".*")
1177    r = re.compile(mask, re.IGNORECASE)
1178    return r.match(nick)
1179
1180_special = "-[]\\`^{}"
1181nick_characters = string.ascii_letters + string.digits + _special
1182_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
1183                                          string.ascii_lowercase + "{}|~")
1184
1185def irc_lower(s):
1186    """Returns a lowercased string.
1187
1188    The definition of lowercased comes from the IRC specification (RFC
1189    1459).
1190    """
1191    return s.translate(_ircstring_translation)
1192
1193def _ctcp_dequote(message):
1194    """[Internal] Dequote a message according to CTCP specifications.
1195
1196    The function returns a list where each element can be either a
1197    string (normal message) or a tuple of one or two strings (tagged
1198    messages).  If a tuple has only one element (ie is a singleton),
1199    that element is the tag; otherwise the tuple has two elements: the
1200    tag and the data.
1201
1202    Arguments:
1203
1204        message -- The message to be decoded.
1205    """
1206
1207    def _low_level_replace(match_obj):
1208        ch = match_obj.group(1)
1209
1210        # If low_level_mapping doesn't have the character as key, we
1211        # should just return the character.
1212        return _low_level_mapping.get(ch, ch)
1213
1214    if _LOW_LEVEL_QUOTE in message:
1215        # Yup, there was a quote.  Release the dequoter, man!
1216        message = _low_level_regexp.sub(_low_level_replace, message)
1217
1218    if _CTCP_DELIMITER not in message:
1219        return [message]
1220    else:
1221        # Split it into parts.  (Does any IRC client actually *use*
1222        # CTCP stacking like this?)
1223        chunks = message.split(_CTCP_DELIMITER)
1224
1225        messages = []
1226        i = 0
1227        while i < len(chunks)-1:
1228            # Add message if it's non-empty.
1229            if len(chunks[i]) > 0:
1230                messages.append(chunks[i])
1231
1232            if i < len(chunks)-2:
1233                # Aye!  CTCP tagged data ahead!
1234                messages.append(tuple(chunks[i+1].split(" ", 1)))
1235
1236            i = i + 2
1237
1238        if len(chunks) % 2 == 0:
1239            # Hey, a lonely _CTCP_DELIMITER at the end!  This means
1240            # that the last chunk, including the delimiter, is a
1241            # normal message!  (This is according to the CTCP
1242            # specification.)
1243            messages.append(_CTCP_DELIMITER + chunks[-1])
1244
1245        return messages
1246
1247def is_channel(string):
1248    """Check if a string is a channel name.
1249
1250    Returns true if the argument is a channel name, otherwise false.
1251    """
1252    return string and string[0] in "#&+!"
1253
1254def ip_numstr_to_quad(num):
1255    """Convert an IP number as an integer given in ASCII
1256    representation (e.g. '3232235521') to an IP address string
1257    (e.g. '192.168.0.1')."""
1258    n = long(num)
1259    p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
1260                           n >> 8 & 0xFF, n & 0xFF]))
1261    return ".".join(p)
1262
1263def ip_quad_to_numstr(quad):
1264    """Convert an IP address string (e.g. '192.168.0.1') to an IP
1265    number as an integer given in ASCII representation
1266    (e.g. '3232235521')."""
1267    p = map(long, quad.split("."))
1268    s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
1269    if s[-1] == "L":
1270        s = s[:-1]
1271    return s
1272
1273def nm_to_n(s):
1274    """Get the nick part of a nickmask.
1275
1276    (The source of an Event is a nickmask.)
1277    """
1278    return s.split("!")[0]
1279
1280def nm_to_uh(s):
1281    """Get the userhost part of a nickmask.
1282
1283    (The source of an Event is a nickmask.)
1284    """
1285    return s.split("!")[1]
1286
1287def nm_to_h(s):
1288    """Get the host part of a nickmask.
1289
1290    (The source of an Event is a nickmask.)
1291    """
1292    return s.split("@")[1]
1293
1294def nm_to_u(s):
1295    """Get the user part of a nickmask.
1296
1297    (The source of an Event is a nickmask.)
1298    """
1299    s = s.split("!")[1]
1300    return s.split("@")[0]
1301
1302def parse_nick_modes(mode_string):
1303    """Parse a nick mode string.
1304
1305    The function returns a list of lists with three members: sign,
1306    mode and argument.  The sign is \"+\" or \"-\".  The argument is
1307    always None.
1308
1309    Example:
1310
1311    >>> irclib.parse_nick_modes(\"+ab-c\")
1312    [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1313    """
1314
1315    return _parse_modes(mode_string, "")
1316
1317def parse_channel_modes(mode_string):
1318    """Parse a channel mode string.
1319
1320    The function returns a list of lists with three members: sign,
1321    mode and argument.  The sign is \"+\" or \"-\".  The argument is
1322    None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
1323
1324    Example:
1325
1326    >>> irclib.parse_channel_modes(\"+ab-c foo\")
1327    [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1328    """
1329
1330    return _parse_modes(mode_string, "bklvo")
1331
1332def _parse_modes(mode_string, unary_modes=""):
1333    """[Internal]"""
1334    modes = []
1335    arg_count = 0
1336
1337    # State variable.
1338    sign = ""
1339
1340    a = mode_string.split()
1341    if len(a) == 0:
1342        return []
1343    else:
1344        mode_part, args = a[0], a[1:]
1345
1346    if mode_part[0] not in "+-":
1347        return []
1348    for ch in mode_part:
1349        if ch in "+-":
1350            sign = ch
1351        elif ch == " ":
1352            collecting_arguments = 1
1353        elif ch in unary_modes:
1354            if len(args) >= arg_count + 1:
1355                modes.append([sign, ch, args[arg_count]])
1356                arg_count = arg_count + 1
1357            else:
1358                modes.append([sign, ch, None])
1359        else:
1360            modes.append([sign, ch, None])
1361    return modes
1362
1363def _ping_ponger(connection, event):
1364    """[Internal]"""
1365    connection.pong(event.target())
1366
1367# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1368numeric_events = {
1369    "001": "welcome",
1370    "002": "yourhost",
1371    "003": "created",
1372    "004": "myinfo",
1373    "005": "featurelist",  # XXX
1374    "200": "tracelink",
1375    "201": "traceconnecting",
1376    "202": "tracehandshake",
1377    "203": "traceunknown",
1378    "204": "traceoperator",
1379    "205": "traceuser",
1380    "206": "traceserver",
1381    "207": "traceservice",
1382    "208": "tracenewtype",
1383    "209": "traceclass",
1384    "210": "tracereconnect",
1385    "211": "statslinkinfo",
1386    "212": "statscommands",
1387    "213": "statscline",
1388    "214": "statsnline",
1389    "215": "statsiline",
1390    "216": "statskline",
1391    "217": "statsqline",
1392    "218": "statsyline",
1393    "219": "endofstats",
1394    "221": "umodeis",
1395    "231": "serviceinfo",
1396    "232": "endofservices",
1397    "233": "service",
1398    "234": "servlist",
1399    "235": "servlistend",
1400    "241": "statslline",
1401    "242": "statsuptime",
1402    "243": "statsoline",
1403    "244": "statshline",
1404    "250": "luserconns",
1405    "251": "luserclient",
1406    "252": "luserop",
1407    "253": "luserunknown",
1408    "254": "luserchannels",
1409    "255": "luserme",
1410    "256": "adminme",
1411    "257": "adminloc1",
1412    "258": "adminloc2",
1413    "259": "adminemail",
1414    "261": "tracelog",
1415    "262": "endoftrace",
1416    "263": "tryagain",
1417    "265": "n_local",
1418    "266": "n_global",
1419    "300": "none",
1420    "301": "away",
1421    "302": "userhost",
1422    "303": "ison",
1423    "305": "unaway",
1424    "306": "nowaway",
1425    "311": "whoisuser",
1426    "312": "whoisserver",
1427    "313": "whoisoperator",
1428    "314": "whowasuser",
1429    "315": "endofwho",
1430    "316": "whoischanop",
1431    "317": "whoisidle",
1432    "318": "endofwhois",
1433    "319": "whoischannels",
1434    "321": "liststart",
1435    "322": "list",
1436    "323": "listend",
1437    "324": "channelmodeis",
1438    "329": "channelcreate",
1439    "331": "notopic",
1440    "332": "currenttopic",
1441    "333": "topicinfo",
1442    "341": "inviting",
1443    "342": "summoning",
1444    "346": "invitelist",
1445    "347": "endofinvitelist",
1446    "348": "exceptlist",
1447    "349": "endofexceptlist",
1448    "351": "version",
1449    "352": "whoreply",
1450    "353": "namreply",
1451    "361": "killdone",
1452    "362": "closing",
1453    "363": "closeend",
1454    "364": "links",
1455    "365": "endoflinks",
1456    "366": "endofnames",
1457    "367": "banlist",
1458    "368": "endofbanlist",
1459    "369": "endofwhowas",
1460    "371": "info",
1461    "372": "motd",
1462    "373": "infostart",
1463    "374": "endofinfo",
1464    "375": "motdstart",
1465    "376": "endofmotd",
1466    "377": "motd2",        # 1997-10-16 -- tkil
1467    "381": "youreoper",
1468    "382": "rehashing",
1469    "384": "myportis",
1470    "391": "time",
1471    "392": "usersstart",
1472    "393": "users",
1473    "394": "endofusers",
1474    "395": "nousers",
1475    "401": "nosuchnick",
1476    "402": "nosuchserver",
1477    "403": "nosuchchannel",
1478    "404": "cannotsendtochan",
1479    "405": "toomanychannels",
1480    "406": "wasnosuchnick",
1481    "407": "toomanytargets",
1482    "409": "noorigin",
1483    "411": "norecipient",
1484    "412": "notexttosend",
1485    "413": "notoplevel",
1486    "414": "wildtoplevel",
1487    "421": "unknowncommand",
1488    "422": "nomotd",
1489    "423": "noadmininfo",
1490    "424": "fileerror",
1491    "431": "nonicknamegiven",
1492    "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1493    "433": "nicknameinuse",
1494    "436": "nickcollision",
1495    "437": "unavailresource",  # "Nick temporally unavailable"
1496    "441": "usernotinchannel",
1497    "442": "notonchannel",
1498    "443": "useronchannel",
1499    "444": "nologin",
1500    "445": "summondisabled",
1501    "446": "usersdisabled",
1502    "451": "notregistered",
1503    "461": "needmoreparams",
1504    "462": "alreadyregistered",
1505    "463": "nopermforhost",
1506    "464": "passwdmismatch",
1507    "465": "yourebannedcreep", # I love this one...
1508    "466": "youwillbebanned",
1509    "467": "keyset",
1510    "471": "channelisfull",
1511    "472": "unknownmode",
1512    "473": "inviteonlychan",
1513    "474": "bannedfromchan",
1514    "475": "badchannelkey",
1515    "476": "badchanmask",
1516    "477": "nochanmodes",  # "Channel doesn't support modes"
1517    "478": "banlistfull",
1518    "481": "noprivileges",
1519    "482": "chanoprivsneeded",
1520    "483": "cantkillserver",
1521    "484": "restricted",   # Connection is restricted
1522    "485": "uniqopprivsneeded",
1523    "491": "nooperhost",
1524    "492": "noservicehost",
1525    "501": "umodeunknownflag",
1526    "502": "usersdontmatch",
1527}
1528
1529generated_events = [
1530    # Generated events
1531    "dcc_connect",
1532    "dcc_disconnect",
1533    "dccmsg",
1534    "disconnect",
1535    "ctcp",
1536    "ctcpreply",
1537]
1538
1539protocol_events = [
1540    # IRC protocol events
1541    "error",
1542    "join",
1543    "kick",
1544    "mode",
1545    "part",
1546    "ping",
1547    "privmsg",
1548    "privnotice",
1549    "pubmsg",
1550    "pubnotice",
1551    "quit",
1552    "invite",
1553    "pong",
1554]
1555
1556all_events = generated_events + protocol_events + numeric_events.values()