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
-# 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
 # This library is free software; you can redistribute it and/or
6
 # This library is free software; you can redistribute it and/or
4
 # modify it under the terms of the GNU Lesser General Public
7
 # modify it under the terms of the GNU Lesser General Public
15
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
18
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
16
 #
19
 #
17
 # Joel Rosdahl <joel@rosdahl.net>
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
 This module contains a single-server IRC bot class that can be used to
25
 This module contains a single-server IRC bot class that can be used to
24
 write simpler bots.
26
 write simpler bots.
25
 """
27
 """
26
 
28
 
27
 import sys
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
     """A single-server IRC bot class.
35
     """A single-server IRC bot class.
37
 
36
 
38
     The bot tries to reconnect if it is disconnected.
37
     The bot tries to reconnect if it is disconnected.
62
             connections.
61
             connections.
63
         """
62
         """
64
 
63
 
65
-        SimpleIRCClient.__init__(self)
64
+        super(SingleServerIRCBot, self).__init__()
66
         self.channels = IRCDict()
65
         self.channels = IRCDict()
67
         self.server_list = server_list
66
         self.server_list = server_list
68
         if not reconnection_interval or reconnection_interval < 0:
67
         if not reconnection_interval or reconnection_interval < 0:
69
-            reconnection_interval = 2**31
68
+            reconnection_interval = 2 ** 31
70
         self.reconnection_interval = reconnection_interval
69
         self.reconnection_interval = reconnection_interval
71
 
70
 
72
         self._nickname = nickname
71
         self._nickname = nickname
75
                   "namreply", "nick", "part", "quit"]:
74
                   "namreply", "nick", "part", "quit"]:
76
             self.connection.add_global_handler(i,
75
             self.connection.add_global_handler(i,
77
                                                getattr(self, "_on_" + i),
76
                                                getattr(self, "_on_" + i),
78
-                                               -10)
77
+                                               -20)
78
+
79
     def _connected_checker(self):
79
     def _connected_checker(self):
80
         """[Internal]"""
80
         """[Internal]"""
81
         if not self.connection.is_connected():
81
         if not self.connection.is_connected():
94
                          self._nickname,
94
                          self._nickname,
95
                          password,
95
                          password,
96
                          ircname=self._realname)
96
                          ircname=self._realname)
97
-        except ServerConnectionError:
97
+        except irclib.ServerConnectionError:
98
             pass
98
             pass
99
 
99
 
100
     def _on_disconnect(self, c, e):
100
     def _on_disconnect(self, c, e):
123
 
123
 
124
     def _on_mode(self, c, e):
124
     def _on_mode(self, c, e):
125
         """[Internal]"""
125
         """[Internal]"""
126
-        modes = parse_channel_modes(" ".join(e.arguments()))
126
+        modes = irclib.parse_channel_modes(" ".join(e.arguments()))
127
         t = e.target()
127
         t = e.target()
128
-        if is_channel(t):
128
+        if irclib.is_channel(t):
129
             ch = self.channels[t]
129
             ch = self.channels[t]
130
             for mode in modes:
130
             for mode in modes:
131
                 if mode[0] == "+":
131
                 if mode[0] == "+":
244
     def start(self):
244
     def start(self):
245
         """Start the bot."""
245
         """Start the bot."""
246
         self._connect()
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
     """A class for keeping information about an IRC channel.
251
     """A class for keeping information about an IRC channel.
313
 
252
 
314
     This class can be improved a lot.
253
     This class can be improved a lot.
354
                 del d[nick]
293
                 del d[nick]
355
 
294
 
356
     def change_nick(self, before, after):
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
         if before in self.operdict:
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
         if before in self.voiceddict:
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
     def set_mode(self, mode, value=None):
306
     def set_mode(self, mode, value=None):
367
         """Set mode on the channel.
307
         """Set mode on the channel.
424
 
364
 
425
     def limit(self):
365
     def limit(self):
426
         if self.has_limit():
366
         if self.has_limit():
427
-            return self.modes[l]
367
+            return self.modes["l"]
428
         else:
368
         else:
429
             return None
369
             return None
430
 
370
 
431
     def has_key(self):
371
     def has_key(self):
432
         return self.has_mode("k")
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
-# 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
 # This library is free software; you can redistribute it and/or
6
 # This library is free software; you can redistribute it and/or
4
 # modify it under the terms of the GNU Lesser General Public
7
 # modify it under the terms of the GNU Lesser General Public
15
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
18
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307  USA
16
 #
19
 #
17
 # keltus <keltus@users.sourceforge.net>
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
 This library is intended to encapsulate the IRC protocol at a quite
