Browse Source

Some fixes for Python 3 port

Page Libre 4 years ago
parent
commit
91741757ed
2 changed files with 363 additions and 192 deletions
  1. 104 91
      ircbot.py
  2. 259 101
      irclib.py

+ 104 - 91
ircbot.py View File

@@ -1,4 +1,7 @@
1
-# Copyright (C) 1999--2002  Joel Rosdahl
1
+#! -*- coding: utf-8 -*-
2
+
3
+# Copyright (C) 1999-2002  Joel Rosdahl
4
+# Portions Copyring © 2011-2012 Jason R. Coombs
2 5
 #
3 6
 # This library is free software; you can redistribute it and/or
4 7
 # modify it under the terms of the GNU Lesser General Public
@@ -15,24 +18,20 @@
15 18
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
16 19
 #
17 20
 # Joel Rosdahl <joel@rosdahl.net>
18
-#
19
-# $Id: ircbot.py,v 1.23 2008/09/11 07:38:30 keltus Exp $
20 21
 
21
-"""ircbot -- Simple IRC bot library.
22
+"""
23
+ircbot -- Simple IRC bot library.
22 24
 
23 25
 This module contains a single-server IRC bot class that can be used to
24 26
 write simpler bots.
25 27
 """
26 28
 
27 29
 import sys
28
-from UserDict import UserDict
29 30
 
30
-from irclib import SimpleIRCClient
31
-from irclib import nm_to_n, irc_lower, all_events
32
-from irclib import parse_channel_modes, is_channel
33
-from irclib import ServerConnectionError
31
+import irclib
32
+from irclib import nm_to_n
34 33
 
35
-class SingleServerIRCBot(SimpleIRCClient):
34
+class SingleServerIRCBot(irclib.SimpleIRCClient):
36 35
     """A single-server IRC bot class.
37 36
 
38 37
     The bot tries to reconnect if it is disconnected.
@@ -62,11 +61,11 @@ class SingleServerIRCBot(SimpleIRCClient):
62 61
             connections.
63 62
         """
64 63
 
65
-        SimpleIRCClient.__init__(self)
64
+        super(SingleServerIRCBot, self).__init__()
66 65
         self.channels = IRCDict()
67 66
         self.server_list = server_list
68 67
         if not reconnection_interval or reconnection_interval < 0:
69
-            reconnection_interval = 2**31
68
+            reconnection_interval = 2 ** 31
70 69
         self.reconnection_interval = reconnection_interval
71 70
 
72 71
         self._nickname = nickname
@@ -75,7 +74,8 @@ class SingleServerIRCBot(SimpleIRCClient):
75 74
                   "namreply", "nick", "part", "quit"]:
76 75
             self.connection.add_global_handler(i,
77 76
                                                getattr(self, "_on_" + i),
78
-                                               -10)
77
+                                               -20)
78
+
79 79
     def _connected_checker(self):
80 80
         """[Internal]"""
81 81
         if not self.connection.is_connected():
@@ -94,7 +94,7 @@ class SingleServerIRCBot(SimpleIRCClient):
94 94
                          self._nickname,
95 95
                          password,
96 96
                          ircname=self._realname)
97
-        except ServerConnectionError:
97
+        except irclib.ServerConnectionError:
98 98
             pass
99 99
 
100 100
     def _on_disconnect(self, c, e):
@@ -123,9 +123,9 @@ class SingleServerIRCBot(SimpleIRCClient):
123 123
 
124 124
     def _on_mode(self, c, e):
125 125
         """[Internal]"""
126
-        modes = parse_channel_modes(" ".join(e.arguments()))
126
+        modes = irclib.parse_channel_modes(" ".join(e.arguments()))
127 127
         t = e.target()
128
-        if is_channel(t):
128
+        if irclib.is_channel(t):
129 129
             ch = self.channels[t]
130 130
             for mode in modes:
131 131
                 if mode[0] == "+":
@@ -244,71 +244,10 @@ class SingleServerIRCBot(SimpleIRCClient):
244 244
     def start(self):
245 245
         """Start the bot."""
246 246
         self._connect()
247
-        SimpleIRCClient.start(self)
248
-
247
+        super(SingleServerIRCBot, self).start()
249 248
 
250
-class IRCDict:
251
-    """A dictionary suitable for storing IRC-related things.
252 249
 
