Changed callCommand to give a name rather than a method; added invalidCommand throttling, ctcp throttling, and whole bunch of other crap.

This commit is contained in:
Jeremy Fincher 2004-09-08 23:34:48 +00:00
parent 3848ca4aa5
commit ee70224aa3
17 changed files with 500 additions and 289 deletions

View File

@ -89,9 +89,9 @@ class Amazon(callbacks.PrivmsgCommandAndRegexp):
threaded = True
regexps = ['amzSnarfer']
def callCommand(self, method, irc, msg, *L, **kwargs):
def callCommand(self, name, irc, msg, *L, **kwargs):
try:
super(Amazon, self).callCommand(method, irc, msg, *L, **kwargs)
super(Amazon, self).callCommand(name, irc, msg, *L, **kwargs)
except amazon.NoLicenseKey, e:
irc.error('You must have a free Amazon web services license key '
'in order to use this command. You can get one at '

View File

@ -65,6 +65,11 @@ conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireRegistration',
conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireCapability',
registry.String('', """Determines what capability (if any) the bot should
require people trying to use this plugin to have."""))
conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'allowPrivateTarget',
registry.Boolean(False, """Determines whether the bot will require targets
of the "say" command to be public (i.e., channels). If this is True, the
bot will allow people to use the "say" command to send private messages to
other users."""))
class Anonymous(callbacks.Privmsg):
@ -89,7 +94,7 @@ class Anonymous(callbacks.Privmsg):
c = ircdb.channels.getChannel(channel)
if c.lobotomized:
irc.error('I\'m lobotomized in %s.' % channel, Raise=True)
if not c.checkCapability(self.__class__.__name__):
if not c.checkCapability(self.name()):
irc.error('That channel has set its capabilities so as to '
'disallow the use of this plugin.', Raise=True)

View File

