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 threaded = True
regexps = ['amzSnarfer'] regexps = ['amzSnarfer']
def callCommand(self, method, irc, msg, *L, **kwargs): def callCommand(self, name, irc, msg, *L, **kwargs):
try: try:
super(Amazon, self).callCommand(method, irc, msg, *L, **kwargs) super(Amazon, self).callCommand(name, irc, msg, *L, **kwargs)
except amazon.NoLicenseKey, e: except amazon.NoLicenseKey, e:
irc.error('You must have a free Amazon web services license key ' irc.error('You must have a free Amazon web services license key '
'in order to use this command. You can get one at ' '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', conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireCapability',
registry.String('', """Determines what capability (if any) the bot should registry.String('', """Determines what capability (if any) the bot should
require people trying to use this plugin to have.""")) 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): class Anonymous(callbacks.Privmsg):
@ -89,7 +94,7 @@ class Anonymous(callbacks.Privmsg):
c = ircdb.channels.getChannel(channel) c = ircdb.channels.getChannel(channel)
if c.lobotomized: if c.lobotomized:
irc.error('I\'m lobotomized in %s.' % channel, Raise=True) 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 ' irc.error('That channel has set its capabilities so as to '
'disallow the use of this plugin.', Raise=True) '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.conf as conf
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.ircutils as ircutils
import supybot.registry as registry
import supybot.callbacks as callbacks 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): class Ctcp(callbacks.PrivmsgRegexp):
public = False 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): def ping(self, irc, msg, match):
"\x01PING (.*)\x01" "\x01PING (.*)\x01"
self.log.info('Received CTCP PING from %s', msg.prefix) 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): def version(self, irc, msg, match):
"\x01VERSION\x01" "\x01VERSION\x01"
self.log.info('Received CTCP VERSION from %s', msg.prefix) self.log.info('Received CTCP VERSION from %s', msg.prefix)
s = '\x01VERSION Supybot %s\x01' % conf.version self._reply(irc, msg, 'VERSION Supybot %s' % conf.version)
irc.queueMsg(notice(msg.nick, s))
def userinfo(self, irc, msg, match): def userinfo(self, irc, msg, match):
"\x01USERINFO\x01" "\x01USERINFO\x01"
self.log.info('Received CTCP USERINFO from %s', msg.prefix) 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): def time(self, irc, msg, match):
"\x01TIME\x01" "\x01TIME\x01"
self.log.info('Received CTCP TIME from %s' % msg.prefix) 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): def finger(self, irc, msg, match):
"\x01FINGER\x01" "\x01FINGER\x01"
self.log.info('Received CTCP FINGER from %s' % msg.prefix) self.log.info('Received CTCP FINGER from %s' % msg.prefix)
s = '\x01Supybot, the best Python bot in existence!\x01' irc._reply(irc, msg, 'Supybot, the best Python IRC bot in existence!')
irc.queueMsg(notice(msg.nick, s))
def source(self, irc, msg, match): def source(self, irc, msg, match):
"\x01SOURCE\x01" "\x01SOURCE\x01"
self.log.info('Received CTCP SOURCE from %s' % msg.prefix) self.log.info('Received CTCP SOURCE from %s' % msg.prefix)
s = 'http://www.sourceforge.net/projects/supybot/' self._reply(irc, msg, 'http://www.sourceforge.net/projects/supybot/')
irc.queueMsg(notice(msg.nick, s))
Class = Ctcp Class = Ctcp
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # 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.""" the name of 'weather' which should override this help."""
weatherCommands = ['ham', 'cnn', 'wunder'] weatherCommands = ['ham', 'cnn', 'wunder']
threaded = True threaded = True
def callCommand(self, method, irc, msg, *L, **kwargs): def callCommand(self, name, irc, msg, *L, **kwargs):
try: try:
super(Weather, self).callCommand(method, irc, msg, *L, **kwargs) super(Weather, self).callCommand(name, irc, msg, *L, **kwargs)
except webutils.WebError, e: except webutils.WebError, e:
irc.error(str(e)) irc.error(str(e))

View File

@ -133,10 +133,10 @@ class HangmanGame:
class Words(callbacks.Privmsg): 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. # We'll catch the IOError here.
try: try:
super(Words, self).callCommand(command, irc, msg, args, *L, **kw) super(Words, self).callCommand(name, irc, msg, args, *L, **kw)
except EnvironmentError, e: except EnvironmentError, e:
irc.error('I couldn\'t open the words file. This plugin expects ' 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 ' '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): class Admin(privmsgs.CapabilityCheckingPrivmsg):
capability = 'admin' capability = 'admin'
def __init__(self): def __init__(self):
privmsgs.CapabilityCheckingPrivmsg.__init__(self) self.__parent = super(Admin, self)
self.__parent.__init__()
self.joins = {} self.joins = {}
self.pendingNickChanges = {} self.pendingNickChanges = {}
@ -157,7 +158,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
else: else:
channels.append(channel) channels.append(channel)
if not ircutils.isChannel(channel): if not ircutils.isChannel(channel):
irc.error('%r is not a valid channel.' % channel) irc.errorInvalid('channel', channel)
return return
conf.supybot.channels().add(original) conf.supybot.channels().add(original)
maxchannels = irc.state.supported.get('maxchannels', sys.maxint) maxchannels = irc.state.supported.get('maxchannels', sys.maxint)
@ -237,7 +238,7 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
irc.queueMsg(ircmsgs.nick(nick)) irc.queueMsg(ircmsgs.nick(nick))
self.pendingNickChanges[irc.getRealIrc()] = irc self.pendingNickChanges[irc.getRealIrc()] = irc
else: else:
irc.error('That\'s not a valid nick.') irc.errorInvalid('nick', nick)
else: else:
irc.reply(irc.nick) irc.reply(irc.nick)
@ -351,21 +352,33 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg):
irc.error(s) irc.error(s)
def ignore(self, irc, msg, args): def ignore(self, irc, msg, args):
"""<hostmask|nick> """<hostmask|nick> [<expires>]
Ignores <hostmask> or, if a nick is given, ignores whatever hostmask 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) (nickOrHostmask, expires) = privmsgs.getArgs(args, optional=1)
if ircutils.isUserHostmask(arg): if ircutils.isUserHostmask(nickOrHostmask):
hostmask = arg hostmask = nickOrHostmask
else: else:
try: try:
hostmask = irc.state.nickToHostmask(arg) hostmask = irc.state.nickToHostmask(nickOrHostmask)
except KeyError: 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 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() irc.replySuccess()
def unignore(self, irc, msg, args): 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) irc.error('I can\'t find a hostmask for %s' % arg)
return return
try: try:
ircdb.ignores.removeHostmask(hostmask) ircdb.ignores.remove(hostmask)
irc.replySuccess() irc.replySuccess()
except KeyError: except KeyError:
irc.error('%s wasn\'t in the ignores database.' % hostmask) 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. Returns the hostmasks currently being globally ignored.
""" """
# XXX Add the expirations.
if ircdb.ignores.hostmasks: if ircdb.ignores.hostmasks:
irc.reply(utils.commaAndify(imap(repr, ircdb.ignores.hostmasks))) irc.reply(utils.commaAndify(imap(repr, ircdb.ignores.hostmasks)))
else: else:

View File

@ -441,23 +441,34 @@ class Channel(callbacks.Privmsg):
unlobotomize = privmsgs.checkChannelCapability(unlobotomize, 'op') unlobotomize = privmsgs.checkChannelCapability(unlobotomize, 'op')
def permban(self, irc, msg, args, channel): 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 If you have the #channel,op capability, this will effect a permanent
(persistent) ban from interacting with the bot on the given <hostmask> (persistent) ban from interacting with the bot on the given <hostmask>
(or the current hostmask associated with <nick>. <channel> is only (or the current hostmask associated with <nick>. Other plugins may
necessary if the message isn't sent in the channel itself. 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) (nickOrHostmask, expires) = privmsgs.getArgs(args, optional=1)
if ircutils.isNick(arg): if ircutils.isNick(nickOrHostmask):
banmask = ircutils.banmask(irc.state.nickToHostmask(arg)) banmask = ircutils.banmask(irc.state.nickToHostmask(nickOrHostmask))
elif ircutils.isUserHostmask(arg): elif ircutils.isUserHostmask(nickOrHostmask):
banmask = arg banmask = nickOrHostmask
else: else:
irc.error('That\'s not a valid nick or hostmask.') irc.errorInvalid('nick or hostmask', nickOrHostmask, Raise=True)
return 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 = ircdb.channels.getChannel(channel)
c.addBan(banmask) c.addBan(banmask, expires)
ircdb.channels.setChannel(channel, c) ircdb.channels.setChannel(channel, c)
irc.replySuccess() irc.replySuccess()
permban = privmsgs.checkChannelCapability(permban, 'op') 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 If you have the #channel,op capability, this will show you the
current bans on #channel. current bans on #channel.
""" """
# XXX Add the expirations.
c = ircdb.channels.getChannel(channel) c = ircdb.channels.getChannel(channel)
if c.bans: if c.bans:
irc.reply(utils.commaAndify(map(utils.dqrepr, c.bans))) irc.reply(utils.commaAndify(map(utils.dqrepr, c.bans)))
@ -491,23 +503,32 @@ class Channel(callbacks.Privmsg):
permbans = privmsgs.checkChannelCapability(permbans, 'op') permbans = privmsgs.checkChannelCapability(permbans, 'op')
def ignore(self, irc, msg, args, channel): 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 If you have the #channel,op capability, this will set a permanent
(persistent) ignore on <hostmask> or the hostmask currently associated (persistent) ignore on <hostmask> or the hostmask currently associated
with <nick>. <channel> is only necessary if the message isn't sent in with <nick>. <expires> is an optional argument specifying when (in
the channel itself. "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) (nickOrHostmask, expires) = privmsgs.getArgs(args, optional=1)
if ircutils.isNick(arg): if ircutils.isNick(nickOrHostmask):
banmask = ircutils.banmask(irc.state.nickToHostmask(arg)) banmask = ircutils.banmask(irc.state.nickToHostmask(nickOrHostmask))
elif ircutils.isUserHostmask(arg): elif ircutils.isUserHostmask(nickOrHostmask):
banmask = arg banmask = nickOrHostmask
else: else:
irc.error('That\'s not a valid nick or hostmask.') irc.errorInvalid('nick or hostmask', nickOrHostmask, Raise=True)
return 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 = ircdb.channels.getChannel(channel)
c.addIgnore(banmask) c.addIgnore(banmask, expires)
ircdb.channels.setChannel(channel, c) ircdb.channels.setChannel(channel, c)
irc.replySuccess() irc.replySuccess()
ignore = privmsgs.checkChannelCapability(ignore, 'op') 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 <channel> is only necessary if the message isn't sent in the channel
itself. itself.
""" """
# XXX Add the expirations.
channelarg = privmsgs.getArgs(args, required=0, optional=1) channelarg = privmsgs.getArgs(args, required=0, optional=1)
channel = channelarg or channel channel = channelarg or channel
c = ircdb.channels.getChannel(channel) c = ircdb.channels.getChannel(channel)

View File

@ -101,7 +101,7 @@ class Config(callbacks.Privmsg):
try: try:
super(Config, self).callCommand(name, irc, msg, *L, **kwargs) super(Config, self).callCommand(name, irc, msg, *L, **kwargs)
except InvalidRegistryName, e: 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: except registry.InvalidRegistryValue, e:
irc.error(str(e)) irc.error(str(e))

View File

@ -66,19 +66,50 @@ conf.registerGlobalValue(conf.supybot.plugins.Misc, 'listPrivatePlugins',
class Misc(callbacks.Privmsg): class Misc(callbacks.Privmsg):
priority = sys.maxint 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): def invalidCommand(self, irc, msg, tokens):
self.log.debug('Misc.invalidCommand called (tokens %s)', 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 '' command = tokens and tokens[0] or ''
irc.error('%r is not a valid command.' % command) irc.errorInvalid('command', command)
else: else:
if tokens: if tokens:
# echo [] will get us an empty token set, but there's no need # 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. # to log this in that case anyway, it being a nested command.
self.log.info('Not replying to %s, not a command.' % tokens[0]) self.log.info('Not replying to %s, not a command.' % tokens[0])
if not isinstance(irc.irc, irclib.Irc): if not isinstance(irc.irc, irclib.Irc):
brackets = conf.supybot.reply.brackets.get(msg.args[0])() brackets = conf.get(conf.supybot.reply.brackets, channel)
irc.reply(''.join([brackets[0],' '.join(tokens), brackets[1]])) 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): def list(self, irc, msg, args):
"""[--private] [<module name>] """[--private] [<module name>]
@ -505,8 +536,7 @@ class Misc(callbacks.Privmsg):
try: try:
i = int(s) i = int(s)
except ValueError: except ValueError:
irc.error('Invalid argument: %s' % arg) irc.errorInvalid('argument', arg, Raise=True)
return
if kind == 'y': if kind == 'y':
seconds += i*31536000 seconds += i*31536000
elif kind == 'w': elif kind == 'w':
@ -522,10 +552,10 @@ class Misc(callbacks.Privmsg):
irc.reply(str(seconds)) irc.reply(str(seconds))
def tell(self, irc, msg, args): def tell(self, irc, msg, args):
"""<nick|channel> <text> """<nick> <text>
Tells the <nick|channel> whatever <text> is. Use nested commands to Tells the <nick> whatever <text> is. Use nested commands to your
your benefit here. benefit here.
""" """
(target, text) = privmsgs.getArgs(args, required=2) (target, text) = privmsgs.getArgs(args, required=2)
if target.lower() == 'me': if target.lower() == 'me':
@ -534,11 +564,9 @@ class Misc(callbacks.Privmsg):
irc.error('Dude, just give the command. No need for the tell.') irc.error('Dude, just give the command. No need for the tell.')
return return
elif not ircutils.isNick(target): elif not ircutils.isNick(target):
irc.error('%s is not a valid nick.' % target) irc.errorInvalid('nick', target, Raise=True)
return
elif ircutils.nickEqual(target, irc.nick): elif ircutils.nickEqual(target, irc.nick):
irc.error('You just told me, why should I tell myself?') irc.error('You just told me, why should I tell myself?',Raise=True)
return
elif target not in irc.state.nicksToHostmasks and \ elif target not in irc.state.nicksToHostmasks and \
not ircdb.checkCapability(msg.prefix, 'owner'): not ircdb.checkCapability(msg.prefix, 'owner'):
# We'll let owners do this. # We'll let owners do this.

View File

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

View File

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

View File

@ -29,6 +29,8 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
### ###
__revision__ = "$Id$"
import sys import sys
import os.path import os.path
@ -40,13 +42,27 @@ othersDir = os.path.join(installDir, 'others')
sys.path.insert(0, srcDir) sys.path.insert(0, srcDir)
sys.path.insert(0, othersDir) sys.path.insert(0, othersDir)
class authors: # This is basically a bag. class Author(object):
jemfinch = 'Jeremy Fincher (jemfinch) <jemfinch@users.sf.net>' def __init__(self, name=None, nick=None, email=None, **kwargs):
jamessan = 'James Vega (jamessan) <jamessan@users.sf.net>' self.__dict__.update(kwargs)
strike = 'Daniel DiPaolo (Strike) <ddipaolo@users.sf.net>' self.name = name
baggins = 'William Robinson (baggins) <airbaggins@users.sf.net>' self.nick = nick
skorobeus = 'Kevin Murphy (Skorobeus) <skoro@skoroworld.com>' self.email = email
inkedmn = 'Brett Kelly (inkedmn) <inkedmn@users.sf.net>'
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: # 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.ircutils as ircutils
import supybot.registry as registry import supybot.registry as registry
def addressed(nick, msg, prefixChars=None, def addressed(nick, msg, prefixChars=None, nicks=None,
prefixStrings=None, whenAddressedByNick=None): prefixStrings=None, whenAddressedByNick=None):
"""If msg is addressed to 'name', returns the portion after the address. """If msg is addressed to 'name', returns the portion after the address.
Otherwise returns the empty string. Otherwise returns the empty string.
@ -88,7 +88,12 @@ def addressed(nick, msg, prefixChars=None,
whenAddressedByNick = get(conf.supybot.reply.whenAddressedBy.nick) whenAddressedByNick = get(conf.supybot.reply.whenAddressedBy.nick)
if prefixStrings is None: if prefixStrings is None:
prefixStrings = get(conf.supybot.reply.whenAddressedBy.strings) 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. # Ok, let's see if it's a private message.
if ircutils.nickEqual(target, nick): if ircutils.nickEqual(target, nick):
payload = stripPrefixStrings(payload) payload = stripPrefixStrings(payload)
@ -96,21 +101,22 @@ def addressed(nick, msg, prefixChars=None,
payload = payload[1:].lstrip() payload = payload[1:].lstrip()
return payload return payload
# Ok, not private. Does it start with our nick? # Ok, not private. Does it start with our nick?
elif whenAddressedByNick and \ elif whenAddressedByNick:
ircutils.toLower(payload).startswith(nick): for nick in nicks:
if ircutils.toLower(payload).startswith(nick):
try: try:
(maybeNick, rest) = payload.split(None, 1) (maybeNick, rest) = payload.split(None, 1)
while not ircutils.isNick(maybeNick, strictRfc=True): while not ircutils.isNick(maybeNick, strictRfc=True):
if maybeNick[-1].isalnum(): if maybeNick[-1].isalnum():
return '' continue
maybeNick = maybeNick[:-1] maybeNick = maybeNick[:-1]
if ircutils.nickEqual(maybeNick, nick): if ircutils.nickEqual(maybeNick, nick):
return rest return rest
else: else:
return '' continue
except ValueError: # split didn't work. except ValueError: # split didn't work.
return '' continue
elif payload and any(payload.startswith, prefixStrings): if payload and any(payload.startswith, prefixStrings):
return stripPrefixStrings(payload) return stripPrefixStrings(payload)
elif payload and payload[0] in prefixChars: elif payload and payload[0] in prefixChars:
return payload[1:].strip() return payload[1:].strip()
@ -213,10 +219,6 @@ class ArgumentError(Error):
"""The bot replies with a help message when this is raised.""" """The bot replies with a help message when this is raised."""
pass pass
class CannotNest(Error):
"""Exception to be raised by commands that cannot be nested."""
pass
class Tokenizer: class Tokenizer:
# This will be used as a global environment to evaluate strings in. # This will be used as a global environment to evaluate strings in.
# Evaluation is, of course, necessary in order to allowed escaped # Evaluation is, of course, necessary in order to allowed escaped
@ -310,10 +312,10 @@ def tokenize(s, brackets=None, channel=None):
start = time.time() start = time.time()
try: try:
if brackets is None: if brackets is None:
tokens = conf.channelValue(conf.supybot.reply.brackets, channel) tokens = conf.get(conf.supybot.reply.brackets, channel)
else: else:
tokens = brackets tokens = brackets
if conf.channelValue(conf.supybot.reply.pipeSyntax, channel): if conf.get(conf.supybot.reply.pipeSyntax, channel):
tokens = '%s|' % tokens tokens = '%s|' % tokens
log.stat('tokenize took %s seconds.' % (time.time() - start)) log.stat('tokenize took %s seconds.' % (time.time() - start))
return Tokenizer(tokens).tokenize(s) return Tokenizer(tokens).tokenize(s)
@ -329,14 +331,15 @@ def getCommands(tokens):
L.extend(getCommands(elt)) L.extend(getCommands(elt))
return L return L
def findCallbackForCommand(irc, commandName): def findCallbackForCommand(irc, name):
"""Given a command name and an Irc object, returns a list of callbacks that """Given a command name and an Irc object, returns a list of callbacks that
commandName is in.""" commandName is in."""
L = [] L = []
name = canonicalName(name)
for callback in irc.callbacks: for callback in irc.callbacks:
if not isinstance(callback, PrivmsgRegexp): if not isinstance(callback, PrivmsgRegexp):
if hasattr(callback, 'isCommand'): if hasattr(callback, 'isCommand'):
if callback.isCommand(commandName): if callback.isCommand(name):
L.append(callback) L.append(callback)
return L return L
@ -344,16 +347,17 @@ def formatArgumentError(method, name=None, channel=None):
if name is None: if name is None:
name = method.__name__ name = method.__name__
if hasattr(method, '__doc__') and method.__doc__: 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) return getSyntax(method, name=name)
else: else:
return getHelp(method, name=name) return getHelp(method, name=name)
else: else:
return 'Invalid arguments for %s.' % method.__name__ 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() plugin = cb.name().lower()
pluginCommand = '%s.%s' % (plugin, command) pluginCommand = '%s.%s' % (plugin, commandName)
def checkCapability(capability): def checkCapability(capability):
assert ircdb.isAntiCapability(capability) assert ircdb.isAntiCapability(capability)
if ircdb.checkCapability(msg.prefix, capability): if ircdb.checkCapability(msg.prefix, capability):
@ -362,12 +366,12 @@ def checkCommandCapability(msg, cb, command):
raise RuntimeError, capability raise RuntimeError, capability
try: try:
antiPlugin = ircdb.makeAntiCapability(plugin) antiPlugin = ircdb.makeAntiCapability(plugin)
antiCommand = ircdb.makeAntiCapability(command) antiCommand = ircdb.makeAntiCapability(commandName)
antiPluginCommand = ircdb.makeAntiCapability(pluginCommand) antiPluginCommand = ircdb.makeAntiCapability(pluginCommand)
checkCapability(antiPlugin) checkCapability(antiPlugin)
checkCapability(antiCommand) checkCapability(antiCommand)
checkCapability(antiPluginCommand) checkCapability(antiPluginCommand)
checkAtEnd = [command, pluginCommand] checkAtEnd = [commandName, pluginCommand]
default = conf.supybot.capabilities.default() default = conf.supybot.capabilities.default()
if ircutils.isChannel(msg.args[0]): if ircutils.isChannel(msg.args[0]):
channel = msg.args[0] channel = msg.args[0]
@ -376,7 +380,7 @@ def checkCommandCapability(msg, cb, command):
checkCapability(ircdb.makeChannelCapability(channel, checkCapability(ircdb.makeChannelCapability(channel,
antiPluginCommand)) antiPluginCommand))
chanPlugin = ircdb.makeChannelCapability(channel, plugin) chanPlugin = ircdb.makeChannelCapability(channel, plugin)
chanCommand = ircdb.makeChannelCapability(channel, command) chanCommand = ircdb.makeChannelCapability(channel, commandName)
chanPluginCommand = ircdb.makeChannelCapability(channel, chanPluginCommand = ircdb.makeChannelCapability(channel,
pluginCommand) pluginCommand)
checkAtEnd += [chanCommand, chanPlugin, chanPluginCommand] checkAtEnd += [chanCommand, chanPlugin, chanPluginCommand]
@ -437,49 +441,56 @@ class RichReplyMethods(object):
else: else:
self.reply(prefixer(s), **kwargs) self.reply(prefixer(s), **kwargs)
def _error(self, s, Raise, **kwargs): def _error(self, s, Raise=False, **kwargs):
if Raise: if Raise:
raise Error, s raise Error, s
else: else:
self.error(s, **kwargs) self.error(s, **kwargs)
def errorNoCapability(self, capability, s='', Raise=False, **kwargs): def errorNoCapability(self, capability, s='', **kwargs):
if isinstance(capability, basestring): # checkCommandCapability! if isinstance(capability, basestring): # checkCommandCapability!
log.warning('Denying %s for lacking %r capability.', log.warning('Denying %s for lacking %r capability.',
self.msg.prefix, capability) self.msg.prefix, capability)
if not self._getConfig(conf.supybot.reply.noCapabilityError): if not self._getConfig(conf.supybot.reply.noCapabilityError):
v = self._getConfig(conf.supybot.replies.noCapability) v = self._getConfig(conf.supybot.replies.noCapability)
s = self.__makeReply(v % capability, s) s = self.__makeReply(v % capability, s)
self._error(s, Raise, **kwargs) self._error(s, **kwargs)
else: else:
log.warning('Denying %s for some unspecified capability ' log.warning('Denying %s for some unspecified capability '
'(or a default).', self.msg.prefix) '(or a default).', self.msg.prefix)
v = self._getConfig(conf.supybot.replies.genericNoCapability) 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) v = self._getConfig(conf.supybot.replies.possibleBug)
if s: if s:
s += ' (%s)' % v s += ' (%s)' % v
else: else:
s = v 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) 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) v = self._getConfig(conf.supybot.replies.noUser)
try: try:
v = v % name v = v % name
except TypeError: except TypeError:
log.warning('supybot.replies.noUser should have one "%s" in it.') 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) 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): class IrcObjectProxy(RichReplyMethods):
@ -542,21 +553,14 @@ class IrcObjectProxy(RichReplyMethods):
log.exception('Uncaught exception in %s.invalidCommand', log.exception('Uncaught exception in %s.invalidCommand',
cb.name()) cb.name())
def _callCommand(self, name, command, cb): def _callCommand(self, name, cb):
try: try:
self.commandMethod = command self.commandMethod = cb.getCommand(name)
try: try:
cb.callCommand(command, self, self.msg, self.args) cb.callCommand(name, 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))
except Exception, e: except Exception, e:
cb.log.exception('Uncaught exception:') cb.log.exception('Uncaught exception in %s.%s:',
cb.name(), name)
if conf.supybot.reply.detailedErrors(): if conf.supybot.reply.detailedErrors():
self.error(utils.exnToString(e)) self.error(utils.exnToString(e))
else: else:
@ -567,27 +571,27 @@ class IrcObjectProxy(RichReplyMethods):
def finalEval(self): def finalEval(self):
assert not self.finalEvaled, 'finalEval called twice.' assert not self.finalEvaled, 'finalEval called twice.'
self.finalEvaled = True self.finalEvaled = True
name = canonicalName(self.args[0]) name = self.args[0]
cbs = findCallbackForCommand(self, name) cbs = findCallbackForCommand(self, name)
if len(cbs) == 0: if len(cbs) == 0:
for cb in self.irc.callbacks: for cb in self.irc.callbacks:
if isinstance(cb, PrivmsgRegexp): if isinstance(cb, PrivmsgRegexp):
for (r, m) in cb.res: for (r, name) in cb.res:
if r.search(self.msg.args[1]): if r.search(self.msg.args[1]):
log.debug('Skipping invalidCommand: %s.%s', log.debug('Skipping invalidCommand: %s.%s',
m.im_class.__name__,m.im_func.func_name) cb.name(), name)
return return
elif isinstance(cb, PrivmsgCommandAndRegexp): elif isinstance(cb, PrivmsgCommandAndRegexp):
for (r, m) in cb.res: for (r, name) in cb.res:
if r.search(self.msg.args[1]): if r.search(self.msg.args[1]):
log.debug('Skipping invalidCommand: %s.%s', log.debug('Skipping invalidCommand: %s.%s',
m.im_class.__name__,m.im_func.func_name) cb.name(), name)
return return
payload = addressed(self.irc.nick, self.msg) payload = addressed(self.irc.nick, self.msg)
for (r, m) in cb.addressedRes: for (r, name) in cb.addressedRes:
if r.search(payload): if r.search(payload):
log.debug('Skipping invalidCommand: %s.%s', log.debug('Skipping invalidCommand: %s.%s',
m.im_class.__name__,m.im_func.func_name) cb.name(), name)
return return
# Ok, no regexp-based things matched. # Ok, no regexp-based things matched.
self._callInvalidCommands() self._callInvalidCommands()
@ -604,18 +608,13 @@ class IrcObjectProxy(RichReplyMethods):
else: else:
del self.args[0] del self.args[0]
cb = cbs[0] cb = cbs[0]
cap = checkCommandCapability(self.msg, cb, name)
if cap:
self.errorNoCapability(cap)
return
command = getattr(cb, name)
Privmsg.handled = True Privmsg.handled = True
if cb.threaded or conf.supybot.debug.threadAllCommands(): if cb.threaded or conf.supybot.debug.threadAllCommands():
t = CommandThread(target=self._callCommand, t = CommandThread(target=self._callCommand,
args=(name, command, cb)) args=(name, cb))
t.start() t.start()
else: else:
self._callCommand(name, command, cb) self._callCommand(name, cb)
def reply(self, s, noLengthCheck=False, prefixName=True, def reply(self, s, noLengthCheck=False, prefixName=True,
action=None, private=None, notice=None, to=None, msg=None): action=None, private=None, notice=None, to=None, msg=None):
@ -768,7 +767,8 @@ class CommandThread(threading.Thread):
to run in threads. to run in threads.
""" """
def __init__(self, target=None, args=(), kwargs={}): 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 world.threadsSpawned += 1
threadName = 'Thread #%s (for %s.%s)' % (world.threadsSpawned, threadName = 'Thread #%s (for %s.%s)' % (world.threadsSpawned,
self.cb.name(), self.name) self.cb.name(), self.name)
@ -925,17 +925,17 @@ class Privmsg(irclib.IrcCallback):
else: else:
self.__parent.__call__(irc, msg) 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.""" """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 # 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. # doPrivmsg or __init__ or whatever, and this is good to stop them.
# Don't canonicalize this name: consider outFilter(self, irc, msg). # Don't canonicalize this name: consider outFilter(self, irc, msg).
# methodName = canonicalName(methodName) # name = canonicalName(name)
if self._disabled.disabled(methodName, plugin=self.name()): if self._disabled.disabled(name, plugin=self.name()):
return False return False
if hasattr(self, methodName): if hasattr(self, name):
method = getattr(self, methodName) method = getattr(self, name)
if inspect.ismethod(method): if inspect.ismethod(method):
code = method.im_func.func_code code = method.im_func.func_code
return inspect.getargs(code)[0] == self.commandArgs return inspect.getargs(code)[0] == self.commandArgs
@ -944,19 +944,32 @@ class Privmsg(irclib.IrcCallback):
else: else:
return False return False
def getCommand(self, methodName): def getCommand(self, name):
"""Gets the given command from this plugin.""" """Gets the given command from this plugin."""
assert self.isCommand(methodName) name = canonicalName(name)
methodName = canonicalName(methodName) assert self.isCommand(name), '%r is not a command.' % name
return getattr(self, methodName) return getattr(self, name)
def callCommand(self, method, irc, msg, *L): def callCommand(self, name, irc, msg, *L, **kwargs):
name = method.im_func.func_name #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.' 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]) self.log.debug('args: %s', L[0])
start = time.time() start = time.time()
try:
method(irc, msg, *L) 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 elapsed = time.time() - start
log.stat('%s took %s seconds', name, elapsed) 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.log.warning('Invalid regexp: %r (%s)',value.__doc__,e)
self.res.sort(lambda (r1, m1), (r2, m2): cmp(m1.__name__, m2.__name__)) 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: try:
self.__parent.callCommand(method, irc, msg, *L) self.__parent.callCommand(name, irc, msg, *L, **kwargs)
except Exception, e: except Exception, e:
# We catch exceptions here because IrcObjectProxy isn't doing our # We catch exceptions here because IrcObjectProxy isn't doing our
# dirty work for us anymore. # 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(): if conf.supybot.reply.detailedErrors():
irc.error(utils.exnToString(e)) irc.error(utils.exnToString(e))
else: else:
@ -1126,17 +1140,31 @@ class PrivmsgCommandAndRegexp(Privmsg):
for name in self.regexps: for name in self.regexps:
method = getattr(self, name) method = getattr(self, name)
r = re.compile(method.__doc__, self.flags) r = re.compile(method.__doc__, self.flags)
self.res.append((r, method)) self.res.append((r, name))
for name in self.addressedRegexps: for name in self.addressedRegexps:
method = getattr(self, name) method = getattr(self, name)
r = re.compile(method.__doc__, self.flags) 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: 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: 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:') self.log.exception('Uncaught exception in callCommand:')
if conf.supybot.reply.detailedErrors(): if conf.supybot.reply.detailedErrors():
irc.error(utils.exnToString(e)) irc.error(utils.exnToString(e))
@ -1147,24 +1175,22 @@ class PrivmsgCommandAndRegexp(Privmsg):
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
if Privmsg.errored: 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()) self.name())
return return
for (r, method) in self.res: for (r, name) in self.res:
name = method.__name__
for m in r.finditer(msg.args[1]): for m in r.finditer(msg.args[1]):
proxy = self.Proxy(irc, msg) 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: if not Privmsg.handled:
s = addressed(irc.nick, msg) s = addressed(irc.nick, msg)
if s: if s:
for (r, method) in self.addressedRes: for (r, name) in self.addressedRes:
name = method.__name__
if Privmsg.handled and name not in self.alwaysCall: if Privmsg.handled and name not in self.alwaysCall:
continue continue
for m in r.finditer(s): for m in r.finditer(s):
proxy = self.Proxy(irc, msg) proxy = self.Proxy(irc, msg)
self.callCommand(method,proxy,msg,m,catchErrors=True) self.callCommand(name, proxy, msg, m, catchErrors=True)
Privmsg.handled = True Privmsg.handled = True

View File

@ -86,7 +86,9 @@ allowDefaultOwner = False
supybot = registry.Group() supybot = registry.Group()
supybot.setName('supybot') 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) return Group.register(name, group)
def registerGlobalValue(group, name, value): def registerGlobalValue(group, name, value):
@ -109,8 +111,8 @@ def registerPlugin(name, currentValue=None, public=True):
supybot.plugins.get(name).setValue(currentValue) supybot.plugins.get(name).setValue(currentValue)
return registerGroup(users.plugins, name) return registerGroup(users.plugins, name)
def channelValue(group, channel=None): def get(group, channel=None):
if channel is None: if channel is None or not group.channelValue:
return group() return group()
else: else:
return group.get(channel)() return group.get(channel)()
@ -120,7 +122,7 @@ def channelValue(group, channel=None):
### ###
users = registry.Group() users = registry.Group()
users.setName('users') users.setName('users')
registerGroup(users, 'plugins') registerGroup(users, 'plugins', orderAlphabetically=True)
def registerUserValue(group, name, value): def registerUserValue(group, name, value):
assert group._name.startswith('users') assert group._name.startswith('users')
@ -188,7 +190,8 @@ class Networks(registry.SpaceSeparatedSetOfStrings):
List = ircutils.IrcSet List = ircutils.IrcSet
registerGlobalValue(supybot, 'networks', 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): class Servers(registry.SpaceSeparatedListOfStrings):
def normalize(self, s): def normalize(self, s):
@ -278,8 +281,8 @@ registerChannelValue(supybot.reply.mores, 'instant',
registerGlobalValue(supybot.reply, 'oneToOne', registerGlobalValue(supybot.reply, 'oneToOne',
registry.Boolean(True, """Determines whether the bot will send registry.Boolean(True, """Determines whether the bot will send
multi-message replies in a single message or in multiple messages. For 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 safety purposes (so the bot is less likely to flood) it will normally send
everything in a single message.""")) everything in a single message, using mores if necessary."""))
class ValidBrackets(registry.OnlySomeStrings): class ValidBrackets(registry.OnlySomeStrings):
validStrings = ('', '[]', '<>', '{}', '()') validStrings = ('', '[]', '<>', '{}', '()')
@ -409,6 +412,11 @@ registerChannelValue(supybot.reply.whenAddressedBy, 'nick',
registry.Boolean(True, """Determines whether the bot will reply when people registry.Boolean(True, """Determines whether the bot will reply when people
address it by its nick, rather than with a prefix character.""")) 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 # Replies
### ###
@ -501,6 +509,7 @@ registerGlobalValue(supybot, 'flush',
inside the bot, your changes won't be flushed. To make this change inside the bot, your changes won't be flushed. To make this change
permanent, you must edit the registry yourself.""")) permanent, you must edit the registry yourself."""))
### ###
# supybot.commands. For stuff relating to commands. # supybot.commands. For stuff relating to commands.
### ###
@ -508,6 +517,42 @@ registerGroup(supybot, 'commands')
# supybot.commands.disabled moved to callbacks for canonicalName. # 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!) # 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 a new one. E.g. you can say: bot: 'config supybot.directories.plugins
[config supybot.directories.plugins], newPluginDirectory'.""")) [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', registerGlobalValue(supybot.plugins, 'alwaysLoadDefault',
registry.Boolean(True, """Determines whether the bot will always load registry.Boolean(True, """Determines whether the bot will always load
the default plugins (Admin, Channel, Config, Misc, Owner, and User) the default plugins (Admin, Channel, Config, Misc, Owner, and User)

View File

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

View File

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

View File

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