Some fixes for Python 3 port

master
Page Libre 2020-03-04 14:05:05 +01:00
parent 797ef1d43f
commit 91741757ed
2 changed files with 366 additions and 195 deletions

195
ircbot.py
View File

@ -1,4 +1,7 @@
# Copyright (C) 1999--2002 Joel Rosdahl #! -*- coding: utf-8 -*-
# Copyright (C) 1999-2002 Joel Rosdahl
# Portions Copyring © 2011-2012 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,24 +18,20 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# #
# Joel Rosdahl <joel@rosdahl.net> # Joel Rosdahl <joel@rosdahl.net>
#
# $Id: ircbot.py,v 1.23 2008/09/11 07:38:30 keltus Exp $
"""ircbot -- Simple IRC bot library. """
ircbot -- Simple IRC bot library.
This module contains a single-server IRC bot class that can be used to This module contains a single-server IRC bot class that can be used to
write simpler bots. write simpler bots.
""" """
import sys import sys
from UserDict import UserDict
from irclib import SimpleIRCClient import irclib
from irclib import nm_to_n, irc_lower, all_events from irclib import nm_to_n
from irclib import parse_channel_modes, is_channel
from irclib import ServerConnectionError
class SingleServerIRCBot(SimpleIRCClient): class SingleServerIRCBot(irclib.SimpleIRCClient):
"""A single-server IRC bot class. """A single-server IRC bot class.
The bot tries to reconnect if it is disconnected. The bot tries to reconnect if it is disconnected.
@ -62,11 +61,11 @@ class SingleServerIRCBot(SimpleIRCClient):
connections. connections.
""" """
SimpleIRCClient.__init__(self) super(SingleServerIRCBot, self).__init__()
self.channels = IRCDict() self.channels = IRCDict()
self.server_list = server_list self.server_list = server_list
if not reconnection_interval or reconnection_interval < 0: if not reconnection_interval or reconnection_interval < 0:
reconnection_interval = 2**31 reconnection_interval = 2 ** 31
self.reconnection_interval = reconnection_interval self.reconnection_interval = reconnection_interval
self._nickname = nickname self._nickname = nickname
@ -75,7 +74,8 @@ class SingleServerIRCBot(SimpleIRCClient):
"namreply", "nick", "part", "quit"]: "namreply", "nick", "part", "quit"]:
self.connection.add_global_handler(i, self.connection.add_global_handler(i,
getattr(self, "_on_" + i), getattr(self, "_on_" + i),
-10) -20)
def _connected_checker(self): def _connected_checker(self):
"""[Internal]""" """[Internal]"""
if not self.connection.is_connected(): if not self.connection.is_connected():
@ -94,7 +94,7 @@ class SingleServerIRCBot(SimpleIRCClient):
self._nickname, self._nickname,
password, password,
ircname=self._realname) ircname=self._realname)
except ServerConnectionError: except irclib.ServerConnectionError:
pass pass
def _on_disconnect(self, c, e): def _on_disconnect(self, c, e):
@ -123,9 +123,9 @@ class SingleServerIRCBot(SimpleIRCClient):
def _on_mode(self, c, e): def _on_mode(self, c, e):
"""[Internal]""" """[Internal]"""
modes = parse_channel_modes(" ".join(e.arguments())) modes = irclib.parse_channel_modes(" ".join(e.arguments()))
t = e.target() t = e.target()
if is_channel(t): if irclib.is_channel(t):
ch = self.channels[t] ch = self.channels[t]
for mode in modes: for mode in modes:
if mode[0] == "+": if mode[0] == "+":
@ -244,71 +244,10 @@ class SingleServerIRCBot(SimpleIRCClient):
def start(self): def start(self):
"""Start the bot.""" """Start the bot."""
self._connect() self._connect()
SimpleIRCClient.start(self) super(SingleServerIRCBot, self).start()
class IRCDict: class Channel(object):
"""A dictionary suitable for storing IRC-related things.
Dictionary keys a and b are considered equal if and only if
irc_lower(a) == irc_lower(b)
Otherwise, it should behave exactly as a normal dictionary.
"""
def __init__(self, dict=None):
self.data = {}
self.canon_keys = {} # Canonical keys
if dict is not None:
self.update(dict)
def __repr__(self):
return repr(self.data)
def __cmp__(self, dict):
if isinstance(dict, IRCDict):
return cmp(self.data, dict.data)
else:
return cmp(self.data, dict)
def __len__(self):
return len(self.data)
def __getitem__(self, key):
return self.data[self.canon_keys[irc_lower(key)]]
def __setitem__(self, key, item):
if key in self:
del self[key]
self.data[key] = item
self.canon_keys[irc_lower(key)] = key
def __delitem__(self, key):
ck = irc_lower(key)
del self.data[self.canon_keys[ck]]
del self.canon_keys[ck]
def __iter__(self):
return iter(self.data)
def __contains__(self, key):
return self.has_key(key)
def clear(self):
self.data.clear()
self.canon_keys.clear()
def copy(self):
if self.__class__ is UserDict:
return UserDict(self.data)
import copy
return copy.copy(self)
def keys(self):
return self.data.keys()
def items(self):
return self.data.items()
def values(self):
return self.data.values()
def has_key(self, key):
return irc_lower(key) in self.canon_keys
def update(self, dict):
for k, v in dict.items():
self.data[k] = v
def get(self, key, failobj=None):
return self.data.get(key, failobj)
class Channel:
"""A class for keeping information about an IRC channel. """A class for keeping information about an IRC channel.
This class can be improved a lot. This class can be improved a lot.
@ -354,14 +293,15 @@ class Channel:
del d[nick] del d[nick]
def change_nick(self, before, after): def change_nick(self, before, after):
self.userdict[after] = 1 self.userdict[after] = self.userdict.pop(before)
del self.userdict[before]
if before in self.operdict: if before in self.operdict:
self.operdict[after] = 1 self.operdict[after] = self.operdict.pop(before)
del self.operdict[before]
if before in self.voiceddict: if before in self.voiceddict:
self.voiceddict[after] = 1 self.voiceddict[after] = self.voiceddict.pop(before)
del self.voiceddict[before]
def set_userdetails(self, nick, details):
if nick in self.userdict:
self.userdict[nick] = details
def set_mode(self, mode, value=None): def set_mode(self, mode, value=None):
"""Set mode on the channel. """Set mode on the channel.
@ -424,15 +364,88 @@ class Channel:
def limit(self): def limit(self):
if self.has_limit(): if self.has_limit():
return self.modes[l] return self.modes["l"]
else: else:
return None return None
def has_key(self): def has_key(self):
return self.has_mode("k") return self.has_mode("k")
def key(self): # from jaraco.util.dictlib
if self.has_key(): class KeyTransformingDict(dict):
return self.modes["k"] """
else: A dict subclass that transforms the keys before they're used.
return None Subclasses may override the default key_transform to customize behavior.
"""
@staticmethod
def key_transform(key):
return key
def __init__(self, *args, **kargs):
super(KeyTransformingDict, self).__init__()
# build a dictionary using the default constructs
d = dict(*args, **kargs)
# build this dictionary using transformed keys.
for item in d.items():
self.__setitem__(*item)
def __setitem__(self, key, val):
key = self.key_transform(key)
super(KeyTransformingDict, self).__setitem__(key, val)
def __getitem__(self, key):
key = self.key_transform(key)
return super(KeyTransformingDict, self).__getitem__(key)
def __contains__(self, key):
key = self.key_transform(key)
return super(KeyTransformingDict, self).__contains__(key)
def __delitem__(self, key):
key = self.key_transform(key)
return super(KeyTransformingDict, self).__delitem__(key)
def setdefault(self, key, *args, **kwargs):
key = self.key_transform(key)
return super(KeyTransformingDict, self).setdefault(key, *args, **kwargs)
def pop(self, key, *args, **kwargs):
key = self.key_transform(key)
return super(KeyTransformingDict, self).pop(key, *args, **kwargs)
class IRCDict(KeyTransformingDict):
"""
A dictionary of names whose keys are case-insensitive according to the
IRC RFC rules.
>>> d = IRCDict({'[This]': 'that'}, A='foo')
The dict maintains the original case:
>>> d.keys()
['A', '[This]']
But the keys can be referenced with a different case
>>> d['a']
'foo'
>>> d['{this}']
'that'
>>> d['{THIS}']
'that'
>>> '{thiS]' in d
True
This should work for operations like delete and pop as well.
>>> d.pop('A')
'foo'
>>> del d['{This}']
>>> len(d)
0
"""
@staticmethod
def key_transform(key):
if isinstance(key, basestring):
key = irclib.IRCFoldedCase(key)
return key

