comparison irclib.py @ 0:4c842d23d4ce

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