253
-    Dictionary keys a and b are considered equal if and only if
254
-    irc_lower(a) == irc_lower(b)
255
-
256
-    Otherwise, it should behave exactly as a normal dictionary.
257
-    """
258
-
259
-    def __init__(self, dict=None):
260
-        self.data = {}
261
-        self.canon_keys = {}  # Canonical keys
262
-        if dict is not None:
263
-            self.update(dict)
264
-    def __repr__(self):
265
-        return repr(self.data)
266
-    def __cmp__(self, dict):
267
-        if isinstance(dict, IRCDict):
268
-            return cmp(self.data, dict.data)
269
-        else:
270
-            return cmp(self.data, dict)
271
-    def __len__(self):
272
-        return len(self.data)
273
-    def __getitem__(self, key):
274
-        return self.data[self.canon_keys[irc_lower(key)]]
275
-    def __setitem__(self, key, item):
276
-        if key in self:
277
-            del self[key]
278
-        self.data[key] = item
279
-        self.canon_keys[irc_lower(key)] = key
280
-    def __delitem__(self, key):
281
-        ck = irc_lower(key)
282
-        del self.data[self.canon_keys[ck]]
283
-        del self.canon_keys[ck]
284
-    def __iter__(self):
285
-        return iter(self.data)
286
-    def __contains__(self, key):
287
-        return self.has_key(key)
288
-    def clear(self):
289
-        self.data.clear()
290
-        self.canon_keys.clear()
291
-    def copy(self):
292
-        if self.__class__ is UserDict:
293
-            return UserDict(self.data)
294
-        import copy
295
-        return copy.copy(self)
296
-    def keys(self):
297
-        return self.data.keys()
298
-    def items(self):
299
-        return self.data.items()
300
-    def values(self):
301
-        return self.data.values()
302
-    def has_key(self, key):
303
-        return irc_lower(key) in self.canon_keys
304
-    def update(self, dict):
305
-        for k, v in dict.items():
306
-            self.data[k] = v
307
-    def get(self, key, failobj=None):
308
-        return self.data.get(key, failobj)
309
-
310
-
311
-class Channel:
250
+class Channel(object):
312 251
     """A class for keeping information about an IRC channel.
313 252
 
314 253
     This class can be improved a lot.
@@ -354,14 +293,15 @@ class Channel:
354 293
                 del d[nick]
355 294
 
356 295
     def change_nick(self, before, after):
357
-        self.userdict[after] = 1
358
-        del self.userdict[before]
296
+        self.userdict[after] = self.userdict.pop(before)
359 297
         if before in self.operdict:
360
-            self.operdict[after] = 1
361
-            del self.operdict[before]
298
+            self.operdict[after] = self.operdict.pop(before)
362 299
         if before in self.voiceddict:
363
-            self.voiceddict[after] = 1
364
-            del self.voiceddict[before]
300
+            self.voiceddict[after] = self.voiceddict.pop(before)
301
+
302
+    def set_userdetails(self, nick, details):
303
+        if nick in self.userdict:
304
+            self.userdict[nick] = details
365 305
 
366 306
     def set_mode(self, mode, value=None):
