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-10-05 14:47:19 +02:00
|
|
|
import fix
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
import re
|
2003-10-20 13:34:21 +02:00
|
|
|
import new
|
2003-10-20 06:26:37 +02:00
|
|
|
import copy
|
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-10-04 14:29:58 +02:00
|
|
|
import string
|
2003-03-12 07:26:59 +01:00
|
|
|
import inspect
|
2003-09-07 06:05:34 +02:00
|
|
|
import textwrap
|
2003-03-12 07:26:59 +01:00
|
|
|
import threading
|
2003-10-24 13:31:09 +02:00
|
|
|
from itertools import imap, ifilter
|
2003-03-12 07:26:59 +01:00
|
|
|
from cStringIO import StringIO
|
|
|
|
|
|
|
|
import conf
|
2003-10-03 00:37:36 +02:00
|
|
|
import debug
|
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
|
|
|
|
|
|
|
|
###
|
|
|
|
# Privmsg: handles privmsg commands in a standard fashion.
|
|
|
|
###
|
|
|
|
def addressed(nick, msg):
|
|
|
|
"""If msg is addressed to 'name', returns the portion after the address.
|
2003-09-07 06:05:34 +02:00
|
|
|
Otherwise returns the empty string.
|
2003-03-12 07:26:59 +01:00
|
|
|
"""
|
2003-10-09 00:38:27 +02:00
|
|
|
nick = ircutils.toLower(nick)
|
|
|
|
if ircutils.nickEqual(msg.args[0], nick):
|
2003-03-12 07:26:59 +01:00
|
|
|
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:
|
2003-09-25 18:07:41 +02:00
|
|
|
(maybeNick, rest) = msg.args[1].split(None, 1)
|
|
|
|
while not ircutils.isNick(maybeNick):
|
|
|
|
maybeNick = maybeNick[:-1]
|
2003-10-09 00:38:27 +02:00
|
|
|
if ircutils.nickEqual(maybeNick, nick):
|
2003-09-25 18:07:41 +02:00
|
|
|
return rest
|
|
|
|
else:
|
|
|
|
return ''
|
2003-09-25 18:09:18 +02:00
|
|
|
except ValueError: # split didn't work.
|
2003-03-12 07:26:59 +01:00
|
|
|
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-10-20 12:10:46 +02:00
|
|
|
def reply(msg, s, prefixName=True, private=False, notice=False):
|
2003-03-15 12:09:52 +01:00
|
|
|
"""Makes a reply to msg with the payload s"""
|
2003-04-03 10:31:47 +02:00
|
|
|
s = ircutils.safeArgument(s)
|
2003-09-23 22:45:00 +02:00
|
|
|
if ircutils.isChannel(msg.args[0]) and not private:
|
2003-10-20 12:10:46 +02:00
|
|
|
if notice or conf.replyWithPrivateNotice:
|
|
|
|
m = ircmsgs.notice(msg.nick, s)
|
|
|
|
elif prefixName:
|
2003-09-08 10:44:51 +02:00
|
|
|
m = ircmsgs.privmsg(msg.args[0], '%s: %s' % (msg.nick, s))
|
|
|
|
else:
|
|
|
|
m = ircmsgs.privmsg(msg.args[0], s)
|
2003-03-15 12:09:52 +01:00
|
|
|
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-10-24 13:31:09 +02:00
|
|
|
def getHelp(method, name=None):
|
|
|
|
if name is None:
|
|
|
|
name = method.__name__
|
|
|
|
doclines = method.__doc__.splitlines()
|
2003-10-25 01:14:27 +02:00
|
|
|
s = '%s %s' % (name, doclines.pop(0))
|
2003-10-24 13:31:09 +02:00
|
|
|
if doclines:
|
|
|
|
doclines = imap(str.strip, ifilter(None, doclines))
|
|
|
|
help = ' '.join(doclines)
|
2003-10-24 13:47:45 +02:00
|
|
|
s = '(%s) -- %s' % (ircutils.bold(s), help)
|
2003-10-24 13:31:09 +02:00
|
|
|
return s
|
|
|
|
|
|
|
|
def getSyntax(method, name=None):
|
|
|
|
if name is None:
|
|
|
|
name = method.__name__
|
|
|
|
doclines = method.__doc__.splitlines()
|
2003-10-25 01:14:27 +02:00
|
|
|
return '%s %s' % (name, doclines[0])
|
2003-10-24 13:31:09 +02:00
|
|
|
|
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):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""Returns the next un-ratelimited message, or the next rate-limited
|
|
|
|
message whose time has come up."""
|
2003-03-12 07:26:59 +01:00
|
|
|
if self.unlimited:
|
|
|
|
return self.unlimited.pop(0)
|
|
|
|
elif self.limited:
|
|
|
|
for i in range(len(self.limited)):
|
|
|
|
msg = self.limited[i]
|
2003-10-04 13:34:44 +02:00
|
|
|
if not self._limit(msg, penalize=False):
|
2003-03-12 07:26:59 +01:00
|
|
|
return self.limited.pop(i)
|
|
|
|
return None
|
|
|
|
else:
|
|
|
|
return None
|
|
|
|
|
|
|
|
def put(self, msg):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""Puts a message in for possible ratelimiting."""
|
|
|
|
t = self._limit(msg)
|
2003-06-05 12:00:31 +02:00
|
|
|
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
|
|
|
|
2003-10-04 13:34:44 +02:00
|
|
|
def _limit(self, msg, penalize=True):
|
2003-03-12 07:26:59 +01:00
|
|
|
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-08-27 09:45:48 +02:00
|
|
|
class CannotNest(Error):
|
|
|
|
"""Exception to be raised by commands that cannot be nested."""
|
|
|
|
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-09-17 21:19:38 +02:00
|
|
|
validChars = string.ascii[33:].translate(string.ascii, '"[]')
|
2003-03-12 07:26:59 +01:00
|
|
|
def __init__(self, tokens=''):
|
2003-09-17 21:19:38 +02:00
|
|
|
# Add a '|' to tokens to have the pipe syntax.
|
2003-03-12 07:26:59 +01:00
|
|
|
self.validChars = self.validChars.translate(string.ascii, tokens)
|
|
|
|
|
2003-10-04 13:34:44 +02:00
|
|
|
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
|
|
|
|
|
2003-10-04 13:34:44 +02:00
|
|
|
def _insideBrackets(self, lexer):
|
2003-03-12 07:26:59 +01:00
|
|
|
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 == '[':
|
2003-10-04 13:34:44 +02:00
|
|
|
ret.append(self._insideBrackets(lexer))
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
2003-10-04 13:34:44 +02:00
|
|
|
ret.append(self._handleToken(token))
|
2003-03-12 07:26:59 +01:00
|
|
|
return ret
|
|
|
|
|
|
|
|
def tokenize(self, s):
|
2003-10-05 22:40:45 +02:00
|
|
|
"""Tokenizes a string according to supybot's nested argument format."""
|
2003-03-12 07:26:59 +01:00
|
|
|
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 = []
|
2003-09-07 11:41:47 +02:00
|
|
|
ends = []
|
2003-03-12 07:26:59 +01:00
|
|
|
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
|
2003-09-07 11:41:47 +02:00
|
|
|
elif token == '|':
|
|
|
|
if not args:
|
|
|
|
raise SyntaxError, '"|" with nothing preceding'
|
|
|
|
ends.append(args)
|
|
|
|
args = []
|
2003-03-12 07:26:59 +01:00
|
|
|
elif token == '[':
|
2003-10-04 13:34:44 +02:00
|
|
|
args.append(self._insideBrackets(lexer))
|
2003-03-12 07:26:59 +01:00
|
|
|
elif token == ']':
|
|
|
|
raise SyntaxError, 'Spurious "["'
|
|
|
|
else:
|
2003-10-04 13:34:44 +02:00
|
|
|
args.append(self._handleToken(token))
|
2003-09-07 11:41:47 +02:00
|
|
|
if ends:
|
|
|
|
if not args:
|
|
|
|
raise SyntaxError, '"|" with nothing following'
|
|
|
|
args.append(ends.pop())
|
|
|
|
while ends:
|
|
|
|
args[-1].append(ends.pop())
|
2003-03-12 07:26:59 +01:00
|
|
|
return args
|
|
|
|
|
2003-10-20 06:16:44 +02:00
|
|
|
_lastTokenized = None
|
|
|
|
_lastTokenizeResult = None
|
|
|
|
def tokenize(s):
|
|
|
|
"""A utility function to create a Tokenizer and tokenize a string."""
|
|
|
|
global _lastTokenized, _lastTokenizeResult
|
|
|
|
start = time.time()
|
|
|
|
try:
|
|
|
|
if s != _lastTokenized:
|
|
|
|
_lastTokenized = s
|
|
|
|
if conf.enablePipeSyntax:
|
|
|
|
tokens = '|'
|
|
|
|
else:
|
|
|
|
tokens = ''
|
|
|
|
_lastTokenizeResult = Tokenizer(tokens).tokenize(s)
|
|
|
|
except ValueError, e:
|
|
|
|
_lastTokenized = None
|
|
|
|
_lastTokenizedResult = None
|
|
|
|
raise SyntaxError, str(e)
|
2003-10-20 06:26:37 +02:00
|
|
|
#debug.msg('tokenize took %s seconds.' % (time.time() - start), 'verbose')
|
|
|
|
return copy.deepcopy(_lastTokenizeResult)
|
2003-08-20 18:26:23 +02:00
|
|
|
|
2003-09-10 10:32:20 +02:00
|
|
|
def getCommands(tokens):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""Given tokens as output by tokenize, returns the command names."""
|
2003-09-10 10:32:20 +02:00
|
|
|
L = []
|
|
|
|
if tokens and isinstance(tokens, list):
|
|
|
|
L.append(tokens[0])
|
|
|
|
for elt in tokens:
|
|
|
|
L.extend(getCommands(elt))
|
|
|
|
return L
|
|
|
|
|
2003-08-25 09:23:36 +02:00
|
|
|
def findCallbackForCommand(irc, commandName):
|
2003-10-20 12:25:13 +02:00
|
|
|
"""Given a command name and an Irc object, returns a list of callbacks that
|
|
|
|
commandName is in."""
|
|
|
|
L = []
|
2003-08-25 09:23:36 +02:00
|
|
|
for callback in irc.callbacks:
|
2003-09-02 09:30:35 +02:00
|
|
|
if not isinstance(callback, PrivmsgRegexp):
|
|
|
|
if hasattr(callback, 'isCommand'):
|
|
|
|
if callback.isCommand(commandName):
|
2003-10-20 12:25:13 +02:00
|
|
|
L.append(callback)
|
|
|
|
return L
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-10-21 09:20:54 +02:00
|
|
|
def formatArgumentError(method, name=None):
|
|
|
|
if name is None:
|
|
|
|
name = method.__name__
|
|
|
|
if hasattr(method, '__doc__') and method.__doc__:
|
2003-10-24 13:31:09 +02:00
|
|
|
if conf.showOnlySyntax:
|
|
|
|
return getSyntax(method, name=name)
|
|
|
|
else:
|
|
|
|
return getHelp(method, name=name)
|
2003-10-21 09:20:54 +02:00
|
|
|
else:
|
2003-10-24 13:31:09 +02:00
|
|
|
return 'Invalid arguments for %s.' % method.__name__
|
2003-10-21 09:20:54 +02:00
|
|
|
|
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)
|
2003-09-05 09:26:55 +02:00
|
|
|
if not args:
|
|
|
|
irc.reply(msg, '[]')
|
|
|
|
else:
|
2003-10-28 01:22:15 +01:00
|
|
|
if isinstance(irc, irclib.Irc):
|
|
|
|
# Let's check for {ambiguous,non}Commands.
|
|
|
|
ambiguousCommands = {}
|
|
|
|
commands = getCommands(args)
|
|
|
|
for command in commands:
|
|
|
|
command = canonicalName(command)
|
|
|
|
cbs = findCallbackForCommand(irc, command)
|
|
|
|
if len(cbs) > 1:
|
|
|
|
ambiguousCommands[command] = [cb.name() for cb in cbs]
|
|
|
|
if ambiguousCommands:
|
|
|
|
if len(ambiguousCommands) == 1: # Common case.
|
|
|
|
(command, names) = ambiguousCommands.popitem()
|
|
|
|
names.sort()
|
|
|
|
s = 'The command %r is available in the %s plugins. '\
|
|
|
|
'Please specify the plugin whose command you ' \
|
|
|
|
'wish to call by using its name as a command ' \
|
|
|
|
'before calling it.' % \
|
|
|
|
(command, utils.commaAndify(names))
|
|
|
|
else:
|
|
|
|
L = []
|
|
|
|
for (command, names) in ambiguousCommands.iteritems():
|
|
|
|
names.sort()
|
|
|
|
L.append('The command %r is available in the %s '
|
|
|
|
'plugins' %
|
|
|
|
(command, utils.commaAndify(names)))
|
|
|
|
s = '%s; please specify from which plugins to ' \
|
|
|
|
'call these commands.' % '; '.join(L)
|
|
|
|
irc.queueMsg(error(msg, s))
|
|
|
|
return
|
2003-09-05 09:26:55 +02:00
|
|
|
self.irc = irc
|
|
|
|
self.msg = msg
|
|
|
|
self.args = args
|
|
|
|
self.counter = 0
|
2003-09-18 09:26:21 +02:00
|
|
|
self.action = False
|
2003-10-20 12:10:46 +02:00
|
|
|
self.notice = False
|
2003-10-28 07:06:21 +01:00
|
|
|
self.private = False
|
|
|
|
self.finished = False
|
2003-09-08 10:44:51 +02:00
|
|
|
self.prefixName = True
|
2003-10-28 07:06:21 +01:00
|
|
|
self.finalEvaled = False
|
2003-09-08 10:44:51 +02:00
|
|
|
self.noLengthCheck = False
|
2003-09-05 09:26:55 +02:00
|
|
|
world.commandsProcessed += 1
|
|
|
|
self.evalArgs()
|
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):
|
2003-10-28 01:22:15 +01:00
|
|
|
assert not self.finalEvaled, 'finalEval called twice.'
|
2003-03-12 07:26:59 +01:00
|
|
|
self.finalEvaled = True
|
2003-10-28 01:22:15 +01:00
|
|
|
name = canonicalName(self.args[0])
|
2003-10-20 12:25:13 +02:00
|
|
|
cbs = findCallbackForCommand(self, name)
|
|
|
|
if len(cbs) == 0:
|
2003-10-28 07:57:52 +01:00
|
|
|
if self.irc.nick == self.msg.nick:
|
|
|
|
return
|
2003-10-28 01:22:15 +01:00
|
|
|
for cb in self.irc.callbacks:
|
|
|
|
if isinstance(cb, PrivmsgRegexp):
|
|
|
|
for (r, _) in cb.res:
|
2003-10-28 06:16:17 +01:00
|
|
|
if r.search(self.msg.args[1]):
|
2003-10-28 01:22:15 +01:00
|
|
|
return
|
2003-10-28 06:16:17 +01:00
|
|
|
elif isinstance(cb, PrivmsgCommandAndRegexp):
|
2003-10-28 01:22:15 +01:00
|
|
|
for (r, _) in cb.res:
|
2003-10-28 06:16:17 +01:00
|
|
|
if r.search(self.msg.args[1]):
|
2003-10-28 01:22:15 +01:00
|
|
|
return
|
|
|
|
for (r, _) in cb.addressedRes:
|
2003-10-28 06:16:17 +01:00
|
|
|
if r.search(self.msg.args[1]):
|
2003-10-28 01:22:15 +01:00
|
|
|
return
|
|
|
|
# Ok, no regexp-based things matched.
|
|
|
|
for cb in self.irc.callbacks:
|
2003-10-28 07:06:21 +01:00
|
|
|
if self.finished:
|
|
|
|
break
|
2003-10-28 01:22:15 +01:00
|
|
|
if hasattr(cb, 'invalidCommand'):
|
|
|
|
cb.invalidCommand(self, self.msg, self.args)
|
2003-10-20 12:25:13 +02:00
|
|
|
else:
|
|
|
|
try:
|
2003-10-28 01:22:15 +01:00
|
|
|
assert len(cbs) == 1
|
|
|
|
del self.args[0]
|
2003-10-20 12:25:13 +02:00
|
|
|
cb = cbs[0]
|
2003-04-01 07:39:36 +02:00
|
|
|
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-10-03 12:04:40 +02:00
|
|
|
s = conf.replyNoCapability % name
|
|
|
|
self.error(self.msg, s, private=True)
|
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-10-03 12:04:40 +02:00
|
|
|
s = conf.replyNoCapability % name
|
|
|
|
self.error(self.msg, s, private=True)
|
2003-04-01 07:39:36 +02:00
|
|
|
return
|
2003-09-17 10:12:59 +02:00
|
|
|
command = getattr(cb, name)
|
|
|
|
if cb.threaded:
|
|
|
|
t = CommandThread(cb.callCommand, command,
|
|
|
|
self, self.msg, self.args)
|
|
|
|
t.start()
|
|
|
|
else:
|
|
|
|
cb.callCommand(command, self, self.msg, self.args)
|
2003-10-20 12:25:13 +02:00
|
|
|
except (getopt.GetoptError, ArgumentError):
|
2003-10-21 09:20:54 +02:00
|
|
|
self.reply(self.msg, formatArgumentError(command, name=name))
|
2003-10-20 12:25:13 +02:00
|
|
|
except CannotNest, e:
|
2003-08-25 09:23:36 +02:00
|
|
|
if not isinstance(self.irc, irclib.Irc):
|
2003-10-20 12:25:13 +02:00
|
|
|
self.error(self.msg, 'Command %r cannot be nested.' % name)
|
|
|
|
except (SyntaxError, Error), e:
|
|
|
|
self.reply(self.msg, debug.exnToString(e))
|
|
|
|
except Exception, e:
|
|
|
|
debug.recoverableException()
|
|
|
|
self.error(self.msg, debug.exnToString(e))
|
2003-03-12 07:26:59 +01:00
|
|
|
|
2003-09-18 09:26:21 +02:00
|
|
|
def reply(self, msg, s, noLengthCheck=False, prefixName=True,
|
2003-10-20 12:10:46 +02:00
|
|
|
action=False, private=False, notice=False):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""reply(msg, text) -> replies to msg with text
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
noLengthCheck=False: True if the length shouldn't be checked
|
|
|
|
(used for 'more' handling)
|
|
|
|
prefixName=True: False if the nick shouldn't be prefixed to the
|
|
|
|
reply.
|
|
|
|
action=False: True if the reply should be an action.
|
|
|
|
private=False: True if the reply should be in private.
|
2003-10-20 12:10:46 +02:00
|
|
|
notice=False: True if the reply should be noticed when the
|
|
|
|
bot is configured to do so.
|
2003-10-04 13:34:44 +02:00
|
|
|
"""
|
2003-09-18 09:26:21 +02:00
|
|
|
# These use |= or &= based on whether or not they default to True or
|
|
|
|
# False. Those that default to True use &=; those that default to
|
|
|
|
# False use |=.
|
|
|
|
self.action |= action
|
|
|
|
self.private |= private
|
2003-10-20 12:10:46 +02:00
|
|
|
self.notice |= notice
|
2003-09-08 10:44:51 +02:00
|
|
|
self.prefixName &= prefixName
|
2003-09-18 09:26:21 +02:00
|
|
|
self.noLengthCheck |= noLengthCheck
|
2003-03-12 07:26:59 +01:00
|
|
|
if self.finalEvaled:
|
|
|
|
if isinstance(self.irc, self.__class__):
|
2003-09-23 22:45:00 +02:00
|
|
|
self.irc.reply(msg, s, self.noLengthCheck, self.prefixName,
|
2003-10-20 12:10:46 +02:00
|
|
|
self.action, self.private, self.notice)
|
2003-09-08 10:44:51 +02:00
|
|
|
elif self.noLengthCheck:
|
|
|
|
self.irc.queueMsg(reply(msg, s, self.prefixName))
|
2003-09-18 09:26:21 +02:00
|
|
|
elif self.action:
|
|
|
|
self.irc.queueMsg(ircmsgs.action(msg.args[0], s))
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
2003-04-03 10:31:47 +02:00
|
|
|
s = ircutils.safeArgument(s)
|
2003-09-23 22:45:00 +02:00
|
|
|
allowedLength = 450 - len(self.irc.prefix)
|
2003-09-07 06:56:26 +02:00
|
|
|
msgs = textwrap.wrap(s, allowedLength-30) # -30 is for "nick:"
|
2003-09-07 06:05:34 +02:00
|
|
|
msgs.reverse()
|
|
|
|
response = msgs.pop()
|
|
|
|
if msgs:
|
2003-09-07 07:13:58 +02:00
|
|
|
response += ' \x02(%s)\x0F' % \
|
|
|
|
utils.nItems(len(msgs), 'message', 'more')
|
2003-09-07 06:05:34 +02:00
|
|
|
mask = msg.prefix.split('!', 1)[1]
|
|
|
|
Privmsg._mores[mask] = msgs
|
2003-09-18 09:26:21 +02:00
|
|
|
private = self.private or not ircutils.isChannel(msg.args[0])
|
|
|
|
Privmsg._mores[msg.nick] = (private, msgs)
|
|
|
|
if self.private:
|
|
|
|
self.irc.queueMsg(ircmsgs.privmsg(msg.nick, response))
|
|
|
|
else:
|
2003-10-20 12:10:46 +02:00
|
|
|
self.irc.queueMsg(reply(msg, response, self.prefixName,
|
|
|
|
notice=self.notice))
|
2003-10-28 07:06:21 +01:00
|
|
|
self.finished = True
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
self.args[self.counter] = s
|
|
|
|
self.evalArgs()
|
|
|
|
|
2003-10-03 00:37:36 +02:00
|
|
|
def error(self, msg, s, private=False):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""error(msg, text) -> replies to msg with an error message of text.
|
|
|
|
|
|
|
|
Keyword arguments:
|
|
|
|
private=False: True if the error should be given in private.
|
|
|
|
"""
|
2003-08-27 09:45:48 +02:00
|
|
|
if isinstance(self.irc, self.__class__):
|
2003-10-03 00:37:36 +02:00
|
|
|
self.irc.error(msg, s, private)
|
2003-08-27 09:45:48 +02:00
|
|
|
else:
|
2003-09-22 11:45:23 +02:00
|
|
|
s = 'Error: ' + s
|
2003-10-03 00:37:36 +02:00
|
|
|
if private or conf.errorReplyPrivate:
|
2003-09-22 11:45:23 +02:00
|
|
|
self.irc.queueMsg(ircmsgs.privmsg(msg.nick, s))
|
|
|
|
else:
|
|
|
|
self.irc.queueMsg(reply(msg, s))
|
2003-10-28 07:06:21 +01:00
|
|
|
self.finished = True
|
2003-03-12 07:26:59 +01:00
|
|
|
|
|
|
|
def killProxy(self):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""Kills this proxy object and all its parents."""
|
2003-03-12 07:26:59 +01:00
|
|
|
if not isinstance(self.irc, irclib.Irc):
|
|
|
|
self.irc.killProxy()
|
|
|
|
self.__dict__ = {}
|
|
|
|
|
|
|
|
def getRealIrc(self):
|
2003-10-04 13:34:44 +02:00
|
|
|
"""Returns the real irclib.Irc object underlying this proxy chain."""
|
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):
|
|
|
|
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-09-18 01:31:45 +02:00
|
|
|
def __init__(self, callCommand, command, irc, msg, args, *L):
|
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-09-17 10:12:59 +02:00
|
|
|
threading.Thread.__init__(self, target=callCommand, name=name,
|
2003-09-18 01:31:45 +02:00
|
|
|
args=(command, irc, msg, args)+L)
|
2003-08-27 03:39:58 +02:00
|
|
|
debug.msg('Spawning thread %s' % name, 'verbose')
|
2003-03-26 03:30:05 +01:00
|
|
|
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:
|
|
|
|
threading.Thread.run(self)
|
2003-08-26 15:44:32 +02:00
|
|
|
except (getopt.GetoptError, ArgumentError):
|
2003-10-21 09:20:54 +02:00
|
|
|
name = self.commandName
|
|
|
|
self.irc.reply(self.msg, formatArgumentError(self.command, name))
|
2003-08-27 09:45:48 +02:00
|
|
|
except CannotNest:
|
|
|
|
if not isinstance(self.irc.irc, irclib.Irc):
|
|
|
|
s = 'Command %r cannot be nested.' % self.commandName
|
|
|
|
self.irc.error(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
|
|
|
|
|
2003-10-03 12:04:40 +02:00
|
|
|
def reply(self, msg, s, *args):
|
2003-08-23 06:42:04 +02:00
|
|
|
return None
|
|
|
|
|
2003-10-03 12:04:40 +02:00
|
|
|
def error(self, msg, s, *args):
|
2003-08-23 06:42:04 +02:00
|
|
|
debug.msg('ConfigIrcProxy saw an error: %s' % s, 'normal')
|
|
|
|
|
|
|
|
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-10-21 23:01:43 +02:00
|
|
|
handled = False
|
|
|
|
alwaysCall = ()
|
2003-03-26 08:02:09 +01:00
|
|
|
commandArgs = ['self', 'irc', 'msg', 'args']
|
2003-09-07 06:05:34 +02:00
|
|
|
_mores = {} # This must be class-scope, so all subclasses use the same one.
|
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-10-20 13:34:21 +02:00
|
|
|
canonicalname = canonicalName(self.name())
|
|
|
|
self._original = getattr(self, canonicalname, None)
|
|
|
|
docstring = """<command> [<args> ...]
|
|
|
|
|
2003-10-23 10:43:50 +02:00
|
|
|
Command dispatcher for the %s plugin. Use 'list %s' to see the
|
|
|
|
commands provided by this plugin. In most cases this dispatcher
|
|
|
|
command is unnecessary; in cases where more than one plugin defines a
|
|
|
|
given command, use this command to tell the bot which plugin's command
|
|
|
|
to use.""" % (self.name(), self.name())
|
2003-10-20 13:34:21 +02:00
|
|
|
def dispatcher(self, irc, msg, args):
|
|
|
|
def handleBadArgs():
|
|
|
|
if self._original:
|
|
|
|
self._original(irc, msg, args)
|
|
|
|
else:
|
2003-10-22 19:19:08 +02:00
|
|
|
cb = irc.getCallback('Misc')
|
|
|
|
cb.help(irc, msg, [self.name()])
|
2003-10-20 13:34:21 +02:00
|
|
|
if args:
|
|
|
|
name = canonicalName(args[0])
|
2003-10-22 19:19:08 +02:00
|
|
|
if name == canonicalName(self.name()):
|
|
|
|
handleBadArgs()
|
|
|
|
elif self.isCommand(name):
|
2003-10-20 13:34:21 +02:00
|
|
|
del args[0]
|
|
|
|
method = getattr(self, name)
|
2003-10-21 09:20:54 +02:00
|
|
|
try:
|
|
|
|
method(irc, msg, args)
|
|
|
|
except (getopt.GetoptError, ArgumentError):
|
|
|
|
irc.reply(msg, formatArgumentError(method, name))
|
2003-10-20 13:34:21 +02:00
|
|
|
else:
|
|
|
|
handleBadArgs()
|
|
|
|
else:
|
|
|
|
handleBadArgs()
|
|
|
|
dispatcher = new.function(dispatcher.func_code,globals(),canonicalname)
|
|
|
|
if self._original:
|
|
|
|
dispatcher.__doc__ = self._original.__doc__
|
|
|
|
else:
|
|
|
|
dispatcher.__doc__ = docstring
|
|
|
|
setattr(self.__class__, canonicalname, dispatcher)
|
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-28 18:33:45 +02:00
|
|
|
for args in conf.commandsOnStart:
|
2003-08-23 01:15:29 +02:00
|
|
|
args = args[:]
|
2003-10-24 20:53:34 +02:00
|
|
|
command = canonicalName(args.pop(0))
|
2003-08-23 01:15:29 +02:00
|
|
|
if self.isCommand(command):
|
|
|
|
#debug.printf('%s: %r' % (command, args))
|
|
|
|
method = getattr(self, command)
|
2003-09-06 10:00:46 +02:00
|
|
|
line = '%s %s' % (command, ' '.join(map(utils.dqrepr, args)))
|
2003-08-23 01:15:29 +02:00
|
|
|
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()
|
2003-08-28 18:33:45 +02:00
|
|
|
while 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:
|
2003-08-27 09:45:48 +02:00
|
|
|
irc.queueMsg(reply(msg, str(e)))
|
2003-08-28 18:33:45 +02:00
|
|
|
msg = self.rateLimiter.get()
|
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-09-18 01:31:45 +02:00
|
|
|
return inspect.getargs(code)[0] == self.commandArgs
|
2003-03-12 07:26:59 +01:00
|
|
|
else:
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
2003-10-14 01:20:15 +02:00
|
|
|
def callCommand(self, f, irc, msg, *L):
|
2003-09-17 10:12:59 +02:00
|
|
|
# Exceptions aren't caught here because IrcObjectProxy.finalEval
|
|
|
|
# catches them and does The Right Thing.
|
2003-10-21 23:01:43 +02:00
|
|
|
Privmsg.handled = True
|
2003-09-17 10:12:59 +02:00
|
|
|
start = time.time()
|
2003-10-14 01:20:15 +02:00
|
|
|
f(irc, msg, *L)
|
2003-09-17 10:12:59 +02:00
|
|
|
elapsed = time.time() - start
|
2003-10-14 01:20:15 +02:00
|
|
|
funcname = '%s.%s' % (f.im_class.__name__, f.im_func.func_name)
|
|
|
|
debug.msg('%s took %s seconds' % (funcname, elapsed), 'verbose')
|
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
|
|
|
|
|
2003-10-03 12:04:40 +02:00
|
|
|
def error(self, msg, s, private=False):
|
|
|
|
private = private or conf.errorReplyPrivate
|
|
|
|
self.reply(msg, 'Error: ' + s, private=private)
|
2003-03-15 12:09:52 +01:00
|
|
|
|
2003-10-20 12:10:46 +02:00
|
|
|
def reply(self, msg, s, prefixName=True, action=False, private=False,
|
|
|
|
notice=False):
|
2003-09-23 22:45:00 +02:00
|
|
|
if action:
|
|
|
|
self.irc.queueMsg(ircmsgs.action(ircutils.replyTo(msg), s))
|
|
|
|
else:
|
2003-10-20 12:10:46 +02:00
|
|
|
self.irc.queueMsg(reply(msg, s, private=private, notice=notice,
|
2003-09-23 22:45:00 +02:00
|
|
|
prefixName=prefixName))
|
2003-03-15 12:09:52 +01:00
|
|
|
|
|
|
|
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 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
|
|
|
|
2003-10-23 16:46:56 +02:00
|
|
|
def callCommand(self, method, irc, msg, *L):
|
2003-10-14 01:20:15 +02:00
|
|
|
try:
|
2003-10-23 16:46:56 +02:00
|
|
|
Privmsg.callCommand(self, method, irc, msg, *L)
|
2003-10-14 01:20:15 +02:00
|
|
|
except Exception, e:
|
|
|
|
debug.recoverableException()
|
|
|
|
irc.error(msg, debug.exnToString(e))
|
|
|
|
|
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-09-12 22:20:18 +02:00
|
|
|
debug.msg('PrivmsgRegexp.doPrivmsg: ignoring %s' % msg.prefix)
|
2003-04-20 03:28:40 +02:00
|
|
|
return
|
2003-10-03 00:37:36 +02:00
|
|
|
fed = False
|
2003-03-22 04:16:20 +01:00
|
|
|
for (r, method) in self.res:
|
2003-10-04 11:59:06 +02:00
|
|
|
spans = sets.Set()
|
2003-10-03 00:37:36 +02:00
|
|
|
for m in r.finditer(msg.args[1]):
|
2003-10-04 11:59:06 +02:00
|
|
|
# There's a bug in finditer: http://www.python.org/sf/817234
|
|
|
|
if m.span() in spans:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
spans.add(m.span())
|
2003-10-03 00:37:36 +02:00
|
|
|
if not fed:
|
|
|
|
fed = True
|
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
2003-03-22 04:16:20 +01:00
|
|
|
if msg:
|
2003-10-23 16:46:56 +02:00
|
|
|
proxy = IrcObjectProxyRegexp(irc)
|
|
|
|
self.callCommand(method, proxy, msg, m)
|
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
|
2003-10-09 06:29:37 +02:00
|
|
|
regexps = () # Use sets.Set() in your own callbacks.
|
|
|
|
addressedRegexps = () # Ditto on the sets.Sets() idea.
|
2003-08-11 05:34:54 +02:00
|
|
|
def __init__(self):
|
|
|
|
Privmsg.__init__(self)
|
|
|
|
self.res = []
|
2003-10-09 06:29:37 +02:00
|
|
|
self.addressedRes = []
|
2003-08-11 05:34:54 +02:00
|
|
|
for name in self.regexps:
|
|
|
|
method = getattr(self, name)
|
|
|
|
r = re.compile(method.__doc__, self.flags)
|
|
|
|
self.res.append((r, method))
|
2003-10-09 06:29:37 +02:00
|
|
|
for name in self.addressedRegexps:
|
|
|
|
method = getattr(self, name)
|
|
|
|
r = re.compile(method.__doc__, self.flags)
|
|
|
|
self.addressedRes.append((r, method))
|
2003-10-20 09:31:17 +02:00
|
|
|
|
|
|
|
def callCommand(self, f, irc, msg, *L, **kwargs):
|
2003-10-14 01:20:15 +02:00
|
|
|
try:
|
|
|
|
Privmsg.callCommand(self, f, irc, msg, *L)
|
|
|
|
except Exception, e:
|
2003-10-20 09:31:17 +02:00
|
|
|
if 'catchErrors' in kwargs and kwargs['catchErrors']:
|
|
|
|
irc.error(msg, debug.exnToString(e))
|
|
|
|
debug.recoverableException()
|
|
|
|
else:
|
|
|
|
raise
|
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
|
2003-10-03 00:37:36 +02:00
|
|
|
fed = False
|
2003-08-11 05:34:54 +02:00
|
|
|
for (r, method) in self.res:
|
2003-10-21 23:01:43 +02:00
|
|
|
originalHandled = self.handled
|
|
|
|
name = method.__name__
|
2003-10-03 00:37:36 +02:00
|
|
|
for m in r.finditer(msg.args[1]):
|
2003-10-21 23:01:43 +02:00
|
|
|
if originalHandled and name not in self.alwaysCall:
|
|
|
|
continue
|
2003-10-03 00:37:36 +02:00
|
|
|
if not fed:
|
|
|
|
fed = True
|
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
2003-08-11 05:34:54 +02:00
|
|
|
if msg:
|
2003-10-14 01:20:15 +02:00
|
|
|
proxy = IrcObjectProxyRegexp(irc)
|
2003-10-20 09:31:17 +02:00
|
|
|
self.callCommand(method, proxy, msg, m, catchErrors=True)
|
2003-10-09 06:29:37 +02:00
|
|
|
s = addressed(irc.nick, msg)
|
|
|
|
if s:
|
|
|
|
for (r, method) in self.addressedRes:
|
2003-10-21 23:01:43 +02:00
|
|
|
originalHandled = self.handled
|
|
|
|
name = method.__name__
|
2003-10-09 06:29:37 +02:00
|
|
|
for m in r.finditer(s):
|
2003-10-21 23:01:43 +02:00
|
|
|
if originalHandled and name not in self.alwaysCall:
|
|
|
|
continue
|
2003-10-09 06:29:37 +02:00
|
|
|
if not fed:
|
|
|
|
fed = True
|
|
|
|
self.rateLimiter.put(msg)
|
|
|
|
msg = self.rateLimiter.get()
|
|
|
|
if msg:
|
|
|
|
proxy = IrcObjectProxyRegexp(irc)
|
2003-10-20 09:31:17 +02:00
|
|
|
self.callCommand(method,proxy,msg,m,catchErrors=True)
|
2003-10-03 00:37:36 +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:
|