*quietly adds a file that should have been in the tree since 9839a*
[halbot.git] / irclib.py
blobafc629a4a14dcb85e0ab56ba5fc1248e20664ab7
1 # Copyright (C) 1999--2002 Joel Rosdahl
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.
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.
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
17 # keltus <keltus@users.sourceforge.net>
19 # $Id: irclib.py,v 1.43 2005/12/24 22:12:40 keltus Exp $
21 """irclib -- Internet Relay Chat (IRC) protocol client library.
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.
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].
32 The main features of the IRC client framework are:
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.
52 Current limitations:
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...
61 .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
62 """
64 import bisect
65 import re
66 import select
67 import socket
68 import string
69 import sys
70 import time
71 import types
73 VERSION = 0, 4, 6
74 DEBUG = 0
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
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.
91 class IRCError(Exception):
92 """Represents an IRC exception."""
93 pass
96 class IRC:
97 """Class that handles one or several IRC server connections.
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.
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.
111 Here is an example:
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()
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.
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.
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.
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.
146 An alternative is to just call ServerConnection.process_once()
147 once in a while.
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
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)
162 self.add_global_handler("ping", _ping_ponger, -42)
164 def server(self):
165 """Creates and returns a ServerConnection object."""
167 c = ServerConnection(self)
168 self.connections.append(c)
169 return c
171 def process_data(self, sockets):
172 """Called when there is more data to read on connection sockets.
174 Arguments:
176 sockets -- A list of socket objects.
178 See documentation for IRC.__init__.
180 for s in sockets:
181 for c in self.connections:
182 if s == c._get_socket():
183 c.process_data()
185 def process_timeout(self):
186 """Called when a timeout notification is due.
188 See documentation for IRC.__init__.
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
198 def process_once(self, timeout=0):
199 """Process data from connections once.
201 Arguments:
203 timeout -- How long the select() call should wait if no
204 data is available.
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.
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()
219 def process_forever(self, timeout=0.2):
220 """Run an infinite loop, processing data from connections.
222 This method repeatedly calls process_once.
224 Arguments:
226 timeout -- Parameter to pass to process_once.
228 while 1:
229 self.process_once(timeout)
231 def disconnect_all(self, message=""):
232 """Disconnects all connections."""
233 for c in self.connections:
234 c.disconnect(message)
236 def add_global_handler(self, event, handler, priority=0):
237 """Adds a global handler function for a specific event type.
239 Arguments:
241 event -- Event type (a string). Check the values of the
242 numeric_events dictionary in irclib.py for possible event
243 types.
245 handler -- Callback function.
247 priority -- A number (the lower number, the higher priority).
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.
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.
258 if not event in self.handlers:
259 self.handlers[event] = []
260 bisect.insort(self.handlers[event], ((priority, handler)))
262 def remove_global_handler(self, event, handler):
263 """Removes a global handler function.
265 Arguments:
267 event -- Event type (a string).
269 handler -- Callback function.
271 Returns 1 on success, otherwise 0.
273 if not event in self.handlers:
274 return 0
275 for h in self.handlers[event]:
276 if handler == h[1]:
277 self.handlers[event].remove(h)
278 return 1
280 def execute_at(self, at, function, arguments=()):
281 """Execute a function at a specified time.
283 Arguments:
285 at -- Execute at this time (standard \"time_t\" time).
287 function -- Function to call.
289 arguments -- Arguments to give the function.
291 self.execute_delayed(at-time.time(), function, arguments)
293 def execute_delayed(self, delay, function, arguments=()):
294 """Execute a function after a specified time.
296 Arguments:
298 delay -- How many seconds to wait.
300 function -- Function to call.
302 arguments -- Arguments to give the function.
304 bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments))
305 if self.fn_to_add_timeout:
306 self.fn_to_add_timeout(delay)
308 def dcc(self, dcctype="chat"):
309 """Creates and returns a DCCConnection object.
311 Arguments:
313 dcctype -- "chat" for DCC CHAT connections or "raw" for
314 DCC SEND (or other DCC types). If "chat",
315 incoming data will be split in newline-separated
316 chunks. If "raw", incoming data is not touched.
318 c = DCCConnection(self, dcctype)
319 self.connections.append(c)
320 return c
322 def _handle_event(self, connection, event):
323 """[Internal]"""
324 h = self.handlers
325 for handler in h.get("all_events", []) + h.get(event.eventtype(), []):
326 if handler[1](connection, event) == "NO MORE":
327 return
329 def _remove_connection(self, connection):
330 """[Internal]"""
331 self.connections.remove(connection)
332 if self.fn_to_remove_socket:
333 self.fn_to_remove_socket(connection._get_socket())
335 _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
338 class Connection:
339 """Base class for IRC connections.
341 Must be overridden.
343 def __init__(self, irclibobj):
344 self.irclibobj = irclibobj
346 def _get_socket():
347 raise IRCError, "Not overridden"
349 ##############################
350 ### Convenience wrappers.
352 def execute_at(self, at, function, arguments=()):
353 self.irclibobj.execute_at(at, function, arguments)
355 def execute_delayed(self, delay, function, arguments=()):
356 self.irclibobj.execute_delayed(delay, function, arguments)
359 class ServerConnectionError(IRCError):
360 pass
362 class ServerNotConnectedError(ServerConnectionError):
363 pass
366 # Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
367 # use \n as message separator! :P
368 _linesep_regexp = re.compile("\r?\n")
370 class ServerConnection(Connection):
371 """This class represents an IRC server connection.
373 ServerConnection objects are instantiated by calling the server
374 method on an IRC object.
377 def __init__(self, irclibobj):
378 Connection.__init__(self, irclibobj)
379 self.connected = 0 # Not connected yet.
380 self.socket = None
382 def connect(self, server, port, nickname, password=None, username=None,
383 ircname=None, localaddress="", localport=0):
384 """Connect/reconnect to a server.
386 Arguments:
388 server -- Server name.
390 port -- Port number.
392 nickname -- The nickname.
394 password -- Password (if any).
396 username -- The username.
398 ircname -- The IRC name ("realname").
400 localaddress -- Bind the connection to a specific local IP address.
402 localport -- Bind the connection to a specific local port.
404 This function can be called to reconnect a closed connection.
406 Returns the ServerConnection object.
408 if self.connected:
409 self.disconnect("Changing servers")
411 self.previous_buffer = ""
412 self.handlers = {}
413 self.real_server_name = ""
414 self.real_nickname = nickname
415 self.server = server
416 self.port = port
417 self.nickname = nickname
418 self.username = username or nickname
419 self.ircname = ircname or nickname
420 self.password = password
421 self.localaddress = localaddress
422 self.localport = localport
423 self.localhost = socket.gethostname()
424 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
425 try:
426 self.socket.bind((self.localaddress, self.localport))
427 self.socket.connect((self.server, self.port))
428 except socket.error, x:
429 self.socket.close()
430 self.socket = None
431 raise ServerConnectionError, "Couldn't connect to socket: %s" % x
432 self.connected = 1
433 if self.irclibobj.fn_to_add_socket:
434 self.irclibobj.fn_to_add_socket(self.socket)
436 # Log on...
437 if self.password:
438 self.pass_(self.password)
439 self.nick(self.nickname)
440 self.user(self.username, self.ircname)
441 return self
443 def close(self):
444 """Close the connection.
446 This method closes the connection permanently; after it has
447 been called, the object is unusable.
450 self.disconnect("Closing object")
451 self.irclibobj._remove_connection(self)
453 def _get_socket(self):
454 """[Internal]"""
455 return self.socket
457 def get_server_name(self):
458 """Get the (real) server name.
460 This method returns the (real) server name, or, more
461 specifically, what the server calls itself.
464 if self.real_server_name:
465 return self.real_server_name
466 else:
467 return ""
469 def get_nickname(self):
470 """Get the (real) nick name.
472 This method returns the (real) nickname. The library keeps
473 track of nick changes, so it might not be the nick name that
474 was passed to the connect() method. """
476 return self.real_nickname
478 def process_data(self):
479 """[Internal]"""
481 try:
482 new_data = self.socket.recv(2**14)
483 except socket.error, x:
484 # The server hung up.
485 self.disconnect("Connection reset by peer")
486 return
487 if not new_data:
488 # Read nothing: connection must be down.
489 self.disconnect("Connection reset by peer")
490 return
492 lines = _linesep_regexp.split(self.previous_buffer + new_data)
494 # Save the last, unfinished line.
495 self.previous_buffer = lines[-1]
496 lines = lines[:-1]
498 for line in lines:
499 if DEBUG:
500 print "FROM SERVER:", line
502 if not line:
503 continue
505 prefix = None
506 command = None
507 arguments = None
508 self._handle_event(Event("all_raw_messages",
509 self.get_server_name(),
510 None,
511 [line]))
513 m = _rfc_1459_command_regexp.match(line)
514 if m.group("prefix"):
515 prefix = m.group("prefix")
516 if not self.real_server_name:
517 self.real_server_name = prefix
519 if m.group("command"):
520 command = m.group("command").lower()
522 if m.group("argument"):
523 a = m.group("argument").split(" :", 1)
524 arguments = a[0].split()
525 if len(a) == 2:
526 arguments.append(a[1])
528 # Translate numerics into more readable strings.
529 if command in numeric_events:
530 command = numeric_events[command]
532 if command == "nick":
533 if nm_to_n(prefix) == self.real_nickname:
534 self.real_nickname = arguments[0]
535 elif command == "welcome":
536 # Record the nickname in case the client changed nick
537 # in a nicknameinuse callback.
538 self.real_nickname = arguments[0]
540 if command in ["privmsg", "notice"]:
541 target, message = arguments[0], arguments[1]
542 messages = _ctcp_dequote(message)
544 if command == "privmsg":
545 if is_channel(target):
546 command = "pubmsg"
547 else:
548 if is_channel(target):
549 command = "pubnotice"
550 else:
551 command = "privnotice"
553 for m in messages:
554 if type(m) is types.TupleType:
555 if command in ["privmsg", "pubmsg"]:
556 command = "ctcp"
557 else:
558 command = "ctcpreply"
560 m = list(m)
561 if DEBUG:
562 print "command: %s, source: %s, target: %s, arguments: %s" % (
563 command, prefix, target, m)
564 self._handle_event(Event(command, prefix, target, m))
565 if command == "ctcp" and m[0] == "ACTION":
566 self._handle_event(Event("action", prefix, target, m[1:]))
567 else:
568 if DEBUG:
569 print "command: %s, source: %s, target: %s, arguments: %s" % (
570 command, prefix, target, [m])
571 self._handle_event(Event(command, prefix, target, [m]))
572 else:
573 target = None
575 if command == "quit":
576 arguments = [arguments[0]]
577 elif command == "ping":
578 target = arguments[0]
579 else:
580 target = arguments[0]
581 arguments = arguments[1:]
583 if command == "mode":
584 if not is_channel(target):
585 command = "umode"
587 if DEBUG:
588 print "command: %s, source: %s, target: %s, arguments: %s" % (
589 command, prefix, target, arguments)
590 self._handle_event(Event(command, prefix, target, arguments))
592 def _handle_event(self, event):
593 """[Internal]"""
594 self.irclibobj._handle_event(self, event)
595 if event.eventtype() in self.handlers:
596 for fn in self.handlers[event.eventtype()]:
597 fn(self, event)
599 def is_connected(self):
600 """Return connection status.
602 Returns true if connected, otherwise false.
604 return self.connected
606 def add_global_handler(self, *args):
607 """Add global handler.
609 See documentation for IRC.add_global_handler.
611 self.irclibobj.add_global_handler(*args)
613 def remove_global_handler(self, *args):
614 """Remove global handler.
616 See documentation for IRC.remove_global_handler.
618 self.irclibobj.remove_global_handler(*args)
620 def action(self, target, action):
621 """Send a CTCP ACTION command."""
622 self.ctcp("ACTION", target, action)
624 def admin(self, server=""):
625 """Send an ADMIN command."""
626 self.send_raw(" ".join(["ADMIN", server]).strip())
628 def ctcp(self, ctcptype, target, parameter=""):
629 """Send a CTCP command."""
630 ctcptype = ctcptype.upper()
631 self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
633 def ctcp_reply(self, target, parameter):
634 """Send a CTCP REPLY command."""
635 self.notice(target, "\001%s\001" % parameter)
637 def disconnect(self, message=""):
638 """Hang up the connection.
640 Arguments:
642 message -- Quit message.
644 if not self.connected:
645 return
647 self.connected = 0
649 self.quit(message)
651 try:
652 self.socket.close()
653 except socket.error, x:
654 pass
655 self.socket = None
656 self._handle_event(Event("disconnect", self.server, "", [message]))
658 def globops(self, text):
659 """Send a GLOBOPS command."""
660 self.send_raw("GLOBOPS :" + text)
662 def info(self, server=""):
663 """Send an INFO command."""
664 self.send_raw(" ".join(["INFO", server]).strip())
666 def invite(self, nick, channel):
667 """Send an INVITE command."""
668 self.send_raw(" ".join(["INVITE", nick, channel]).strip())
670 def ison(self, nicks):
671 """Send an ISON command.
673 Arguments:
675 nicks -- List of nicks.
677 self.send_raw("ISON " + " ".join(nicks))
679 def join(self, channel, key=""):
680 """Send a JOIN command."""
681 self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
683 def kick(self, channel, nick, comment=""):
684 """Send a KICK command."""
685 self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
687 def links(self, remote_server="", server_mask=""):
688 """Send a LINKS command."""
689 command = "LINKS"
690 if remote_server:
691 command = command + " " + remote_server
692 if server_mask:
693 command = command + " " + server_mask
694 self.send_raw(command)
696 def list(self, channels=None, server=""):
697 """Send a LIST command."""
698 command = "LIST"
699 if channels:
700 command = command + " " + ",".join(channels)
701 if server:
702 command = command + " " + server
703 self.send_raw(command)
705 def lusers(self, server=""):
706 """Send a LUSERS command."""
707 self.send_raw("LUSERS" + (server and (" " + server)))
709 def mode(self, target, command):
710 """Send a MODE command."""
711 self.send_raw("MODE %s %s" % (target, command))
713 def motd(self, server=""):
714 """Send an MOTD command."""
715 self.send_raw("MOTD" + (server and (" " + server)))
717 def names(self, channels=None):
718 """Send a NAMES command."""
719 self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or ""))
721 def nick(self, newnick):
722 """Send a NICK command."""
723 self.send_raw("NICK " + newnick)
725 def notice(self, target, text):
726 """Send a NOTICE command."""
727 # Should limit len(text) here!
728 self.send_raw("NOTICE %s :%s" % (target, text))
730 def oper(self, nick, password):
731 """Send an OPER command."""
732 self.send_raw("OPER %s %s" % (nick, password))
734 def part(self, channels, message=""):
735 """Send a PART command."""
736 if type(channels) == types.StringType:
737 self.send_raw("PART " + channels + (message and (" " + message)))
738 else:
739 self.send_raw("PART " + ",".join(channels) + (message and (" " + message)))
741 def pass_(self, password):
742 """Send a PASS command."""
743 self.send_raw("PASS " + password)
745 def ping(self, target, target2=""):
746 """Send a PING command."""
747 self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
749 def pong(self, target, target2=""):
750 """Send a PONG command."""
751 self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
753 def privmsg(self, target, text):
754 """Send a PRIVMSG command."""
755 # Should limit len(text) here!
756 self.send_raw("PRIVMSG %s :%s" % (target, text))
758 def privmsg_many(self, targets, text):
759 """Send a PRIVMSG command to multiple targets."""
760 # Should limit len(text) here!
761 self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text))
763 def quit(self, message=""):
764 """Send a QUIT command."""
765 # Note that many IRC servers don't use your QUIT message
766 # unless you've been connected for at least 5 minutes!
767 self.send_raw("QUIT" + (message and (" :" + message)))
769 def sconnect(self, target, port="", server=""):
770 """Send an SCONNECT command."""
771 self.send_raw("CONNECT %s%s%s" % (target,
772 port and (" " + port),
773 server and (" " + server)))
775 def send_raw(self, string):
776 """Send raw string to the server.
778 The string will be padded with appropriate CR LF.
780 if self.socket is None:
781 raise ServerNotConnectedError, "Not connected."
782 try:
783 self.socket.send(string + "\r\n")
784 if DEBUG:
785 print "TO SERVER:", string
786 except socket.error, x:
787 # Ouch!
788 self.disconnect("Connection reset by peer.")
790 def squit(self, server, comment=""):
791 """Send an SQUIT command."""
792 self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
794 def stats(self, statstype, server=""):
795 """Send a STATS command."""
796 self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
798 def time(self, server=""):
799 """Send a TIME command."""
800 self.send_raw("TIME" + (server and (" " + server)))
802 def topic(self, channel, new_topic=None):
803 """Send a TOPIC command."""
804 if new_topic is None:
805 self.send_raw("TOPIC " + channel)
806 else:
807 self.send_raw("TOPIC %s :%s" % (channel, new_topic))
809 def trace(self, target=""):
810 """Send a TRACE command."""
811 self.send_raw("TRACE" + (target and (" " + target)))
813 def user(self, username, realname):
814 """Send a USER command."""
815 self.send_raw("USER %s 0 * :%s" % (username, realname))
817 def userhost(self, nicks):
818 """Send a USERHOST command."""
819 self.send_raw("USERHOST " + ",".join(nicks))
821 def users(self, server=""):
822 """Send a USERS command."""
823 self.send_raw("USERS" + (server and (" " + server)))
825 def version(self, server=""):
826 """Send a VERSION command."""
827 self.send_raw("VERSION" + (server and (" " + server)))
829 def wallops(self, text):
830 """Send a WALLOPS command."""
831 self.send_raw("WALLOPS :" + text)
833 def who(self, target="", op=""):
834 """Send a WHO command."""
835 self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
837 def whois(self, targets):
838 """Send a WHOIS command."""
839 self.send_raw("WHOIS " + ",".join(targets))
841 def whowas(self, nick, max="", server=""):
842 """Send a WHOWAS command."""
843 self.send_raw("WHOWAS %s%s%s" % (nick,
844 max and (" " + max),
845 server and (" " + server)))
848 class DCCConnectionError(IRCError):
849 pass
852 class DCCConnection(Connection):
853 """This class represents a DCC connection.
855 DCCConnection objects are instantiated by calling the dcc
856 method on an IRC object.
858 def __init__(self, irclibobj, dcctype):
859 Connection.__init__(self, irclibobj)
860 self.connected = 0
861 self.passive = 0
862 self.dcctype = dcctype
863 self.peeraddress = None
864 self.peerport = None
866 def connect(self, address, port):
867 """Connect/reconnect to a DCC peer.
869 Arguments:
870 address -- Host/IP address of the peer.
872 port -- The port number to connect to.
874 Returns the DCCConnection object.
876 self.peeraddress = socket.gethostbyname(address)
877 self.peerport = port
878 self.socket = None
879 self.previous_buffer = ""
880 self.handlers = {}
881 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
882 self.passive = 0
883 try:
884 self.socket.connect((self.peeraddress, self.peerport))
885 except socket.error, x:
886 raise DCCConnectionError, "Couldn't connect to socket: %s" % x
887 self.connected = 1
888 if self.irclibobj.fn_to_add_socket:
889 self.irclibobj.fn_to_add_socket(self.socket)
890 return self
892 def listen(self):
893 """Wait for a connection/reconnection from a DCC peer.
895 Returns the DCCConnection object.
897 The local IP address and port are available as
898 self.localaddress and self.localport. After connection from a
899 peer, the peer address and port are available as
900 self.peeraddress and self.peerport.
902 self.previous_buffer = ""
903 self.handlers = {}
904 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
905 self.passive = 1
906 try:
907 self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
908 self.localaddress, self.localport = self.socket.getsockname()
909 self.socket.listen(10)
910 except socket.error, x:
911 raise DCCConnectionError, "Couldn't bind socket: %s" % x
912 return self
914 def disconnect(self, message=""):
915 """Hang up the connection and close the object.
917 Arguments:
919 message -- Quit message.
921 if not self.connected:
922 return
924 self.connected = 0
925 try:
926 self.socket.close()
927 except socket.error, x:
928 pass
929 self.socket = None
930 self.irclibobj._handle_event(
931 self,
932 Event("dcc_disconnect", self.peeraddress, "", [message]))
933 self.irclibobj._remove_connection(self)
935 def process_data(self):
936 """[Internal]"""
938 if self.passive and not self.connected:
939 conn, (self.peeraddress, self.peerport) = self.socket.accept()
940 self.socket.close()
941 self.socket = conn
942 self.connected = 1
943 if DEBUG:
944 print "DCC connection from %s:%d" % (
945 self.peeraddress, self.peerport)
946 self.irclibobj._handle_event(
947 self,
948 Event("dcc_connect", self.peeraddress, None, None))
949 return
951 try:
952 new_data = self.socket.recv(2**14)
953 except socket.error, x:
954 # The server hung up.
955 self.disconnect("Connection reset by peer")
956 return
957 if not new_data:
958 # Read nothing: connection must be down.
959 self.disconnect("Connection reset by peer")
960 return
962 if self.dcctype == "chat":
963 # The specification says lines are terminated with LF, but
964 # it seems safer to handle CR LF terminations too.
965 chunks = _linesep_regexp.split(self.previous_buffer + new_data)
967 # Save the last, unfinished line.
968 self.previous_buffer = chunks[-1]
969 if len(self.previous_buffer) > 2**14:
970 # Bad peer! Naughty peer!
971 self.disconnect()
972 return
973 chunks = chunks[:-1]
974 else:
975 chunks = [new_data]
977 command = "dccmsg"
978 prefix = self.peeraddress
979 target = None
980 for chunk in chunks:
981 if DEBUG:
982 print "FROM PEER:", chunk
983 arguments = [chunk]
984 if DEBUG:
985 print "command: %s, source: %s, target: %s, arguments: %s" % (
986 command, prefix, target, arguments)
987 self.irclibobj._handle_event(
988 self,
989 Event(command, prefix, target, arguments))
991 def _get_socket(self):
992 """[Internal]"""
993 return self.socket
995 def privmsg(self, string):
996 """Send data to DCC peer.
998 The string will be padded with appropriate LF if it's a DCC
999 CHAT session.
1001 try:
1002 self.socket.send(string)
1003 if self.dcctype == "chat":
1004 self.socket.send("\n")
1005 if DEBUG:
1006 print "TO PEER: %s\n" % string
1007 except socket.error, x:
1008 # Ouch!
1009 self.disconnect("Connection reset by peer.")
1011 class SimpleIRCClient:
1012 """A simple single-server IRC client class.
1014 This is an example of an object-oriented wrapper of the IRC
1015 framework. A real IRC client can be made by subclassing this
1016 class and adding appropriate methods.
1018 The method on_join will be called when a "join" event is created
1019 (which is done when the server sends a JOIN messsage/command),
1020 on_privmsg will be called for "privmsg" events, and so on. The
1021 handler methods get two arguments: the connection object (same as
1022 self.connection) and the event object.
1024 Instance attributes that can be used by sub classes:
1026 ircobj -- The IRC instance.
1028 connection -- The ServerConnection instance.
1030 dcc_connections -- A list of DCCConnection instances.
1032 def __init__(self):
1033 self.ircobj = IRC()
1034 self.connection = self.ircobj.server()
1035 self.dcc_connections = []
1036 self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
1037 self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
1039 def _dispatcher(self, c, e):
1040 """[Internal]"""
1041 m = "on_" + e.eventtype()
1042 if hasattr(self, m):
1043 getattr(self, m)(c, e)
1045 def _dcc_disconnect(self, c, e):
1046 self.dcc_connections.remove(c)
1048 def connect(self, server, port, nickname, password=None, username=None,
1049 ircname=None, localaddress="", localport=0):
1050 """Connect/reconnect to a server.
1052 Arguments:
1054 server -- Server name.
1056 port -- Port number.
1058 nickname -- The nickname.
1060 password -- Password (if any).
1062 username -- The username.
1064 ircname -- The IRC name.
1066 localaddress -- Bind the connection to a specific local IP address.
1068 localport -- Bind the connection to a specific local port.
1070 This function can be called to reconnect a closed connection.
1072 self.connection.connect(server, port, nickname,
1073 password, username, ircname,
1074 localaddress, localport)
1076 def dcc_connect(self, address, port, dcctype="chat"):
1077 """Connect to a DCC peer.
1079 Arguments:
1081 address -- IP address of the peer.
1083 port -- Port to connect to.
1085 Returns a DCCConnection instance.
1087 dcc = self.ircobj.dcc(dcctype)
1088 self.dcc_connections.append(dcc)
1089 dcc.connect(address, port)
1090 return dcc
1092 def dcc_listen(self, dcctype="chat"):
1093 """Listen for connections from a DCC peer.
1095 Returns a DCCConnection instance.
1097 dcc = self.ircobj.dcc(dcctype)
1098 self.dcc_connections.append(dcc)
1099 dcc.listen()
1100 return dcc
1102 def start(self):
1103 """Start the IRC client."""
1104 self.ircobj.process_forever()
1107 class Event:
1108 """Class representing an IRC event."""
1109 def __init__(self, eventtype, source, target, arguments=None):
1110 """Constructor of Event objects.
1112 Arguments:
1114 eventtype -- A string describing the event.
1116 source -- The originator of the event (a nick mask or a server).
1118 target -- The target of the event (a nick or a channel).
1120 arguments -- Any event specific arguments.
1122 self._eventtype = eventtype
1123 self._source = source
1124 self._target = target
1125 if arguments:
1126 self._arguments = arguments
1127 else:
1128 self._arguments = []
1130 def eventtype(self):
1131 """Get the event type."""
1132 return self._eventtype
1134 def source(self):
1135 """Get the event source."""
1136 return self._source
1138 def target(self):
1139 """Get the event target."""
1140 return self._target
1142 def arguments(self):
1143 """Get the event arguments."""
1144 return self._arguments
1146 _LOW_LEVEL_QUOTE = "\020"
1147 _CTCP_LEVEL_QUOTE = "\134"
1148 _CTCP_DELIMITER = "\001"
1150 _low_level_mapping = {
1151 "0": "\000",
1152 "n": "\n",
1153 "r": "\r",
1154 _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
1157 _low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
1159 def mask_matches(nick, mask):
1160 """Check if a nick matches a mask.
1162 Returns true if the nick matches, otherwise false.
1164 nick = irc_lower(nick)
1165 mask = irc_lower(mask)
1166 mask = mask.replace("\\", "\\\\")
1167 for ch in ".$|[](){}+":
1168 mask = mask.replace(ch, "\\" + ch)
1169 mask = mask.replace("?", ".")
1170 mask = mask.replace("*", ".*")
1171 r = re.compile(mask, re.IGNORECASE)
1172 return r.match(nick)
1174 _special = "-[]\\`^{}"
1175 nick_characters = string.ascii_letters + string.digits + _special
1176 _ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
1177 string.ascii_lowercase + "{}|~")
1179 def irc_lower(s):
1180 """Returns a lowercased string.
1182 The definition of lowercased comes from the IRC specification (RFC
1183 1459).
1185 return s.translate(_ircstring_translation)
1187 def _ctcp_dequote(message):
1188 """[Internal] Dequote a message according to CTCP specifications.
1190 The function returns a list where each element can be either a
1191 string (normal message) or a tuple of one or two strings (tagged
1192 messages). If a tuple has only one element (ie is a singleton),
1193 that element is the tag; otherwise the tuple has two elements: the
1194 tag and the data.
1196 Arguments:
1198 message -- The message to be decoded.
1201 def _low_level_replace(match_obj):
1202 ch = match_obj.group(1)
1204 # If low_level_mapping doesn't have the character as key, we
1205 # should just return the character.
1206 return _low_level_mapping.get(ch, ch)
1208 if _LOW_LEVEL_QUOTE in message:
1209 # Yup, there was a quote. Release the dequoter, man!
1210 message = _low_level_regexp.sub(_low_level_replace, message)
1212 if _CTCP_DELIMITER not in message:
1213 return [message]
1214 else:
1215 # Split it into parts. (Does any IRC client actually *use*
1216 # CTCP stacking like this?)
1217 chunks = message.split(_CTCP_DELIMITER)
1219 messages = []
1220 i = 0
1221 while i < len(chunks)-1:
1222 # Add message if it's non-empty.
1223 if len(chunks[i]) > 0:
1224 messages.append(chunks[i])
1226 if i < len(chunks)-2:
1227 # Aye! CTCP tagged data ahead!
1228 messages.append(tuple(chunks[i+1].split(" ", 1)))
1230 i = i + 2
1232 if len(chunks) % 2 == 0:
1233 # Hey, a lonely _CTCP_DELIMITER at the end! This means
1234 # that the last chunk, including the delimiter, is a
1235 # normal message! (This is according to the CTCP
1236 # specification.)
1237 messages.append(_CTCP_DELIMITER + chunks[-1])
1239 return messages
1241 def is_channel(string):
1242 """Check if a string is a channel name.
1244 Returns true if the argument is a channel name, otherwise false.
1246 return string and string[0] in "#&+!"
1248 def ip_numstr_to_quad(num):
1249 """Convert an IP number as an integer given in ASCII
1250 representation (e.g. '3232235521') to an IP address string
1251 (e.g. '192.168.0.1')."""
1252 n = long(num)
1253 p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
1254 n >> 8 & 0xFF, n & 0xFF]))
1255 return ".".join(p)
1257 def ip_quad_to_numstr(quad):
1258 """Convert an IP address string (e.g. '192.168.0.1') to an IP
1259 number as an integer given in ASCII representation
1260 (e.g. '3232235521')."""
1261 p = map(long, quad.split("."))
1262 s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
1263 if s[-1] == "L":
1264 s = s[:-1]
1265 return s
1267 def nm_to_n(s):
1268 """Get the nick part of a nickmask.
1270 (The source of an Event is a nickmask.)
1272 return s.split("!")[0]
1274 def nm_to_uh(s):
1275 """Get the userhost part of a nickmask.
1277 (The source of an Event is a nickmask.)
1279 return s.split("!")[1]
1281 def nm_to_h(s):
1282 """Get the host part of a nickmask.
1284 (The source of an Event is a nickmask.)
1286 return s.split("@")[1]
1288 def nm_to_u(s):
1289 """Get the user part of a nickmask.
1291 (The source of an Event is a nickmask.)
1293 s = s.split("!")[1]
1294 return s.split("@")[0]
1296 def parse_nick_modes(mode_string):
1297 """Parse a nick mode string.
1299 The function returns a list of lists with three members: sign,
1300 mode and argument. The sign is \"+\" or \"-\". The argument is
1301 always None.
1303 Example:
1305 >>> irclib.parse_nick_modes(\"+ab-c\")
1306 [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1309 return _parse_modes(mode_string, "")
1311 def parse_channel_modes(mode_string):
1312 """Parse a channel mode string.
1314 The function returns a list of lists with three members: sign,
1315 mode and argument. The sign is \"+\" or \"-\". The argument is
1316 None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\".
1318 Example:
1320 >>> irclib.parse_channel_modes(\"+ab-c foo\")
1321 [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1324 return _parse_modes(mode_string, "bklvhoaq")
1326 def _parse_modes(mode_string, unary_modes=""):
1327 """[Internal]"""
1328 modes = []
1329 arg_count = 0
1331 # State variable.
1332 sign = ""
1334 a = mode_string.split()
1335 if len(a) == 0:
1336 return []
1337 else:
1338 mode_part, args = a[0], a[1:]
1340 if mode_part[0] not in "+-":
1341 return []
1342 for ch in mode_part:
1343 if ch in "+-":
1344 sign = ch
1345 elif ch == " ":
1346 collecting_arguments = 1
1347 elif ch in unary_modes:
1348 if len(args) >= arg_count + 1:
1349 modes.append([sign, ch, args[arg_count]])
1350 arg_count = arg_count + 1
1351 else:
1352 modes.append([sign, ch, None])
1353 else:
1354 modes.append([sign, ch, None])
1355 return modes
1357 def _ping_ponger(connection, event):
1358 """[Internal]"""
1359 connection.pong(event.target())
1361 # Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1362 numeric_events = {
1363 "001": "welcome",
1364 "002": "yourhost",
1365 "003": "created",
1366 "004": "myinfo",
1367 "005": "featurelist", # XXX
1368 "200": "tracelink",
1369 "201": "traceconnecting",
1370 "202": "tracehandshake",
1371 "203": "traceunknown",
1372 "204": "traceoperator",
1373 "205": "traceuser",
1374 "206": "traceserver",
1375 "207": "traceservice",
1376 "208": "tracenewtype",
1377 "209": "traceclass",
1378 "210": "tracereconnect",
1379 "211": "statslinkinfo",
1380 "212": "statscommands",
1381 "213": "statscline",
1382 "214": "statsnline",
1383 "215": "statsiline",
1384 "216": "statskline",
1385 "217": "statsqline",
1386 "218": "statsyline",
1387 "219": "endofstats",
1388 "221": "umodeis",
1389 "231": "serviceinfo",
1390 "232": "endofservices",
1391 "233": "service",
1392 "234": "servlist",
1393 "235": "servlistend",
1394 "241": "statslline",
1395 "242": "statsuptime",
1396 "243": "statsoline",
1397 "244": "statshline",
1398 "250": "luserconns",
1399 "251": "luserclient",
1400 "252": "luserop",
1401 "253": "luserunknown",
1402 "254": "luserchannels",
1403 "255": "luserme",
1404 "256": "adminme",
1405 "257": "adminloc1",
1406 "258": "adminloc2",
1407 "259": "adminemail",
1408 "261": "tracelog",
1409 "262": "endoftrace",
1410 "263": "tryagain",
1411 "265": "n_local",
1412 "266": "n_global",
1413 "300": "none",
1414 "301": "away",
1415 "302": "userhost",
1416 "303": "ison",
1417 "305": "unaway",
1418 "306": "nowaway",
1419 "311": "whoisuser",
1420 "312": "whoisserver",
1421 "313": "whoisoperator",
1422 "314": "whowasuser",
1423 "315": "endofwho",
1424 "316": "whoischanop",
1425 "317": "whoisidle",
1426 "318": "endofwhois",
1427 "319": "whoischannels",
1428 "321": "liststart",
1429 "322": "list",
1430 "323": "listend",
1431 "324": "channelmodeis",
1432 "329": "channelcreate",
1433 "331": "notopic",
1434 "332": "currenttopic",
1435 "333": "topicinfo",
1436 "341": "inviting",
1437 "342": "summoning",
1438 "346": "invitelist",
1439 "347": "endofinvitelist",
1440 "348": "exceptlist",
1441 "349": "endofexceptlist",
1442 "351": "version",
1443 "352": "whoreply",
1444 "353": "namreply",
1445 "361": "killdone",
1446 "362": "closing",
1447 "363": "closeend",
1448 "364": "links",
1449 "365": "endoflinks",
1450 "366": "endofnames",
1451 "367": "banlist",
1452 "368": "endofbanlist",
1453 "369": "endofwhowas",
1454 "371": "info",
1455 "372": "motd",
1456 "373": "infostart",
1457 "374": "endofinfo",
1458 "375": "motdstart",
1459 "376": "endofmotd",
1460 "377": "motd2", # 1997-10-16 -- tkil
1461 "381": "youreoper",
1462 "382": "rehashing",
1463 "384": "myportis",
1464 "391": "time",
1465 "392": "usersstart",
1466 "393": "users",
1467 "394": "endofusers",
1468 "395": "nousers",
1469 "401": "nosuchnick",
1470 "402": "nosuchserver",
1471 "403": "nosuchchannel",
1472 "404": "cannotsendtochan",
1473 "405": "toomanychannels",
1474 "406": "wasnosuchnick",
1475 "407": "toomanytargets",
1476 "409": "noorigin",
1477 "411": "norecipient",
1478 "412": "notexttosend",
1479 "413": "notoplevel",
1480 "414": "wildtoplevel",
1481 "421": "unknowncommand",
1482 "422": "nomotd",
1483 "423": "noadmininfo",
1484 "424": "fileerror",
1485 "431": "nonicknamegiven",
1486 "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1487 "433": "nicknameinuse",
1488 "436": "nickcollision",
1489 "437": "unavailresource", # "Nick temporally unavailable"
1490 "441": "usernotinchannel",
1491 "442": "notonchannel",
1492 "443": "useronchannel",
1493 "444": "nologin",
1494 "445": "summondisabled",
1495 "446": "usersdisabled",
1496 "451": "notregistered",
1497 "461": "needmoreparams",
1498 "462": "alreadyregistered",
1499 "463": "nopermforhost",
1500 "464": "passwdmismatch",
1501 "465": "yourebannedcreep", # I love this one...
1502 "466": "youwillbebanned",
1503 "467": "keyset",
1504 "471": "channelisfull",
1505 "472": "unknownmode",
1506 "473": "inviteonlychan",
1507 "474": "bannedfromchan",
1508 "475": "badchannelkey",
1509 "476": "badchanmask",
1510 "477": "nochanmodes", # "Channel doesn't support modes"
1511 "478": "banlistfull",
1512 "481": "noprivileges",
1513 "482": "chanoprivsneeded",
1514 "483": "cantkillserver",
1515 "484": "restricted", # Connection is restricted
1516 "485": "uniqopprivsneeded",
1517 "491": "nooperhost",
1518 "492": "noservicehost",
1519 "501": "umodeunknownflag",
1520 "502": "usersdontmatch",
1523 generated_events = [
1524 # Generated events
1525 "dcc_connect",
1526 "dcc_disconnect",
1527 "dccmsg",
1528 "disconnect",
1529 "ctcp",
1530 "ctcpreply",
1533 protocol_events = [
1534 # IRC protocol events
1535 "error",
1536 "join",
1537 "kick",
1538 "mode",
1539 "part",
1540 "ping",
1541 "privmsg",
1542 "privnotice",
1543 "pubmsg",
1544 "pubnotice",
1545 "quit",
1546 "invite",
1547 "pong",
1550 all_events = generated_events + protocol_events + numeric_events.values()