367 307
         """Set mode on the channel.
@@ -424,15 +364,88 @@ class Channel:
424 364
 
425 365
     def limit(self):
426 366
         if self.has_limit():
427
-            return self.modes[l]
367
+            return self.modes["l"]
428 368
         else:
429 369
             return None
430 370
 
431 371
     def has_key(self):
432 372
         return self.has_mode("k")
433 373
 
434
-    def key(self):
435
-        if self.has_key():
436
-            return self.modes["k"]
437
-        else:
438
-            return None
374
+# from jaraco.util.dictlib
375
+class KeyTransformingDict(dict):
376
+    """
377
+    A dict subclass that transforms the keys before they're used.
378
+    Subclasses may override the default key_transform to customize behavior.
379
+    """
380
+    @staticmethod
381
+    def key_transform(key):
382
+        return key
383
+
384
+    def __init__(self, *args, **kargs):
385
+        super(KeyTransformingDict, self).__init__()
386
+        # build a dictionary using the default constructs
387
+        d = dict(*args, **kargs)
388
+        # build this dictionary using transformed keys.
389
+        for item in d.items():
390
+            self.__setitem__(*item)
391
+
392
+    def __setitem__(self, key, val):
393
+        key = self.key_transform(key)
394
+        super(KeyTransformingDict, self).__setitem__(key, val)
395
+
396
+    def __getitem__(self, key):
397
+        key = self.key_transform(key)
398
+        return super(KeyTransformingDict, self).__getitem__(key)
399
+
400
+    def __contains__(self, key):
401
+        key = self.key_transform(key)
402
+        return super(KeyTransformingDict, self).__contains__(key)
403
+
404
+    def __delitem__(self, key):
405
+        key = self.key_transform(key)
406
+        return super(KeyTransformingDict, self).__delitem__(key)
407
+
408
+    def setdefault(self, key, *args, **kwargs):
409
+        key = self.key_transform(key)
410
+        return super(KeyTransformingDict, self).setdefault(key, *args, **kwargs)
411
+
412
+    def pop(self, key, *args, **kwargs):
413
+        key = self.key_transform(key)
414
+        return super(KeyTransformingDict, self).pop(key, *args, **kwargs)
415
+
416
+class IRCDict(KeyTransformingDict):
417
+    """
418
+    A dictionary of names whose keys are case-insensitive according to the
419
+    IRC RFC rules.
420
+
421
+    >>> d = IRCDict({'[This]': 'that'}, A='foo')
422
+
423
+    The dict maintains the original case:
424
+    >>> d.keys()
425
+    ['A', '[This]']
426
+
427
+    But the keys can be referenced with a different case
428
+    >>> d['a']
429
+    'foo'
430
+
431
+    >>> d['{this}']
432
+    'that'
433
+
434
+    >>> d['{THIS}']
435
+    'that'
436
+
437
+    >>> '{thiS]' in d
438
+    True
439
+
440
+    This should work for operations like delete and pop as well.
441
+    >>> d.pop('A')
442
+    'foo'
443
+    >>> del d['{This}']
444
+    >>> len(d)
445
+    0
446
+    """
447
+    @staticmethod
448
+    def key_transform(key):
449
+        if isinstance(key, basestring):
450
+            key = irclib.IRCFoldedCase(key)
451
+        return key

+ 259 - 101
irclib.py View File

@@ -1,4 +1,7 @@
1
-# Copyright (C) 1999--2002  Joel Rosdahl
1
+# -*- coding: utf-8 -*-
2
+
3
+# Copyright (C) 1999-2002  Joel Rosdahl
4
+# Portions Copyright © 2011 Jason R. Coombs
2 5
 #
3 6
 # This library is free software; you can redistribute it and/or
4 7
 # modify it under the terms of the GNU Lesser General Public
@@ -15,10 +18,9 @@
15 18
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
16 19
 #
17 20
 # keltus <keltus@users.sourceforge.net>
18
-#
19
-# $Id: irclib.py,v 1.47 2008/09/25 22:00:59 keltus Exp $
20 21
 
21
-"""irclib -- Internet Relay Chat (IRC) protocol client library.
22
+"""
23
+irclib -- Internet Relay Chat (IRC) protocol client library.
22 24
 
23 25
 This library is intended to encapsulate the IRC protocol at a quite
24 26
 low level.  It provides an event-driven IRC client framework.  It has
@@ -66,12 +68,19 @@ import re
66 68
 import select
67 69
 import socket
68 70
 import string
69
-import sys
70 71
 import time
71 72
 import types
73
+import ssl as ssl_mod
74
+import datetime
75
+
76
+try:
77
+    import pkg_resources
78
+    _pkg = pkg_resources.require('python-irclib')[0]
79
+    VERSION = tuple(int(res) for res in re.findall('\d+', _pkg.version))
80
+except Exception:
81
+    VERSION = ()
72 82
 
73
-VERSION = 0, 4, 8
74
-DEBUG = 0
83
+DEBUG = False
75 84
 
76 85
 # TODO
77 86
 # ----
@@ -93,7 +102,7 @@ class IRCError(Exception):
93 102
     pass
94 103
 
95 104
 
96
-class IRC:
105
+class IRC(object):
97 106
     """Class that handles one or several IRC server connections.
98 107
 
99 108
     When an IRC object has been instantiated, it can be used to create
@@ -112,12 +121,12 @@ class IRC:
112 121
 
113 122
         irc = irclib.IRC()
114 123
         server = irc.server()
