1 # -*- coding: utf-8 -*-
3 # Copyright (C) 1999-2002 Joel Rosdahl
4 # Portions Copyright 2011 Jason R. Coombs
6 # This library is free software; you can redistribute it and/or
7 # modify it under the terms of the GNU Lesser General Public
8 # License as published by the Free Software Foundation; either
9 # version 2.1 of the License, or (at your option) any later version.
11 # This library is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 # Lesser General Public License for more details.
16 # You should have received a copy of the GNU Lesser General Public
17 # License along with this library; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 # keltus <keltus@users.sourceforge.net>
23 irclib -- Internet Relay Chat (IRC) protocol client library.
25 This library is intended to encapsulate the IRC protocol at a quite
26 low level. It provides an event-driven IRC client framework. It has
27 a fairly thorough support for the basic IRC protocol, CTCP, DCC chat,
28 but DCC file transfers is not yet supported.
30 In order to understand how to make an IRC client, I'm afraid you more
31 or less must understand the IRC specifications. They are available
32 here: [IRC specifications].
34 The main features of the IRC client framework are:
36 * Abstraction of the IRC protocol.
37 * Handles multiple simultaneous IRC server connections.
38 * Handles server PONGing transparently.
39 * Messages to the IRC server are done by calling methods on an IRC
41 * Messages from an IRC server triggers events, which can be caught
43 * Reading from and writing to IRC server sockets are normally done
44 by an internal select() loop, but the select()ing may be done by
45 an external main loop.
46 * Functions can be registered to execute at specified times by the
48 * Decodes CTCP tagging correctly (hopefully); I haven't seen any
49 other IRC client implementation that handles the CTCP
50 specification subtilties.
51 * A kind of simple, single-server, object-oriented IRC client class
52 that dispatches events to instance methods is included.
56 * The IRC protocol shines through the abstraction a bit too much.
57 * Data is not written asynchronously to the server, i.e. the write()
58 may block if the TCP buffers are stuffed.
59 * There are no support for DCC file transfers.
60 * The author haven't even read RFC 2810, 2811, 2812 and 2813.
61 * Like most projects, documentation is lacking...
63 .. [IRC specifications] http://www.irchelp.org/irchelp/rfc/
79 _pkg = pkg_resources.require('python-irclib')[0]
80 VERSION = tuple(int(res) for res in re.findall('\d+', _pkg.version))
86 # (maybe) thread safety
87 # (maybe) color parser convenience functions
88 # documentation (including all event types)
89 # (maybe) add awareness of different types of ircds
90 # send data asynchronously to the server (and DCC connections)
91 # (maybe) automatically close unused, passive DCC connections after a while
95 # connection.quit() only sends QUIT to the server.
96 # ERROR from the server triggers the error event and the disconnect event.
97 # dropping of the connection triggers the disconnect event.
99 class IRCError(Exception):
100 """Represents an IRC exception."""
105 """Class that handles one or several IRC server connections.
107 When an IRC object has been instantiated, it can be used to create
108 Connection objects that represent the IRC connections. The
109 responsibility of the IRC object is to provide an event-driven
110 framework for the connections and to keep the connections alive.
111 It runs a select loop to poll each connection's TCP socket and
112 hands over the sockets with incoming data for processing by the
113 corresponding connection.
115 The methods of most interest for an IRC client writer are server,
116 add_global_handler, remove_global_handler, execute_at,
117 execute_delayed, process_once and process_forever.
122 server = irc.server()
123 server.connect("irc.some.where", 6667, "my_nickname")
124 server.privmsg("a_nickname", "Hi there!")
125 irc.process_forever()
127 This will connect to the IRC server irc.some.where on port 6667
128 using the nickname my_nickname and send the message "Hi there!"
129 to the nickname a_nickname.
132 def __init__(self, fn_to_add_socket=None,
133 fn_to_remove_socket=None,
134 fn_to_add_timeout=None,
136 """Constructor for IRC objects.
138 Optional arguments are fn_to_add_socket, fn_to_remove_socket
139 and fn_to_add_timeout. The first two specify functions that
140 will be called with a socket object as argument when the IRC
141 object wants to be notified (or stop being notified) of data
142 coming on a new socket. When new data arrives, the method
143 process_data should be called. Similarly, fn_to_add_timeout
144 is called with a number of seconds (a floating point number)
145 as first argument when the IRC object wants to receive a
146 notification (by calling the process_timeout method). So, if
147 e.g. the argument is 42.17, the object wants the
148 process_timeout method to be called after 42 seconds and 170
151 The three arguments mainly exist to be able to use an external
152 main loop (for example Tkinter's or PyGTK's main app loop)
153 instead of calling the process_forever method.
155 An alternative is to just call ServerConnection.process_once()
159 if fn_to_add_socket and fn_to_remove_socket:
160 self.fn_to_add_socket = fn_to_add_socket
161 self.fn_to_remove_socket = fn_to_remove_socket
163 self.fn_to_add_socket = None
164 self.fn_to_remove_socket = None
166 self.fn_to_add_timeout = fn_to_add_timeout
167 self.debuglevel = debuglevel
168 self.connections = []
170 self.delayed_commands = [] # list of DelayedCommands
172 self.add_global_handler("ping", _ping_ponger, -42)
175 """Creates and returns a ServerConnection object."""
177 c = ServerConnection(self)
178 self.connections.append(c)
181 def process_data(self, sockets):
182 """Called when there is more data to read on connection sockets.
186 sockets -- A list of socket objects.
188 See documentation for IRC.__init__.
190 self.debug(2, "process_data()")
192 for c in self.connections:
193 if s == c._get_socket():
196 def process_timeout(self):
197 """Called when a timeout notification is due.
199 See documentation for IRC.__init__.
201 while self.delayed_commands:
202 command = self.delayed_commands[0]
203 if not command.due():
205 command.function(*command.arguments)
206 if isinstance(command, PeriodicCommand):
207 self._schedule_command(command.next())
208 del self.delayed_commands[0]
210 def process_once(self, timeout=0):
211 """Process data from connections once.
215 timeout -- How long the select() call should wait if no
218 This method should be called periodically to check and process
219 incoming data, if there are any. If that seems boring, look
220 at the process_forever method.
222 self.debug(2, "process_once()")
223 sockets = map(lambda x: x._get_socket(), self.connections)
224 sockets = filter(lambda x: x != None, sockets)
226 (i, o, e) = select.select(sockets, [], [], timeout)
230 self.process_timeout()
232 def process_forever(self, timeout=0.2):
233 """Run an infinite loop, processing data from connections.
235 This method repeatedly calls process_once.
239 timeout -- Parameter to pass to process_once.
241 self.debug(1, "process_forever(timeout=%s)" % (timeout))
243 self.process_once(timeout)
245 def disconnect_all(self, message=""):
246 """Disconnects all connections."""
247 for c in self.connections:
248 c.disconnect(message)
250 def add_global_handler(self, event, handler, priority=0):
251 """Adds a global handler function for a specific event type.
255 event -- Event type (a string). Check the values of the
256 numeric_events dictionary in irclib.py for possible event
259 handler -- Callback function.
261 priority -- A number (the lower number, the higher priority).
263 The handler function is called whenever the specified event is
264 triggered in any of the connections. See documentation for
267 The handler functions are called in priority order (lowest
268 number is highest priority). If a handler function returns
269 "NO MORE", no more handlers will be called.
271 if not event in self.handlers:
272 self.handlers[event] = []
273 bisect.insort(self.handlers[event], ((priority, handler)))
275 def remove_global_handler(self, event, handler):
276 """Removes a global handler function.
280 event -- Event type (a string).
281 handler -- Callback function.
283 Returns 1 on success, otherwise 0.
285 if not event in self.handlers:
287 for h in self.handlers[event]:
289 self.handlers[event].remove(h)
292 def execute_at(self, at, function, arguments=()):
293 """Execute a function at a specified time.
297 at -- Execute at this time (standard "time_t" time).
298 function -- Function to call.
299 arguments -- Arguments to give the function.
301 command = DelayedCommand.at_time(at, function, arguments)
302 self._schedule_command(command)
304 def execute_delayed(self, delay, function, arguments=()):
306 Execute a function after a specified time.
308 delay -- How many seconds to wait.
309 function -- Function to call.
310 arguments -- Arguments to give the function.
312 command = DelayedCommand(delay, function, arguments)
313 self._schedule_command(command)
315 def execute_every(self, period, function, arguments=()):
317 Execute a function every 'period' seconds.
319 period -- How often to run (always waits this long for first).
320 function -- Function to call.
321 arguments -- Arguments to give the function.
323 command = PeriodicCommand(period, function, arguments)
324 self._schedule_command(command)
326 def _schedule_command(self, command):
327 bisect.insort(self.delayed_commands, command)
328 if self.fn_to_add_timeout:
329 self.fn_to_add_timeout(total_seconds(command.delay))
331 def dcc(self, dcctype="chat"):
332 """Creates and returns a DCCConnection object.
336 dcctype -- "chat" for DCC CHAT connections or "raw" for
337 DCC SEND (or other DCC types). If "chat",
338 incoming data will be split in newline-separated
339 chunks. If "raw", incoming data is not touched.
341 c = DCCConnection(self, dcctype)
342 self.connections.append(c)
345 def _handle_event(self, connection, event):
348 th = sorted(h.get("all_events", []) + h.get(event.eventtype(), []))
350 if handler[1](connection, event) == "NO MORE":
353 def debug(self, level, errmsg):
354 """Display debugging information."""
355 if self.debuglevel >= level:
356 sys.stderr.write("irclib[%d]: %s\n" % (self.debuglevel, errmsg))
358 def _remove_connection(self, connection):
360 self.connections.remove(connection)
361 if self.fn_to_remove_socket:
362 self.fn_to_remove_socket(connection._get_socket())
364 class DelayedCommand(datetime.datetime):
366 A command to be executed after some delay (seconds or timedelta).
368 def __new__(cls, delay, function, arguments):
369 if not isinstance(delay, datetime.timedelta):
370 delay = datetime.timedelta(seconds=delay)
371 at = datetime.datetime.utcnow() + delay
372 cmd = datetime.datetime.__new__(DelayedCommand, at.year,
373 at.month, at.day, at.hour, at.minute, at.second,
374 at.microsecond, at.tzinfo)
376 cmd.function = function
377 cmd.arguments = arguments
380 def at_time(cls, at, function, arguments):
382 Construct a DelayedCommand to come due at `at`, where `at` may be
383 a datetime or timestamp.
385 if isinstance(at, int):
386 at = datetime.datetime.utcfromtimestamp(at)
387 delay = at - datetime.datetime.utcnow()
388 return cls(delay, function, arguments)
389 at_time = classmethod(at_time)
392 return datetime.datetime.utcnow() >= self
394 class PeriodicCommand(DelayedCommand):
396 Like a deferred command, but expect this command to run every delay
400 return PeriodicCommand(self.delay, self.function,
403 _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
405 class Connection(object):
406 """Base class for IRC connections.
410 def __init__(self, irclibobj):
411 self.irclibobj = irclibobj
414 raise IRCError("Not overridden")
416 ##############################
417 ### Convenience wrappers.
419 def execute_at(self, at, function, arguments=()):
420 self.irclibobj.execute_at(at, function, arguments)
422 def execute_delayed(self, delay, function, arguments=()):
423 self.irclibobj.execute_delayed(delay, function, arguments)
425 def execute_every(self, period, function, arguments=()):
426 self.irclibobj.execute_every(period, function, arguments)
428 class ServerConnectionError(IRCError):
431 class ServerNotConnectedError(ServerConnectionError):
435 # Huh!? Crrrrazy EFNet doesn't follow the RFC: their ircd seems to
436 # use \n as message separator! :P
437 _linesep_regexp = re.compile("\r?\n")
439 class ServerConnection(Connection):
440 """This class represents an IRC server connection.
442 ServerConnection objects are instantiated by calling the server
443 method on an IRC object.
446 def __init__(self, irclibobj):
447 super(ServerConnection, self).__init__(irclibobj)
448 self.irclibobj = irclibobj
449 self.connected = 0 # Not connected yet.
453 def connect(self, server, port, nickname, password=None, username=None,
454 ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
455 """Connect/reconnect to a server.
459 server -- Server name.
463 nickname -- The nickname.
465 password -- Password (if any).
467 username -- The username.
469 ircname -- The IRC name ("realname").
471 localaddress -- Bind the connection to a specific local IP address.
473 localport -- Bind the connection to a specific local port.
475 ssl -- Enable support for ssl.
477 ipv6 -- Enable support for ipv6.
479 This function can be called to reconnect a closed connection.
481 Returns the ServerConnection object.
484 self.disconnect("Changing servers")
486 self.previous_buffer = ""
488 self.real_server_name = ""
489 self.real_nickname = nickname
492 self.nickname = nickname
493 self.username = username or nickname
494 self.ircname = ircname or nickname
495 self.password = password
496 self.localaddress = localaddress
497 self.localport = localport
499 self.irclibobj.debug(1, "connect(server=%s, port=%s, nick=%s)" \
500 % (self.server, self.port, self.nickname))
502 self.localhost = socket.gethostname()
504 self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
506 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
508 self.socket.bind((self.localaddress, self.localport))
509 self.socket.connect((self.server, self.port))
511 self.ssl = ssl_mod.wrap_socket(self.socket)
512 except socket.error, x:
515 raise ServerConnectionError("Couldn't connect to socket: %s" % x)
517 if self.irclibobj.fn_to_add_socket:
518 self.irclibobj.fn_to_add_socket(self.socket)
522 self.pass_(self.password)
523 self.nick(self.nickname)
524 self.user(self.username, self.ircname)
528 """Close the connection.
530 This method closes the connection permanently; after it has
531 been called, the object is unusable.
534 self.disconnect("Closing object")
535 self.irclibobj._remove_connection(self)
537 def _get_socket(self):
541 def get_server_name(self):
542 """Get the (real) server name.
544 This method returns the (real) server name, or, more
545 specifically, what the server calls itself.
548 if self.real_server_name:
549 return self.real_server_name
553 def get_nickname(self):
554 """Get the (real) nick name.
556 This method returns the (real) nickname. The library keeps
557 track of nick changes, so it might not be the nick name that
558 was passed to the connect() method. """
560 return self.real_nickname
562 def process_data(self):
567 new_data = self.ssl.read(2 ** 14)
569 new_data = self.socket.recv(2 ** 14)
571 # The server hung up.
572 self.disconnect("Connection reset by peer")
575 # Read nothing: connection must be down.
576 self.disconnect("Connection reset by peer")
579 lines = _linesep_regexp.split(self.previous_buffer + new_data)
581 # Save the last, unfinished line.
582 self.previous_buffer = lines.pop()
585 self.irclibobj.debug(1, "FROM SERVER: %s" % repr(line))
593 self._handle_event(Event("all_raw_messages",
594 self.get_server_name(),
598 m = _rfc_1459_command_regexp.match(line)
599 if m.group("prefix"):
600 prefix = m.group("prefix")
601 if not self.real_server_name:
602 self.real_server_name = prefix
604 if m.group("command"):
605 command = m.group("command").lower()
607 if m.group("argument"):
608 a = m.group("argument").split(" :", 1)
609 arguments = a[0].split()
611 arguments.append(a[1])
613 # Translate numerics into more readable strings.
614 if command in numeric_events:
615 command = numeric_events[command]
617 if command == "nick":
618 if nm_to_n(prefix) == self.real_nickname:
619 self.real_nickname = arguments[0]
620 elif command == "welcome":
621 # Record the nickname in case the client changed nick
622 # in a nicknameinuse callback.
623 self.real_nickname = arguments[0]
625 if command in ["privmsg", "notice"]:
626 target, message = arguments[0], arguments[1]
627 messages = _ctcp_dequote(message)
629 if command == "privmsg":
630 if is_channel(target):
633 if is_channel(target):
634 command = "pubnotice"
636 command = "privnotice"
639 if type(m) is types.TupleType:
640 if command in ["privmsg", "pubmsg"]:
643 command = "ctcpreply"
646 self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (command, prefix, target, m))
647 self._handle_event(Event(command, prefix, target, m))
648 if command == "ctcp" and m[0] == "ACTION":
649 self._handle_event(Event("action", prefix, target, m[1:]))
651 self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (command, prefix, target, [m]))
652 self._handle_event(Event(command, prefix, target, [m]))
656 if command == "quit":
657 arguments = [arguments[0]]
658 elif command == "ping":
659 target = arguments[0]
661 target = arguments[0]
662 arguments = arguments[1:]
664 if command == "mode":
665 if not is_channel(target):
668 self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (command, prefix, target, m))
669 self._handle_event(Event(command, prefix, target, arguments))
671 def _handle_event(self, event):
673 self.irclibobj._handle_event(self, event)
674 if event.eventtype() in self.handlers:
675 for fn in self.handlers[event.eventtype()]:
678 def is_connected(self):
679 """Return connection status.
681 Returns true if connected, otherwise false.
683 return self.connected
685 def add_global_handler(self, *args):
686 """Add global handler.
688 See documentation for IRC.add_global_handler.
690 self.irclibobj.add_global_handler(*args)
692 def remove_global_handler(self, *args):
693 """Remove global handler.
695 See documentation for IRC.remove_global_handler.
697 self.irclibobj.remove_global_handler(*args)
699 def action(self, target, action):
700 """Send a CTCP ACTION command."""
701 self.ctcp("ACTION", target, action)
703 def admin(self, server=""):
704 """Send an ADMIN command."""
705 self.send_raw(" ".join(["ADMIN", server]).strip())
707 def ctcp(self, ctcptype, target, parameter=""):
708 """Send a CTCP command."""
709 ctcptype = ctcptype.upper()
710 self.privmsg(target, "\001%s%s\001" % (ctcptype, parameter and (" " + parameter) or ""))
712 def ctcp_reply(self, target, parameter):
713 """Send a CTCP REPLY command."""
714 self.notice(target, "\001%s\001" % parameter)
716 def disconnect(self, message=""):
717 """Hang up the connection.
721 message -- Quit message.
723 if not self.connected:
735 self._handle_event(Event("disconnect", self.server, "", [message]))
737 def globops(self, text):
738 """Send a GLOBOPS command."""
739 self.send_raw("GLOBOPS :" + text)
741 def info(self, server=""):
742 """Send an INFO command."""
743 self.send_raw(" ".join(["INFO", server]).strip())
745 def invite(self, nick, channel):
746 """Send an INVITE command."""
747 self.send_raw(" ".join(["INVITE", nick, channel]).strip())
749 def ison(self, nicks):
750 """Send an ISON command.
754 nicks -- List of nicks.
756 self.send_raw("ISON " + " ".join(nicks))
758 def join(self, channel, key=""):
759 """Send a JOIN command."""
760 self.send_raw("JOIN %s%s" % (channel, (key and (" " + key))))
762 def kick(self, channel, nick, comment=""):
763 """Send a KICK command."""
764 self.send_raw("KICK %s %s%s" % (channel, nick, (comment and (" :" + comment))))
766 def links(self, remote_server="", server_mask=""):
767 """Send a LINKS command."""
770 command = command + " " + remote_server
772 command = command + " " + server_mask
773 self.send_raw(command)
775 def list(self, channels=None, server=""):
776 """Send a LIST command."""
779 command = command + " " + ",".join(channels)
781 command = command + " " + server
782 self.send_raw(command)
784 def lusers(self, server=""):
785 """Send a LUSERS command."""
786 self.send_raw("LUSERS" + (server and (" " + server)))
788 def mode(self, target, command):
789 """Send a MODE command."""
790 self.send_raw("MODE %s %s" % (target, command))
792 def motd(self, server=""):
793 """Send an MOTD command."""
794 self.send_raw("MOTD" + (server and (" " + server)))
796 def names(self, channels=None):
797 """Send a NAMES command."""
798 self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or ""))
800 def nick(self, newnick):
801 """Send a NICK command."""
802 self.send_raw("NICK " + newnick)
804 def notice(self, target, text):
805 """Send a NOTICE command."""
806 # Should limit len(text) here!
807 self.send_raw("NOTICE %s :%s" % (target, text))
809 def oper(self, nick, password):
810 """Send an OPER command."""
811 self.send_raw("OPER %s %s" % (nick, password))
813 def part(self, channels, message=""):
814 """Send a PART command."""
815 channels = always_iterable(channels)
820 if message: cmd_parts.append(message)
821 self.send_raw(' '.join(cmd_parts))
823 def pass_(self, password):
824 """Send a PASS command."""
825 self.send_raw("PASS " + password)
827 def ping(self, target, target2=""):
828 """Send a PING command."""
829 self.send_raw("PING %s%s" % (target, target2 and (" " + target2)))
831 def pong(self, target, target2=""):
832 """Send a PONG command."""
833 self.send_raw("PONG %s%s" % (target, target2 and (" " + target2)))
835 def privmsg(self, target, text):
836 """Send a PRIVMSG command."""
837 # Should limit len(text) here!
838 self.send_raw("PRIVMSG %s :%s" % (target, text))
840 def privmsg_many(self, targets, text):
841 """Send a PRIVMSG command to multiple targets."""
842 # Should limit len(text) here!
843 self.send_raw("PRIVMSG %s :%s" % (",".join(targets), text))
845 def quit(self, message=""):
846 """Send a QUIT command."""
847 # Note that many IRC servers don't use your QUIT message
848 # unless you've been connected for at least 5 minutes!
849 self.send_raw("QUIT" + (message and (" :" + message)))
851 def send_raw(self, string):
852 """Send raw string to the server.
854 The string will be padded with appropriate CR LF.
856 if self.socket is None:
857 raise ServerNotConnectedError("Not connected.")
860 self.ssl.write(string + "\r\n")
862 self.socket.send(string + "\r\n")
863 self.irclibobj.debug(1, "TO SERVER: " + repr(string))
866 self.disconnect("Connection reset by peer.")
868 def squit(self, server, comment=""):
869 """Send an SQUIT command."""
870 self.send_raw("SQUIT %s%s" % (server, comment and (" :" + comment)))
872 def stats(self, statstype, server=""):
873 """Send a STATS command."""
874 self.send_raw("STATS %s%s" % (statstype, server and (" " + server)))
876 def time(self, server=""):
877 """Send a TIME command."""
878 self.send_raw("TIME" + (server and (" " + server)))
880 def topic(self, channel, new_topic=None):
881 """Send a TOPIC command."""
882 if new_topic is None:
883 self.send_raw("TOPIC " + channel)
885 self.send_raw("TOPIC %s :%s" % (channel, new_topic))
887 def trace(self, target=""):
888 """Send a TRACE command."""
889 self.send_raw("TRACE" + (target and (" " + target)))
891 def user(self, username, realname):
892 """Send a USER command."""
893 self.send_raw("USER %s 0 * :%s" % (username, realname))
895 def userhost(self, nicks):
896 """Send a USERHOST command."""
897 self.send_raw("USERHOST " + ",".join(nicks))
899 def users(self, server=""):
900 """Send a USERS command."""
901 self.send_raw("USERS" + (server and (" " + server)))
903 def version(self, server=""):
904 """Send a VERSION command."""
905 self.send_raw("VERSION" + (server and (" " + server)))
907 def wallops(self, text):
908 """Send a WALLOPS command."""
909 self.send_raw("WALLOPS :" + text)
911 def who(self, target="", op=""):
912 """Send a WHO command."""
913 self.send_raw("WHO%s%s" % (target and (" " + target), op and (" o")))
915 def whois(self, targets):
916 """Send a WHOIS command."""
917 self.send_raw("WHOIS " + ",".join(targets))
919 def whowas(self, nick, max="", server=""):
920 """Send a WHOWAS command."""
921 self.send_raw("WHOWAS %s%s%s" % (nick,
923 server and (" " + server)))
925 class DCCConnectionError(IRCError):
929 class DCCConnection(Connection):
930 """This class represents a DCC connection.
932 DCCConnection objects are instantiated by calling the dcc
933 method on an IRC object.
935 def __init__(self, irclibobj, dcctype):
936 super(DCCConnection, self).__init__(irclibobj)
937 self.irclibobj = irclibobj
940 self.dcctype = dcctype
941 self.peeraddress = None
944 def connect(self, address, port):
945 """Connect/reconnect to a DCC peer.
948 address -- Host/IP address of the peer.
950 port -- The port number to connect to.
952 Returns the DCCConnection object.
954 self.peeraddress = socket.gethostbyname(address)
957 self.previous_buffer = ""
959 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
962 self.socket.connect((self.peeraddress, self.peerport))
963 except socket.error, x:
964 raise DCCConnectionError("Couldn't connect to socket: %s" % x)
966 if self.irclibobj.fn_to_add_socket:
967 self.irclibobj.fn_to_add_socket(self.socket)
971 """Wait for a connection/reconnection from a DCC peer.
973 Returns the DCCConnection object.
975 The local IP address and port are available as
976 self.localaddress and self.localport. After connection from a
977 peer, the peer address and port are available as
978 self.peeraddress and self.peerport.
980 self.previous_buffer = ""
982 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
985 self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
986 self.localaddress, self.localport = self.socket.getsockname()
987 self.socket.listen(10)
988 except socket.error, x:
989 raise DCCConnectionError("Couldn't bind socket: %s" % x)
992 def disconnect(self, message=""):
993 """Hang up the connection and close the object.
997 message -- Quit message.
999 if not self.connected:
1005 except socket.error:
1008 self.irclibobj._handle_event(
1010 Event("dcc_disconnect", self.peeraddress, "", [message]))
1011 self.irclibobj._remove_connection(self)
1013 def process_data(self):
1016 if self.passive and not self.connected:
1017 conn, (self.peeraddress, self.peerport) = self.socket.accept()
1021 self.irclibobj.debug(1, "DCC connection from %s:%d" % (
1022 self.peeraddress, self.peerport))
1023 self.irclibobj._handle_event(
1025 Event("dcc_connect", self.peeraddress, None, None))
1029 new_data = self.socket.recv(2 ** 14)
1030 except socket.error:
1031 # The server hung up.
1032 self.disconnect("Connection reset by peer")
1035 # Read nothing: connection must be down.
1036 self.disconnect("Connection reset by peer")
1039 if self.dcctype == "chat":
1040 # The specification says lines are terminated with LF, but
1041 # it seems safer to handle CR LF terminations too.
1042 chunks = _linesep_regexp.split(self.previous_buffer + new_data)
1044 # Save the last, unfinished line.
1045 self.previous_buffer = chunks[-1]
1046 if len(self.previous_buffer) > 2 ** 14:
1047 # Bad peer! Naughty peer!
1050 chunks = chunks[:-1]
1055 prefix = self.peeraddress
1057 for chunk in chunks:
1058 self.irclibobj.debug(1, "FROM PEER: " + repr(chunk))
1060 self.irclibobj.debug(1, "command: %s, source: %s, target: %s, arguments: %s" % (
1061 command, prefix, target, arguments))
1062 self.irclibobj._handle_event(
1064 Event(command, prefix, target, arguments))
1066 def _get_socket(self):
1070 def privmsg(self, string):
1071 """Send data to DCC peer.
1073 The string will be padded with appropriate LF if it's a DCC
1077 self.socket.send(string)
1078 if self.dcctype == "chat":
1079 self.socket.send("\n")
1080 self.irclibobj.debug("TO PEER: %s\n" % repr(string))
1081 except socket.error:
1083 self.disconnect("Connection reset by peer.")
1085 class SimpleIRCClient(object):
1086 """A simple single-server IRC client class.
1088 This is an example of an object-oriented wrapper of the IRC
1089 framework. A real IRC client can be made by subclassing this
1090 class and adding appropriate methods.
1092 The method on_join will be called when a "join" event is created
1093 (which is done when the server sends a JOIN messsage/command),
1094 on_privmsg will be called for "privmsg" events, and so on. The
1095 handler methods get two arguments: the connection object (same as
1096 self.connection) and the event object.
1098 Instance attributes that can be used by sub classes:
1100 ircobj -- The IRC instance.
1102 connection -- The ServerConnection instance.
1104 dcc_connections -- A list of DCCConnection instances.
1108 self.connection = self.ircobj.server()
1109 self.dcc_connections = []
1110 self.ircobj.add_global_handler("all_events", self._dispatcher, -10)
1111 self.ircobj.add_global_handler("dcc_disconnect", self._dcc_disconnect, -10)
1113 def _dispatcher(self, c, e):
1115 self.irclibobj.debug("dispatcher:%s" % e.eventtype())
1117 m = "on_" + e.eventtype()
1118 if hasattr(self, m):
1119 getattr(self, m)(c, e)
1121 def _dcc_disconnect(self, c, e):
1122 self.dcc_connections.remove(c)
1124 def connect(self, server, port, nickname, password=None, username=None,
1125 ircname=None, localaddress="", localport=0, ssl=False, ipv6=False):
1126 """Connect/reconnect to a server.
1130 server -- Server name.
1132 port -- Port number.
1134 nickname -- The nickname.
1136 password -- Password (if any).
1138 username -- The username.
1140 ircname -- The IRC name.
1142 localaddress -- Bind the connection to a specific local IP address.
1144 localport -- Bind the connection to a specific local port.
1146 ssl -- Enable support for ssl.
1148 ipv6 -- Enable support for ipv6.
1150 This function can be called to reconnect a closed connection.
1152 self.connection.connect(server, port, nickname,
1153 password, username, ircname,
1154 localaddress, localport, ssl, ipv6)
1156 def dcc_connect(self, address, port, dcctype="chat"):
1157 """Connect to a DCC peer.
1161 address -- IP address of the peer.
1163 port -- Port to connect to.
1165 Returns a DCCConnection instance.
1167 dcc = self.ircobj.dcc(dcctype)
1168 self.dcc_connections.append(dcc)
1169 dcc.connect(address, port)
1172 def dcc_listen(self, dcctype="chat"):
1173 """Listen for connections from a DCC peer.
1175 Returns a DCCConnection instance.
1177 dcc = self.ircobj.dcc(dcctype)
1178 self.dcc_connections.append(dcc)
1183 """Start the IRC client."""
1184 self.ircobj.process_forever()
1187 class Event(object):
1188 """Class representing an IRC event."""
1189 def __init__(self, eventtype, source, target, arguments=None):
1190 """Constructor of Event objects.
1194 eventtype -- A string describing the event.
1196 source -- The originator of the event (a nick mask or a server).
1198 target -- The target of the event (a nick or a channel).
1200 arguments -- Any event specific arguments.
1202 self._eventtype = eventtype
1203 self._source = source
1204 self._target = target
1206 self._arguments = arguments
1208 self._arguments = []
1210 def eventtype(self):
1211 """Get the event type."""
1212 return self._eventtype
1215 """Get the event source."""
1219 """Get the event target."""
1222 def arguments(self):
1223 """Get the event arguments."""
1224 return self._arguments
1226 _LOW_LEVEL_QUOTE = "\020"
1227 _CTCP_LEVEL_QUOTE = "\134"
1228 _CTCP_DELIMITER = "\001"
1230 _low_level_mapping = {
1234 _LOW_LEVEL_QUOTE: _LOW_LEVEL_QUOTE
1237 _low_level_regexp = re.compile(_LOW_LEVEL_QUOTE + "(.)")
1239 def mask_matches(nick, mask):
1240 """Check if a nick matches a mask.
1242 Returns true if the nick matches, otherwise false.
1244 nick = irc_lower(nick)
1245 mask = irc_lower(mask)
1246 mask = mask.replace("\\", "\\\\")
1247 for ch in ".$|[](){}+":
1248 mask = mask.replace(ch, "\\" + ch)
1249 mask = mask.replace("?", ".")
1250 mask = mask.replace("*", ".*")
1251 r = re.compile(mask, re.IGNORECASE)
1252 return r.match(nick)
1254 _special = "-[]\\`^{}"
1255 nick_characters = string.ascii_letters + string.digits + _special
1257 def _ctcp_dequote(message):
1258 """[Internal] Dequote a message according to CTCP specifications.
1260 The function returns a list where each element can be either a
1261 string (normal message) or a tuple of one or two strings (tagged
1262 messages). If a tuple has only one element (ie is a singleton),
1263 that element is the tag; otherwise the tuple has two elements: the
1268 message -- The message to be decoded.
1271 def _low_level_replace(match_obj):
1272 ch = match_obj.group(1)
1274 # If low_level_mapping doesn't have the character as key, we
1275 # should just return the character.
1276 return _low_level_mapping.get(ch, ch)
1278 if _LOW_LEVEL_QUOTE in message:
1279 # Yup, there was a quote. Release the dequoter, man!
1280 message = _low_level_regexp.sub(_low_level_replace, message)
1282 if _CTCP_DELIMITER not in message:
1285 # Split it into parts. (Does any IRC client actually *use*
1286 # CTCP stacking like this?)
1287 chunks = message.split(_CTCP_DELIMITER)
1291 while i < len(chunks) - 1:
1292 # Add message if it's non-empty.
1293 if len(chunks[i]) > 0:
1294 messages.append(chunks[i])
1296 if i < len(chunks) - 2:
1297 # Aye! CTCP tagged data ahead!
1298 messages.append(tuple(chunks[i + 1].split(" ", 1)))
1302 if len(chunks) % 2 == 0:
1303 # Hey, a lonely _CTCP_DELIMITER at the end! This means
1304 # that the last chunk, including the delimiter, is a
1305 # normal message! (This is according to the CTCP
1307 messages.append(_CTCP_DELIMITER + chunks[-1])
1311 def is_channel(string):
1312 """Check if a string is a channel name.
1314 Returns true if the argument is a channel name, otherwise false.
1316 return string and string[0] in "#&+!"
1318 def ip_numstr_to_quad(num):
1319 """Convert an IP number as an integer given in ASCII
1320 representation (e.g. '3232235521') to an IP address string
1321 (e.g. '192.168.0.1')."""
1323 p = map(str, map(int, [n >> 24 & 0xFF, n >> 16 & 0xFF,
1324 n >> 8 & 0xFF, n & 0xFF]))
1327 def ip_quad_to_numstr(quad):
1328 """Convert an IP address string (e.g. '192.168.0.1') to an IP
1329 number as an integer given in ASCII representation
1330 (e.g. '3232235521')."""
1331 p = map(long, quad.split("."))
1332 s = str((p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3])
1338 """Get the nick part of a nickmask.
1340 (The source of an Event is a nickmask.)
1342 return s.split("!")[0]
1345 """Get the userhost part of a nickmask.
1347 (The source of an Event is a nickmask.)
1349 return s.split("!")[1]
1352 """Get the host part of a nickmask.
1354 (The source of an Event is a nickmask.)
1356 return s.split("@")[1]
1359 """Get the user part of a nickmask.
1361 (The source of an Event is a nickmask.)
1364 return s.split("@")[0]
1366 def parse_nick_modes(mode_string):
1367 """Parse a nick mode string.
1369 The function returns a list of lists with three members: sign,
1370 mode and argument. The sign is "+" or "-". The argument is
1375 >>> parse_nick_modes("+ab-c")
1376 [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
1379 return _parse_modes(mode_string, "")
1381 def parse_channel_modes(mode_string):
1382 """Parse a channel mode string.
1384 The function returns a list of lists with three members: sign,
1385 mode and argument. The sign is "+" or "-". The argument is
1386 None if mode isn't one of "b", "k", "l", "v" or "o".
1390 >>> parse_channel_modes("+ab-c foo")
1391 [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
1394 return _parse_modes(mode_string, "bklvo")
1396 def _parse_modes(mode_string, unary_modes=""):
1404 a = mode_string.split()
1408 mode_part, args = a[0], a[1:]
1410 if mode_part[0] not in "+-":
1412 for ch in mode_part:
1417 elif ch in unary_modes:
1418 if len(args) >= arg_count + 1:
1419 modes.append([sign, ch, args[arg_count]])
1420 arg_count = arg_count + 1
1422 modes.append([sign, ch, None])
1424 modes.append([sign, ch, None])
1427 def _ping_ponger(connection, event):
1429 connection.pong(event.target())
1431 # Numeric table mostly stolen from the Perl IRC module (Net::IRC).
1437 "005": "featurelist", # XXX
1439 "201": "traceconnecting",
1440 "202": "tracehandshake",
1441 "203": "traceunknown",
1442 "204": "traceoperator",
1444 "206": "traceserver",
1445 "207": "traceservice",
1446 "208": "tracenewtype",
1447 "209": "traceclass",
1448 "210": "tracereconnect",
1449 "211": "statslinkinfo",
1450 "212": "statscommands",
1451 "213": "statscline",
1452 "214": "statsnline",
1453 "215": "statsiline",
1454 "216": "statskline",
1455 "217": "statsqline",
1456 "218": "statsyline",
1457 "219": "endofstats",
1459 "231": "serviceinfo",
1460 "232": "endofservices",
1463 "235": "servlistend",
1464 "241": "statslline",
1465 "242": "statsuptime",
1466 "243": "statsoline",
1467 "244": "statshline",
1468 "250": "luserconns",
1469 "251": "luserclient",
1471 "253": "luserunknown",
1472 "254": "luserchannels",
1477 "259": "adminemail",
1479 "262": "endoftrace",
1490 "312": "whoisserver",
1491 "313": "whoisoperator",
1492 "314": "whowasuser",
1494 "316": "whoischanop",
1496 "318": "endofwhois",
1497 "319": "whoischannels",
1501 "324": "channelmodeis",
1502 "329": "channelcreate",
1504 "332": "currenttopic",
1508 "346": "invitelist",
1509 "347": "endofinvitelist",
1510 "348": "exceptlist",
1511 "349": "endofexceptlist",
1519 "365": "endoflinks",
1520 "366": "endofnames",
1522 "368": "endofbanlist",
1523 "369": "endofwhowas",
1530 "377": "motd2", # 1997-10-16 -- tkil
1535 "392": "usersstart",
1537 "394": "endofusers",
1539 "401": "nosuchnick",
1540 "402": "nosuchserver",
1541 "403": "nosuchchannel",
1542 "404": "cannotsendtochan",
1543 "405": "toomanychannels",
1544 "406": "wasnosuchnick",
1545 "407": "toomanytargets",
1547 "411": "norecipient",
1548 "412": "notexttosend",
1549 "413": "notoplevel",
1550 "414": "wildtoplevel",
1551 "421": "unknowncommand",
1553 "423": "noadmininfo",
1555 "431": "nonicknamegiven",
1556 "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
1557 "433": "nicknameinuse",
1558 "436": "nickcollision",
1559 "437": "unavailresource", # "Nick temporally unavailable"
1560 "441": "usernotinchannel",
1561 "442": "notonchannel",
1562 "443": "useronchannel",
1564 "445": "summondisabled",
1565 "446": "usersdisabled",
1566 "451": "notregistered",
1567 "461": "needmoreparams",
1568 "462": "alreadyregistered",
1569 "463": "nopermforhost",
1570 "464": "passwdmismatch",
1571 "465": "yourebannedcreep", # I love this one...
1572 "466": "youwillbebanned",
1574 "471": "channelisfull",
1575 "472": "unknownmode",
1576 "473": "inviteonlychan",
1577 "474": "bannedfromchan",
1578 "475": "badchannelkey",
1579 "476": "badchanmask",
1580 "477": "nochanmodes", # "Channel doesn't support modes"
1581 "478": "banlistfull",
1582 "481": "noprivileges",
1583 "482": "chanoprivsneeded",
1584 "483": "cantkillserver",
1585 "484": "restricted", # Connection is restricted
1586 "485": "uniqopprivsneeded",
1587 "491": "nooperhost",
1588 "492": "noservicehost",
1589 "501": "umodeunknownflag",
1590 "502": "usersdontmatch",
1593 generated_events = [
1604 # IRC protocol events
1620 all_events = generated_events + protocol_events + numeric_events.values()
1622 # from jaraco.util.itertools
1623 def always_iterable(item):
1625 Given an object, always return an iterable. If the item is not
1626 already iterable, return a tuple containing only the item.
1628 >>> always_iterable([1,2,3])
1630 >>> always_iterable('foo')
1632 >>> always_iterable(None)
1634 >>> always_iterable(xrange(10))
1637 if isinstance(item, basestring) or not hasattr(item, '__iter__'):
1641 # from jaraco.util.string
1642 class FoldedCase(str):
1644 A case insensitive string class; behaves just like str
1645 except compares equal when the only variation is case.
1646 >>> s = FoldedCase('hello world')
1648 >>> s == 'Hello World'
1651 >>> 'Hello World' == s
1658 ['hell', ' w', 'rld']
1660 >>> names = map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])
1663 ['alpha', 'Beta', 'GAMMA']
1665 def __lt__(self, other):
1666 return self.lower() < other.lower()
1668 def __gt__(self, other):
1669 return self.lower() > other.lower()
1671 def __eq__(self, other):
1672 return self.lower() == other.lower()
1675 return hash(self.lower())
1677 # cache lower since it's likely to be called frequently.
1679 self._lower = super(FoldedCase, self).lower()
1680 self.lower = lambda: self._lower
1683 def index(self, sub):
1684 return self.lower().index(sub.lower())
1686 def split(self, splitter=' ', maxsplit=0):
1687 pattern = re.compile(re.escape(splitter), re.I)
1688 return pattern.split(self, maxsplit)
1690 class IRCFoldedCase(FoldedCase):
1692 A version of FoldedCase that honors the IRC specification for lowercased
1695 >>> IRCFoldedCase('Foo^').lower()
1697 >>> IRCFoldedCase('[this]') == IRCFoldedCase('{THIS}')
1700 translation = string.maketrans(
1701 string.ascii_uppercase + r"[]\^",
1702 string.ascii_lowercase + r"{}|~",
1706 return self.translate(self.translation)
1710 return IRCFoldedCase(str).lower()
1712 def total_seconds(td):
1714 Python 2.7 adds a total_seconds method to timedelta objects.
1715 See http://docs.python.org/library/datetime.html#datetime.timedelta.total_seconds
1718 result = td.total_seconds()
1719 except AttributeError:
1720 result = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6