• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.47 2008/09/25 22:00:59 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, 8
74DEBUG = 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
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        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
336class 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
357class ServerConnectionError(IRCError):
358    pass
359
360class 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
368class 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
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, 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
1117class 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
1169def 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 = "-[]\\`^{}"
1185nick_characters = string.ascii_letters + string.digits + _special
1186_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
1187                                          string.ascii_lowercase + "{}|~")
1188
1189def 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
1197def _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
1251def 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
1258def 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
1267def 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
1277def 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
1284def 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
1291def 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
1298def 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
1306def 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
1321def 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
1336def _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
1367def _ping_ponger(connection, event):
1368    """[Internal]"""
1369    connection.pong(event.target())
1370
1371# Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1372numeric_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
1533generated_events = [
1534    # Generated events
1535    "dcc_connect",
1536    "dcc_disconnect",
1537    "dccmsg",
1538    "disconnect",
1539    "ctcp",
1540    "ctcpreply",
1541]
1542
1543protocol_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
1560all_events = generated_events + protocol_events + numeric_events.values()
1561