@ -46,44 +46,85 @@ sys.path.append(os.pardir)
import supybot.conf as conf
import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.registry as registry
import supybot.callbacks as callbacks
notice = ircmsgs.notice
conf.registerPlugin('Ctcp')
conf.registerGlobalValue(conf.supybot.abuse.flood, 'ctcp',
registry.Boolean(True, """Determines whether the bot will defend itself
against CTCP flooding."""))
conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'maximum',
registry.PositiveInteger(5, """Determines how many CTCP messages (not
including actions) the bot will reply to from a given user in a minute.
If a user sends more than this many CTCP messages in a 60 second period,
the bot will ignore CTCP messages from this user for
supybot.abuse.flood.ctcp.punishment seconds."""))
conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'punishment',
registry.PositiveInteger(300, """Determines how many seconds the bot will
ignore CTCP messages from users who flood it with CTCP messages."""))
class Ctcp(callbacks.PrivmsgRegexp):
public = False
def __init__(self):
self.__parent = super(Ctcp, self)
self.__parent.__init__()
self.ignores = ircutils.IrcDict()
self.floods = ircutils.FloodQueue(60)
def callCommand(self, name, irc, msg, *L, **kwargs):
if conf.supybot.abuse.flood.ctcp():
now = time.time()
for (ignore, expiration) in self.ignores.items():
if expiration < now:
del self.ignores[ignore]
elif ircutils.hostmaskPatternEqual(ignore, msg.prefix):
return
self.floods.enqueue(msg)
max = conf.supybot.abuse.flood.ctcp.maximum()
if self.floods.len(msg) > max:
expires = conf.supybot.abuse.flood.ctcp.punishment()
self.log.warning('Apparent CTCP flood from %s, '
'ignoring CTCP messages for %s seconds.',
msg.prefix, expires)
ignoreMask = '*!%s@%s' % (msg.user, msg.host)
self.ignores[ignoreMask] = now + expires
return
self.__parent.callCommand(name, irc, msg, *L, **kwargs)
def _reply(self, irc, msg, s):
s = '\x01%s\x01' % s
irc.reply(s, notice=True, private=True, to=msg.nick)
def ping(self, irc, msg, match):
"\x01PING (.*)\x01"
self.log.info('Received CTCP PING from %s', msg.prefix)
irc.queueMsg(notice(msg.nick, '\x01PING %s\x01' % match.group(1)))
self._reply(irc, msg, 'PING %s' % match.group(1))
def version(self, irc, msg, match):
"\x01VERSION\x01"
self.log.info('Received CTCP VERSION from %s', msg.prefix)
s = '\x01VERSION Supybot %s\x01' % conf.version
irc.queueMsg(notice(msg.nick, s))
self._reply(irc, msg, 'VERSION Supybot %s' % conf.version)
def userinfo(self, irc, msg, match):
"\x01USERINFO\x01"
self.log.info('Received CTCP USERINFO from %s', msg.prefix)
irc.queueMsg(notice(msg.nick, '\x01USERINFO\x01'))
self._reply(irc, msg, 'USERINFO')
def time(self, irc, msg, match):
"\x01TIME\x01"
self.log.info('Received CTCP TIME from %s' % msg.prefix)
irc.queueMsg(notice(msg.nick, '\x01%s\x01' % time.ctime()))
self._reply(irc, msg, time.ctime())
def finger(self, irc, msg, match):
"\x01FINGER\x01"
self.log.info('Received CTCP FINGER from %s' % msg.prefix)
s = '\x01Supybot, the best Python bot in existence!\x01'
irc.queueMsg(notice(msg.nick, s))
irc._reply(irc, msg, 'Supybot, the best Python IRC bot in existence!')
def source(self, irc, msg, match):
"\x01SOURCE\x01"
self.log.info('Received CTCP SOURCE from %s' % msg.prefix)
s = 'http://www.sourceforge.net/projects/supybot/'
irc.queueMsg(notice(msg.nick, s))
self._reply(irc, msg, 'http://www.sourceforge.net/projects/supybot/')
Class = Ctcp
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -84,9 +84,9 @@ class Weather(callbacks.Privmsg):
the name of 'weather' which should override this help."""
weatherCommands = ['ham', 'cnn', 'wunder']
threaded = True
def callCommand(self, method, irc, msg, *L, **kwargs):
def callCommand(self, name, irc, msg, *L, **kwargs):
try:
super(Weather, self).callCommand(method, irc, msg, *L, **kwargs)
super(Weather, self).callCommand(name, irc, msg, *L, **kwargs)
except webutils.WebError, e:
irc.error(str(e))

View File

@ -133,10 +133,10 @@ class HangmanGame:
class Words(callbacks.Privmsg):
def callCommand(self, command, irc, msg, args, *L, **kw):
def callCommand(self, name, irc, msg, args, *L, **kw):
# We'll catch the IOError here.
try:
super(Words, self).callCommand(command, irc, msg, args, *L, **kw)
super(Words, self).callCommand(name, irc, msg, args, *L, **kw)
except EnvironmentError, e:
irc.error('I couldn\'t open the words file. This plugin expects '
'a words file (i.e., a file with one word per line, in '

View File

@ -60,7 +60,8 @@ import supybot.callbacks as callbacks
class Admin(privmsgs.CapabilityCheckingPrivmsg):
capability = 'admin'
def __init__(self):
privmsgs.CapabilityCheckingPrivmsg.__init__(self)
self.__parent = super(Admin, self)
self.__parent.__init__()
self.joins = {}
self.pendingNickChanges = {}
@ -157,7 +158,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
else:
channels.append(channel)
if not ircutils.isChannel(channel):
irc.error('%r is not a valid channel.' % channel)
irc.errorInvalid('channel', channel)
return
conf.supybot.channels().add(original)
maxchannels = irc.state.supported.get('maxchannels', sys.maxint)
@ -237,7 +238,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
irc.queueMsg(ircmsgs.nick(nick))
self.pendingNickChanges[irc.getRealIrc()] = irc
else:
irc.error('That\'s not a valid nick.')
irc.errorInvalid('nick', nick)
else:
irc.reply(irc.nick)
@ -351,21 +352,33 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
irc.error(s)
def ignore(self, irc, msg, args):
"""<hostmask|nick>
"""<hostmask|nick> [<expires>]
Ignores <hostmask> or, if a nick is given, ignores whatever hostmask
that nick is currently using.
that nick is currently using. <expires> is a "seconds from now" value
that determines when the ignore will expire; if, for instance, you wish
for the ignore to expire in an hour, you could give an <expires> of
3600. If no <expires> is given, the ignore will never automatically
expire.
"""
arg = privmsgs.getArgs(args)
if ircutils.isUserHostmask(arg):
hostmask = arg
(nickOrHostmask, expires) = privmsgs.getArgs(args, optional=1)
if ircutils.isUserHostmask(nickOrHostmask):
hostmask = nickOrHostmask
else:
try:
hostmask = irc.state.nickToHostmask(arg)
hostmask = irc.state.nickToHostmask(nickOrHostmask)
except KeyError:
irc.error('I can\'t find a hostmask for %s' % arg)
irc.error('I can\'t find a hostmask for %s.' % nickOrHostmask)
return
ircdb.ignores.addHostmask(hostmask)
if expires:
try:
expires = int(float(expires))
expires += int(time.time())
except ValueError:
irc.errorInvalid('number of seconds', expires, Raise=True)
else:
expires = 0
ircdb.ignores.add(hostmask, expires)
irc.replySuccess()
def unignore(self, irc, msg, args):
@ -384,7 +397,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
irc.error('I can\'t find a hostmask for %s' % arg)
return
try:
ircdb.ignores.removeHostmask(hostmask)
ircdb.ignores.remove(hostmask)
irc.replySuccess()
except KeyError:
irc.error('%s wasn\'t in the ignores database.' % hostmask)
@ -394,6 +407,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
Returns the hostmasks currently being globally ignored.
"""
# XXX Add the expirations.
if ircdb.ignores.hostmasks:
irc.reply(utils.commaAndify(imap(repr, ircdb.ignores.hostmasks)))
else:

View File

@ -441,23 +441,34 @@ class Channel(callbacks.Privmsg):
unlobotomize = privmsgs.checkChannelCapability(unlobotomize, 'op')
def permban(self, irc, msg, args, channel):
"""[<channel>] <nick|hostmask>
"""[<channel>] <nick|hostmask> [<expires>]
If you have the #channel,op capability, this will effect a permanent
(persistent) ban from interacting with the bot on the given <hostmask>
(or the current hostmask associated with <nick>. <channel> is only
necessary if the message isn't sent in the channel itself.
(or the current hostmask associated with <nick>. Other plugins may
enforce this ban by actually banning users with matching hostmasks when
they join. <expires> is an optional argument specifying when (in
"seconds from now") the ban should expire; if none is given, the ban
will never automatically expire. <channel> is only necessary if the
message isn't sent in the channel itself.
"""
arg = privmsgs.getArgs(args)
if ircutils.isNick(arg):
banmask = ircutils.banmask(irc.state.nickToHostmask(arg))
elif ircutils.isUserHostmask(arg):
banmask = arg
(nickOrHostmask, expires) = privmsgs.getArgs(args, optional=1)
if ircutils.isNick(nickOrHostmask):
banmask = ircutils.banmask(irc.state.nickToHostmask(nickOrHostmask))
elif ircutils.isUserHostmask(nickOrHostmask):
banmask = nickOrHostmask
else:
irc.error('That\'s not a valid nick or hostmask.')
return
irc.errorInvalid('nick or hostmask', nickOrHostmask, Raise=True)
if expires:
try:
expires = int(float(expires))
expires += int(time.time())
except ValueError:
irc.errorInvalid('number of seconds',nickOrHostmask,Raise=True)
else:
expires = 0
c = ircdb.channels.getChannel(channel)
c.addBan(banmask)
c.addBan(banmask, expires)
ircdb.channels.setChannel(channel, c)
irc.replySuccess()
permban = privmsgs.checkChannelCapability(permban, 'op')
@ -483,6 +494,7 @@ class Channel(callbacks.Privmsg):
If you have the #channel,op capability, this will show you the
current bans on #channel.
"""
# XXX Add the expirations.
c = ircdb.channels.getChannel(channel)
if c.bans:
irc.reply(utils.commaAndify(map(utils.dqrepr, c.bans)))
@ -491,23 +503,32 @@ class Channel(callbacks.Privmsg):
permbans = privmsgs.checkChannelCapability(permbans, 'op')
def ignore(self, irc, msg, args, channel):
"""[<channel>] <nick|hostmask>
"""[<channel>] <nick|hostmask> [<expires>]
If you have the #channel,op capability, this will set a permanent
(persistent) ignore on <hostmask> or the hostmask currently associated
with <nick>. <channel> is only necessary if the message isn't sent in
the channel itself.
with <nick>. <expires> is an optional argument specifying when (in
"seconds from now") the ignore will expire; if it isn't given, the
ignore will never automatically expire. <channel> is only necessary
if the message isn't sent in the channel itself.
"""
arg = privmsgs.getArgs(args)
if ircutils.isNick(arg):
banmask = ircutils.banmask(irc.state.nickToHostmask(arg))
elif ircutils.isUserHostmask(arg):
banmask = arg
(nickOrHostmask, expires) = privmsgs.getArgs(args, optional=1)
if ircutils.isNick(nickOrHostmask):
banmask = ircutils.banmask(irc.state.nickToHostmask(nickOrHostmask))
elif ircutils.isUserHostmask(nickOrHostmask):
banmask = nickOrHostmask
else:
irc.error('That\'s not a valid nick or hostmask.')
return
irc.errorInvalid('nick or hostmask', nickOrHostmask, Raise=True)
if expires:
try:
expires = int(float(expires))
expires += int(time.time())
except ValueError:
irc.errorInvalid('number of seconds',nickOrHostmask,Raise=True)
else:
expires = 0
c = ircdb.channels.getChannel(channel)
c.addIgnore(banmask)
c.addIgnore(banmask, expires)
ircdb.channels.setChannel(channel, c)
irc.replySuccess()
ignore = privmsgs.checkChannelCapability(ignore, 'op')
@ -533,6 +554,7 @@ class Channel(callbacks.Privmsg):
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
# XXX Add the expirations.
channelarg = privmsgs.getArgs(args, required=0, optional=1)
channel = channelarg or channel
c = ircdb.channels.getChannel(channel)

View File

@ -101,7 +101,7 @@ class Config(callbacks.Privmsg):
try:
super(Config, self).callCommand(name, irc, msg, *L, **kwargs)
except InvalidRegistryName, e:
irc.error('%r is not a valid configuration variable.' % e.args[0])
irc.errorInvalid('configuration variable', e.args[0])
except registry.InvalidRegistryValue, e:
irc.error(str(e))

View File

