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