|
@@ -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
|