@ -66,19 +66,50 @@ conf.registerGlobalValue(conf.supybot.plugins.Misc, 'listPrivatePlugins',
class Misc(callbacks.Privmsg):
priority = sys.maxint
def __init__(self):
super(Misc, self).__init__()
timeout = conf.supybot.abuse.flood.command.invalid
self.invalidCommands = ircutils.FloodQueue(timeout)
def invalidCommand(self, irc, msg, tokens):
self.log.debug('Misc.invalidCommand called (tokens %s)', tokens)
if conf.supybot.reply.whenNotCommand():
# First, we check for invalidCommand floods. This is rightfully done
# here since this will be the last invalidCommand called, and thus it
# will only be called if this is *truly* an invalid command.
maximum = conf.supybot.abuse.flood.command.invalid.maximum()
self.invalidCommands.enqueue(msg)
if self.invalidCommands.len(msg) > maximum and \
not ircdb.checkCapability(msg.prefix, 'owner'):
punishment = conf.supybot.abuse.flood.command.invalid.punishment()
banmask = '*!%s@%s' % (msg.user, msg.host)
self.log.info('Ignoring %s for %s seconds due to an apparent '
'invalid command flood.', banmask, punishment)
if tokens and tokens[0] == 'Error:':
self.log.warning('Apparent error loop with another Supybot '
'observed at %s. Consider ignoring this bot '
'permanently.', log.timestamp())
ircdb.ignores.add(banmask, time.time() + punishment)
irc.reply('You\'ve given me %s invalid commands within the last '
'minute; I\'m now ignoring you for %s.' %
(maximum, utils.timeElapsed(punishment)))
return
# Now, for normal handling.
channel = msg.args[0]
if conf.get(conf.supybot.reply.whenNotCommand, channel):
command = tokens and tokens[0] or ''
irc.error('%r is not a valid command.' % command)
irc.errorInvalid('command', command)
else:
if tokens:
# echo [] will get us an empty token set, but there's no need
# to log this in that case anyway, it being a nested command.
self.log.info('Not replying to %s, not a command.' % tokens[0])
if not isinstance(irc.irc, irclib.Irc):
brackets = conf.supybot.reply.brackets.get(msg.args[0])()
irc.reply(''.join([brackets[0],' '.join(tokens), brackets[1]]))
brackets = conf.get(conf.supybot.reply.brackets, channel)
if brackets:
(left, right) = brackets
irc.reply(left + ' '.join(tokens) + right)
else:
pass # Let's just do nothing, I can't think of better.
def list(self, irc, msg, args):
"""[--private] [<module name>]
@ -505,8 +536,7 @@ class Misc(callbacks.Privmsg):
try:
i = int(s)
except ValueError:
irc.error('Invalid argument: %s' % arg)
return
irc.errorInvalid('argument', arg, Raise=True)
if kind == 'y':
seconds += i*31536000
elif kind == 'w':
@ -522,10 +552,10 @@ class Misc(callbacks.Privmsg):
irc.reply(str(seconds))
def tell(self, irc, msg, args):
"""<nick|channel> <text>
"""<nick> <text>
Tells the <nick|channel> whatever <text> is. Use nested commands to
your benefit here.
Tells the <nick> whatever <text> is. Use nested commands to your
benefit here.
"""
(target, text) = privmsgs.getArgs(args, required=2)
if target.lower() == 'me':
@ -534,11 +564,9 @@ class Misc(callbacks.Privmsg):
irc.error('Dude, just give the command. No need for the tell.')
return
elif not ircutils.isNick(target):
irc.error('%s is not a valid nick.' % target)
return
irc.errorInvalid('nick', target, Raise=True)
elif ircutils.nickEqual(target, irc.nick):
irc.error('You just told me, why should I tell myself?')
return
irc.error('You just told me, why should I tell myself?',Raise=True)
elif target not in irc.state.nicksToHostmasks and \
not ircdb.checkCapability(msg.prefix, 'owner'):
# We'll let owners do this.

View File

@ -63,6 +63,7 @@ import supybot.ircutils as ircutils
import supybot.privmsgs as privmsgs
import supybot.registry as registry
import supybot.callbacks as callbacks
import supybot.structures as structures
class Deprecated(ImportError):
pass
@ -131,7 +132,8 @@ conf.supybot.plugins.Owner.register('public', registry.Boolean(True,
# supybot.commands.
###
conf.registerGroup(conf.supybot.commands, 'defaultPlugins')
conf.registerGroup(conf.supybot.commands, 'defaultPlugins',
orderAlphabetically=True)
conf.supybot.commands.defaultPlugins.help = utils.normalizeWhitespace("""
Determines what commands have default plugins set, and which plugins are set to
be the default for each of those commands.""".strip())
@ -185,10 +187,13 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
capability = 'owner'
_srcPlugins = ircutils.IrcSet(('Admin', 'Channel', 'Config',
'Misc', 'Owner', 'User'))
def __init__(self):
callbacks.Privmsg.__init__(self)
def __init__(self, *args, **kwargs):
self.__parent = super(Owner, self)
self.__parent.__init__()
# Setup log object/command.
self.log = LogProxy(self.log)
# Setup command flood detection.
self.commands = ircutils.FloodQueue(60)
# Setup exec command.
setattr(self.__class__, 'exec', self.__class__._exec)
# Setup Irc objects, connected to networks. If world.ircs is already
@ -236,14 +241,14 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
return None
return msg
def isCommand(self, methodName):
return methodName == 'log' or \
privmsgs.CapabilityCheckingPrivmsg.isCommand(self, methodName)
def isCommand(self, name):
return name == 'log' or \
self.__parent.isCommand(name)
def reset(self):
# This has to be done somewhere, I figure here is as good place as any.
callbacks.Privmsg._mores.clear()
privmsgs.CapabilityCheckingPrivmsg.reset(self)
self.__parent.reset()
def do001(self, irc, msg):
self.log.info('Loading plugins.')
@ -416,8 +421,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
except Exception, e:
irc.reply(utils.exnToString(e))
else:
# This should never happen, so I haven't bothered updating
# this error string to say --allow-eval.
# There's a potential that allowEval got changed after we were
# loaded. Let's be extra-special-safe.
irc.error('You must run Supybot with the --allow-eval '
'option for this command to be enabled.')
@ -434,7 +439,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
except Exception, e:
irc.reply(utils.exnToString(e))
else:
# This should never happen.
# There's a potential that allowEval got changed after we were
# loaded. Let's be extra-special-safe.
irc.error('You must run Supybot with the --allow-eval '
'option for this command to be enabled.')
else:
@ -481,13 +487,11 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
s = 'I don\'t have a default plugin set for that command.'
irc.error(s)
elif not cbs:
irc.error('That\'s not a valid command.')
return
irc.errorInvalid('command', command, Raise=True)
elif plugin:
cb = irc.getCallback(plugin)
if cb is None:
irc.error('That\'s not a valid plugin.')
return
irc.errorInvalid('plugin', plugin, Raise=True)
registerDefaultPlugin(command, plugin)
irc.replySuccess()
else:
@ -568,7 +572,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
utils.nItems('line', len(linecache.cache)))
linecache.clearcache()
sys.exc_clear()
collected = world.upkeep(scheduleNext=False)
collected = world.upkeep()
if gc.garbage:
L.append('Garbage! %r.' % gc.garbage)
L.append('%s collected.' % utils.nItems('object', collected))
@ -686,7 +690,7 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
irc.error('I couldn\'t reconnect. You should restart me instead.')
def defaultcapability(self, irc, msg, args):
"""<add|remove> <capability>
"""{add|remove} <capability>
Adds or removes (according to the first argument) <capability> from the
default capabilities given to users (the configuration variable
@ -709,8 +713,8 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
conf.supybot.capabilities().add(anticap)
irc.replySuccess()
else:
irc.error('That\'s not a valid action to take. Valid actions '
'are "add" and "remove"')
irc.errorInvalid('action to take', action,
'Valid actions include "add" and "remove".')
def disable(self, irc, msg, args):
"""[<plugin>] <command>
@ -751,7 +755,6 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
self._disabled.remove(command, plugin)
irc.replySuccess()
except KeyError:
raise
irc.error('That command wasn\'t disabled.')
def rename(self, irc, msg, args):
@ -762,17 +765,14 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg):
(plugin, command, newName) = privmsgs.getArgs(args, required=3)
name = callbacks.canonicalName(newName)
if name != newName:
irc.error('%s is a not a valid new command name. '
'Try making it lowercase and removing - and _.' %newName)
return
irc.errorInvalid('command name', name,
'Try making it lowercase and removing dashes '
'and underscores.', Raise=True)
cb = irc.getCallback(plugin)
if cb is None:
irc.error('%s is not a valid plugin.' % plugin)
return
irc.errorInvalid('plugin', plugin, Raise=True)
if not cb.isCommand(command):
s = '%s is not a valid command in the %s plugin.' % (name, plugin)
irc.error(s)
return
irc.errorInvalid('command in the %s plugin'%plugin,name,Raise=True)
if hasattr(cb, name):
irc.error('The %s plugin already has an attribute named %s.' %
(plugin, name))

View File

@ -116,13 +116,12 @@ class User(callbacks.Privmsg):
self._checkNotChannel(irc, msg, password)
try:
ircdb.users.getUserId(name)
irc.error('That name is already assigned to someone.')
return
irc.error('That name is already assigned to someone.', Raise=True)
except KeyError:
pass
if ircutils.isUserHostmask(name):
irc.error('Hostmasks aren\'t valid usernames.')
return
irc.errorInvalid('username', name,
'Hostmasks are not valid usernames.', Raise=True)
try:
u = ircdb.users.getUser(msg.prefix)
if u.checkCapability('owner'):
@ -208,7 +207,7 @@ class User(callbacks.Privmsg):
if not name:
name = msg.prefix
if not ircutils.isUserHostmask(hostmask):
irc.error('That\'s not a valid hostmask. Make sure your hostmask '
irc.errorInvalid('hostmask', hostmask, 'Make sure your hostmask '
'includes a nick, then an exclamation point (!), then '
'a user, then an at symbol (@), then a host. Feel '
'free to use wildcards (* and ?, which work just like '
@ -404,7 +403,10 @@ class User(callbacks.Privmsg):
def unidentify(self, irc, msg, args):
"""takes no arguments
Un-identifies the user.
Un-identifies you. Note that this may not result in the desired
effect of causing the bot not to recognize you anymore, since you may
have added hostmasks to your user that can cause the bot to continue to
recognize you.
"""
try:
id = ircdb.users.getUserId(msg.prefix)
@ -450,11 +452,12 @@ class User(callbacks.Privmsg):
irc.errorNotRegistered()
if value == '':
value = not user.secure
elif value.lower() in ('true', 'false'):
value = eval(value.capitalize())
elif value.lower() in ('true', 'on', 'enable'):
value = True
elif value.lower() in ('false', 'off', 'disable'):
value = False
else:
irc.error('%s is not a valid boolean value.' % value)
return
irc.errorInvalid('boolean value', value, Raise=True)
if user.checkPassword(password) and \
user.checkHostmask(msg.prefix, useAuth=False):
user.secure = value
@ -518,8 +521,7 @@ class User(callbacks.Privmsg):
## wrapper = Config.getWrapper(name)
## wrapper = wrapper.get(str(id))
## except InvalidRegistryValue, e:
## irc.error('%r is not a valid configuration variable.' % name)
## return
## irc.errorInvalid('configuration variable', name, Raise=True)
## if list:
## pass
## else:

View File

@ -29,6 +29,8 @@
# POSSIBILITY OF SUCH DAMAGE.
###
__revision__ = "$Id$"
import sys
import os.path
@ -40,13 +42,27 @@ othersDir = os.path.join(installDir, 'others')
sys.path.insert(0, srcDir)
sys.path.insert(0, othersDir)
class authors: # This is basically a bag.
jemfinch = 'Jeremy Fincher (jemfinch) <jemfinch@users.sf.net>'
jamessan = 'James Vega (jamessan) <jamessan@users.sf.net>'
strike = 'Daniel DiPaolo (Strike) <ddipaolo@users.sf.net>'
baggins = 'William Robinson (baggins) <airbaggins@users.sf.net>'
skorobeus = 'Kevin Murphy (Skorobeus) <skoro@skoroworld.com>'
inkedmn = 'Brett Kelly (inkedmn) <inkedmn@users.sf.net>'
class Author(object):
def __init__(self, name=None, nick=None, email=None, **kwargs):
self.__dict__.update(kwargs)
self.name = name
self.nick = nick
self.email = email
def __str__(self):
return '%s (%s) <%s>' % (self.name, self.nick, self.email)
class authors(object): # This is basically a bag.
jemfinch = Author('Jeremy Fincher', 'jemfinch', 'jemfinch@users.sf.net')
jamessan = Author('James Vega', 'jamessan', 'jamessan@users.sf.net')
strike = Author('Daniel DiPaolo', 'Strike', 'ddipaolo@users.sf.net')
baggins = Author('William Robinson', 'baggins', 'airbaggins@users.sf.net')
skorobeus = Author('Kevin Murphy', 'Skorobeus', 'skoro@skoroworld.com')
inkedmn = Author('Brett Kelly', 'inkedmn', 'inkedmn@users.sf.net')
bwp = Author('Brett Phipps', 'bwp', 'phippsb@gmail.com')
# Let's be somewhat safe about this.
def __getattr__(self, attr):
return Author('Unknown author', 'unknown', 'unknown@supybot.org')
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:

View File

@ -65,7 +65,7 @@ import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.registry as registry
def addressed(nick, msg, prefixChars=None,
def addressed(nick, msg, prefixChars=None, nicks=None,
prefixStrings=None, whenAddressedByNick=None):
"""If msg is addressed to 'name', returns the portion after the address.
Otherwise returns the empty string.
@ -88,7 +88,12 @@ def addressed(nick, msg, prefixChars=None,
whenAddressedByNick = get(conf.supybot.reply.whenAddressedBy.nick)
if prefixStrings is None:
prefixStrings = get(conf.supybot.reply.whenAddressedBy.strings)
nick = ircutils.toLower(nick)
if nicks is None:
nicks = get(conf.supybot.reply.whenAddressedBy.nicks)
nicks = map(ircutils.toLower, nicks)
else:
nicks = list(nicks) # Just in case.
nicks.insert(0, ircutils.toLower(nick))
# Ok, let's see if it's a private message.
if ircutils.nickEqual(target, nick):
payload = stripPrefixStrings(payload)
@ -96,21 +101,22 @@ def addressed(nick, msg, prefixChars=None,
payload = payload[1:].lstrip()
return payload
# Ok, not private. Does it start with our nick?
elif whenAddressedByNick and \
ircutils.toLower(payload).startswith(nick):
elif whenAddressedByNick:
for nick in nicks:
if ircutils.toLower(payload).startswith(nick):
try:
(maybeNick, rest) = payload.split(None, 1)
while not ircutils.isNick(maybeNick, strictRfc=True):
if maybeNick[-1].isalnum():
return ''
continue
maybeNick = maybeNick[:-1]
if ircutils.nickEqual(maybeNick, nick):
return rest
else:
return ''
continue
except ValueError: # split didn't work.
return ''
elif payload and any(payload.startswith, prefixStrings):
continue
if payload and any(payload.startswith, prefixStrings):
return stripPrefixStrings(payload)
elif payload and payload[0] in prefixChars:
return payload[1:].strip()
@ -213,10 +219,6 @@ class ArgumentError(Error):
"""The bot replies with a help message when this is raised."""
pass
class CannotNest(Error):
"""Exception to be raised by commands that cannot be nested."""
pass
class Tokenizer:
# This will be used as a global environment to evaluate strings in.
# Evaluation is, of course, necessary in order to allowed escaped
@ -310,10 +312,10 @@ def tokenize(s, brackets=None, channel=None):
start = time.time()
try:
if brackets is None:
tokens = conf.channelValue(conf.supybot.reply.brackets, channel)
tokens = conf.get(conf.supybot.reply.brackets, channel)
else:
tokens = brackets
if conf.channelValue(conf.supybot.reply.pipeSyntax, channel):
if conf.get(conf.supybot.reply.pipeSyntax, channel):
tokens = '%s|' % tokens
log.stat('tokenize took %s seconds.' % (time.time() - start))
return Tokenizer(tokens).tokenize(s)
@ -329,14 +331,15 @@ def getCommands(tokens):
L.extend(getCommands(elt))
return L
def findCallbackForCommand(irc, commandName):
def findCallbackForCommand(irc, name):
"""Given a command name and an Irc object, returns a list of callbacks that
commandName is in."""
L = []
name = canonicalName(name)
for callback in irc.callbacks:
if not isinstance(callback, PrivmsgRegexp):
if hasattr(callback, 'isCommand'):
if callback.isCommand(commandName):
if callback.isCommand(name):
L.append(callback)
return L
@ -344,16 +347,17 @@ def formatArgumentError(method, name=None, channel=None):
if name is None:
name = method.__name__
if hasattr(method, '__doc__') and method.__doc__:
if conf.channelValue(conf.supybot.reply.showSimpleSyntax, channel):
if conf.get(conf.supybot.reply.showSimpleSyntax, channel):
return getSyntax(method, name=name)
else:
return getHelp(method, name=name)
else:
return 'Invalid arguments for %s.' % method.__name__
def checkCommandCapability(msg, cb, command):
def checkCommandCapability(msg, cb, commandName):
assert isinstance(commandName, basestring), commandName
plugin = cb.name().lower()
pluginCommand = '%s.%s' % (plugin, command)
pluginCommand = '%s.%s' % (plugin, commandName)
def checkCapability(capability):
assert ircdb.isAntiCapability(capability)
if ircdb.checkCapability(msg.prefix, capability):
@ -362,12 +366,12 @@ def checkCommandCapability(msg, cb, command):
raise RuntimeError, capability
try:
antiPlugin = ircdb.makeAntiCapability(plugin)
antiCommand = ircdb.makeAntiCapability(command)
antiCommand = ircdb.makeAntiCapability(commandName)
antiPluginCommand = ircdb.makeAntiCapability(pluginCommand)
checkCapability(antiPlugin)
checkCapability(antiCommand)
checkCapability(antiPluginCommand)
checkAtEnd = [command, pluginCommand]
checkAtEnd = [commandName, pluginCommand]
default = conf.supybot.capabilities.default()
if ircutils.isChannel(msg.args[0]):
channel = msg.args[0]
@ -376,7 +380,7 @@ def checkCommandCapability(msg, cb, command):
checkCapability(ircdb.makeChannelCapability(channel,
antiPluginCommand))
chanPlugin = ircdb.makeChannelCapability(channel, plugin)
chanCommand = ircdb.makeChannelCapability(channel, command)
chanCommand = ircdb.makeChannelCapability(channel, commandName)
chanPluginCommand = ircdb.makeChannelCapability(channel,
pluginCommand)
checkAtEnd += [chanCommand, chanPlugin, chanPluginCommand]
@ -437,49 +441,56 @@ class RichReplyMethods(object):
else:
self.reply(prefixer(s), **kwargs)
def _error(self, s, Raise, **kwargs):
def _error(self, s, Raise=False, **kwargs):
if Raise:
raise Error, s
else:
self.error(s, **kwargs)
def errorNoCapability(self, capability, s='', Raise=False, **kwargs):
def errorNoCapability(self, capability, s='', **kwargs):
if isinstance(capability, basestring): # checkCommandCapability!
log.warning('Denying %s for lacking %r capability.',
self.msg.prefix, capability)
if not self._getConfig(conf.supybot.reply.noCapabilityError):
v = self._getConfig(conf.supybot.replies.noCapability)
s = self.__makeReply(v % capability, s)
self._error(s, Raise, **kwargs)
self._error(s, **kwargs)
else:
log.warning('Denying %s for some unspecified capability '
'(or a default).', self.msg.prefix)
v = self._getConfig(conf.supybot.replies.genericNoCapability)
self._error(self.__makeReply(v, s), Raise, **kwargs)
self._error(self.__makeReply(v, s), **kwargs)
def errorPossibleBug(self, s='', Raise=False, **kwargs):
def errorPossibleBug(self, s='', **kwargs):
v = self._getConfig(conf.supybot.replies.possibleBug)
if s:
s += ' (%s)' % v
else:
s = v
self._error(s, Raise, **kwargs)
self._error(s, **kwargs)
def errorNotRegistered(self, s='', Raise=False, **kwargs):
def errorNotRegistered(self, s='', **kwargs):
v = self._getConfig(conf.supybot.replies.notRegistered)
self._error(self.__makeReply(v, s), Raise, **kwargs)
self._error(self.__makeReply(v, s), **kwargs)
def errorNoUser(self, s='', name='that user', Raise=False, **kwargs):
def errorNoUser(self, s='', name='that user', **kwargs):
v = self._getConfig(conf.supybot.replies.noUser)
try:
v = v % name
except TypeError:
log.warning('supybot.replies.noUser should have one "%s" in it.')
self._error(self.__makeReply(v, s), Raise, **kwargs)
self._error(self.__makeReply(v, s), **kwargs)
def errorRequiresPrivacy(self, s='', Raise=False, **kwargs):
def errorRequiresPrivacy(self, s='', **kwargs):
v = self._getConfig(conf.supybot.replies.requiresPrivacy)
self._error(self.__makeReply(v, s), Raise, **kwargs)
self._error(self.__makeReply(v, s), **kwargs)
def errorInvalid(self, what, given=None, s='', **kwargs):
if given is not None:
v = '%r is not a valid %s.' % (given, what)
else:
v = 'That\'s not a valid %s.' % what
self._error(self.__makeReply(v, s), **kwargs)
class IrcObjectProxy(RichReplyMethods):
@ -542,21 +553,14 @@ class IrcObjectProxy(RichReplyMethods):
log.exception('Uncaught exception in %s.invalidCommand',
cb.name())
def _callCommand(self, name, command, cb):
def _callCommand(self, name, cb):
try:
self.commandMethod = command
self.commandMethod = cb.getCommand(name)
try:
cb.callCommand(command, self, self.msg, self.args)
except (getopt.GetoptError, ArgumentError):
self.reply(formatArgumentError(command, name=name))
except CannotNest, e:
if not isinstance(self.irc, irclib.Irc):
self.error('Command %r cannot be nested.' % name)
except (SyntaxError, Error), e:
cb.log.info('Error return: %s', e)
self.error(str(e))
cb.callCommand(name, self, self.msg, self.args)
except Exception, e:
cb.log.exception('Uncaught exception:')
cb.log.exception('Uncaught exception in %s.%s:',
cb.name(), name)
if conf.supybot.reply.detailedErrors():
self.error(utils.exnToString(e))
else:
@ -567,27 +571,27 @@ class IrcObjectProxy(RichReplyMethods):
def finalEval(self):
assert not self.finalEvaled, 'finalEval called twice.'
self.finalEvaled = True
name = canonicalName(self.args[0])
name = self.args[0]
cbs = findCallbackForCommand(self, name)
if len(cbs) == 0:
for cb in self.irc.callbacks:
if isinstance(cb, PrivmsgRegexp):
for (r, m) in cb.res:
for (r, name) in cb.res:
if r.search(self.msg.args[1]):
log.debug('Skipping invalidCommand: %s.%s',
m.im_class.__name__,m.im_func.func_name)
cb.name(), name)
return
elif isinstance(cb, PrivmsgCommandAndRegexp):
for (r, m) in cb.res:
for (r, name) in cb.res:
if r.search(self.msg.args[1]):
log.debug('Skipping invalidCommand: %s.%s',
m.im_class.__name__,m.im_func.func_name)
cb.name(), name)
return
payload = addressed(self.irc.nick, self.msg)
for (r, m) in cb.addressedRes:
for (r, name) in cb.addressedRes:
if r.search(payload):
log.debug('Skipping invalidCommand: %s.%s',
m.im_class.__name__,m.im_func.func_name)
cb.name(), name)
return
# Ok, no regexp-based things matched.
self._callInvalidCommands()
@ -604,18 +608,13 @@ class IrcObjectProxy(RichReplyMethods):
else:
del self.args[0]
cb = cbs[0]
cap = checkCommandCapability(self.msg, cb, name)
if cap:
self.errorNoCapability(cap)
return
command = getattr(cb, name)
Privmsg.handled = True
if cb.threaded or conf.supybot.debug.threadAllCommands():
t = CommandThread(target=self._callCommand,
args=(name, command, cb))
args=(name, cb))
t.start()
else:
self._callCommand(name, command, cb)
self._callCommand(name, cb)
def reply(self, s, noLengthCheck=False, prefixName=True,
action=None, private=None, notice=None, to=None, msg=None):
@ -768,7 +767,8 @@ class CommandThread(threading.Thread):
to run in threads.
"""
def __init__(self, target=None, args=(), kwargs={}):
(self.name, self.command, self.cb) = args
(self.name, self.cb) = args
self.command = self.cb.getCommand(self.name)
world.threadsSpawned += 1
threadName = 'Thread #%s (for %s.%s)' % (world.threadsSpawned,
self.cb.name(), self.name)
@ -925,17 +925,17 @@ class Privmsg(irclib.IrcCallback):
else:
self.__parent.__call__(irc, msg)
def isCommand(self, methodName):
def isCommand(self, name):
"""Returns whether a given method name is a command in this plugin."""
# 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.
# Don't canonicalize this name: consider outFilter(self, irc, msg).
# methodName = canonicalName(methodName)
if self._disabled.disabled(methodName, plugin=self.name()):
# name = canonicalName(name)
if self._disabled.disabled(name, plugin=self.name()):
return False
if hasattr(self, methodName):
method = getattr(self, methodName)
if hasattr(self, name):
method = getattr(self, name)
if inspect.ismethod(method):
code = method.im_func.func_code
return inspect.getargs(code)[0] == self.commandArgs
@ -944,19 +944,32 @@ class Privmsg(irclib.IrcCallback):
else:
return False
def getCommand(self, methodName):
def getCommand(self, name):
"""Gets the given command from this plugin."""
assert self.isCommand(methodName)
methodName = canonicalName(methodName)
return getattr(self, methodName)
name = canonicalName(name)
assert self.isCommand(name), '%r is not a command.' % name
return getattr(self, name)
def callCommand(self, method, irc, msg, *L):
name = method.im_func.func_name
def callCommand(self, name, irc, msg, *L, **kwargs):
#print '*', name, utils.stackTrace()
checkCapabilities = kwargs.pop('checkCapabilities', True)
if checkCapabilities:
cap = checkCommandCapability(msg, self, name)
if cap:
irc.errorNoCapability(cap)
return
method = self.getCommand(name)
assert L, 'Odd, nothing in L. This can\'t happen.'
self.log.info('%r called by %s', name, msg.prefix)
self.log.info('%s.%s called by %s.', self.name(), name, msg.prefix)
self.log.debug('args: %s', L[0])
start = time.time()
try:
method(irc, msg, *L)
except (getopt.GetoptError, ArgumentError):
irc.reply(formatArgumentError(method, name=name))
except (SyntaxError, Error), e:
self.log.debug('Error return: %s', utils.exnToString(e))
irc.error(str(e))
elapsed = time.time() - start
log.stat('%s took %s seconds', name, elapsed)
@ -1080,13 +1093,14 @@ class PrivmsgRegexp(Privmsg):
self.log.warning('Invalid regexp: %r (%s)',value.__doc__,e)
self.res.sort(lambda (r1, m1), (r2, m2): cmp(m1.__name__, m2.__name__))
def callCommand(self, method, irc, msg, *L):
def callCommand(self, name, irc, msg, *L, **kwargs):
try:
self.__parent.callCommand(method, irc, msg, *L)
self.__parent.callCommand(name, irc, msg, *L, **kwargs)
except Exception, e:
# We catch exceptions here because IrcObjectProxy isn't doing our
# dirty work for us anymore.
self.log.exception('Uncaught exception from callCommand:')
self.log.exception('Uncaught exception in %s.%s:',
self.name(), name)
if conf.supybot.reply.detailedErrors():
irc.error(utils.exnToString(e))
else:
@ -1126,17 +1140,31 @@ class PrivmsgCommandAndRegexp(Privmsg):
for name in self.regexps:
method = getattr(self, name)
r = re.compile(method.__doc__, self.flags)
self.res.append((r, method))
self.res.append((r, name))
for name in self.addressedRegexps:
method = getattr(self, name)
r = re.compile(method.__doc__, self.flags)
self.addressedRes.append((r, method))
self.addressedRes.append((r, name))
def callCommand(self, f, irc, msg, *L, **kwargs):
def isCommand(self, name):
return self.__parent.isCommand(name) or \
name in self.regexps or \
name in self.addressedRegexps
def getCommand(self, name):
try:
self.__parent.callCommand(f, irc, msg, *L)
return getattr(self, name) # Regexp stuff.
except AttributeError:
return self.__parent.getCommand(name)
def callCommand(self, name, irc, msg, *L, **kwargs):
try:
self.__parent.callCommand(name, irc, msg, *L, **kwargs)
except Exception, e:
if 'catchErrors' in kwargs and kwargs['catchErrors']:
# As annoying as it is, Python doesn't allow *L in addition to
# well-defined keyword arguments. So we have to do this trick.
catchErrors = kwargs.pop('catchErrors', False)
if catchErrors:
self.log.exception('Uncaught exception in callCommand:')
if conf.supybot.reply.detailedErrors():
irc.error(utils.exnToString(e))
@ -1147,24 +1175,22 @@ class PrivmsgCommandAndRegexp(Privmsg):
def doPrivmsg(self, irc, msg):
if Privmsg.errored:
self.log.info('%s not running due to Privmsg.errored.',
self.log.debug('%s not running due to Privmsg.errored.',
self.name())
return
for (r, method) in self.res:
name = method.__name__
for (r, name) in self.res:
for m in r.finditer(msg.args[1]):
proxy = self.Proxy(irc, msg)
self.callCommand(method, proxy, msg, m, catchErrors=True)
self.callCommand(name, proxy, msg, m, catchErrors=True)
if not Privmsg.handled:
s = addressed(irc.nick, msg)
if s:
for (r, method) in self.addressedRes:
name = method.__name__
for (r, name) in self.addressedRes:
if Privmsg.handled and name not in self.alwaysCall:
continue
for m in r.finditer(s):
proxy = self.Proxy(irc, msg)
self.callCommand(method,proxy,msg,m,catchErrors=True)
self.callCommand(name, proxy, msg, m, catchErrors=True)
Privmsg.handled = True

View File

@ -86,7 +86,9 @@ allowDefaultOwner = False
supybot = registry.Group()
supybot.setName('supybot')
def registerGroup(Group, name, group=None):
def registerGroup(Group, name, group=None, **kwargs):
if kwargs:
group = registry.Group(**kwargs)
return Group.register(name, group)
def registerGlobalValue(group, name, value):
@ -109,8 +111,8 @@ def registerPlugin(name, currentValue=None, public=True):
supybot.plugins.get(name).setValue(currentValue)
return registerGroup(users.plugins, name)
def channelValue(group, channel=None):
if channel is None:
def get(group, channel=None):
if channel is None or not group.channelValue:
return group()
else:
return group.get(channel)()
@ -120,7 +122,7 @@ def channelValue(group, channel=None):
###
users = registry.Group()
users.setName('users')
registerGroup(users, 'plugins')
registerGroup(users, 'plugins', orderAlphabetically=True)
def registerUserValue(group, name, value):
assert group._name.startswith('users')
@ -188,7 +190,8 @@ class Networks(registry.SpaceSeparatedSetOfStrings):
List = ircutils.IrcSet
registerGlobalValue(supybot, 'networks',
Networks([], """Determines what networks the bot will connect to."""))
Networks([], """Determines what networks the bot will connect to.""",
orderAlphabetically=True))
class Servers(registry.SpaceSeparatedListOfStrings):
def normalize(self, s):
@ -278,8 +281,8 @@ registerChannelValue(supybot.reply.mores, 'instant',
registerGlobalValue(supybot.reply, 'oneToOne',
registry.Boolean(True, """Determines whether the bot will send
multi-message replies in a single message or in multiple messages. For
safety purposes (so the bot can't possibly flood) it will normally send
everything in a single message."""))
safety purposes (so the bot is less likely to flood) it will normally send
everything in a single message, using mores if necessary."""))
class ValidBrackets(registry.OnlySomeStrings):
validStrings = ('', '[]', '<>', '{}', '()')
@ -409,6 +412,11 @@ registerChannelValue(supybot.reply.whenAddressedBy, 'nick',
registry.Boolean(True, """Determines whether the bot will reply when people
address it by its nick, rather than with a prefix character."""))
registerChannelValue(supybot.reply.whenAddressedBy, 'nicks',
registry.SpaceSeparatedSetOfStrings([], """Determines what extra nicks the
bot will always respond to when addressed by, even if its current nick is
something else."""))
###
# Replies
###
@ -501,6 +509,7 @@ registerGlobalValue(supybot, 'flush',
inside the bot, your changes won't be flushed. To make this change
permanent, you must edit the registry yourself."""))
###
# supybot.commands. For stuff relating to commands.
###
@ -508,6 +517,42 @@ registerGroup(supybot, 'commands')
# supybot.commands.disabled moved to callbacks for canonicalName.
###
# supybot.abuse. For stuff relating to abuse of the bot.
###
registerGroup(supybot, 'abuse')
registerGroup(supybot.abuse, 'flood')
registerGlobalValue(supybot.abuse.flood, 'command',
registry.Boolean(True, """Determines whether the bot will defend itself
against command-flooding."""))
registerGlobalValue(supybot.abuse.flood.command, 'maximum',
registry.PositiveInteger(12, """Determines how many commands users are
allowed per minute. If a user sends more than this many commands in any
60 second period, he or she will be ignored for
supybot.abuse.flood.command.punishment seconds."""))
registerGlobalValue(supybot.abuse.flood.command, 'punishment',
registry.PositiveInteger(300, """Determines how many seconds the bot
will ignore users who flood it with commands."""))
registerGlobalValue(supybot.abuse.flood.command, 'invalid',
registry.Boolean(True, """Determines whether the bot will defend itself
against invalid command-flooding."""))
registerGlobalValue(supybot.abuse.flood.command.invalid, 'maximum',
registry.PositiveInteger(5, """Determines how many invalid commands users
are allowed per minute. If a user sends more than this many invalid
commands in any 60 second period, he or she will be ignored for
supybot.abuse.flood.command.invalid.punishment seconds. Typically, this
value is lower than supybot.abuse.flood.command.maximum, since it's far
less likely (and far more annoying) for users to flood with invalid
commands than for them to flood with valid commands."""))
registerGlobalValue(supybot.abuse.flood.command.invalid, 'punishment',
registry.PositiveInteger(600, """Determines how many seconds the bot
will ignore users who flood it with invalid commands. Typically, this
value is higher than supybot.abuse.flood.command.punishment, since it's far
less likely (and far more annoying) for users to flood witih invalid
commands than for them to flood with valid commands."""))
###
# supybot.drivers. For stuff relating to Supybot's drivers (duh!)
###
@ -601,7 +646,7 @@ registerGlobalValue(supybot.directories, 'plugins',
a new one. E.g. you can say: bot: 'config supybot.directories.plugins
[config supybot.directories.plugins], newPluginDirectory'."""))
registerGroup(supybot, 'plugins') # This will be used by plugins, but not here.
registerGroup(supybot, 'plugins', orderAlphabetically=True)
registerGlobalValue(supybot.plugins, 'alwaysLoadDefault',
registry.Boolean(True, """Determines whether the bot will always load
the default plugins (Admin, Channel, Config, Misc, Owner, and User)

View File

@ -39,6 +39,7 @@ import os
import sets
import time
import string
import operator
from itertools import imap, ilen, ifilter
import supybot.log as log
@ -310,32 +311,17 @@ class IrcUser(object):
class IrcChannel(object):
"""This class holds the capabilities, bans, and ignores of a channel.
"""
"""This class holds the capabilities, bans, and ignores of a channel."""
defaultOff = ('op', 'halfop', 'voice', 'protected')
def __init__(self, bans=None, silences=None, exceptions=None, ignores=None,
capabilities=None, lobotomized=False, defaultAllow=True):
self.defaultAllow = defaultAllow
if bans is None:
self.bans = []
else:
self.bans = bans
if exceptions is None:
self.exceptions = []
else:
self.exceptions = exceptions
if silences is None:
self.silences = []
else:
self.silences = silences
if ignores is None:
self.ignores = []
else:
self.ignores = ignores
if capabilities is None:
self.capabilities = CapabilitySet()
else:
self.capabilities = capabilities
self.expiredBans = []
self.bans = bans or {}
self.ignores = ignores or {}
self.silences = silences or []
self.exceptions = exceptions or []
self.capabilities = capabilities or CapabilitySet()
for capability in self.defaultOff:
if capability not in self.capabilities:
self.capabilities.add(makeAntiCapability(capability))
@ -349,28 +335,33 @@ class IrcChannel(object):
self.capabilities, self.lobotomized,
self.defaultAllow, self.silences, self.exceptions)
def addBan(self, hostmask):
def addBan(self, hostmask, expiration=0):
"""Adds a ban to the channel banlist."""
self.bans.append(hostmask)
self.bans[hostmask] = int(expiration)
def removeBan(self, hostmask):
"""Removes a ban from the channel banlist."""
self.bans = [s for s in self.bans if s != hostmask]
return self.bans.pop(hostmask)
def checkBan(self, hostmask):
"""Checks whether a given hostmask is banned by the channel banlist."""
for pat in self.bans:
if ircutils.hostmaskPatternEqual(pat, hostmask):
now = time.time()
for (pattern, expiration) in self.bans.items():
if now < expiration or not expiration:
if ircutils.hostmaskPatternEqual(pattern, hostmask):
return True
else:
self.expiredBans.append((pattern, expiration))
del self.bans[pattern]
return False
def addIgnore(self, hostmask):
def addIgnore(self, hostmask, expiration=0):
"""Adds an ignore to the channel ignore list."""
self.ignores.append(hostmask)
self.ignores[hostmask] = int(expiration)
def removeIgnore(self, hostmask):
"""Removes an ignore from the channel ignore list."""
self.ignores = [s for s in self.ignores if s != hostmask]
return self.ignores.pop(hostmask)
def addCapability(self, capability):
"""Adds a capability to the channel's default capabilities."""
@ -398,12 +389,16 @@ class IrcChannel(object):
"""Checks whether a given hostmask is to be ignored by the channel."""
if self.lobotomized:
return True
for mask in self.bans:
if ircutils.hostmaskPatternEqual(mask, hostmask):
if self.checkBan(hostmask):
return True
for mask in self.ignores:
if ircutils.hostmaskPatternEqual(mask, hostmask):
now = time.time()
for (pattern, expiration) in self.ignores.items():
if now < expiration or not expiration:
if ircutils.hostmaskPatternEqual(pattern, hostmask):
return True
else:
del self.ignores[pattern]
# Later we may wish to keep expiredIgnores, but not now.
return False
def preserve(self, fd, indent=''):
@ -415,14 +410,14 @@ class IrcChannel(object):
write('defaultAllow %s' % self.defaultAllow)
for capability in self.capabilities:
write('capability ' + capability)
for ban in self.bans:
write('ban ' + ban)
for silence in self.silences:
write('silence ' + silence)
for exception in self.exceptions:
write('exception ' + exception)
for ignore in self.ignores:
write('ignore ' + ignore)
bans = self.bans.items()
utils.sortBy(operator.itemgetter(1), bans)
for (ban, expiration) in bans:
write('ban %s %d' % (ban, expiration))
ignores = self.ignores.items()
utils.sortBy(operator.itemgetter(1), ignores)
for (ignore, expiration) in ignores:
write('ignore %s %d' % (ignore, expiration))
fd.write(os.linesep)
@ -511,22 +506,14 @@ class IrcChannelCreator(Creator):
def ban(self, rest, lineno):
if self.name is None:
raise ValueError, 'Unexpected channel description without channel.'
self.c.bans.append(rest)
(pattern, expiration) = rest
self.c.bans[pattern] = int(float(expiration))
def ignore(self, rest, lineno):
if self.name is None:
raise ValueError, 'Unexpected channel description without channel.'
self.c.ignores.append(rest)
def silence(self, rest, lineno):
if self.name is None:
raise ValueError, 'Unexpected channel description without channel.'
self.c.silences.append(rest)
def exception(self, rest, lineno):
if self.name is None:
raise ValueError, 'Unexpected channel description without channel.'
self.c.exceptions.append(rest)
(pattern, expiration) = rest
self.c.ignores[pattern] = int(float(expiration))
def finish(self):
if self.hadChannel:
@ -782,20 +769,32 @@ class ChannelsDictionary(utils.IterableMap):
class IgnoresDB(object):
def __init__(self):
self.filename = None
self.hostmasks = sets.Set()
self.hostmasks = {}
def open(self, filename):
self.filename = filename
fd = file(self.filename)
for line in utils.nonCommentNonEmptyLines(fd):
self.hostmasks.add(line.rstrip('\r\n'))
try:
line = line.rstrip('\r\n')
L = line.split()
hostmask = L.pop(0)
if L:
expiration = int(float(L.pop(0)))
else:
expiration = 0
self.add(hostmask, expiration)
except Exception, e:
log.error('Invalid line in ignores database: %r', line)
fd.close()
def flush(self):
if self.filename is not None:
fd = utils.transactionalFile(self.filename)
for hostmask in self.hostmasks:
fd.write(hostmask)
now = time.time()
for (hostmask, expiration) in self.hostmasks.items():
if now < expiration or not expiration:
fd.write('%s %s' % (hostmask, expiration))
fd.write(os.linesep)
fd.close()
else:
@ -809,26 +808,33 @@ class IgnoresDB(object):
def reload(self):
if self.filename is not None:
oldhostmasks = self.hostmasks.copy()
self.hostmasks.clear()
try:
self.open(self.filename)
except EnvironmentError, e:
log.warning('IgnoresDB.reload failed: %s', e)
# Let's be somewhat transactional.
self.hostmasks.update(oldhostmasks)
else:
log.warning('IgnoresDB.reload called without self.filename.')
def checkIgnored(self, prefix):
for hostmask in self.hostmasks:
now = time.time()
for (hostmask, expiration) in self.hostmasks.items():
if expiration and now > expiration:
del self.hostmasks[hostmask]
else:
if ircutils.hostmaskPatternEqual(hostmask, prefix):
return True
return False
def addHostmask(self, hostmask):
def add(self, hostmask, expiration=0):
assert ircutils.isUserHostmask(hostmask)
self.hostmasks.add(hostmask)
self.hostmasks[hostmask] = expiration
def removeHostmask(self, hostmask):
self.hostmasks.remove(hostmask)
def remove(self, hostmask):
del self.hostmasks[hostmask]
confDir = conf.supybot.directories.conf()

View File

@ -55,8 +55,7 @@ reconnectWaits = (0, 60, 300)
class SocketDriver(drivers.IrcDriver, drivers.ServersMixin):
def __init__(self, irc):
self.irc = irc
drivers.ServersMixin.__init__(self, irc)
drivers.IrcDriver.__init__(self) # Must come after setting irc.
super(SocketDriver, self).__init__(irc)
self.conn = None
self.servers = ()
self.eagains = 0

View File

@ -182,6 +182,11 @@ class FunctionsTestCase(SupyTestCase):
finally:
conf.supybot.reply.whenNotAddressed.setValue(original)
def testAddressedWithMultipleNicks(self):
msg = ircmsgs.privmsg('#foo', 'bar: baz')
self.failUnless(callbacks.addressed('bar', msg))
self.failUnless(callbacks.addressed('biff', msg, nicks=['bar']))
def testReply(self):
prefix = 'foo!bar@baz'
channelMsg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
@ -259,6 +264,7 @@ class PrivmsgTestCase(ChannelPluginTestCase):
original = conf.supybot.reply.errorInPrivate()
conf.supybot.reply.errorInPrivate.setValue(False)
m = self.getMsg("eval irc.error('foo', private=True)")
self.failUnless(m, 'No message returned.')
self.failIf(ircutils.isChannel(m.args[0]))
finally:
conf.supybot.reply.errorInPrivate.setValue(original)
@ -271,6 +277,7 @@ class PrivmsgTestCase(ChannelPluginTestCase):
original = conf.supybot.reply.errorWithNotice()
conf.supybot.reply.errorWithNotice.setValue(True)
m = self.getMsg("eval irc.error('foo')")
self.failUnless(m, 'No message returned.')
self.failUnless(m.command == 'NOTICE')
finally:
conf.supybot.reply.errorWithNotice.setValue(original)