366
irclib.py
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.
Arguments:
delay -- How many seconds to wait.
function -- Function to call.
arguments -- Arguments to give the function.
""" """
bisect.insort(self.delayed_commands, (delay+time.time(), function, arguments)) Execute a function after a specified time.
delay -- How many seconds to wait.
function -- Function to call.
arguments -- Arguments to give the function.
"""
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 as 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)
@ -488,10 +550,10 @@ class ServerConnection(Connection):
try: try:
if self.ssl: if self.ssl:
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
@ -500,14 +562,14 @@ class ServerConnection(Connection):
self.disconnect("Connection reset by peer") self.disconnect("Connection reset by peer")
return return
lines = _linesep_regexp.split(self.previous_buffer + new_data) lines = _linesep_regexp.split(self.previous_buffer + str(new_data))
# Save the last, unfinished line. # Save the last, unfinished line.
self.previous_buffer = lines.pop() self.previous_buffer = lines.pop()
for line in lines: for line in lines:
if DEBUG: if DEBUG:
print "FROM SERVER:", line print("FROM SERVER:", line)
if not line: if not line:
continue continue
@ -569,15 +631,15 @@ class ServerConnection(Connection):
m = list(m) m = list(m)
if DEBUG: if DEBUG:
print "command: %s, source: %s, target: %s, arguments: %s" % ( print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, m) command, prefix, target, m))
self._handle_event(Event(command, prefix, target, m)) self._handle_event(Event(command, prefix, target, m))
if command == "ctcp" and m[0] == "ACTION": if command == "ctcp" and m[0] == "ACTION":
self._handle_event(Event("action", prefix, target, m[1:])) self._handle_event(Event("action", prefix, target, m[1:]))
else: else:
if DEBUG: if DEBUG:
print "command: %s, source: %s, target: %s, arguments: %s" % ( print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, [m]) command, prefix, target, [m]))
self._handle_event(Event(command, prefix, target, [m])) self._handle_event(Event(command, prefix, target, [m]))
else: else:
target = None target = None
@ -595,8 +657,8 @@ class ServerConnection(Connection):
command = "umode" command = "umode"
if DEBUG: if DEBUG:
print "command: %s, source: %s, target: %s, arguments: %s" % ( print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, arguments) command, prefix, target, arguments))
self._handle_event(Event(command, prefix, target, arguments)) self._handle_event(Event(command, prefix, target, arguments))
def _handle_event(self, event): def _handle_event(self, event):
@ -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."""
@ -780,20 +845,19 @@ class ServerConnection(Connection):
"""Send raw string to the server. """Send raw string to the server.
The string will be padded with appropriate CR LF. The string will be padded with appropriate CR LF.
From here everything need to be encoded in str from utf-8
""" """
string=string.encode('utf-8')
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")
else: else:
self.socket.send(string + "\r\n") #print(type(string + "\r\n"))
obj = string + "\r\n"
self.socket.send(obj.encode())
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.")
@ -865,7 +929,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
@ -891,8 +955,8 @@ class DCCConnection(Connection):
self.passive = 0 self.passive = 0
try: try:
self.socket.connect((self.peeraddress, self.peerport)) self.socket.connect((self.peeraddress, self.peerport))
except socket.error, x: except socket.error as 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)
@ -916,8 +980,8 @@ class DCCConnection(Connection):
self.socket.bind((socket.gethostbyname(socket.gethostname()), 0)) self.socket.bind((socket.gethostbyname(socket.gethostname()), 0))
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 as 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=""):
@ -933,7 +997,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(
@ -950,16 +1014,16 @@ class DCCConnection(Connection):
self.socket = conn self.socket = conn
self.connected = 1 self.connected = 1
if DEBUG: if DEBUG:
print "DCC connection from %s:%d" % ( print("DCC connection from %s:%d" % (
self.peeraddress, self.peerport) self.peeraddress, self.peerport))
self.irclibobj._handle_event( self.irclibobj._handle_event(
self, self,
Event("dcc_connect", self.peeraddress, None, None)) Event("dcc_connect", self.peeraddress, None, None))
return return
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
@ -975,7 +1039,7 @@ class DCCConnection(Connection):
# Save the last, unfinished line. # Save the last, unfinished line.
self.previous_buffer = chunks[-1] self.previous_buffer = chunks[-1]
if len(self.previous_buffer) > 2**14: if len(self.previous_buffer) > 2 ** 14:
# Bad peer! Naughty peer! # Bad peer! Naughty peer!
self.disconnect() self.disconnect()
return return
@ -988,11 +1052,11 @@ class DCCConnection(Connection):
target = None target = None
for chunk in chunks: for chunk in chunks:
if DEBUG: if DEBUG:
print "FROM PEER:", chunk print("FROM PEER:", chunk)
arguments = [chunk] arguments = [chunk]
if DEBUG: if DEBUG:
print "command: %s, source: %s, target: %s, arguments: %s" % ( print("command: %s, source: %s, target: %s, arguments: %s" % (
command, prefix, target, arguments) command, prefix, target, arguments))
self.irclibobj._handle_event( self.irclibobj._handle_event(
self, self,
Event(command, prefix, target, arguments)) Event(command, prefix, target, arguments))
@ -1012,12 +1076,12 @@ class DCCConnection(Connection):
if self.dcctype == "chat": if self.dcctype == "chat":
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
@ -1047,6 +1111,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)
@ -1117,7 +1184,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.
@ -1186,16 +1253,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.
@ -1231,14 +1288,14 @@ def _ctcp_dequote(message):
messages = [] messages = []
i = 0 i = 0
while i < len(chunks)-1: while i < len(chunks) - 1:
# Add message if it's non-empty. # Add message if it's non-empty.
if len(chunks[i]) > 0: if len(chunks[i]) > 0:
messages.append(chunks[i]) messages.append(chunks[i])
if i < len(chunks)-2: if i < len(chunks) - 2:
# Aye! CTCP tagged data ahead! # Aye! CTCP tagged data ahead!
messages.append(tuple(chunks[i+1].split(" ", 1))) messages.append(tuple(chunks[i + 1].split(" ", 1)))
i = i + 2 i = i + 2
@ -1310,12 +1367,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]]
""" """
@ -1325,12 +1382,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]]
""" """
@ -1356,7 +1413,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]])
@ -1496,7 +1553,7 @@ numeric_events = {
"423": "noadmininfo", "423": "noadmininfo",
"424": "fileerror", "424": "fileerror",
"431": "nonicknamegiven", "431": "nonicknamegiven",
"432": "erroneusnickname", # Thiss iz how its speld in thee RFC. "432": "erroneusnickname", # Thiss iz how its speld in thee RFC.
"433": "nicknameinuse", "433": "nicknameinuse",
"436": "nickcollision", "436": "nickcollision",
"437": "unavailresource", # "Nick temporally unavailable" "437": "unavailresource", # "Nick temporally unavailable"
@ -1511,7 +1568,7 @@ numeric_events = {
"462": "alreadyregistered", "462": "alreadyregistered",
"463": "nopermforhost", "463": "nopermforhost",
"464": "passwdmismatch", "464": "passwdmismatch",
"465": "yourebannedcreep", # I love this one... "465": "yourebannedcreep", # I love this one...
"466": "youwillbebanned", "466": "youwillbebanned",
"467": "keyset", "467": "keyset",
"471": "channelisfull", "471": "channelisfull",
@ -1560,4 +1617,105 @@ protocol_events = [
"pong", "pong",
] ]
all_events = generated_events + protocol_events + numeric_events.values() all_events = generated_events + protocol_events + list(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 = str.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