25
 This library is intended to encapsulate the IRC protocol at a quite
24
 low level.  It provides an event-driven IRC client framework.  It has
26
 low level.  It provides an event-driven IRC client framework.  It has
66
 import select
68
 import select
67
 import socket
69
 import socket
68
 import string
70
 import string
69
-import sys
70
 import time
71
 import time
71
 import types
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
 # TODO
85
 # TODO
77
 # ----
86
 # ----
93
     pass
102
     pass
94
 
103
 
95
 
104
 
96
-class IRC:
105
+class IRC(object):
97
     """Class that handles one or several IRC server connections.
106
     """Class that handles one or several IRC server connections.
98
 
107
 
99
     When an IRC object has been instantiated, it can be used to create
108
     When an IRC object has been instantiated, it can be used to create
112
 
121
 
113
         irc = irclib.IRC()
122
         irc = irclib.IRC()
114
         server = irc.server()
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
         irc.process_forever()
126
         irc.process_forever()
118
 
127
 
119
     This will connect to the IRC server irc.some.where on port 6667
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
     to the nickname a_nickname.
130
     to the nickname a_nickname.
122
     """
131
     """
123
 
132
 
157
         self.fn_to_add_timeout = fn_to_add_timeout
166
         self.fn_to_add_timeout = fn_to_add_timeout
158
         self.connections = []
167
         self.connections = []
159
         self.handlers = {}
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
         self.add_global_handler("ping", _ping_ponger, -42)
171
         self.add_global_handler("ping", _ping_ponger, -42)
163
 
172
 
187
 
196
 
188
         See documentation for IRC.__init__.
197
         See documentation for IRC.__init__.
189
         """
198
         """
190
-        t = time.time()
191
         while self.delayed_commands:
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
                 break
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
     def process_once(self, timeout=0):
208
     def process_once(self, timeout=0):
199
         """Process data from connections once.
209
         """Process data from connections once.
252
 
262
 
