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