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