mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-11-23 11:09:23 +01:00
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:
parent
3848ca4aa5
commit
ee70224aa3
@ -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 '
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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))
|
||||
|
||||
|
@ -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 '
|
||||
|
38
src/Admin.py
38
src/Admin.py
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
||||
|
54
src/Misc.py
54
src/Misc.py
@ -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.
|
||||
|
54
src/Owner.py
54
src/Owner.py
@ -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))
|
||||
|
26
src/User.py
26
src/User.py
@ -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:
|
||||
|
@ -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:
|
||||
|
224
src/callbacks.py
224
src/callbacks.py
@ -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):
|
||||
try:
|
||||
(maybeNick, rest) = payload.split(None, 1)
|
||||
while not ircutils.isNick(maybeNick, strictRfc=True):
|
||||
if maybeNick[-1].isalnum():
|
||||
return ''
|
||||
maybeNick = maybeNick[:-1]
|
||||
if ircutils.nickEqual(maybeNick, nick):
|
||||
return rest
|
||||
else:
|
||||
return ''
|
||||
except ValueError: # split didn't work.
|
||||
return ''
|
||||
elif payload and any(payload.startswith, prefixStrings):
|
||||
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():
|
||||
continue
|
||||
maybeNick = maybeNick[:-1]
|
||||
if ircutils.nickEqual(maybeNick, nick):
|
||||
return rest
|
||||
else:
|
||||
continue
|
||||
except ValueError: # split didn't work.
|
||||
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()
|
||||
method(irc, msg, *L)
|
||||
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.name())
|
||||
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
|
||||
|
||||
|
||||
|
61
src/conf.py
61
src/conf.py
@ -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)
|
||||
|
144
src/ircdb.py
144
src/ircdb.py
@ -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):
|
||||
return True
|
||||
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):
|
||||
return True
|
||||
for mask in self.ignores:
|
||||
if ircutils.hostmaskPatternEqual(mask, hostmask):
|
||||
return True
|
||||
if self.checkBan(hostmask):
|
||||
return True
|
||||
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,21 +769,33 @@ 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)
|
||||
fd.write(os.linesep)
|
||||
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:
|
||||
log.warning('IgnoresDB.flush called without self.filename.')
|
||||
@ -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:
|
||||
if ircutils.hostmaskPatternEqual(hostmask, prefix):
|
||||
return True
|
||||
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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user