115
-        server.connect(\"irc.some.where\", 6667, \"my_nickname\")
116
-        server.privmsg(\"a_nickname\", \"Hi there!\")
124
+        server.connect("irc.some.where", 6667, "my_nickname")
125
+        server.privmsg("a_nickname", "Hi there!")
117 126
         irc.process_forever()
118 127
 
119 128
     This will connect to the IRC server irc.some.where on port 6667
120
-    using the nickname my_nickname and send the message \"Hi there!\"
129
+    using the nickname my_nickname and send the message "Hi there!"
121 130
     to the nickname a_nickname.
122 131
     """
123 132
 
@@ -157,7 +166,7 @@ class IRC:
157 166
         self.fn_to_add_timeout = fn_to_add_timeout
158 167
         self.connections = []
159 168
         self.handlers = {}
160
-        self.delayed_commands = [] # list of tuples in the format (time, function, arguments)
169
+        self.delayed_commands = []  # list of DelayedCommands
161 170
 
162 171
         self.add_global_handler("ping", _ping_ponger, -42)
163 172
 
@@ -187,13 +196,14 @@ class IRC:
187 196
 
188 197
         See documentation for IRC.__init__.
189 198
         """
190
-        t = time.time()
191 199
         while self.delayed_commands:
192
-            if t >= self.delayed_commands[0][0]:
193
-                self.delayed_commands[0][1](*self.delayed_commands[0][2])
194
-                del self.delayed_commands[0]
195
-            else:
200
+            command = self.delayed_commands[0]
201
+            if not command.due():
196 202
                 break
203
+            command.function(*command.arguments)
204
+            if isinstance(command, PeriodicCommand):
205
+                self._schedule_command(command.next())
206
+            del self.delayed_commands[0]
197 207
 
198 208
     def process_once(self, timeout=0):
199 209
         """Process data from connections once.
@@ -252,7 +262,7 @@ class IRC:
252 262
 
253 263
         The handler functions are called in priority order (lowest
254 264
         number is highest priority).  If a handler function returns
255
-        \"NO MORE\", no more handlers will be called.
265
+        "NO MORE", no more handlers will be called.
256 266
         """
257 267
         if not event in self.handlers:
258 268
             self.handlers[event] = []
@@ -264,7 +274,6 @@ class IRC:
264 274
         Arguments:
265 275
 
266 276
             event -- Event type (a string).
267
-
268 277
             handler -- Callback function.
269 278
 
270 279
         Returns 1 on success, otherwise 0.
@@ -281,28 +290,39 @@ class IRC:
281 290
 
282 291
         Arguments:
283 292
 
284
-            at -- Execute at this time (standard \"time_t\" time).
285
-
293
+            at -- Execute at this time (standard "time_t" time).
286 294
             function -- Function to call.
287
-
288 295
             arguments -- Arguments to give the function.
289 296
         """
290
-        self.execute_delayed(at-time.time(), function, arguments)
297
+        command = DelayedCommand.at_time(at, function, arguments)
298
+        self._schedule_command(command)
291 299
 
292 300
     def execute_delayed(self, delay, function, arguments=()):
293
-        """Execute a function after a specified time.
294
-
295
-        Arguments:
301
+        """
302
+        Execute a function after a specified time.
296 303
 
297
-            delay -- How many seconds to wait.
304
+        delay -- How many seconds to wait.
305
+        function -- Function to call.
306
+        arguments -- Arguments to give the function.
307
+        """
308
+        command = DelayedCommand(delay, function, arguments)
309
+        self._schedule_command(command)
298 310
 
299
-            function -- Function to call.
311
+    def execute_every(self, period, function, arguments=()):
312
+        """
313
+        Execute a function every 'period' seconds.
300 314
 
301
-            arguments -- Arguments to give the function.
315
+        period -- How often to run (always waits this long for first).
316
+        function -- Function to call.
317
+        arguments -- Arguments to give the function.
302 318
         """
303
-        bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
319
+        command = PeriodicCommand(period, function, arguments)
320
+        self._schedule_command(command)
321
+
322
+    def _schedule_command(self, command):
323
+        bisect.insort(self.delayed_commands, command)
304 324
         if self.fn_to_add_timeout:
305
-            self.fn_to_add_timeout(delay)
325
+            self.fn_to_add_timeout(total_seconds(command.delay))
306 326
 
307 327
     def dcc(self, dcctype="chat"):
308 328
         """Creates and returns a DCCConnection object.
@@ -321,7 +341,8 @@ class IRC:
321 341
     def _handle_event(self, connection, event):
322 342
         """[Internal]"""
323 343
         h = self.handlers
324
-        for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
344
+        th = sorted(h.get("all_events", []) + h.get(event.eventtype(), []))
345
+        for handler in th:
325 346
             if handler[1](connection, event) == "NO MORE":
326 347
                 return
327 348
 
@@ -331,9 +352,48 @@ class IRC:
331 352
         if self.fn_to_remove_socket:
332 353
             self.fn_to_remove_socket(connection._get_socket())
333 354
 
355
+class DelayedCommand(datetime.datetime):
356
+    """
357
+    A command to be executed after some delay (seconds or timedelta).
358
+    """
359
+    def __new__(cls, delay, function, arguments):
360
+        if not isinstance(delay, datetime.timedelta):
361
+            delay = datetime.timedelta(seconds=delay)
362
+        at = datetime.datetime.utcnow() + delay
363
+        cmd = datetime.datetime.__new__(DelayedCommand, at.year,
364
+            at.month, at.day, at.hour, at.minute, at.second,
365
+            at.microsecond, at.tzinfo)
366
+        cmd.delay = delay
367
+        cmd.function = function
368
+        cmd.arguments = arguments
369
+        return cmd
370
+
371
+    def at_time(cls, at, function, arguments):
372
+        """
373
+        Construct a DelayedCommand to come due at `at`, where `at` may be
374
+        a datetime or timestamp.
375
+        """
376
+        if isinstance(at, int):
377
+            at = datetime.datetime.utcfromtimestamp(at)
378
+        delay = at - datetime.datetime.utcnow()
379
+        return cls(delay, function, arguments)
380
+    at_time = classmethod(at_time)
381
+
382
+    def due(self):
383
+        return datetime.datetime.utcnow() >= self
384
+
385
+class PeriodicCommand(DelayedCommand):
386
+    """
387
+    Like a deferred command, but expect this command to run every delay
388
+    seconds.
389
+    """
390
+    def next(self):
391
+        return PeriodicCommand(self.delay, self.function,
392
+            self.arguments)
393
+
334 394
 _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
335 395
 
336
-class Connection:
396
+class Connection(object):
337 397
     """Base class for IRC connections.
338 398
 
339 399
     Must be overridden.
@@ -342,7 +402,7 @@ class Connection:
342 402
         self.irclibobj = irclibobj
343 403
 
344 404
     def _get_socket():
345
-        raise IRCError, "Not overridden"
405
+        raise IRCError("Not overridden")
346 406
 
347 407
     ##############################
348 408
     ### Convenience wrappers.
@@ -353,6 +413,8 @@ class Connection:
353 413
     def execute_delayed(self, delay, function, arguments=()):
354 414
         self.irclibobj.execute_delayed(delay, function, arguments)
355 415
 
416
+    def execute_every(self, period, function, arguments=()):
417
+        self.irclibobj.execute_every(period, function, arguments)
356 418
 
357 419
 class ServerConnectionError(IRCError):
358 420
     pass
@@ -373,7 +435,7 @@ class ServerConnection(Connection):
373 435
     """
374 436
 
375 437
     def __init__(self, irclibobj):
376
-        Connection.__init__(self, irclibobj)
438
+        super(ServerConnection, self).__init__(irclibobj)
377 439
         self.connected = 0  # Not connected yet.
378 440
         self.socket = None
379 441
         self.ssl = None
@@ -432,11 +494,11 @@ class ServerConnection(Connection):
432 494
             self.socket.bind((self.localaddress, self.localport))
433 495
             self.socket.connect((self.server, self.port))
434 496
             if ssl:
435
-                self.ssl = socket.ssl(self.socket)
436
-        except socket.error, x:
497
+                self.ssl = ssl_mod.wrap_socket(self.socket)
498
+        except socket.error as x:
437 499
             self.socket.close()
438 500
             self.socket = None
439
-            raise ServerConnectionError, "Couldn't connect to socket: %s" % x
501
+            raise ServerConnectionError("Couldn't connect to socket: %s" % x)
440 502
         self.connected = 1
441 503
         if self.irclibobj.fn_to_add_socket:
442 504
             self.irclibobj.fn_to_add_socket(self.socket)
@@ -488,10 +550,10 @@ class ServerConnection(Connection):
488 550
 
489 551
         try:
490 552
             if self.ssl:
491
-                new_data = self.ssl.read(2**14)
553
+                new_data = self.ssl.read(2 ** 14)
492 554
             else:
493
-                new_data = self.socket.recv(2**14)
494
-        except socket.error, x:
555
+                new_data = self.socket.recv(2 ** 14)
556
+        except socket.error:
495 557
             # The server hung up.
496 558
             self.disconnect("Connection reset by peer")
497 559
             return
@@ -500,14 +562,14 @@ class ServerConnection(Connection):
500 562
             self.disconnect("Connection reset by peer")
501 563
             return
502 564
 
503
-        lines = _linesep_regexp.split(self.previous_buffer + new_data)
565
+        lines = _linesep_regexp.split(self.previous_buffer + str(new_data))
504 566
 
505 567
         # Save the last, unfinished line.
506 568
         self.previous_buffer = lines.pop()
507 569
 
508 570
         for line in lines:
509 571
             if DEBUG:
510
-                print "FROM SERVER:", line
572
+                print("FROM SERVER:", line)
511 573
 
512 574
             if not line:
513 575
                 continue
@@ -569,15 +631,15 @@ class ServerConnection(Connection):
569 631
 
570 632
                         m = list(m)
571 633
                         if DEBUG:
572
-                            print "command: %s, source: %s, target: %s, arguments: %s" % (
573
-                                command, prefix, target, m)
634
+                            print("command: %s, source: %s, target: %s, arguments: %s" % (
635
+                                command, prefix, target, m))
574 636
                         self._handle_event(Event(command, prefix, target, m))
575 637
                         if command == "ctcp" and m[0] == "ACTION":
576 638
                             self._handle_event(Event("action", prefix, target, m[1:]))
577 639
                     else:
578 640
                         if DEBUG:
579
-                            print "command: %s, source: %s, target: %s, arguments: %s" % (
580
-                                command, prefix, target, [m])
641
+                            print("command: %s, source: %s, target: %s, arguments: %s" % (
642
+                                command, prefix, target, [m]))
581 643
                         self._handle_event(Event(command, prefix, target, [m]))
582 644
             else:
583 645
                 target = None
@@ -595,8 +657,8 @@ class ServerConnection(Connection):
595 657
                         command = "umode"
596 658
 
597 659
                 if DEBUG:
598
-                    print "command: %s, source: %s, target: %s, arguments: %s" % (
599
-                        command, prefix, target, arguments)
660
+                    print("command: %s, source: %s, target: %s, arguments: %s" % (
661
+                        command, prefix, target, arguments))
600 662
                 self._handle_event(Event(command, prefix, target, arguments))
601 663
 
602 664
     def _handle_event(self, event):
@@ -660,7 +722,7 @@ class ServerConnection(Connection):
660 722
 
661 723
         try:
662 724
             self.socket.close()
663
-        except socket.error, x:
725
+        except socket.error:
664 726
             pass
665 727
         self.socket = None
666 728
         self._handle_event(Event("disconnect", self.server, "", [message]))
@@ -743,10 +805,13 @@ class ServerConnection(Connection):
743 805
 
744 806
     def part(self, channels, message=""):
745 807
         """Send a PART command."""
746
-        if type(channels) == types.StringType:
747
-            self.send_raw("PART " + channels + (message and (" " + message)))
748
-        else:
749
-            self.send_raw("PART " + ",".join(channels) + (message and (" " + message)))
808
+        channels = always_iterable(channels)
809
+        cmd_parts = [
810
+            'PART',
811
+            ','.join(channels),
812
+        ]
813
+        if message: cmd_parts.append(message)
814
+        self.send_raw(' '.join(cmd_parts))
750 815
 
751 816
     def pass_(self, password):
752 817
         """Send a PASS command."""
@@ -780,20 +845,19 @@ class ServerConnection(Connection):
780 845
         """Send raw string to the server.
781 846
 
782 847
         The string will be padded with appropriate CR LF.
783
-        From here everything need to be encoded in str from utf-8
784 848
         """
785
-        string=string.encode('utf-8')
786
-
787 849
         if self.socket is None:
788
-            raise ServerNotConnectedError, "Not connected."
850
+            raise ServerNotConnectedError("Not connected.")
789 851
         try:
790 852
             if self.ssl:
791 853
                 self.ssl.write(string + "\r\n")
792 854
             else:
793
-                self.socket.send(string + "\r\n")
855
+                #print(type(string + "\r\n"))
856
+                obj = string + "\r\n"
857
+                self.socket.send(obj.encode())
794 858
             if DEBUG:
795
-                print "TO SERVER:", string
796
-        except socket.error, x:
859
+                print("TO SERVER:", string)
860
+        except socket.error:
797 861
             # Ouch!
798 862
             self.disconnect("Connection reset by peer.")
799 863
 
@@ -865,7 +929,7 @@ class DCCConnection(Connection):
865 929
     method on an IRC object.
866 930
     """
867 931
     def __init__(self, irclibobj, dcctype):
868
-        Connection.__init__(self, irclibobj)
932
+        super(DCCConnection, self).__init__(irclibobj)
869 933
         self.connected = 0
870 934
         self.passive = 0
871 935
         self.dcctype = dcctype
@@ -891,8 +955,8 @@ class DCCConnection(Connection):
891 955
         self.passive = 0
892 956
         try:
893 957
             self.socket.connect((self.peeraddress, self.peerport))
894
-        except socket.error, x:
895
-            raise DCCConnectionError, "Couldn't connect to socket: %s" % x
958
+        except socket.error as x:
959
+            raise DCCConnectionError("Couldn't connect to socket: %s" % x)
896 960
         self.connected = 1
897 961
         if self.irclibobj.fn_to_add_socket:
898 962
             self.irclibobj.fn_to_add_socket(self.socket)
@@ -916,8 +980,8 @@ class DCCConnection(Connection):
916 980
             self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
917 981
             self.localaddress, self.localport = self.socket.getsockname()
918 982
             self.socket.listen(10)
919
-        except socket.error, x:
920
-            raise DCCConnectionError, "Couldn't bind socket: %s" % x
983
+        except socket.error as x:
984
+            raise DCCConnectionError("Couldn't bind socket: %s" % x)
921 985
         return self
922 986
 
923 987
     def disconnect(self, message=""):
@@ -933,7 +997,7 @@ class DCCConnection(Connection):
933 997
         self.connected = 0
934 998
         try:
935 999
             self.socket.close()
936
-        except socket.error, x:
1000
+        except socket.error:
937 1001
             pass
938 1002
         self.socket = None
939 1003
         self.irclibobj._handle_event(
@@ -950,16 +1014,16 @@ class DCCConnection(Connection):
950 1014
             self.socket = conn
951 1015
             self.connected = 1
952 1016
             if DEBUG:
953
-                print "DCC connection from %s:%d" % (
954
-                    self.peeraddress, self.peerport)
1017
+                print("DCC connection from %s:%d" % (
1018
+                    self.peeraddress, self.peerport))
955 1019
             self.irclibobj._handle_event(
956 1020
                 self,
957 1021
                 Event("dcc_connect", self.peeraddress, None, None))
958 1022
             return
959 1023
 
960 1024
         try:
961
-            new_data = self.socket.recv(2**14)
962
-        except socket.error, x:
1025
+            new_data = self.socket.recv(2 ** 14)
1026
+        except socket.error:
963 1027
             # The server hung up.
964 1028
             self.disconnect("Connection reset by peer")
965 1029
             return
@@ -975,7 +1039,7 @@ class DCCConnection(Connection):
975 1039
 
976 1040
             # Save the last, unfinished line.
977 1041
             self.previous_buffer = chunks[-1]
978
-            if len(self.previous_buffer) > 2**14:
1042
+            if len(self.previous_buffer) > 2 ** 14:
979 1043
                 # Bad peer! Naughty peer!
980 1044
                 self.disconnect()
981 1045
                 return
@@ -988,11 +1052,11 @@ class DCCConnection(Connection):
988 1052
         target = None
989 1053
         for chunk in chunks:
990 1054
             if DEBUG:
991
-                print "FROM PEER:", chunk
1055
+                print("FROM PEER:", chunk)
992 1056
             arguments = [chunk]
993 1057
             if DEBUG:
994
-                print "command: %s, source: %s, target: %s, arguments: %s" % (
995
-                    command, prefix, target, arguments)
1058
+                print("command: %s, source: %s, target: %s, arguments: %s" % (
1059
+                    command, prefix, target, arguments))
996 1060
             self.irclibobj._handle_event(
997 1061
                 self,
998 1062
                 Event(command, prefix, target, arguments))
@@ -1012,12 +1076,12 @@ class DCCConnection(Connection):
1012 1076
             if self.dcctype == "chat":
1013 1077
                 self.socket.send("\n")
1014 1078
             if DEBUG:
1015
-                print "TO PEER: %s\n" % string
1016
-        except socket.error, x:
1079
+                print("TO PEER: %s\n" % string)
1080
+        except socket.error:
1017 1081
             # Ouch!
1018 1082
             self.disconnect("Connection reset by peer.")
1019 1083
 
1020
-class SimpleIRCClient:
1084
+class SimpleIRCClient(object):
1021 1085
     """A simple single-server IRC client class.
1022 1086
 
1023 1087
     This is an example of an object-oriented wrapper of the IRC
@@ -1047,6 +1111,9 @@ class SimpleIRCClient:
1047 1111
 
1048 1112
     def _dispatcher(self, c, e):
1049 1113
         """[Internal]"""
1114
+        if DEBUG:
1115
+            print("irclib.py:_dispatcher:%s" % e.eventtype())
1116
+
1050 1117
         m = "on_" + e.eventtype()
1051 1118
         if hasattr(self, m):
1052 1119
             getattr(self, m)(c, e)
@@ -1117,7 +1184,7 @@ class SimpleIRCClient:
1117 1184
         self.ircobj.process_forever()
1118 1185
 
1119 1186
 
1120
-class Event:
1187
+class Event(object):
1121 1188
     """Class representing an IRC event."""
1122 1189
     def __init__(self, eventtype, source, target, arguments=None):
1123 1190
         """Constructor of Event objects.
@@ -1186,16 +1253,6 @@ def mask_matches(nick, mask):
1186 1253
 
1187 1254
 _special = "-[]\\`^{}"
1188 1255
 nick_characters = string.ascii_letters + string.digits + _special
1189
-_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
1190
-                                          string.ascii_lowercase + "{}|~")
1191
-
1192
-def irc_lower(s):
1193
-    """Returns a lowercased string.
1194
-
1195
-    The definition of lowercased comes from the IRC specification (RFC
1196
-    1459).
1197
-    """
1198
-    return s.translate(_ircstring_translation)
1199 1256
 
1200 1257
 def _ctcp_dequote(message):
1201 1258
     """[Internal] Dequote a message according to CTCP specifications.
@@ -1231,14 +1288,14 @@ def _ctcp_dequote(message):
1231 1288
 
1232 1289
         messages = []
1233 1290
         i = 0
1234
-        while i < len(chunks)-1:
1291
+        while i < len(chunks) - 1:
1235 1292
             # Add message if it's non-empty.
1236 1293
             if len(chunks[i]) > 0:
1237 1294
                 messages.append(chunks[i])
1238 1295
 
1239
-            if i < len(chunks)-2:
1296
+            if i < len(chunks) - 2:
1240 1297
                 # Aye!  CTCP tagged data ahead!
1241
-                messages.append(tuple(chunks[i+1].split(" ", 1)))
1298
+                messages.append(tuple(chunks[i + 1].split(" ", 1)))
1242 1299
 
1243 1300
             i = i + 2
1244 1301
 
@@ -1310,12 +1367,12 @@ def parse_nick_modes(mode_string):
1310 1367
     """Parse a nick mode string.
1311 1368
 
1312 1369
     The function returns a list of lists with three members: sign,
1313
-    mode and argument.  The sign is \"+\" or \"-\".  The argument is
1370
+    mode and argument.  The sign is "+" or "-".  The argument is
1314 1371
     always None.
1315 1372
 
1316 1373
     Example:
1317 1374
 
1318
-    >>> irclib.parse_nick_modes(\"+ab-c\")
1375
+    >>> parse_nick_modes("+ab-c")
1319 1376
     [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1320 1377
     """
1321 1378
 
@@ -1325,12 +1382,12 @@ def parse_channel_modes(mode_string):
1325 1382
     """Parse a channel mode string.
1326 1383
 
1327 1384
     The function returns a list of lists with three members: sign,
1328
-    mode and argument.  The sign is \"+\" or \"-\".  The argument is
1329
-    None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
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".
1330 1387
 
1331 1388
     Example:
1332 1389
 
1333
-    >>> irclib.parse_channel_modes(\"+ab-c foo\")
1390
+    >>> parse_channel_modes("+ab-c foo")
1334 1391
     [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1335 1392
     """
1336 1393
 
@@ -1356,7 +1413,7 @@ def _parse_modes(mode_string, unary_modes=""):
1356 1413
         if ch in "+-":
1357 1414
             sign = ch
1358 1415
         elif ch == " ":
1359
-            collecting_arguments = 1
1416
+            pass
1360 1417
         elif ch in unary_modes:
1361 1418
             if len(args) >= arg_count + 1:
1362 1419
                 modes.append([sign, ch, args[arg_count]])
@@ -1496,7 +1553,7 @@ numeric_events = {
1496 1553
     "423": "noadmininfo",
1497 1554
     "424": "fileerror",
1498 1555
     "431": "nonicknamegiven",
1499
-    "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1556
+    "432": "erroneusnickname",  # Thiss iz how its speld in thee RFC.
1500 1557
     "433": "nicknameinuse",
1501 1558
     "436": "nickcollision",
1502 1559
     "437": "unavailresource",  # "Nick temporally unavailable"
@@ -1511,7 +1568,7 @@ numeric_events = {
1511 1568
     "462": "alreadyregistered",
1512 1569
     "463": "nopermforhost",
1513 1570
     "464": "passwdmismatch",
1514
-    "465": "yourebannedcreep", # I love this one...
1571
+    "465": "yourebannedcreep",  # I love this one...
1515 1572
     "466": "youwillbebanned",
1516 1573
     "467": "keyset",
1517 1574
     "471": "channelisfull",
@@ -1560,4 +1617,105 @@ protocol_events = [
1560 1617
     "pong",
1561 1618
 ]
1562 1619
 
1563
-all_events = generated_events + protocol_events + numeric_events.values()
1620
+all_events = generated_events + protocol_events + list(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 = str.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