253
         The handler functions are called in priority order (lowest
263
         The handler functions are called in priority order (lowest
254
         number is highest priority).  If a handler function returns
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
         if not event in self.handlers:
267
         if not event in self.handlers:
258
             self.handlers[event] = []
268
             self.handlers[event] = []
264
         Arguments:
274
         Arguments:
265
 
275
 
266
             event -- Event type (a string).
276
             event -- Event type (a string).
267
-
268
             handler -- Callback function.
277
             handler -- Callback function.
269
 
278
 
270
         Returns 1 on success, otherwise 0.
279
         Returns 1 on success, otherwise 0.
281
 
290
 
282
         Arguments:
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
             function -- Function to call.
294
             function -- Function to call.
287
-
288
             arguments -- Arguments to give the function.
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
     def execute_delayed(self, delay, function, arguments=()):
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
         if self.fn_to_add_timeout:
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
     def dcc(self, dcctype="chat"):
327
     def dcc(self, dcctype="chat"):
308
         """Creates and returns a DCCConnection object.
328
         """Creates and returns a DCCConnection object.
321
     def _handle_event(self, connection, event):
341
     def _handle_event(self, connection, event):
322
         """[Internal]"""
342
         """[Internal]"""
323
         h = self.handlers
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
             if handler[1](connection, event) == "NO MORE":
346
             if handler[1](connection, event) == "NO MORE":
326
                 return
347
                 return
327
 
348
 
331
         if self.fn_to_remove_socket:
352
         if self.fn_to_remove_socket:
332
             self.fn_to_remove_socket(connection._get_socket())
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
 _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
394
 _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
335
 
395
 
336
-class Connection:
396
+class Connection(object):
337
     """Base class for IRC connections.
397
     """Base class for IRC connections.
338
 
398
 
339
     Must be overridden.
399
     Must be overridden.
342
         self.irclibobj = irclibobj
402
         self.irclibobj = irclibobj
343
 
403
 
344
     def _get_socket():
404
     def _get_socket():
345
-        raise IRCError, "Not overridden"
405
+        raise IRCError("Not overridden")
346
 
406
 
347
     ##############################
407
     ##############################
348
     ### Convenience wrappers.
408
     ### Convenience wrappers.
353
     def execute_delayed(self, delay, function, arguments=()):
413
     def execute_delayed(self, delay, function, arguments=()):
354
         self.irclibobj.execute_delayed(delay, function, arguments)
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
 class ServerConnectionError(IRCError):
419
 class ServerConnectionError(IRCError):
358
     pass
420
     pass
373
     """
435
     """
374
 
436
 
375
     def __init__(self, irclibobj):
437
     def __init__(self, irclibobj):
376
-        Connection.__init__(self, irclibobj)
438
+        super(ServerConnection, self).__init__(irclibobj)
377
         self.connected = 0  # Not connected yet.
439
         self.connected = 0  # Not connected yet.
378
         self.socket = None
440
         self.socket = None
379
         self.ssl = None
441
         self.ssl = None
432
             self.socket.bind((self.localaddress, self.localport))
494
             self.socket.bind((self.localaddress, self.localport))
433
             self.socket.connect((self.server, self.port))
495
             self.socket.connect((self.server, self.port))
434
             if ssl:
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
             self.socket.close()
499
             self.socket.close()
438
             self.socket = None
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
         self.connected = 1
502
         self.connected = 1
441
         if self.irclibobj.fn_to_add_socket:
503
         if self.irclibobj.fn_to_add_socket:
442
             self.irclibobj.fn_to_add_socket(self.socket)
504
             self.irclibobj.fn_to_add_socket(self.socket)
488
 
550
 
489
         try:
551
         try:
490
             if self.ssl:
552
             if self.ssl:
491
-                new_data = self.ssl.read(2**14)
553
+                new_data = self.ssl.read(2 ** 14)
492
             else:
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
             # The server hung up.
557
             # The server hung up.
496
             self.disconnect("Connection reset by peer")
558
             self.disconnect("Connection reset by peer")
497
             return
559
             return
500
             self.disconnect("Connection reset by peer")
562
             self.disconnect("Connection reset by peer")
501
             return
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
         # Save the last, unfinished line.
567
         # Save the last, unfinished line.
506
         self.previous_buffer = lines.pop()
568
         self.previous_buffer = lines.pop()
507
 
569
 
508
         for line in lines:
570
         for line in lines:
509
             if DEBUG:
571
             if DEBUG:
510
-                print "FROM SERVER:", line
572
+                print("FROM SERVER:", line)
511
 
573
 
512
             if not line:
574
             if not line:
513
                 continue
575
                 continue
569
 
631
 
570
                         m = list(m)
632
                         m = list(m)
571
                         if DEBUG:
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
                         self._handle_event(Event(command, prefix, target, m))
636
                         self._handle_event(Event(command, prefix, target, m))
575
                         if command == "ctcp" and m[0] == "ACTION":
637
                         if command == "ctcp" and m[0] == "ACTION":
576
                             self._handle_event(Event("action", prefix, target, m[1:]))
638
                             self._handle_event(Event("action", prefix, target, m[1:]))
577
                     else:
639
                     else:
578
                         if DEBUG:
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
                         self._handle_event(Event(command, prefix, target, [m]))
643
                         self._handle_event(Event(command, prefix, target, [m]))
582
             else:
644
             else:
583
                 target = None
645
                 target = None
595
                         command = "umode"
657
                         command = "umode"
596
 
658
 
597
                 if DEBUG:
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
                 self._handle_event(Event(command, prefix, target, arguments))
662
                 self._handle_event(Event(command, prefix, target, arguments))
601
 
663
 
602
     def _handle_event(self, event):
664
     def _handle_event(self, event):
660
 
722
 
661
         try:
723
         try:
662
             self.socket.close()
724
             self.socket.close()
663
-        except socket.error, x:
725
+        except socket.error:
664
             pass
726
             pass
665
         self.socket = None
727
         self.socket = None
666
         self._handle_event(Event("disconnect", self.server, "", [message]))
728
         self._handle_event(Event("disconnect", self.server, "", [message]))
743
 
805
 
744
     def part(self, channels, message=""):
806
     def part(self, channels, message=""):
745
         """Send a PART command."""
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
     def pass_(self, password):
816
     def pass_(self, password):
752
         """Send a PASS command."""
817
         """Send a PASS command."""
780
         """Send raw string to the server.
845
         """Send raw string to the server.
781
 
846
 
782
         The string will be padded with appropriate CR LF.
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
         if self.socket is None:
849
         if self.socket is None:
788
-            raise ServerNotConnectedError, "Not connected."
850
+            raise ServerNotConnectedError("Not connected.")
789
         try:
851
         try:
790
             if self.ssl:
852
             if self.ssl:
791
                 self.ssl.write(string + "\r\n")
853
                 self.ssl.write(string + "\r\n")
792
             else:
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
             if DEBUG:
858
             if DEBUG:
795
-                print "TO SERVER:", string
796
-        except socket.error, x:
859
+                print("TO SERVER:", string)
860
+        except socket.error:
797
             # Ouch!
861
             # Ouch!
798
             self.disconnect("Connection reset by peer.")
862
             self.disconnect("Connection reset by peer.")
799
 
863
 
865
     method on an IRC object.
929
     method on an IRC object.
866
     """
930
     """
867
     def __init__(self, irclibobj, dcctype):
931
     def __init__(self, irclibobj, dcctype):
868
-        Connection.__init__(self, irclibobj)
932
+        super(DCCConnection, self).__init__(irclibobj)
869
         self.connected = 0
933
         self.connected = 0
870
         self.passive = 0
934
         self.passive = 0
871
         self.dcctype = dcctype
935
         self.dcctype = dcctype
891
         self.passive = 0
955
         self.passive = 0
892
         try:
956
         try:
893
             self.socket.connect((self.peeraddress, self.peerport))
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
         self.connected = 1
960
         self.connected = 1
897
         if self.irclibobj.fn_to_add_socket:
961
         if self.irclibobj.fn_to_add_socket:
898
             self.irclibobj.fn_to_add_socket(self.socket)
962
             self.irclibobj.fn_to_add_socket(self.socket)
916
             self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
980
             self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
917
             self.localaddress, self.localport = self.socket.getsockname()
981
             self.localaddress, self.localport = self.socket.getsockname()
918
             self.socket.listen(10)
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
         return self
985
         return self
922
 
986
 
923
     def disconnect(self, message=""):
987
     def disconnect(self, message=""):
933
         self.connected = 0
997
         self.connected = 0
934
         try:
998
         try:
935
             self.socket.close()
999
             self.socket.close()
936
-        except socket.error, x:
1000
+        except socket.error:
937
             pass
1001
             pass
938
         self.socket = None
1002
         self.socket = None
939
         self.irclibobj._handle_event(
1003
         self.irclibobj._handle_event(
950
             self.socket = conn
1014
             self.socket = conn
951
             self.connected = 1
1015
             self.connected = 1
952
             if DEBUG:
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
             self.irclibobj._handle_event(
1019
             self.irclibobj._handle_event(
956
                 self,
1020
                 self,
957
                 Event("dcc_connect", self.peeraddress, None, None))
1021
                 Event("dcc_connect", self.peeraddress, None, None))
958
             return
1022
             return
959
 
1023
 
960
         try:
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
             # The server hung up.
1027
             # The server hung up.
964
             self.disconnect("Connection reset by peer")
1028
             self.disconnect("Connection reset by peer")
965
             return
1029
             return
975
 
1039
 
976
             # Save the last, unfinished line.
1040
             # Save the last, unfinished line.
977
             self.previous_buffer = chunks[-1]
1041
             self.previous_buffer = chunks[-1]
978
-            if len(self.previous_buffer) > 2**14:
1042
+            if len(self.previous_buffer) > 2 ** 14:
979
                 # Bad peer! Naughty peer!
1043
                 # Bad peer! Naughty peer!
980
                 self.disconnect()
1044
                 self.disconnect()
981
                 return
1045
                 return
988
         target = None
1052
         target = None
989
         for chunk in chunks:
1053
         for chunk in chunks:
990
             if DEBUG:
1054
             if DEBUG:
991
-                print "FROM PEER:", chunk
1055
+                print("FROM PEER:", chunk)
992
             arguments = [chunk]
1056
             arguments = [chunk]
993
             if DEBUG:
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
             self.irclibobj._handle_event(
1060
             self.irclibobj._handle_event(
997
                 self,
1061
                 self,
998
                 Event(command, prefix, target, arguments))
1062
                 Event(command, prefix, target, arguments))
1012
             if self.dcctype == "chat":
1076
             if self.dcctype == "chat":
1013
                 self.socket.send("\n")
1077
                 self.socket.send("\n")
1014
             if DEBUG:
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
             # Ouch!
1081
             # Ouch!
1018
             self.disconnect("Connection reset by peer.")
1082
             self.disconnect("Connection reset by peer.")
1019
 
1083
 
1020
-class SimpleIRCClient:
1084
+class SimpleIRCClient(object):
1021
     """A simple single-server IRC client class.
1085
     """A simple single-server IRC client class.
1022
 
1086
 
1023
     This is an example of an object-oriented wrapper of the IRC
1087
     This is an example of an object-oriented wrapper of the IRC
1047
 
1111
 
1048
     def _dispatcher(self, c, e):
1112
     def _dispatcher(self, c, e):
1049
         """[Internal]"""
1113
         """[Internal]"""
1114
+        if DEBUG:
1115
+            print("irclib.py:_dispatcher:%s" % e.eventtype())
1116
+
1050
         m = "on_" + e.eventtype()
1117
         m = "on_" + e.eventtype()
1051
         if hasattr(self, m):
1118
         if hasattr(self, m):
1052
             getattr(self, m)(c, e)
1119
             getattr(self, m)(c, e)
1117
         self.ircobj.process_forever()
1184
         self.ircobj.process_forever()
1118
 
1185
 
1119
 
1186
 
1120
-class Event:
1187
+class Event(object):
1121
     """Class representing an IRC event."""
1188
     """Class representing an IRC event."""
1122
     def __init__(self, eventtype, source, target, arguments=None):
1189
     def __init__(self, eventtype, source, target, arguments=None):
1123
         """Constructor of Event objects.
1190
         """Constructor of Event objects.
1186
 
1253
 
1187
 _special = "-[]\\`^{}"
1254
 _special = "-[]\\`^{}"
1188
 nick_characters = string.ascii_letters + string.digits + _special
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
 def _ctcp_dequote(message):
1257
 def _ctcp_dequote(message):
1201
     """[Internal] Dequote a message according to CTCP specifications.
1258
     """[Internal] Dequote a message according to CTCP specifications.
1231
 
1288
 
1232
         messages = []
1289
         messages = []
1233
         i = 0
1290
         i = 0
1234
-        while i < len(chunks)-1:
1291
+        while i < len(chunks) - 1:
1235
             # Add message if it's non-empty.
1292
             # Add message if it's non-empty.
1236
             if len(chunks[i]) > 0:
1293
             if len(chunks[i]) > 0:
1237
                 messages.append(chunks[i])
1294
                 messages.append(chunks[i])
1238
 
1295
 
1239
-            if i < len(chunks)-2:
1296
+            if i < len(chunks) - 2:
1240
                 # Aye!  CTCP tagged data ahead!
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
             i = i + 2
1300
             i = i + 2
1244
 
1301
 
1310
     """Parse a nick mode string.
1367
     """Parse a nick mode string.
1311
 
1368
 
1312
     The function returns a list of lists with three members: sign,
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
     always None.
1371
     always None.
1315
 
1372
 
1316
     Example:
1373
     Example:
1317
 
1374
 
1318
-    >>> irclib.parse_nick_modes(\"+ab-c\")
1375
+    >>> parse_nick_modes("+ab-c")
1319
     [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1376
     [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1320
     """
1377
     """
1321
 
1378
 
1325
     """Parse a channel mode string.
1382
     """Parse a channel mode string.
1326
 
1383
 
1327
     The function returns a list of lists with three members: sign,
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
     Example:
1388
     Example:
1332
 
1389
 
1333
-    >>> irclib.parse_channel_modes(\"+ab-c foo\")
1390
+    >>> parse_channel_modes("+ab-c foo")
1334
     [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1391
     [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1335
     """
1392
     """
1336
 
1393
 
1356
         if ch in "+-":
1413
         if ch in "+-":
1357
             sign = ch
1414
             sign = ch
1358
         elif ch == " ":
1415
         elif ch == " ":
1359
-            collecting_arguments = 1
1416
+            pass
1360
         elif ch in unary_modes:
1417
         elif ch in unary_modes:
1361
             if len(args) >= arg_count + 1:
1418
             if len(args) >= arg_count + 1:
1362
                 modes.append([sign, ch, args[arg_count]])
1419
                 modes.append([sign, ch, args[arg_count]])
1496
     "423": "noadmininfo",
1553
     "423": "noadmininfo",
1497
     "424": "fileerror",
1554
     "424": "fileerror",
1498
     "431": "nonicknamegiven",
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
     "433": "nicknameinuse",
1557
     "433": "nicknameinuse",
1501
     "436": "nickcollision",
1558
     "436": "nickcollision",
1502
     "437": "unavailresource",  # "Nick temporally unavailable"
1559
     "437": "unavailresource",  # "Nick temporally unavailable"
1511
     "462": "alreadyregistered",
1568
     "462": "alreadyregistered",
1512
     "463": "nopermforhost",
1569
     "463": "nopermforhost",
1513
     "464": "passwdmismatch",
1570
     "464": "passwdmismatch",
1514
-    "465": "yourebannedcreep", # I love this one...
1571
+    "465": "yourebannedcreep",  # I love this one...
1515
     "466": "youwillbebanned",
1572
     "466": "youwillbebanned",
1516
     "467": "keyset",
1573
     "467": "keyset",
1517
     "471": "channelisfull",
1574
     "471": "channelisfull",
1560
     "pong",
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