bump python-irclib to 0.6.4, the latest i care to merge right now

This commit is contained in:
Brian S. Stephan 2012-07-26 19:47:36 -05:00
parent db0cfbc997
commit 17040c1cde
2 changed files with 241 additions and 82 deletions

View File

@ -1,6 +1,6 @@
Python IRC library (python-irclib) Python IRC library (python-irclib)
* http://python-irclib.sourceforge.net/ * http://python-irclib.sourceforge.net/
* 0.4.8 * 0.6.4
* LGPLv2 * LGPLv2
python-weather-api python-weather-api

View File

@ -1,4 +1,7 @@
# Copyright (C) 1999--2002 Joel Rosdahl # -*- coding: utf-8 -*-
# Copyright (C) 1999-2002 Joel Rosdahl
# Portions Copyright © 2011 Jason R. Coombs
# #
# This library is free software; you can redistribute it and/or # This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public # modify it under the terms of the GNU Lesser General Public
@ -15,10 +18,9 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# keltus <keltus@users.sourceforge.net> # keltus <keltus@users.sourceforge.net>
#
# $Id: irclib.py,v 1.47 2008/09/25 22:00:59 keltus Exp $
"""irclib -- Internet Relay Chat (IRC) protocol client library. """
irclib -- Internet Relay Chat (IRC) protocol client library.
This library is intended to encapsulate the IRC protocol at a quite This library is intended to encapsulate the IRC protocol at a quite
low level. It provides an event-driven IRC client framework. It has low level. It provides an event-driven IRC client framework. It has
@ -66,12 +68,19 @@ import re
import select import select
import socket import socket
import string import string
import sys
import time import time
import types import types
import ssl as ssl_mod
import datetime
VERSION = 0, 4, 8 try:
DEBUG = 0 import pkg_resources
_pkg = pkg_resources.require('python-irclib')[0]
VERSION = tuple(int(res) for res in re.findall('\d+', _pkg.version))
except Exception:
VERSION = ()
DEBUG = False
# TODO # TODO
# ---- # ----
@ -93,7 +102,7 @@ class IRCError(Exception):
pass pass
class IRC: class IRC(object):
"""Class that handles one or several IRC server connections. """Class that handles one or several IRC server connections.
When an IRC object has been instantiated, it can be used to create When an IRC object has been instantiated, it can be used to create
@ -112,12 +121,12 @@ class IRC:
irc = irclib.IRC() irc = irclib.IRC()
server = irc.server() server = irc.server()
server.connect(\"irc.some.where\", 6667, \"my_nickname\") server.connect("irc.some.where", 6667, "my_nickname")
server.privmsg(\"a_nickname\", \"Hi there!\") server.privmsg("a_nickname", "Hi there!")
irc.process_forever() irc.process_forever()
This will connect to the IRC server irc.some.where on port 6667 This will connect to the IRC server irc.some.where on port 6667
using the nickname my_nickname and send the message \"Hi there!\" using the nickname my_nickname and send the message "Hi there!"
to the nickname a_nickname. to the nickname a_nickname.
""" """
@ -157,7 +166,7 @@ class IRC:
self.fn_to_add_timeout = fn_to_add_timeout self.fn_to_add_timeout = fn_to_add_timeout
self.connections = [] self.connections = []
self.handlers = {} self.handlers = {}
self.delayed_commands = [] # list of tuples in the format (time, function, arguments) self.delayed_commands = [] # list of DelayedCommands
self.add_global_handler("ping", _ping_ponger, -42) self.add_global_handler("ping", _ping_ponger, -42)
@ -187,13 +196,14 @@ class IRC:
See documentation for IRC.__init__. See documentation for IRC.__init__.
""" """
t = time.time()
while self.delayed_commands: while self.delayed_commands:
if t >= self.delayed_commands[0][0]: command = self.delayed_commands[0]
self.delayed_commands[0][1](*self.delayed_commands[0][2]) if not command.due():
del self.delayed_commands[0]
else:
break break
command.function(*command.arguments)
if isinstance(command, PeriodicCommand):
self._schedule_command(command.next())
del self.delayed_commands[0]
def process_once(self, timeout=0): def process_once(self, timeout=0):
"""Process data from connections once. """Process data from connections once.
@ -252,7 +262,7 @@ class IRC:
The handler functions are called in priority order (lowest The handler functions are called in priority order (lowest
number is highest priority). If a handler function returns number is highest priority). If a handler function returns
\"NO MORE\", no more handlers will be called. "NO MORE", no more handlers will be called.
""" """
if not event in self.handlers: if not event in self.handlers:
self.handlers[event] = [] self.handlers[event] = []
@ -264,7 +274,6 @@ class IRC:
Arguments: Arguments:
event -- Event type (a string). event -- Event type (a string).
handler -- Callback function. handler -- Callback function.
Returns 1 on success, otherwise 0. Returns 1 on success, otherwise 0.
@ -281,28 +290,39 @@ class IRC:
Arguments: Arguments:
at -- Execute at this time (standard \"time_t\" time). at -- Execute at this time (standard "time_t" time).
function -- Function to call. function -- Function to call.
arguments -- Arguments to give the function. arguments -- Arguments to give the function.
""" """
self.execute_delayed(at-time.time(), function, arguments) command = DelayedCommand.at_time(at, function, arguments)
self._schedule_command(command)
def execute_delayed(self, delay, function, arguments=()): def execute_delayed(self, delay, function, arguments=()):
"""Execute a function after a specified time. """
Execute a function after a specified time.
Arguments:
delay -- How many seconds to wait. delay -- How many seconds to wait.
function -- Function to call. function -- Function to call.
arguments -- Arguments to give the function. arguments -- Arguments to give the function.
""" """
bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments)) command = DelayedCommand(delay, function, arguments)
self._schedule_command(command)
def execute_every(self, period, function, arguments=()):
"""
Execute a function every 'period' seconds.
period -- How often to run (always waits this long for first).
function -- Function to call.
arguments -- Arguments to give the function.
"""
command = PeriodicCommand(period, function, arguments)
self._schedule_command(command)
def _schedule_command(self, command):
bisect.insort(self.delayed_commands, command)
if self.fn_to_add_timeout: if self.fn_to_add_timeout:
self.fn_to_add_timeout(delay) self.fn_to_add_timeout(total_seconds(command.delay))
def dcc(self, dcctype="chat"): def dcc(self, dcctype="chat"):
"""Creates and returns a DCCConnection object. """Creates and returns a DCCConnection object.
@ -321,7 +341,8 @@ class IRC:
def _handle_event(self, connection, event): def _handle_event(self, connection, event):
"""[Internal]""" """[Internal]"""
h = self.handlers h = self.handlers
for handler in h.get("all_events", []) + h.get(event.eventtype(), []): th = sorted(h.get("all_events", []) + h.get(event.eventtype(), []))
for handler in th:
if handler[1](connection, event) == "NO MORE": if handler[1](connection, event) == "NO MORE":
return return
@ -331,9 +352,48 @@ class IRC:
if self.fn_to_remove_socket: if self.fn_to_remove_socket:
self.fn_to_remove_socket(connection._get_socket()) self.fn_to_remove_socket(connection._get_socket())
class DelayedCommand(datetime.datetime):
"""
A command to be executed after some delay (seconds or timedelta).
"""
def __new__(cls, delay, function, arguments):
if not isinstance(delay, datetime.timedelta):
delay = datetime.timedelta(seconds=delay)
at = datetime.datetime.utcnow() + delay
cmd = datetime.datetime.__new__(DelayedCommand, at.year,
at.month, at.day, at.hour, at.minute, at.second,
at.microsecond, at.tzinfo)
cmd.delay = delay
cmd.function = function
cmd.arguments = arguments
return cmd
def at_time(cls, at, function, arguments):
"""
Construct a DelayedCommand to come due at `at`, where `at` may be
a datetime or timestamp.
"""
if isinstance(at, int):
at = datetime.datetime.utcfromtimestamp(at)
delay = at - datetime.datetime.utcnow()
return cls(delay, function, arguments)
at_time = classmethod(at_time)
def due(self):
return datetime.datetime.utcnow() >= self
class PeriodicCommand(DelayedCommand):
"""
Like a deferred command, but expect this command to run every delay
seconds.
"""
def next(self):
return PeriodicCommand(self.delay, self.function,
self.arguments)
_rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?") _rfc_1459_command_regexp = re.compile("^(:(?P<prefix>[^ ]+) +)?(?P<command>[^ ]+)( *(?P<argument> .+))?")
class Connection: class Connection(object):
"""Base class for IRC connections. """Base class for IRC connections.
Must be overridden. Must be overridden.
@ -342,7 +402,7 @@ class Connection:
self.irclibobj = irclibobj self.irclibobj = irclibobj
def _get_socket(): def _get_socket():
raise IRCError, "Not overridden" raise IRCError("Not overridden")
############################## ##############################
### Convenience wrappers. ### Convenience wrappers.
@ -353,6 +413,8 @@ class Connection:
def execute_delayed(self, delay, function, arguments=()): def execute_delayed(self, delay, function, arguments=()):
self.irclibobj.execute_delayed(delay, function, arguments) self.irclibobj.execute_delayed(delay, function, arguments)
def execute_every(self, period, function, arguments=()):
self.irclibobj.execute_every(period, function, arguments)
class ServerConnectionError(IRCError): class ServerConnectionError(IRCError):
pass pass
@ -373,7 +435,7 @@ class ServerConnection(Connection):
""" """
def __init__(self, irclibobj): def __init__(self, irclibobj):
Connection.__init__(self, irclibobj) super(ServerConnection, self).__init__(irclibobj)
self.connected = 0 # Not connected yet. self.connected = 0 # Not connected yet.
self.socket = None self.socket = None
self.ssl = None self.ssl = None
@ -432,11 +494,11 @@ class ServerConnection(Connection):
self.socket.bind((self.localaddress, self.localport)) self.socket.bind((self.localaddress, self.localport))
self.socket.connect((self.server, self.port)) self.socket.connect((self.server, self.port))
if ssl: if ssl:
self.ssl = socket.ssl(self.socket) self.ssl = ssl_mod.wrap_socket(self.socket)
except socket.error, x: except socket.error, x:
self.socket.close() self.socket.close()
self.socket = None self.socket = None
raise ServerConnectionError, "Couldn't connect to socket: %s" % x raise ServerConnectionError("Couldn't connect to socket: %s" % x)
self.connected = 1 self.connected = 1
if self.irclibobj.fn_to_add_socket: if self.irclibobj.fn_to_add_socket:
self.irclibobj.fn_to_add_socket(self.socket) self.irclibobj.fn_to_add_socket(self.socket)
@ -491,7 +553,7 @@ class ServerConnection(Connection):
new_data = self.ssl.read(2 ** 14) new_data = self.ssl.read(2 ** 14)
else: else:
new_data = self.socket.recv(2 ** 14) new_data = self.socket.recv(2 ** 14)
except socket.error, x: except socket.error:
# The server hung up. # The server hung up.
self.disconnect("Connection reset by peer") self.disconnect("Connection reset by peer")
return return
@ -660,7 +722,7 @@ class ServerConnection(Connection):
try: try:
self.socket.close() self.socket.close()
except socket.error, x: except socket.error:
pass pass
self.socket = None self.socket = None
self._handle_event(Event("disconnect", self.server, "", [message])) self._handle_event(Event("disconnect", self.server, "", [message]))
@ -743,10 +805,13 @@ class ServerConnection(Connection):
def part(self, channels, message=""): def part(self, channels, message=""):
"""Send a PART command.""" """Send a PART command."""
if type(channels) == types.StringType: channels = always_iterable(channels)
self.send_raw("PART " + channels + (message and (" " + message))) cmd_parts = [
else: 'PART',
self.send_raw("PART " + ",".join(channels) + (message and (" " + message))) ','.join(channels),
]
if message: cmd_parts.append(message)
self.send_raw(' '.join(cmd_parts))
def pass_(self, password): def pass_(self, password):
"""Send a PASS command.""" """Send a PASS command."""
@ -782,7 +847,7 @@ class ServerConnection(Connection):
The string will be padded with appropriate CR LF. The string will be padded with appropriate CR LF.
""" """
if self.socket is None: if self.socket is None:
raise ServerNotConnectedError, "Not connected." raise ServerNotConnectedError("Not connected.")
try: try:
if self.ssl: if self.ssl:
self.ssl.write(string + "\r\n") self.ssl.write(string + "\r\n")
@ -790,7 +855,7 @@ class ServerConnection(Connection):
self.socket.send(string + "\r\n") self.socket.send(string + "\r\n")
if DEBUG: if DEBUG:
print "TO SERVER:", string print "TO SERVER:", string
except socket.error, x: except socket.error:
# Ouch! # Ouch!
self.disconnect("Connection reset by peer.") self.disconnect("Connection reset by peer.")
@ -862,7 +927,7 @@ class DCCConnection(Connection):
method on an IRC object. method on an IRC object.
""" """
def __init__(self, irclibobj, dcctype): def __init__(self, irclibobj, dcctype):
Connection.__init__(self, irclibobj) super(DCCConnection, self).__init__(irclibobj)
self.connected = 0 self.connected = 0
self.passive = 0 self.passive = 0
self.dcctype = dcctype self.dcctype = dcctype
@ -889,7 +954,7 @@ class DCCConnection(Connection):
try: try:
self.socket.connect((self.peeraddress, self.peerport)) self.socket.connect((self.peeraddress, self.peerport))
except socket.error, x: except socket.error, x:
raise DCCConnectionError, "Couldn't connect to socket: %s" % x raise DCCConnectionError("Couldn't connect to socket: %s" % x)
self.connected = 1 self.connected = 1
if self.irclibobj.fn_to_add_socket: if self.irclibobj.fn_to_add_socket:
self.irclibobj.fn_to_add_socket(self.socket) self.irclibobj.fn_to_add_socket(self.socket)
@ -914,7 +979,7 @@ class DCCConnection(Connection):
self.localaddress, self.localport = self.socket.getsockname() self.localaddress, self.localport = self.socket.getsockname()
self.socket.listen(10) self.socket.listen(10)
except socket.error, x: except socket.error, x:
raise DCCConnectionError, "Couldn't bind socket: %s" % x raise DCCConnectionError("Couldn't bind socket: %s" % x)
return self return self
def disconnect(self, message=""): def disconnect(self, message=""):
@ -930,7 +995,7 @@ class DCCConnection(Connection):
self.connected = 0 self.connected = 0
try: try:
self.socket.close() self.socket.close()
except socket.error, x: except socket.error:
pass pass
self.socket = None self.socket = None
self.irclibobj._handle_event( self.irclibobj._handle_event(
@ -956,7 +1021,7 @@ class DCCConnection(Connection):
try: try:
new_data = self.socket.recv(2 ** 14) new_data = self.socket.recv(2 ** 14)
except socket.error, x: except socket.error:
# The server hung up. # The server hung up.
self.disconnect("Connection reset by peer") self.disconnect("Connection reset by peer")
return return
@ -1010,11 +1075,11 @@ class DCCConnection(Connection):
self.socket.send("\n") self.socket.send("\n")
if DEBUG: if DEBUG:
print "TO PEER: %s\n" % string print "TO PEER: %s\n" % string
except socket.error, x: except socket.error:
# Ouch! # Ouch!
self.disconnect("Connection reset by peer.") self.disconnect("Connection reset by peer.")
class SimpleIRCClient: class SimpleIRCClient(object):
"""A simple single-server IRC client class. """A simple single-server IRC client class.
This is an example of an object-oriented wrapper of the IRC This is an example of an object-oriented wrapper of the IRC
@ -1044,6 +1109,9 @@ class SimpleIRCClient:
def _dispatcher(self, c, e): def _dispatcher(self, c, e):
"""[Internal]""" """[Internal]"""
if DEBUG:
print("irclib.py:_dispatcher:%s" % e.eventtype())
m = "on_" + e.eventtype() m = "on_" + e.eventtype()
if hasattr(self, m): if hasattr(self, m):
getattr(self, m)(c, e) getattr(self, m)(c, e)
@ -1114,7 +1182,7 @@ class SimpleIRCClient:
self.ircobj.process_forever() self.ircobj.process_forever()
class Event: class Event(object):
"""Class representing an IRC event.""" """Class representing an IRC event."""
def __init__(self, eventtype, source, target, arguments=None): def __init__(self, eventtype, source, target, arguments=None):
"""Constructor of Event objects. """Constructor of Event objects.
@ -1184,16 +1252,6 @@ def mask_matches(nick, mask):
_special = "-[]\\`^{}" _special = "-[]\\`^{}"
nick_characters = string.ascii_letters + string.digits + _special nick_characters = string.ascii_letters + string.digits + _special
_ircstring_translation = string.maketrans(string.ascii_uppercase + "[]\\^",
string.ascii_lowercase + "{}|~")
def irc_lower(s):
"""Returns a lowercased string.
The definition of lowercased comes from the IRC specification (RFC
1459).
"""
return s.translate(_ircstring_translation)
def _ctcp_dequote(message): def _ctcp_dequote(message):
"""[Internal] Dequote a message according to CTCP specifications. """[Internal] Dequote a message according to CTCP specifications.
@ -1308,12 +1366,12 @@ def parse_nick_modes(mode_string):
"""Parse a nick mode string. """Parse a nick mode string.
The function returns a list of lists with three members: sign, The function returns a list of lists with three members: sign,
mode and argument. The sign is \"+\" or \"-\". The argument is mode and argument. The sign is "+" or "-". The argument is
always None. always None.
Example: Example:
>>> irclib.parse_nick_modes(\"+ab-c\") >>> parse_nick_modes("+ab-c")
[['+', 'a', None], ['+', 'b', None], ['-', 'c', None]] [['+', 'a', None], ['+', 'b', None], ['-', 'c', None]]
""" """
@ -1323,12 +1381,12 @@ def parse_channel_modes(mode_string):
"""Parse a channel mode string. """Parse a channel mode string.
The function returns a list of lists with three members: sign, The function returns a list of lists with three members: sign,
mode and argument. The sign is \"+\" or \"-\". The argument is mode and argument. The sign is "+" or "-". The argument is
None if mode isn't one of \"b\", \"k\", \"l\", \"v\" or \"o\". None if mode isn't one of "b", "k", "l", "v" or "o".
Example: Example:
>>> irclib.parse_channel_modes(\"+ab-c foo\") >>> parse_channel_modes("+ab-c foo")
[['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]] [['+', 'a', None], ['+', 'b', 'foo'], ['-', 'c', None]]
""" """
@ -1354,7 +1412,7 @@ def _parse_modes(mode_string, unary_modes=""):
if ch in "+-": if ch in "+-":
sign = ch sign = ch
elif ch == " ": elif ch == " ":
collecting_arguments = 1 pass
elif ch in unary_modes: elif ch in unary_modes:
if len(args) >= arg_count + 1: if len(args) >= arg_count + 1:
modes.append([sign, ch, args[arg_count]]) modes.append([sign, ch, args[arg_count]])
@ -1559,3 +1617,104 @@ protocol_events = [
] ]
all_events = generated_events + protocol_events + numeric_events.values() all_events = generated_events + protocol_events + numeric_events.values()
# from jaraco.util.itertools
def always_iterable(item):
"""
Given an object, always return an iterable. If the item is not
already iterable, return a tuple containing only the item.
>>> always_iterable([1,2,3])
[1, 2, 3]
>>> always_iterable('foo')
('foo',)
>>> always_iterable(None)
(None,)
>>> always_iterable(xrange(10))
xrange(10)
"""
if isinstance(item, basestring) or not hasattr(item, '__iter__'):
item = item,
return item
# from jaraco.util.string
class FoldedCase(str):
"""
A case insensitive string class; behaves just like str
except compares equal when the only variation is case.
>>> s = FoldedCase('hello world')
>>> s == 'Hello World'
True
>>> 'Hello World' == s
True
>>> s.index('O')
4
>>> s.split('O')
['hell', ' w', 'rld']
>>> names = map(FoldedCase, ['GAMMA', 'alpha', 'Beta'])
>>> names.sort()
>>> names
['alpha', 'Beta', 'GAMMA']
"""
def __lt__(self, other):
return self.lower() < other.lower()
def __gt__(self, other):
return self.lower() > other.lower()
def __eq__(self, other):
return self.lower() == other.lower()
def __hash__(self):
return hash(self.lower())
# cache lower since it's likely to be called frequently.
def lower(self):
self._lower = super(FoldedCase, self).lower()
self.lower = lambda: self._lower
return self._lower
def index(self, sub):
return self.lower().index(sub.lower())
def split(self, splitter=' ', maxsplit=0):
pattern = re.compile(re.escape(splitter), re.I)
return pattern.split(self, maxsplit)
class IRCFoldedCase(FoldedCase):
"""
A version of FoldedCase that honors the IRC specification for lowercased
strings (RFC 1459).
>>> IRCFoldedCase('Foo^').lower()
'foo~'
>>> IRCFoldedCase('[this]') == IRCFoldedCase('{THIS}')
True
"""
translation = string.maketrans(
string.ascii_uppercase + r"[]\^",
string.ascii_lowercase + r"{}|~",
)
def lower(self):
return self.translate(self.translation)
# for compatibility
def irc_lower(str):
return IRCFoldedCase(str).lower()
def total_seconds(td):
"""
Python 2.7 adds a total_seconds method to timedelta objects.
See http://docs.python.org/library/datetime.html#datetime.timedelta.total_seconds
"""
try:
result = td.total_seconds()
except AttributeError:
result = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6
return result