1# -*- coding: utf-8 -*-
2
3"""
4Internet Relay Chat (IRC) protocol client library.
5
6This library is intended to encapsulate the IRC protocol in Python.
7It provides an event-driven IRC client framework.  It has
8a fairly thorough support for the basic IRC protocol, CTCP, and DCC chat.
9
10To best understand how to make an IRC client, the reader more
11or less must understand the IRC specifications.  They are available
12here: [IRC specifications].
13
14The main features of the IRC client framework are:
15
16  * Abstraction of the IRC protocol.
17  * Handles multiple simultaneous IRC server connections.
18  * Handles server PONGing transparently.
19  * Messages to the IRC server are done by calling methods on an IRC
20    connection object.
21  * Messages from an IRC server triggers events, which can be caught
22    by event handlers.
23  * Reading from and writing to IRC server sockets are normally done
24    by an internal select() loop, but the select()ing may be done by
25    an external main loop.
26  * Functions can be registered to execute at specified times by the
27    event-loop.
28  * Decodes CTCP tagging correctly (hopefully); I haven't seen any
29    other IRC client implementation that handles the CTCP
30    specification subtilties.
31  * A kind of simple, single-server, object-oriented IRC client class
32    that dispatches events to instance methods is included.
33
34Current limitations:
35
36  * Data is not written asynchronously to the server, i.e. the write()
37    may block if the TCP buffers are stuffed.
38  * DCC file transfers are not supported.
39  * RFCs 2810, 2811, 2812, and 2813 have not been considered.
40
41Notes:
42  * connection.quit() only sends QUIT to the server.
43  * ERROR from the server triggers the error event and the disconnect event.
44  * dropping of the connection triggers the disconnect event.
45
46
47.. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
48"""
49
50from __future__ import absolute_import, division
51
52import bisect
53import re
54import select
55import socket
56import time
57import struct
58import logging
59import threading
60import abc
61import collections
62import functools
63import itertools
64import contextlib
65
66import six
67from jaraco.itertools import always_iterable
68from jaraco.functools import Throttler
69
70try:
71    import pkg_resources
72except ImportError:
73    pass
74
75from . import connection
76from . import events
77from . import functools as irc_functools
78from . import buffer
79from . import schedule
80from . import features
81from . import ctcp
82from . import message
83
84log = logging.getLogger(__name__)
85
86# set the version tuple
87try:
88    VERSION_STRING = pkg_resources.require('irc')[0].version
89    VERSION = tuple(int(res) for res in re.findall('\d+', VERSION_STRING))
90except Exception:
91    VERSION_STRING = 'unknown'
92    VERSION = ()
93
94
95class IRCError(Exception):
96    "An IRC exception"
97
98class InvalidCharacters(ValueError):
99    "Invalid characters were encountered in the message"
100
101class MessageTooLong(ValueError):
102    "Message is too long"
103
104class PrioritizedHandler(
105        collections.namedtuple('Base', ('priority', 'callback'))):
106    def __lt__(self, other):
107        "when sorting prioritized handlers, only use the priority"
108        return self.priority < other.priority
109
110class Reactor(object):
111    """
112    Processes events from one or more IRC server connections.
113
114    This class implements a reactor in the style of the `reactor pattern
115    <http://en.wikipedia.org/wiki/Reactor_pattern>`_.
116
117    When a Reactor object has been instantiated, it can be used to create
118    Connection objects that represent the IRC connections.  The
119    responsibility of the reactor object is to provide an event-driven
120    framework for the connections and to keep the connections alive.
121    It runs a select loop to poll each connection's TCP socket and
122    hands over the sockets with incoming data for processing by the
123    corresponding connection.
124
125    The methods of most interest for an IRC client writer are server,
126    add_global_handler, remove_global_handler, execute_at,
127    execute_delayed, execute_every, process_once, and process_forever.
128
129    This is functionally an event-loop which can either use it's own
130    internal polling loop, or tie into an external event-loop, by
131    having the external event-system periodically call `process_once`
132    on the instantiated reactor class. This will allow the reactor
133    to process any queued data and/or events.
134
135    Calling `process_forever` will hand off execution to the reactor's
136    internal event-loop, which will not return for the life of the
137    reactor.
138
139    Here is an example:
140
141        client = irc.client.Reactor()
142        server = client.server()
143        server.connect("irc.some.where", 6667, "my_nickname")
144        server.privmsg("a_nickname", "Hi there!")
145        client.process_forever()
146
147    This will connect to the IRC server irc.some.where on port 6667
148    using the nickname my_nickname and send the message "Hi there!"
149    to the nickname a_nickname.
150
151    The methods of this class are thread-safe; accesses to and modifications
152    of its internal lists of connections, handlers, and delayed commands
153    are guarded by a mutex.
154    """
155
156    def __do_nothing(*args, **kwargs):
157        pass
158
159    def __init__(self, on_connect=__do_nothing, on_disconnect=__do_nothing,
160            on_schedule=__do_nothing):
161        """Constructor for Reactor objects.
162
163        on_connect: optional callback invoked when a new connection
164        is made.
165
166        on_disconnect: optional callback invoked when a socket is
167        disconnected.
168
169        on_schedule: optional callback, usually supplied by an external
170        event loop, to indicate in float seconds that the client needs to
171        process events that many seconds in the future. An external event
172        loop will implement this callback to schedule a call to
173        process_timeout.
174
175        The three arguments mainly exist to be able to use an external
176        main loop (for example Tkinter's or PyGTK's main app loop)
177        instead of calling the process_forever method.
178
179        An alternative is to just call ServerConnection.process_once()
180        once in a while.
181        """
182
183        self._on_connect = on_connect
184        self._on_disconnect = on_disconnect
185        self._on_schedule = on_schedule
186
187        self.connections = []
188        self.handlers = {}
189        self.delayed_commands = []  # list of DelayedCommands
190        # Modifications to these shared lists and dict need to be thread-safe
191        self.mutex = threading.RLock()
192
193        self.add_global_handler("ping", _ping_ponger, -42)
194
195    def server(self):
196        """Creates and returns a ServerConnection object."""
197
198        c = ServerConnection(self)
199        with self.mutex:
200            self.connections.append(c)
201        return c
202
203    def process_data(self, sockets):
204        """Called when there is more data to read on connection sockets.
205
206        Arguments:
207
208            sockets -- A list of socket objects.
209
210        See documentation for Reactor.__init__.
211        """
212        with self.mutex:
213            log.log(logging.DEBUG-2, "process_data()")
214            for s, c in itertools.product(sockets, self.connections):
215                if s == c.socket:
216                    c.process_data()
217
218    def process_timeout(self):
219        """Called when a timeout notification is due.
220
221        See documentation for Reactor.__init__.
222        """
223        with self.mutex:
224            while self.delayed_commands:
225                command = self.delayed_commands[0]
226                if not command.due():
227                    break
228                command.function()
229                if isinstance(command, schedule.PeriodicCommand):
230                    self._schedule_command(command.next())
231                del self.delayed_commands[0]
232
233    @property
234    def sockets(self):
235        with self.mutex:
236            return [
237                conn.socket
238                for conn in self.connections
239                if conn is not None
240                and conn.socket is not None
241            ]
242
243    def process_once(self, timeout=0):
244        """Process data from connections once.
245
246        Arguments:
247
248            timeout -- How long the select() call should wait if no
249                       data is available.
250
251        This method should be called periodically to check and process
252        incoming data, if there are any.  If that seems boring, look
253        at the process_forever method.
254        """
255        log.log(logging.DEBUG-2, "process_once()")
256        sockets = self.sockets
257        if sockets:
258            (i, o, e) = select.select(sockets, [], [], timeout)
259            self.process_data(i)
260        else:
261            time.sleep(timeout)
262        self.process_timeout()
263
264    def process_forever(self, timeout=0.2):
265        """Run an infinite loop, processing data from connections.
266
267        This method repeatedly calls process_once.
268
269        Arguments:
270
271            timeout -- Parameter to pass to process_once.
272        """
273        # This loop should specifically *not* be mutex-locked.
274        # Otherwise no other thread would ever be able to change
275        # the shared state of a Reactor object running this function.
276        log.debug("process_forever(timeout=%s)", timeout)
277        while 1:
278            self.process_once(timeout)
279
280    def disconnect_all(self, message=""):
281        """Disconnects all connections."""
282        with self.mutex:
283            for c in self.connections:
284                c.disconnect(message)
285
286    def add_global_handler(self, event, handler, priority=0):
287        """Adds a global handler function for a specific event type.
288
289        Arguments:
290
291            event -- Event type (a string).  Check the values of
292                     numeric_events for possible event types.
293
294            handler -- Callback function taking 'connection' and 'event'
295                       parameters.
296
297            priority -- A number (the lower number, the higher priority).
298
299        The handler function is called whenever the specified event is
300        triggered in any of the connections.  See documentation for
301        the Event class.
302
303        The handler functions are called in priority order (lowest
304        number is highest priority).  If a handler function returns
305        "NO MORE", no more handlers will be called.
306        """
307        handler = PrioritizedHandler(priority, handler)
308        with self.mutex:
309            event_handlers = self.handlers.setdefault(event, [])
310            bisect.insort(event_handlers, handler)
311
312    def remove_global_handler(self, event, handler):
313        """Removes a global handler function.
314
315        Arguments:
316
317            event -- Event type (a string).
318            handler -- Callback function.
319
320        Returns 1 on success, otherwise 0.
321        """
322        with self.mutex:
323            if not event in self.handlers:
324                return 0
325            for h in self.handlers[event]:
326                if handler == h.callback:
327                    self.handlers[event].remove(h)
328        return 1
329
330    def execute_at(self, at, function, arguments=()):
331        """Execute a function at a specified time.
332
333        Arguments:
334
335            at -- Execute at this time (a standard Unix timestamp).
336            function -- Function to call.
337            arguments -- Arguments to give the function.
338        """
339        function = functools.partial(function, *arguments)
340        command = schedule.DelayedCommand.at_time(at, function)
341        self._schedule_command(command)
342
343    def execute_delayed(self, delay, function, arguments=()):
344        """
345        Execute a function after a specified time.
346
347        delay -- How many seconds to wait.
348        function -- Function to call.
349        arguments -- Arguments to give the function.
350        """
351        function = functools.partial(function, *arguments)
352        command = schedule.DelayedCommand.after(delay, function)
353        self._schedule_command(command)
354
355    def execute_every(self, period, function, arguments=()):
356        """
357        Execute a function every 'period' seconds.
358
359        period -- How often to run (always waits this long for first).
360        function -- Function to call.
361        arguments -- Arguments to give the function.
362        """
363        function = functools.partial(function, *arguments)
364        command = schedule.PeriodicCommand.after(period, function)
365        self._schedule_command(command)
366
367    def _schedule_command(self, command):
368        with self.mutex:
369            bisect.insort(self.delayed_commands, command)
370            self._on_schedule(command.delay.total_seconds())
371
372    def dcc(self, dcctype="chat"):
373        """Creates and returns a DCCConnection object.
374
375        Arguments:
376
377            dcctype -- "chat" for DCC CHAT connections or "raw" for
378                       DCC SEND (or other DCC types). If "chat",
379                       incoming data will be split in newline-separated
380                       chunks. If "raw", incoming data is not touched.
381        """
382        with self.mutex:
383            c = DCCConnection(self, dcctype)
384            self.connections.append(c)
385        return c
386
387    def _handle_event(self, connection, event):
388        """
389        Handle an Event event incoming on ServerConnection connection.
390        """
391        with self.mutex:
392            h = self.handlers
393            matching_handlers = sorted(
394                h.get("all_events", []) +
395                h.get(event.type, [])
396            )
397            for handler in matching_handlers:
398                result = handler.callback(connection, event)
399                if result == "NO MORE":
400                    return
401
402    def _remove_connection(self, connection):
403        """[Internal]"""
404        with self.mutex:
405            self.connections.remove(connection)
406            self._on_disconnect(connection.socket)
407
408_cmd_pat = "^(@(?P<tags>[^ ]*) )?(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?"
409_rfc_1459_command_regexp = re.compile(_cmd_pat)
410
411
412@six.add_metaclass(abc.ABCMeta)
413class Connection(object):
414    """
415    Base class for IRC connections.
416    """
417
418    @abc.abstractproperty
419    def socket(self):
420        "The socket for this connection"
421
422    def __init__(self, reactor):
423        self.reactor = reactor
424
425    ##############################
426    ### Convenience wrappers.
427
428    def execute_at(self, at, function, arguments=()):
429        self.reactor.execute_at(at, function, arguments)
430
431    def execute_delayed(self, delay, function, arguments=()):
432        self.reactor.execute_delayed(delay, function, arguments)
433
434    def execute_every(self, period, function, arguments=()):
435        self.reactor.execute_every(period, function, arguments)
436
437class ServerConnectionError(IRCError):
438    pass
439
440class ServerNotConnectedError(ServerConnectionError):
441    pass
442
443
444class ServerConnection(Connection):
445    """
446    An IRC server connection.
447
448    ServerConnection objects are instantiated by calling the server
449    method on a Reactor object.
450    """
451
452    buffer_class = buffer.DecodingLineBuffer
453    socket = None
454
455    def __init__(self, reactor):
456        super(ServerConnection, self).__init__(reactor)
457        self.connected = False
458        self.features = features.FeatureSet()
459
460    # save the method args to allow for easier reconnection.
461    @irc_functools.save_method_args
462    def connect(self, server, port, nickname, password=None, username=None,
463            ircname=None, connect_factory=connection.Factory()):
464        """Connect/reconnect to a server.
465
466        Arguments:
467
468        * server - Server name
469        * port - Port number
470        * nickname - The nickname
471        * password - Password (if any)
472        * username - The username
473        * ircname - The IRC name ("realname")
474        * server_address - The remote host/port of the server
475        * connect_factory - A callable that takes the server address and
476          returns a connection (with a socket interface)
477
478        This function can be called to reconnect a closed connection.
479
480        Returns the ServerConnection object.
481        """
482        log.debug("connect(server=%r, port=%r, nickname=%r, ...)", server,
483            port, nickname)
484
485        if self.connected:
486            self.disconnect("Changing servers")
487
488        self.buffer = self.buffer_class()
489        self.handlers = {}
490        self.real_server_name = ""
491        self.real_nickname = nickname
492        self.server = server
493        self.port = port
494        self.server_address = (server, port)
495        self.nickname = nickname
496        self.username = username or nickname
497        self.ircname = ircname or nickname
498        self.password = password
499        self.connect_factory = connect_factory
500        try:
501            self.socket = self.connect_factory(self.server_address)
502        except socket.error as ex:
503            raise ServerConnectionError("Couldn't connect to socket: %s" % ex)
504        self.connected = True
505        self.reactor._on_connect(self.socket)
506
507        # Log on...
508        if self.password:
509            self.pass_(self.password)
510        self.nick(self.nickname)
511        self.user(self.username, self.ircname)
512        return self
513
514    def reconnect(self):
515        """
516        Reconnect with the last arguments passed to self.connect()
517        """
518        self.connect(*self._saved_connect.args, **self._saved_connect.kwargs)
519
520    def close(self):
521        """Close the connection.
522
523        This method closes the connection permanently; after it has
524        been called, the object is unusable.
525        """
526        # Without this thread lock, there is a window during which
527        # select() can find a closed socket, leading to an EBADF error.
528        with self.reactor.mutex:
529            self.disconnect("Closing object")
530            self.reactor._remove_connection(self)
531
532    def get_server_name(self):
533        """Get the (real) server name.
534
535        This method returns the (real) server name, or, more
536        specifically, what the server calls itself.
537        """
538        return self.real_server_name or ""
539
540    def get_nickname(self):
541        """Get the (real) nick name.
542
543        This method returns the (real) nickname.  The library keeps
544        track of nick changes, so it might not be the nick name that
545        was passed to the connect() method.
546        """
547        return self.real_nickname
548
549    @contextlib.contextmanager
550    def as_nick(self, name):
551        """
552        Set the nick for the duration of the context.
553        """
554        orig = self.get_nickname()
555        self.nick(name)
556        try:
557            yield orig
558        finally:
559            self.nick(orig)
560
561    def process_data(self):
562        "read and process input from self.socket"
563
564        try:
565            reader = getattr(self.socket, 'read', self.socket.recv)
566            new_data = reader(2 ** 14)
567        except socket.error:
568            # The server hung up.
569            self.disconnect("Connection reset by peer")
570            return
571        if not new_data:
572            # Read nothing: connection must be down.
573            self.disconnect("Connection reset by peer")
574            return
575
576        self.buffer.feed(new_data)
577
578        # process each non-empty line after logging all lines
579        for line in self.buffer:
580            log.debug("FROM SERVER: %s", line)
581            if not line: continue
582            self._process_line(line)
583
584    def _process_line(self, line):
585        event = Event("all_raw_messages", self.get_server_name(), None,
586            [line])
587        self._handle_event(event)
588
589        grp = _rfc_1459_command_regexp.match(line).group
590
591        source = NickMask.from_group(grp("prefix"))
592        command = self._command_from_group(grp("command"))
593        arguments = message.Arguments.from_group(grp('argument'))
594        tags = message.Tag.from_group(grp('tags'))
595
596        if source and not self.real_server_name:
597            self.real_server_name = source
598
599        if command == "nick":
600            if source.nick == self.real_nickname:
601                self.real_nickname = arguments[0]
602        elif command == "welcome":
603            # Record the nickname in case the client changed nick
604            # in a nicknameinuse callback.
605            self.real_nickname = arguments[0]
606        elif command == "featurelist":
607            self.features.load(arguments)
608
609        handler = (
610            self._handle_message
611            if command in ["privmsg", "notice"]
612            else self._handle_other
613        )
614        handler(arguments, command, source, tags)
615
616    def _handle_message(self, arguments, command, source, tags):
617        target, msg = arguments[:2]
618        messages = ctcp.dequote(msg)
619        if command == "privmsg":
620            if is_channel(target):
621                command = "pubmsg"
622        else:
623            if is_channel(target):
624                command = "pubnotice"
625            else:
626                command = "privnotice"
627        for m in messages:
628            if isinstance(m, tuple):
629                if command in ["privmsg", "pubmsg"]:
630                    command = "ctcp"
631                else:
632                    command = "ctcpreply"
633
634                m = list(m)
635                log.debug("command: %s, source: %s, target: %s, "
636                          "arguments: %s, tags: %s", command, source, target, m, tags)
637                event = Event(command, source, target, m, tags)
638                self._handle_event(event)
639                if command == "ctcp" and m[0] == "ACTION":
640                    event = Event("action", source, target, m[1:], tags)
641                    self._handle_event(event)
642            else:
643                log.debug("command: %s, source: %s, target: %s, "
644                          "arguments: %s, tags: %s", command, source, target, [m], tags)
645                event = Event(command, source, target, [m], tags)
646                self._handle_event(event)
647
648    def _handle_other(self, arguments, command, source, tags):
649        target = None
650        if command == "quit":
651            arguments = [arguments[0]]
652        elif command == "ping":
653            target = arguments[0]
654        else:
655            target = arguments[0] if arguments else None
656            arguments = arguments[1:]
657        if command == "mode":
658            if not is_channel(target):
659                command = "umode"
660        log.debug("command: %s, source: %s, target: %s, "
661                  "arguments: %s, tags: %s", command, source, target, arguments, tags)
662        event = Event(command, source, target, arguments, tags)
663        self._handle_event(event)
664
665    @staticmethod
666    def _command_from_group(group):
667        command = group.lower()
668        # Translate numerics into more readable strings.
669        return events.numeric.get(command, command)
670
671    def _handle_event(self, event):
672        """[Internal]"""
673        self.reactor._handle_event(self, event)
674        if event.type in self.handlers:
675            for fn in self.handlers[event.type]:
676                fn(self, event)
677
678    def is_connected(self):
679        """Return connection status.
680
681        Returns true if connected, otherwise false.
682        """
683        return self.connected
684
685    def add_global_handler(self, *args):
686        """Add global handler.
687
688        See documentation for IRC.add_global_handler.
689        """
690        self.reactor.add_global_handler(*args)
691
692    def remove_global_handler(self, *args):
693        """Remove global handler.
694
695        See documentation for IRC.remove_global_handler.
696        """
697        self.reactor.remove_global_handler(*args)
698
699    def action(self, target, action):
700        """Send a CTCP ACTION command."""
701        self.ctcp("ACTION", target, action)
702
703    def admin(self, server=""):
704        """Send an ADMIN command."""
705        self.send_raw(" ".join(["ADMIN", server]).strip())
706
707    def cap(self, subcommand, *args):
708        """
709        Send a CAP command according to `the spec
710        <http://ircv3.atheme.org/specification/capability-negotiation-3.1>`_.
711
712        Arguments:
713
714            subcommand -- LS, LIST, REQ, ACK, CLEAR, END
715            args -- capabilities, if required for given subcommand
716
717        Example:
718
719            .cap('LS')
720            .cap('REQ', 'multi-prefix', 'sasl')
721            .cap('END')
722        """
723        cap_subcommands = set('LS LIST REQ ACK NAK CLEAR END'.split())
724        client_subcommands = set(cap_subcommands) - set('NAK')
725        assert subcommand in client_subcommands, "invalid subcommand"
726
727        def _multi_parameter(args):
728            """
729            According to the spec::
730
731                If more than one capability is named, the RFC1459 designated
732                sentinel (:) for a multi-parameter argument must be present.
733
734            It's not obvious where the sentinel should be present or if it
735            must be omitted for a single parameter, so follow convention and
736            only include the sentinel prefixed to the first parameter if more
737            than one parameter is present.
738            """
739            if len(args) > 1:
740                return (':' + args[0],) + args[1:]
741            return args
742
743        args = _multi_parameter(args)
744        self.send_raw(' '.join(('CAP', subcommand) + args))
745
746    def ctcp(self, ctcptype, target, parameter=""):
747        """Send a CTCP command."""
748        ctcptype = ctcptype.upper()
749        tmpl = (
750            "\001{ctcptype} {parameter}\001" if parameter else
751            "\001{ctcptype}\001"
752        )
753        self.privmsg(target, tmpl.format(**vars()))
754
755    def ctcp_reply(self, target, parameter):
756        """Send a CTCP REPLY command."""
757        self.notice(target, "\001%s\001" % parameter)
758
759    def disconnect(self, message=""):
760        """Hang up the connection.
761
762        Arguments:
763
764            message -- Quit message.
765        """
766        if not self.connected:
767            return
768
769        self.connected = 0
770
771        self.quit(message)
772
773        try:
774            self.socket.shutdown(socket.SHUT_WR)
775            self.socket.close()
776        except socket.error:
777            pass
778        del self.socket
779        self._handle_event(Event("disconnect", self.server, "", [message]))
780
781    def globops(self, text):
782        """Send a GLOBOPS command."""
783        self.send_raw("GLOBOPS :" + text)
784
785    def info(self, server=""):
786        """Send an INFO command."""
787        self.send_raw(" ".join(["INFO", server]).strip())
788
789    def invite(self, nick, channel):
790        """Send an INVITE command."""
791        self.send_raw(" ".join(["INVITE", nick, channel]).strip())
792
793    def ison(self, nicks):
794        """Send an ISON command.
795
796        Arguments:
797
798            nicks -- List of nicks.
799        """
800        self.send_raw("ISON " + " ".join(nicks))
801
802    def join(self, channel, key=""):
803        """Send a JOIN command."""
804        self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
805
806    def kick(self, channel, nick, comment=""):
807        """Send a KICK command."""
808        tmpl = "KICK {channel} {nick}"
809        if comment:
810            tmpl += " :{comment}"
811        self.send_raw(tmpl.format(**vars()))
812
813    def links(self, remote_server="", server_mask=""):
814        """Send a LINKS command."""
815        command = "LINKS"
816        if remote_server:
817            command = command + " " + remote_server
818        if server_mask:
819            command = command + " " + server_mask
820        self.send_raw(command)
821
822    def list(self, channels=None, server=""):
823        """Send a LIST command."""
824        command = "LIST"
825        channels = ",".join(always_iterable(channels))
826        if channels:
827            command += ' ' + channels
828        if server:
829            command = command + " " + server
830        self.send_raw(command)
831
832    def lusers(self, server=""):
833        """Send a LUSERS command."""
834        self.send_raw("LUSERS" + (server and (" " + server)))
835
836    def mode(self, target, command):
837        """Send a MODE command."""
838        self.send_raw("MODE %s %s" % (target, command))
839
840    def motd(self, server=""):
841        """Send an MOTD command."""
842        self.send_raw("MOTD" + (server and (" " + server)))
843
844    def names(self, channels=None):
845        """Send a NAMES command."""
846        tmpl = "NAMES {channels}" if channels else "NAMES"
847        channels = ','.join(always_iterable(channels))
848        self.send_raw(tmpl.format(channels=channels))
849
850    def nick(self, newnick):
851        """Send a NICK command."""
852        self.send_raw("NICK " + newnick)
853
854    def notice(self, target, text):
855        """Send a NOTICE command."""
856        # Should limit len(text) here!
857        self.send_raw("NOTICE %s :%s" % (target, text))
858
859    def oper(self, nick, password):
860        """Send an OPER command."""
861        self.send_raw("OPER %s %s" % (nick, password))
862
863    def part(self, channels, message=""):
864        """Send a PART command."""
865        channels = always_iterable(channels)
866        cmd_parts = [
867            'PART',
868            ','.join(channels),
869        ]
870        if message: cmd_parts.append(message)
871        self.send_raw(' '.join(cmd_parts))
872
873    def pass_(self, password):
874        """Send a PASS command."""
875        self.send_raw("PASS " + password)
876
877    def ping(self, target, target2=""):
878        """Send a PING command."""
879        self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
880
881    def pong(self, target, target2=""):
882        """Send a PONG command."""
883        self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
884
885    def privmsg(self, target, text):
886        """Send a PRIVMSG command."""
887        self.send_raw("PRIVMSG %s :%s" % (target, text))
888
889    def privmsg_many(self, targets, text):
890        """Send a PRIVMSG command to multiple targets."""
891        target = ','.join(targets)
892        return self.privmsg(target, text)
893
894    def quit(self, message=""):
895        """Send a QUIT command."""
896        # Note that many IRC servers don't use your QUIT message
897        # unless you've been connected for at least 5 minutes!
898        self.send_raw("QUIT" + (message and (" :" + message)))
899
900    def send_raw(self, string):
901        """Send raw string to the server.
902
903        The string will be padded with appropriate CR LF.
904        """
905        # The string should not contain any carriage return other than the
906        # one added here.
907        if '\n' in string:
908            raise InvalidCharacters(
909                "Carriage returns not allowed in privmsg(text)")
910        bytes = string.encode('utf-8') + b'\r\n'
911        # According to the RFC http://tools.ietf.org/html/rfc2812#page-6,
912        # clients should not transmit more than 512 bytes.
913        if len(bytes) > 512:
914            raise MessageTooLong(
915                "Messages limited to 512 bytes including CR/LF")
916        if self.socket is None:
917            raise ServerNotConnectedError("Not connected.")
918        sender = getattr(self.socket, 'write', self.socket.send)
919        try:
920            sender(bytes)
921            log.debug("TO SERVER: %s", string)
922        except socket.error:
923            # Ouch!
924            self.disconnect("Connection reset by peer.")
925
926    def squit(self, server, comment=""):
927        """Send an SQUIT command."""
928        self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
929
930    def stats(self, statstype, server=""):
931        """Send a STATS command."""
932        self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
933
934    def time(self, server=""):
935        """Send a TIME command."""
936        self.send_raw("TIME" + (server and (" " + server)))
937
938    def topic(self, channel, new_topic=None):
939        """Send a TOPIC command."""
940        if new_topic is None:
941            self.send_raw("TOPIC " + channel)
942        else:
943            self.send_raw("TOPIC %s :%s" % (channel, new_topic))
944
945    def trace(self, target=""):
946        """Send a TRACE command."""
947        self.send_raw("TRACE" + (target and (" " + target)))
948
949    def user(self, username, realname):
950        """Send a USER command."""
951        self.send_raw("USER %s 0 * :%s" % (username, realname))
952
953    def userhost(self, nicks):
954        """Send a USERHOST command."""
955        self.send_raw("USERHOST " + ",".join(nicks))
956
957    def users(self, server=""):
958        """Send a USERS command."""
959        self.send_raw("USERS" + (server and (" " + server)))
960
961    def version(self, server=""):
962        """Send a VERSION command."""
963        self.send_raw("VERSION" + (server and (" " + server)))
964
965    def wallops(self, text):
966        """Send a WALLOPS command."""
967        self.send_raw("WALLOPS :" + text)
968
969    def who(self, target="", op=""):
970        """Send a WHO command."""
971        self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
972
973    def whois(self, targets):
974        """Send a WHOIS command."""
975        self.send_raw("WHOIS " + ",".join(always_iterable(targets)))
976
977    def whowas(self, nick, max="", server=""):
978        """Send a WHOWAS command."""
979        self.send_raw("WHOWAS %s%s%s" % (nick,
980                                         max and (" " + max),
981                                         server and (" " + server)))
982
983    def set_rate_limit(self, frequency):
984        """
985        Set a `frequency` limit (messages per second) for this connection.
986        Any attempts to send faster than this rate will block.
987        """
988        self.send_raw = Throttler(self.send_raw, frequency)
989
990    def set_keepalive(self, interval):
991        """
992        Set a keepalive to occur every ``interval`` on this connection.
993        """
994        pinger = functools.partial(self.ping, 'keep-alive')
995        self.reactor.execute_every(period=interval, function=pinger)
996
997
998class DCCConnectionError(IRCError):
999    pass
1000
1001
1002class DCCConnection(Connection):
1003    """
1004    A DCC (Direct Client Connection).
1005
1006    DCCConnection objects are instantiated by calling the dcc
1007    method on a Reactor object.
1008    """
1009    socket = None
1010
1011    def __init__(self, reactor, dcctype):
1012        super(DCCConnection, self).__init__(reactor)
1013        self.connected = 0
1014        self.passive = 0
1015        self.dcctype = dcctype
1016        self.peeraddress = None
1017        self.peerport = None
1018
1019    def connect(self, address, port):
1020        """Connect/reconnect to a DCC peer.
1021
1022        Arguments:
1023            address -- Host/IP address of the peer.
1024
1025            port -- The port number to connect to.
1026
1027        Returns the DCCConnection object.
1028        """
1029        self.peeraddress = socket.gethostbyname(address)
1030        self.peerport = port
1031        self.buffer = buffer.LineBuffer()
1032        self.handlers = {}
1033        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1034        self.passive = 0
1035        try:
1036            self.socket.connect((self.peeraddress, self.peerport))
1037        except socket.error as x:
1038            raise DCCConnectionError("Couldn't connect to socket: %s" % x)
1039        self.connected = 1
1040        self.reactor._on_connect(self.socket)
1041        return self
1042
1043    def listen(self):
1044        """Wait for a connection/reconnection from a DCC peer.
1045
1046        Returns the DCCConnection object.
1047
1048        The local IP address and port are available as
1049        self.localaddress and self.localport.  After connection from a
1050        peer, the peer address and port are available as
1051        self.peeraddress and self.peerport.
1052        """
1053        self.buffer = buffer.LineBuffer()
1054        self.handlers = {}
1055        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1056        self.passive = 1
1057        try:
1058            self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
1059            self.localaddress, self.localport = self.socket.getsockname()
1060            self.socket.listen(10)
1061        except socket.error as x:
1062            raise DCCConnectionError("Couldn't bind socket: %s" % x)
1063        return self
1064
1065    def disconnect(self, message=""):
1066        """Hang up the connection and close the object.
1067
1068        Arguments:
1069
1070            message -- Quit message.
1071        """
1072        if not self.connected:
1073            return
1074
1075        self.connected = 0
1076        try:
1077            self.socket.shutdown(socket.SHUT_WR)
1078            self.socket.close()
1079        except socket.error:
1080            pass
1081        del self.socket
1082        self.reactor._handle_event(
1083            self,
1084            Event("dcc_disconnect", self.peeraddress, "", [message]))
1085        self.reactor._remove_connection(self)
1086
1087    def process_data(self):
1088        """[Internal]"""
1089
1090        if self.passive and not self.connected:
1091            conn, (self.peeraddress, self.peerport) = self.socket.accept()
1092            self.socket.close()
1093            self.socket = conn
1094            self.connected = 1
1095            log.debug("DCC connection from %s:%d", self.peeraddress,
1096                self.peerport)
1097            self.reactor._handle_event(
1098                self,
1099                Event("dcc_connect", self.peeraddress, None, None))
1100            return
1101
1102        try:
1103            new_data = self.socket.recv(2 ** 14)
1104        except socket.error:
1105            # The server hung up.
1106            self.disconnect("Connection reset by peer")
1107            return
1108        if not new_data:
1109            # Read nothing: connection must be down.
1110            self.disconnect("Connection reset by peer")
1111            return
1112
1113        if self.dcctype == "chat":
1114            self.buffer.feed(new_data)
1115
1116            chunks = list(self.buffer)
1117
1118            if len(self.buffer) > 2 ** 14:
1119                # Bad peer! Naughty peer!
1120                log.info("Received >16k from a peer without a newline; "
1121                    "disconnecting.")
1122                self.disconnect()
1123                return
1124        else:
1125            chunks = [new_data]
1126
1127        command = "dccmsg"
1128        prefix = self.peeraddress
1129        target = None
1130        for chunk in chunks:
1131            log.debug("FROM PEER: %s", chunk)
1132            arguments = [chunk]
1133            log.debug("command: %s, source: %s, target: %s, arguments: %s",
1134                command, prefix, target, arguments)
1135            event = Event(command, prefix, target, arguments)
1136            self.reactor._handle_event(self, event)
1137
1138    def privmsg(self, text):
1139        """
1140        Send text to DCC peer.
1141
1142        The text will be padded with a newline if it's a DCC CHAT session.
1143        """
1144        if self.dcctype == 'chat':
1145            text += '\n'
1146        bytes = text.encode('utf-8')
1147        return self.send_bytes(bytes)
1148
1149    def send_bytes(self, bytes):
1150        """
1151        Send data to DCC peer.
1152        """
1153        try:
1154            self.socket.send(bytes)
1155            log.debug("TO PEER: %r\n", bytes)
1156        except socket.error:
1157            self.disconnect("Connection reset by peer.")
1158
1159
1160class SimpleIRCClient(object):
1161    """A simple single-server IRC client class.
1162
1163    This is an example of an object-oriented wrapper of the IRC
1164    framework.  A real IRC client can be made by subclassing this
1165    class and adding appropriate methods.
1166
1167    The method on_join will be called when a "join" event is created
1168    (which is done when the server sends a JOIN messsage/command),
1169    on_privmsg will be called for "privmsg" events, and so on.  The
1170    handler methods get two arguments: the connection object (same as
1171    self.connection) and the event object.
1172
1173    Functionally, any of the event names in `events.py` my be subscribed
1174    to by prefixing them with `on_`, and creating a function of that
1175    name in the child-class of `SimpleIRCClient`. When the event of
1176    `event_name` is received, the appropriately named method will be
1177    called (if it exists) by runtime class introspection.
1178
1179    See `_dispatcher()`, which takes the event name, postpends it to
1180    `on_`, and then attemps to look up the class member function by
1181    name and call it.
1182
1183    Instance attributes that can be used by sub classes:
1184
1185        reactor -- The Reactor instance.
1186
1187        connection -- The ServerConnection instance.
1188
1189        dcc_connections -- A list of DCCConnection instances.
1190    """
1191    reactor_class = Reactor
1192
1193    def __init__(self):
1194        self.reactor = self.reactor_class()
1195        self.connection = self.reactor.server()
1196        self.dcc_connections = []
1197        self.reactor.add_global_handler("all_events", self._dispatcher, -10)
1198        self.reactor.add_global_handler("dcc_disconnect",
1199            self._dcc_disconnect, -10)
1200
1201    def _dispatcher(self, connection, event):
1202        """
1203        Dispatch events to on_<event.type> method, if present.
1204        """
1205        log.debug("_dispatcher: %s", event.type)
1206
1207        do_nothing = lambda c, e: None
1208        method = getattr(self, "on_" + event.type, do_nothing)
1209        method(connection, event)
1210
1211    def _dcc_disconnect(self, c, e):
1212        self.dcc_connections.remove(c)
1213
1214    def connect(self, *args, **kwargs):
1215        """Connect using the underlying connection"""
1216        self.connection.connect(*args, **kwargs)
1217
1218    def dcc_connect(self, address, port, dcctype="chat"):
1219        """Connect to a DCC peer.
1220
1221        Arguments:
1222
1223            address -- IP address of the peer.
1224
1225            port -- Port to connect to.
1226
1227        Returns a DCCConnection instance.
1228        """
1229        dcc = self.reactor.dcc(dcctype)
1230        self.dcc_connections.append(dcc)
1231        dcc.connect(address, port)
1232        return dcc
1233
1234    def dcc_listen(self, dcctype="chat"):
1235        """Listen for connections from a DCC peer.
1236
1237        Returns a DCCConnection instance.
1238        """
1239        dcc = self.reactor.dcc(dcctype)
1240        self.dcc_connections.append(dcc)
1241        dcc.listen()
1242        return dcc
1243
1244    def start(self):
1245        """Start the IRC client."""
1246        self.reactor.process_forever()
1247
1248
1249class Event(object):
1250    "An IRC event."
1251    def __init__(self, type, source, target, arguments=None, tags=None):
1252        """
1253        Initialize an Event.
1254
1255        Arguments:
1256
1257            type -- A string describing the event.
1258
1259            source -- The originator of the event (a nick mask or a server).
1260
1261            target -- The target of the event (a nick or a channel).
1262
1263            arguments -- Any event-specific arguments.
1264        """
1265        self.type = type
1266        self.source = source
1267        self.target = target
1268        if arguments is None:
1269            arguments = []
1270        self.arguments = arguments
1271        if tags is None:
1272            tags = []
1273        self.tags = tags
1274
1275def is_channel(string):
1276    """Check if a string is a channel name.
1277
1278    Returns true if the argument is a channel name, otherwise false.
1279    """
1280    return string and string[0] in "#&+!"
1281
1282def ip_numstr_to_quad(num):
1283    """
1284    Convert an IP number as an integer given in ASCII
1285    representation to an IP address string.
1286
1287    >>> ip_numstr_to_quad('3232235521')
1288    '192.168.0.1'
1289    >>> ip_numstr_to_quad(3232235521)
1290    '192.168.0.1'
1291    """
1292    n = int(num)
1293    packed = struct.pack('>L', n)
1294    bytes = struct.unpack('BBBB', packed)
1295    return ".".join(map(str, bytes))
1296
1297def ip_quad_to_numstr(quad):
1298    """
1299    Convert an IP address string (e.g. '192.168.0.1') to an IP
1300    number as a base-10 integer given in ASCII representation.
1301
1302    >>> ip_quad_to_numstr('192.168.0.1')
1303    '3232235521'
1304    """
1305    bytes = map(int, quad.split("."))
1306    packed = struct.pack('BBBB', *bytes)
1307    return str(struct.unpack('>L', packed)[0])
1308
1309class NickMask(six.text_type):
1310    """
1311    A nickmask (the source of an Event)
1312
1313    >>> nm = NickMask('pinky!username@example.com')
1314    >>> print(nm.nick)
1315    pinky
1316
1317    >>> print(nm.host)
1318    example.com
1319
1320    >>> print(nm.user)
1321    username
1322
1323    >>> isinstance(nm, six.text_type)
1324    True
1325
1326    >>> nm = 'красный!red@yahoo.ru'
1327    >>> if not six.PY3: nm = nm.decode('utf-8')
1328    >>> nm = NickMask(nm)
1329
1330    >>> isinstance(nm.nick, six.text_type)
1331    True
1332
1333    Some messages omit the userhost. In that case, None is returned.
1334
1335    >>> nm = NickMask('irc.server.net')
1336    >>> print(nm.nick)
1337    irc.server.net
1338    >>> nm.userhost
1339    >>> nm.host
1340    >>> nm.user
1341    """
1342    @classmethod
1343    def from_params(cls, nick, user, host):
1344        return cls('{nick}!{user}@{host}'.format(**vars()))
1345
1346    @property
1347    def nick(self):
1348        nick, sep, userhost = self.partition("!")
1349        return nick
1350
1351    @property
1352    def userhost(self):
1353        nick, sep, userhost = self.partition("!")
1354        return userhost or None
1355
1356    @property
1357    def host(self):
1358        nick, sep, userhost = self.partition("!")
1359        user, sep, host = userhost.partition('@')
1360        return host or None
1361
1362    @property
1363    def user(self):
1364        nick, sep, userhost = self.partition("!")
1365        user, sep, host = userhost.partition('@')
1366        return user or None
1367
1368    @classmethod
1369    def from_group(cls, group):
1370        return cls(group) if group else None
1371
1372
1373def _ping_ponger(connection, event):
1374    "A global handler for the 'ping' event"
1375    connection.pong(event.target)
1376