2003-03-12 07:26:59 +01:00
|
|
|
#!/usr/bin/env python
|
|
|
|
|
|
|
|
###
|
|
|
|
# Copyright (c) 2002, Jeremiah Fincher
|
|
|
|
# All rights reserved.
|
|
|
|
#
|
|
|
|
# Redistribution and use in source and binary forms, with or without
|
|
|
|
# modification, are permitted provided that the following conditions are met:
|
|
|
|
#
|
|
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
|
|
# this list of conditions, and the following disclaimer.
|
|
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
|
|
# this list of conditions, and the following disclaimer in the
|
|
|
|
# documentation and/or other materials provided with the distribution.
|
|
|
|
# * Neither the name of the author of this software nor the name of
|
|
|
|
# contributors to this software may be used to endorse or promote products
|
|
|
|
# derived from this software without specific prior written consent.
|
|
|
|
#
|
|
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
###
|
|
|
|
|
2003-07-21 08:10:35 +02:00
|
|
|
"""
|
|
|
|
This module contains the basic callbacks for handling PRIVMSGs. Both Privmsg
|
|
|
|
and PrivmsgRegexp classes are provided; for offering callbacks based on
|
|
|
|
commands and their arguments (much like *nix command line programs) use the
|
|
|
|
Privmsg class; for offering callbacks based on regular expressions, use the
|
|
|
|
PrivmsgRegexp class. Read their respective docstrings for more information on
|
|
|
|
how to use them.
|
|
|
|
"""
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
from fix import *
|
|
|
|
|
|
|
|
import re
|
2003-04-11 22:17:35 +02:00
|
|
|
import new
|
2003-08-11 05:34:54 +02:00
|
|
|
import sets
|
2003-03-12 07:26:59 +01:00
|
|
|
import time
|
|
|
|
import shlex
|
2003-08-26 15:44:32 +02:00
|
|
|
import getopt
|
2003-03-12 07:26:59 +01:00
|
|
|
import inspect
|
|
|
|
import threading
|
|
|
|
from cStringIO import StringIO
|
|
|
|
|
|
|
|
import conf
|
2003-08-23 01:15:29 +02:00
|
|
|
import utils
|
2003-04-21 08:17:19 +02:00
|
|
|
import world
|
2003-03-12 07:26:59 +01:00
|
|
|
import ircdb
|
|
|
|
import irclib
|
|
|
|
import ircmsgs
|
|
|
|
import ircutils
|
|
|
|
|
|
|
|
import debug
|
|
|
|
|
|
|
|
###
|
|
|
|
# Privmsg: handles privmsg commands in a standard fashion.
|
|
|
|
###
|
|
|
|
def addressed(nick, msg):
|
|
|
|
"""If msg is addressed to 'name', returns the portion after the address.
|
|
|
|
"""
|
|
|
|
if msg.args[0] == nick:
|
|
|
|
if msg.args[1][0] in conf.prefixChars:
|
|
|
|
return msg.args[1][1:].strip()
|
|
|
|
else:
|
|
|
|
return msg.args[1].strip()
|
2003-04-04 09:07:19 +02:00
|
|
|
elif ircutils.toLower(msg.args[1]).startswith(nick):
|
2003-03-12 07:26:59 +01:00
|
|
|
try:
|
|
|
|
return msg.args[1].split(None, 1)[1].strip()
|
|
|
|
except IndexError:
|
|
|
|
return ''
|
|
|
|
elif msg.args[1] and msg.args[1][0] in conf.prefixChars:
|
|
|
|
return msg.args[1][1:].strip()
|
|
|
|
else:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
def canonicalName(command):
|
|
|
|
"""Turn a command into its canonical form.
|
|
|
|
|
|
|
|
Currently, this makes everything lowercase and removes all dashes and
|
|
|
|
underscores.
|
|
|
|
"""
|
|
|
|
return command.translate(string.ascii, '\t -_').lower()
|
|
|
|
|
2003-03-15 12:09:52 +01:00
|
|
|
def reply(msg, s):
|
|
|
|
"""Makes a reply to msg with the payload s"""
|
2003-04-03 10:31:47 +02:00
|
|
|
s = ircutils.safeArgument(s)
|
2003-03-15 12:09:52 +01:00
|
|
|
if ircutils.isChannel(msg.args[0]):
|
|
|
|
m = ircmsgs.privmsg(msg.args[0], '%s: %s' % (msg.nick, s))
|
|
|
|
else:
|
|
|
|
m = ircmsgs.privmsg(msg.nick, s)
|
|
|
|
return m
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-08-23 06:42:04 +02:00
|
|
|
def error(msg, s):
|
|
|
|
"""Makes an error reply to msg with the appropriate error payload."""
|
|
|
|
return reply(msg, 'Error: ' + s)
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
class RateLimiter:
|
2003-08-26 19:18:35 +02:00
|
|
|
"""This class is used to rate limit replies to certain people, in order to
|
|
|
|
prevent abuse of the bot. Basically, you put messages in with the .put
|
|
|
|
method, and then take a message out with the .get method. .get may return
|
|
|
|
None if there is no message waiting that isn't being rate limited.
|
|
|
|
"""
|
|
|
|
# lastRequest must be class-global, so each instance of it uses the same
|
|
|
|
# information. Otherwise, if it was an instance variable, then rate
|
|
|
|
# limiting would only work within a single plugin.
|
2003-03-22 04:16:20 +01:00
|
|
|
lastRequest = {}
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self):
|
|
|
|
self.limited = []
|
|
|
|
self.unlimited = []
|
|
|
|
|
|
|
|
def get(self):
|
|
|
|
if self.unlimited:
|
|
|
|
return self.unlimited.pop(0)
|
|
|
|
elif self.limited:
|
|
|
|
for i in range(len(self.limited)):
|
|
|
|
msg = self.limited[i]
|
|
|
|
if not self.limit(msg, penalize=False):
|
|
|
|
return self.limited.pop(i)
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def put(self, msg):
|
2003-06-05 12:00:31 +02:00
|
|
|
t = self.limit(msg)
|
|
|
|
if t and not world.testing:
|
|
|
|
s = 'Limiting message from %s for %s seconds' % (msg.prefix, t)
|
|
|
|
debug.msg(s, 'normal')
|
2003-03-12 07:26:59 +01:00
|
|
|
self.limited.append(msg)
|
2003-04-29 15:13:55 +02:00
|
|
|
else:
|
|
|
|
self.unlimited.append(msg)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def limit(self, msg, penalize=True):
|
|
|
|
if msg.prefix and ircutils.isUserHostmask(msg.prefix):
|
|
|
|
(nick, user, host) = ircutils.splitHostmask(msg.prefix)
|
|
|
|
key = '@'.join((user, host))
|
|
|
|
now = time.time()
|
|
|
|
if ircdb.checkCapabilities(msg.prefix, ('owner', 'admin')):
|
2003-06-05 12:00:31 +02:00
|
|
|
return 0
|
2003-03-12 07:26:59 +01:00
|
|
|
if key in self.lastRequest:
|
|
|
|
# Here's how we throttle requests. We keep a dictionary of
|
|
|
|
# (lastRequest, wait period) tuples. When a request arrives,
|
|
|
|
# we check to see if we have a lastRequest tuple, and if so,
|
|
|
|
# we check to make sure that lastRequest was more than wait
|
|
|
|
# seconds ago. If not, we're getting flooded, and we set
|
|
|
|
# the lastRequest time to the current time and increment wait,
|
|
|
|
# thus making it even harder for the flooder to get us to
|
|
|
|
# send them messages.
|
|
|
|
(t, wait) = self.lastRequest[key]
|
|
|
|
if now - t <= wait:
|
|
|
|
if penalize:
|
2003-06-05 12:00:31 +02:00
|
|
|
newWait = wait + conf.throttleTime
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
2003-06-05 12:00:31 +02:00
|
|
|
newWait = wait - (now - t)
|
|
|
|
self.lastRequest[key] = (now, newWait)
|
|
|
|
return newWait
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.lastRequest[key] = (now, conf.throttleTime)
|
2003-06-05 12:00:31 +02:00
|
|
|
return 0
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.lastRequest[key] = (now, conf.throttleTime)
|
2003-06-05 12:00:31 +02:00
|
|
|
return 0
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
2003-06-05 12:00:31 +02:00
|
|
|
return 0
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Error(Exception):
|
|
|
|
"""Generic class for errors in Privmsg callbacks."""
|
|
|
|
pass
|
|
|
|
|
2003-04-01 00:22:59 +02:00
|
|
|
class ArgumentError(Error):
|
2003-08-26 19:18:35 +02:00
|
|
|
"""The bot replies with a help message when this is raised."""
|
2003-04-01 00:22:59 +02:00
|
|
|
pass
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
class Tokenizer:
|
2003-07-21 08:10:35 +02:00
|
|
|
# This will be used as a global environment to evaluate strings in.
|
|
|
|
# Evaluation is, of course, necessary in order to allowed escaped
|
|
|
|
# characters to be properly handled.
|
|
|
|
#
|
|
|
|
# These are the characters valid in a token. Everything printable except
|
|
|
|
# double-quote, left-bracket, and right-bracket.
|
2003-04-11 22:30:02 +02:00
|
|
|
validChars = string.ascii[33:].translate(string.ascii, '"[]')
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self, tokens=''):
|
|
|
|
self.validChars = self.validChars.translate(string.ascii, tokens)
|
|
|
|
|
|
|
|
def handleToken(self, token):
|
2003-04-11 22:17:35 +02:00
|
|
|
while token and token[0] == '"' and token[-1] == token[0]:
|
2003-03-12 07:26:59 +01:00
|
|
|
if len(token) > 1:
|
2003-07-31 08:45:03 +02:00
|
|
|
token = token[1:-1].decode('string_escape') # 2.3+
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
break
|
|
|
|
return token
|
|
|
|
|
|
|
|
def insideBrackets(self, lexer):
|
|
|
|
ret = []
|
|
|
|
while True:
|
|
|
|
token = lexer.get_token()
|
2003-08-17 04:02:53 +02:00
|
|
|
if not token:
|
2003-03-12 07:26:59 +01:00
|
|
|
raise SyntaxError, 'Missing "]"'
|
|
|
|
elif token == ']':
|
|
|
|
return ret
|
|
|
|
elif token == '[':
|
|
|
|
ret.append(self.insideBrackets(lexer))
|
|
|
|
else:
|
|
|
|
ret.append(self.handleToken(token))
|
|
|
|
return ret
|
|
|
|
|
|
|
|
def tokenize(self, s):
|
|
|
|
lexer = shlex.shlex(StringIO(s))
|
|
|
|
lexer.commenters = ''
|
2003-04-11 22:17:35 +02:00
|
|
|
lexer.quotes = '"'
|
2003-03-12 07:26:59 +01:00
|
|
|
lexer.wordchars = self.validChars
|
|
|
|
args = []
|
|
|
|
while True:
|
|
|
|
token = lexer.get_token()
|
2003-08-17 04:02:53 +02:00
|
|
|
#debug.printf(repr(token))
|
|
|
|
if not token:
|
2003-03-12 07:26:59 +01:00
|
|
|
break
|
|
|
|
elif token == '[':
|
|
|
|
args.append(self.insideBrackets(lexer))
|
|
|
|
elif token == ']':
|
|
|
|
raise SyntaxError, 'Spurious "["'
|
|
|
|
else:
|
|
|
|
args.append(self.handleToken(token))
|
|
|
|
return args
|
|
|
|
|
2003-03-25 23:32:14 +01:00
|
|
|
def tokenize(s):
|
2003-08-26 19:18:35 +02:00
|
|
|
"""A utility function to create a Tokenizer and tokenize a string."""
|
2003-04-12 14:19:27 +02:00
|
|
|
start = time.time()
|
2003-04-04 10:29:13 +02:00
|
|
|
try:
|
2003-04-12 14:19:27 +02:00
|
|
|
args = Tokenizer().tokenize(s)
|
2003-04-04 10:29:13 +02:00
|
|
|
except ValueError, e:
|
|
|
|
raise SyntaxError, str(e)
|
2003-04-12 14:19:27 +02:00
|
|
|
debug.msg('tokenize took %s seconds.' % (time.time() - start), 'verbose')
|
|
|
|
return args
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-08-25 09:23:36 +02:00
|
|
|
def findCallbackForCommand(irc, commandName):
|
2003-08-26 19:18:35 +02:00
|
|
|
"""Given a command name and an Irc object, returns the callback that
|
|
|
|
command is in. Returns None if there is no callback with that command."""
|
2003-08-25 09:23:36 +02:00
|
|
|
for callback in irc.callbacks:
|
|
|
|
if hasattr(callback, 'isCommand'):
|
|
|
|
if callback.isCommand(commandName):
|
|
|
|
return callback
|
|
|
|
return None
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
class IrcObjectProxy:
|
2003-08-26 19:18:35 +02:00
|
|
|
"A proxy object to allow proper nested of commands (even threaded ones)."
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self, irc, msg, args):
|
|
|
|
#debug.printf('__init__: %s' % args)
|
|
|
|
self.irc = irc
|
|
|
|
self.msg = msg
|
|
|
|
self.args = args
|
|
|
|
self.counter = 0
|
|
|
|
self.finalEvaled = False
|
|
|
|
self.evalArgs()
|
|
|
|
|
|
|
|
def findCallback(self, commandName):
|
2003-08-25 09:23:36 +02:00
|
|
|
# Mostly for backwards compatibility now.
|
|
|
|
return findCallbackForCommand(self.irc, commandName)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def evalArgs(self):
|
|
|
|
while self.counter < len(self.args):
|
|
|
|
if type(self.args[self.counter]) == str:
|
|
|
|
self.counter += 1
|
|
|
|
else:
|
|
|
|
IrcObjectProxy(self, self.msg, self.args[self.counter])
|
|
|
|
return
|
|
|
|
self.finalEval()
|
|
|
|
|
|
|
|
def finalEval(self):
|
|
|
|
self.finalEvaled = True
|
2003-04-01 07:39:36 +02:00
|
|
|
name = canonicalName(self.args.pop(0))
|
2003-03-12 07:26:59 +01:00
|
|
|
callback = self.findCallback(name)
|
|
|
|
try:
|
2003-04-01 07:39:36 +02:00
|
|
|
if callback is not None:
|
|
|
|
anticap = ircdb.makeAntiCapability(name)
|
2003-04-21 07:54:38 +02:00
|
|
|
#debug.printf('Checking for %s' % anticap)
|
2003-04-01 07:39:36 +02:00
|
|
|
if ircdb.checkCapability(self.msg.prefix, anticap):
|
2003-04-21 07:54:38 +02:00
|
|
|
#debug.printf('Being prevented with anticap')
|
2003-04-03 12:06:11 +02:00
|
|
|
debug.msg('Preventing %s from calling %s' % \
|
2003-04-21 07:54:38 +02:00
|
|
|
(self.msg.nick, name), 'normal')
|
2003-04-01 07:39:36 +02:00
|
|
|
return
|
|
|
|
recipient = self.msg.args[0]
|
|
|
|
if ircutils.isChannel(recipient):
|
|
|
|
chancap = ircdb.makeChannelCapability(recipient, anticap)
|
2003-04-21 07:54:38 +02:00
|
|
|
#debug.printf('Checking for %s' % chancap)
|
2003-04-01 07:39:36 +02:00
|
|
|
if ircdb.checkCapability(self.msg.prefix, chancap):
|
2003-04-21 07:54:38 +02:00
|
|
|
#debug.printf('Being prevented with chancap')
|
2003-04-03 12:06:11 +02:00
|
|
|
debug.msg('Preventing %s from calling %s' % \
|
2003-04-21 07:54:38 +02:00
|
|
|
(self.msg.nick, name), 'normal')
|
2003-04-01 07:39:36 +02:00
|
|
|
return
|
2003-03-31 09:00:25 +02:00
|
|
|
command = getattr(callback, name)
|
|
|
|
callback.callCommand(command, self, self.msg, self.args)
|
2003-04-01 07:39:36 +02:00
|
|
|
else:
|
|
|
|
self.args.insert(0, name)
|
2003-08-25 09:23:36 +02:00
|
|
|
if not isinstance(self.irc, irclib.Irc):
|
|
|
|
# If self.irc is an actual irclib.Irc, then this is the
|
|
|
|
# first command given, and should be ignored as usual.
|
|
|
|
self.reply(self.msg, '[%s]' % ' '.join(self.args))
|
2003-08-26 15:44:32 +02:00
|
|
|
except (getopt.GetoptError, ArgumentError):
|
2003-08-17 08:24:17 +02:00
|
|
|
if hasattr(command, '__doc__'):
|
2003-08-26 14:44:49 +02:00
|
|
|
s = '%s %s' % (name, command.__doc__.splitlines()[0])
|
2003-08-17 08:24:17 +02:00
|
|
|
else:
|
2003-08-26 14:44:49 +02:00
|
|
|
s = 'Invalid arguments for %s.' % name
|
|
|
|
self.reply(self.msg, s)
|
2003-08-17 08:24:17 +02:00
|
|
|
except (SyntaxError, Error), e:
|
2003-04-01 00:22:59 +02:00
|
|
|
self.reply(self.msg, debug.exnToString(e))
|
2003-03-12 07:26:59 +01:00
|
|
|
except Exception, e:
|
|
|
|
debug.recoverableException()
|
2003-08-23 06:42:04 +02:00
|
|
|
self.error(self.msg, debug.exnToString(e))
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def reply(self, msg, s):
|
|
|
|
if self.finalEvaled:
|
|
|
|
if isinstance(self.irc, self.__class__):
|
|
|
|
self.irc.reply(msg, s)
|
|
|
|
else:
|
2003-04-03 10:31:47 +02:00
|
|
|
s = ircutils.safeArgument(s)
|
2003-08-26 17:03:34 +02:00
|
|
|
if len(s) + len(self.irc.prefix) > 512:
|
|
|
|
s = 'My response would\'ve been too long.'
|
2003-03-15 12:09:52 +01:00
|
|
|
self.irc.queueMsg(reply(msg, s))
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.args[self.counter] = s
|
|
|
|
self.evalArgs()
|
|
|
|
|
|
|
|
def error(self, msg, s):
|
2003-04-02 09:27:32 +02:00
|
|
|
self.reply(msg, 'Error: ' + s)
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def killProxy(self):
|
|
|
|
if not isinstance(self.irc, irclib.Irc):
|
|
|
|
self.irc.killProxy()
|
|
|
|
self.__dict__ = {}
|
|
|
|
|
|
|
|
def getRealIrc(self):
|
2003-03-26 08:39:34 +01:00
|
|
|
if isinstance(self.irc, irclib.Irc):
|
2003-03-12 07:26:59 +01:00
|
|
|
return self.irc
|
|
|
|
else:
|
|
|
|
return self.irc.getRealIrc()
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
2003-08-26 19:08:46 +02:00
|
|
|
#return getattr(self.getRealIrc(), attr)
|
2003-03-12 07:26:59 +01:00
|
|
|
return getattr(self.irc, attr)
|
|
|
|
|
2003-03-15 12:09:52 +01:00
|
|
|
|
2003-03-26 03:30:05 +01:00
|
|
|
class CommandThread(threading.Thread):
|
2003-08-26 19:18:35 +02:00
|
|
|
"""Just does some extra logging and error-recovery for commands that need
|
|
|
|
to run in threads.
|
|
|
|
"""
|
2003-03-26 03:30:05 +01:00
|
|
|
def __init__(self, command, irc, msg, args):
|
2003-04-19 23:42:55 +02:00
|
|
|
self.command = command
|
2003-04-20 18:15:35 +02:00
|
|
|
world.threadsSpawned += 1
|
2003-08-25 21:50:46 +02:00
|
|
|
try:
|
|
|
|
self.commandName = command.im_func.func_name
|
|
|
|
except AttributeError:
|
|
|
|
self.commandName = command.__name__
|
|
|
|
try:
|
|
|
|
self.className = command.im_class.__name__
|
|
|
|
except AttributeError:
|
|
|
|
self.className = '<unknown>'
|
2003-04-07 17:23:12 +02:00
|
|
|
name = '%s.%s with args %r' % (self.className, self.commandName, args)
|
2003-03-26 03:30:05 +01:00
|
|
|
threading.Thread.__init__(self, target=command, name=name,
|
|
|
|
args=(irc, msg, args))
|
|
|
|
self.irc = irc
|
|
|
|
self.msg = msg
|
2003-03-27 07:04:56 +01:00
|
|
|
self.setDaemon(True)
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-03-26 03:30:05 +01:00
|
|
|
def run(self):
|
|
|
|
try:
|
2003-04-07 17:23:12 +02:00
|
|
|
start = time.time()
|
2003-03-26 03:30:05 +01:00
|
|
|
threading.Thread.run(self)
|
2003-04-07 17:23:12 +02:00
|
|
|
elapsed = time.time() - start
|
|
|
|
debug.msg('%s took %s seconds.' % \
|
|
|
|
(self.commandName, elapsed), 'verbose')
|
2003-08-26 15:44:32 +02:00
|
|
|
except (getopt.GetoptError, ArgumentError):
|
2003-08-26 14:44:49 +02:00
|
|
|
if hasattr(self.command, '__doc__'):
|
|
|
|
help = self.command.__doc__.splitlines()[0]
|
|
|
|
s = '%s %s' % (self.commandName, help)
|
|
|
|
else:
|
|
|
|
s = 'Invalid arguments for %s.' % self.commandName
|
|
|
|
self.irc.reply(self.msg, s)
|
2003-08-17 08:24:17 +02:00
|
|
|
except (SyntaxError, Error), e:
|
2003-03-26 03:30:05 +01:00
|
|
|
self.irc.reply(self.msg, debug.exnToString(e))
|
|
|
|
except Exception, e:
|
|
|
|
debug.recoverableException()
|
|
|
|
self.irc.error(self.msg, debug.exnToString(e))
|
|
|
|
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-08-23 06:42:04 +02:00
|
|
|
class ConfigIrcProxy(object):
|
2003-08-26 19:18:35 +02:00
|
|
|
"""Used as a proxy Irc object during configuration. """
|
2003-08-23 06:42:04 +02:00
|
|
|
def __init__(self, irc):
|
|
|
|
self.__dict__['irc'] = irc
|
|
|
|
|
|
|
|
def reply(self, msg, s):
|
|
|
|
return None
|
|
|
|
|
|
|
|
def error(self, msg, s):
|
|
|
|
debug.msg('ConfigIrcProxy saw an error: %s' % s, 'normal')
|
|
|
|
|
|
|
|
findCallback = IrcObjectProxy.findCallback.im_func
|
|
|
|
|
|
|
|
def getRealIrc(self):
|
|
|
|
irc = self.__dict__['irc']
|
|
|
|
while(hasattr(irc, 'getRealIrc')):
|
|
|
|
irc = irc.getRealIrc()
|
|
|
|
return irc
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
return getattr(self.getRealIrc(), attr)
|
|
|
|
|
|
|
|
def __setattr__(self, attr, value):
|
|
|
|
setattr(self.getRealIrc(), attr, value)
|
|
|
|
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
class Privmsg(irclib.IrcCallback):
|
|
|
|
"""Base class for all Privmsg handlers."""
|
|
|
|
threaded = False
|
|
|
|
public = True
|
2003-03-26 08:02:09 +01:00
|
|
|
commandArgs = ['self', 'irc', 'msg', 'args']
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self):
|
|
|
|
self.rateLimiter = RateLimiter()
|
2003-04-02 09:27:32 +02:00
|
|
|
self.Proxy = IrcObjectProxy
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-08-23 06:42:04 +02:00
|
|
|
def configure(self, irc):
|
|
|
|
fakeIrc = ConfigIrcProxy(irc)
|
2003-08-23 01:15:29 +02:00
|
|
|
for args in conf.config['onStart']:
|
|
|
|
args = args[:]
|
|
|
|
command = args.pop(0)
|
|
|
|
if self.isCommand(command):
|
|
|
|
#debug.printf('%s: %r' % (command, args))
|
|
|
|
method = getattr(self, command)
|
|
|
|
line = ' '.join(map(utils.dqrepr, args))
|
|
|
|
msg = ircmsgs.privmsg(fakeIrc.nick, line, fakeIrc.prefix)
|
|
|
|
try:
|
|
|
|
world.startup = True
|
|
|
|
method(fakeIrc, msg, args)
|
|
|
|
finally:
|
|
|
|
world.startup = False
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
def __call__(self, irc, msg):
|
2003-03-22 04:16:20 +01:00
|
|
|
irclib.IrcCallback.__call__(self, irc, msg)
|
|
|
|
# Now, if there's anything in the rateLimiter...
|
2003-03-12 07:26:59 +01:00
|
|
|
msg = self.rateLimiter.get()
|
|
|
|
if msg:
|
2003-03-22 04:16:20 +01:00
|
|
|
s = addressed(irc.nick, msg)
|
2003-04-04 10:29:13 +02:00
|
|
|
try:
|
|
|
|
args = tokenize(s)
|
2003-08-20 09:19:20 +02:00
|
|
|
self.Proxy(irc, msg, args)
|
2003-04-04 10:29:13 +02:00
|
|
|
except SyntaxError, e:
|
|
|
|
irc.queueMsg(reply(msg, debug.exnToString(e)))
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def isCommand(self, methodName):
|
|
|
|
# This function is ugly, but I don't want users to call methods like
|
|
|
|
# doPrivmsg or __init__ or whatever, and this is good to stop them.
|
|
|
|
if hasattr(self, methodName):
|
|
|
|
method = getattr(self, methodName)
|
|
|
|
if inspect.ismethod(method):
|
|
|
|
code = method.im_func.func_code
|
2003-03-26 08:02:09 +01:00
|
|
|
return inspect.getargs(code) == (self.commandArgs, None, None)
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2003-03-26 03:30:05 +01:00
|
|
|
def callCommand(self, f, irc, msg, args):
|
2003-03-12 07:26:59 +01:00
|
|
|
if self.threaded:
|
2003-03-26 03:30:05 +01:00
|
|
|
thread = CommandThread(f, irc, msg, args)
|
2003-03-12 07:26:59 +01:00
|
|
|
thread.start()
|
2003-08-23 01:15:29 +02:00
|
|
|
#debug.printf('Spawned new thread: %s' % thread)
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
2003-04-02 09:42:07 +02:00
|
|
|
# Exceptions aren't caught here because IrcObjectProxy.finalEval
|
|
|
|
# catches them and does The Right Thing.
|
2003-04-08 20:42:10 +02:00
|
|
|
start = time.time()
|
2003-04-02 09:42:07 +02:00
|
|
|
f(irc, msg, args)
|
2003-04-08 20:42:10 +02:00
|
|
|
elapsed = time.time() - start
|
|
|
|
funcname = f.im_func.func_name
|
|
|
|
debug.msg('%s took %s seconds' % (funcname, elapsed), 'verbose')
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-08-25 09:23:36 +02:00
|
|
|
_r = re.compile(r'^([\w_-]+)(?:\s+|$)')
|
2003-03-12 07:26:59 +01:00
|
|
|
def doPrivmsg(self, irc, msg):
|
|
|
|
s = addressed(irc.nick, msg)
|
|
|
|
#debug.printf('Privmsg.doPrivmsg: s == %r' % s)
|
|
|
|
if s:
|
|
|
|
recipient = msg.args[0]
|
|
|
|
if ircdb.checkIgnored(msg.prefix, recipient):
|
2003-08-23 01:15:29 +02:00
|
|
|
debug.printf('Privmsg.doPrivmsg: ignoring %s.' % recipient)
|
2003-03-12 07:26:59 +01:00
|
|
|
return
|
|
|
|
m = self._r.match(s)
|
|
|
|
if m and self.isCommand(canonicalName(m.group(1))):
|
2003-03-22 04:16:20 +01:00
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
2003-04-06 14:23:35 +02:00
|
|
|
if msg:
|
2003-08-20 09:19:20 +02:00
|
|
|
try:
|
|
|
|
args = tokenize(s)
|
|
|
|
self.Proxy(irc, msg, args)
|
|
|
|
except SyntaxError, e:
|
|
|
|
irc.queueMsg(reply(msg, debug.exnToString(e)))
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
|
2003-03-15 12:09:52 +01:00
|
|
|
class IrcObjectProxyRegexp:
|
2003-03-28 03:01:51 +01:00
|
|
|
def __init__(self, irc, *args):
|
2003-03-15 12:09:52 +01:00
|
|
|
self.irc = irc
|
|
|
|
|
|
|
|
def error(self, msg, s):
|
2003-04-02 09:27:32 +02:00
|
|
|
self.reply(msg, 'Error: ' + s)
|
2003-03-15 12:09:52 +01:00
|
|
|
|
|
|
|
def reply(self, msg, s):
|
|
|
|
self.irc.queueMsg(reply(msg, s))
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
return getattr(self.irc, attr)
|
|
|
|
|
2003-03-22 04:16:20 +01:00
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
class PrivmsgRegexp(Privmsg):
|
|
|
|
"""A class to allow a person to create regular expression callbacks.
|
|
|
|
|
|
|
|
Much more primitive, but more flexible than the 'normal' method of using
|
|
|
|
the Privmsg class and its lexer, PrivmsgRegexp allows you to write
|
|
|
|
callbacks that aren't addressed to the bot, for instance. There are, of
|
|
|
|
course, several other possibilities. Callbacks are registered with a
|
|
|
|
string (the regular expression) and a function to be called (with the Irc
|
|
|
|
object, the IrcMsg object, and the match object) when the regular
|
|
|
|
expression matches. Callbacks must have the signature (self, irc, msg,
|
|
|
|
match) to be counted as such.
|
|
|
|
|
2003-04-01 09:59:17 +02:00
|
|
|
A class-level flags attribute is used to determine what regexp flags to
|
|
|
|
compile the regular expressions with. By default, it's re.I, which means
|
|
|
|
regular expressions are by default case-insensitive.
|
|
|
|
|
2003-03-12 07:26:59 +01:00
|
|
|
If you have a standard command-type callback, though, Privmsg is a much
|
|
|
|
better class to use, at the very least for consistency's sake, but also
|
|
|
|
because it's much more easily coded and maintained.
|
|
|
|
"""
|
|
|
|
threaded = False # Again, like Privmsg...
|
2003-04-01 09:59:17 +02:00
|
|
|
flags = re.I
|
2003-08-23 01:15:29 +02:00
|
|
|
onlyFirstMatch = False
|
2003-08-23 08:05:01 +02:00
|
|
|
commandArgs = ['self', 'irc', 'msg', 'match']
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self):
|
|
|
|
Privmsg.__init__(self)
|
2003-04-02 09:27:32 +02:00
|
|
|
self.Proxy = IrcObjectProxyRegexp
|
2003-03-22 04:16:20 +01:00
|
|
|
self.res = []
|
|
|
|
#for name, value in self.__class__.__dict__.iteritems():
|
|
|
|
for name, value in self.__class__.__dict__.items():
|
|
|
|
value = getattr(self, name)
|
2003-04-14 09:01:20 +02:00
|
|
|
if self.isCommand(name):
|
2003-03-22 04:16:20 +01:00
|
|
|
try:
|
2003-04-01 09:59:17 +02:00
|
|
|
r = re.compile(value.__doc__, self.flags)
|
2003-03-22 04:16:20 +01:00
|
|
|
self.res.append((r, value))
|
2003-08-26 19:08:46 +02:00
|
|
|
except re.error, e:
|
2003-03-22 04:16:20 +01:00
|
|
|
s = '%s.%s has an invalid regexp %s: %s' % \
|
|
|
|
(self.__class__.__name__, name,
|
|
|
|
value.__doc__, debug.exnToString(e))
|
2003-04-03 12:06:11 +02:00
|
|
|
debug.msg(s)
|
2003-08-23 01:15:29 +02:00
|
|
|
self.res.sort(lambda (r1, m1), (r2, m2): cmp(m1.__name__, m2.__name__))
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def doPrivmsg(self, irc, msg):
|
2003-04-20 03:35:05 +02:00
|
|
|
if ircdb.checkIgnored(msg.prefix, msg.args[0]):
|
2003-08-11 05:34:54 +02:00
|
|
|
debug.msg('PrivmsgRegexp.doPrivmsg: ignoring %s' % msg.args[0])
|
2003-04-20 03:28:40 +02:00
|
|
|
return
|
2003-03-22 04:16:20 +01:00
|
|
|
for (r, method) in self.res:
|
|
|
|
m = r.search(msg.args[1])
|
|
|
|
if m:
|
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
|
|
|
if msg:
|
|
|
|
irc = IrcObjectProxyRegexp(irc)
|
2003-03-26 03:30:05 +01:00
|
|
|
self.callCommand(method, irc, msg, m)
|
2003-08-23 01:15:29 +02:00
|
|
|
if self.onlyFirstMatch:
|
|
|
|
return
|
2003-03-26 03:41:39 +01:00
|
|
|
|
2003-04-18 10:24:04 +02:00
|
|
|
|
2003-08-11 05:34:54 +02:00
|
|
|
class PrivmsgCommandAndRegexp(Privmsg):
|
2003-08-19 12:46:52 +02:00
|
|
|
"""Same as Privmsg, except allows the user to also include regexp-based
|
|
|
|
callbacks. All regexp-based callbacks must be specified in a sets.Set
|
|
|
|
(or list) attribute "regexps".
|
|
|
|
"""
|
2003-08-11 05:34:54 +02:00
|
|
|
flags = re.I
|
|
|
|
regexps = sets.Set()
|
|
|
|
def __init__(self):
|
|
|
|
Privmsg.__init__(self)
|
|
|
|
self.res = []
|
|
|
|
for name in self.regexps:
|
|
|
|
method = getattr(self, name)
|
|
|
|
r = re.compile(method.__doc__, self.flags)
|
|
|
|
self.res.append((r, method))
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-08-11 05:34:54 +02:00
|
|
|
def doPrivmsg(self, irc, msg):
|
|
|
|
if ircdb.checkIgnored(msg.prefix, msg.args[0]):
|
|
|
|
return
|
|
|
|
for (r, method) in self.res:
|
|
|
|
m = r.search(msg.args[1])
|
|
|
|
if m:
|
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
|
|
|
if msg:
|
|
|
|
self.callCommand(method, IrcObjectProxyRegexp(irc), msg, m)
|
|
|
|
s = addressed(irc.nick, msg)
|
|
|
|
if s:
|
|
|
|
m = self._r.match(s)
|
|
|
|
if m and self.isCommand(canonicalName(m.group(1))):
|
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
|
|
|
if msg:
|
|
|
|
args = tokenize(s)
|
|
|
|
self.Proxy(irc, msg, args)
|
2003-08-20 18:26:23 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2003-04-18 10:24:04 +02:00
|
|
|
|
2003-03-24 09:41:19 +01:00
|
|
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|