diff --git a/src/Admin.py b/src/Admin.py index 7b3d33adb..ae62b0d0d 100755 --- a/src/Admin.py +++ b/src/Admin.py @@ -288,45 +288,6 @@ class Admin(privmsgs.CapabilityCheckingPrivmsg): if not inAtLeastOneChannel: irc.replySuccess() - def disable(self, irc, msg, args): - """ - - Disables the command for all non-owner users. - """ - command = privmsgs.getArgs(args) - if command in ('enable', 'identify'): - irc.error('You can\'t disable %s!' % command) - else: - try: - capability = ircdb.makeAntiCapability(command) - except ValueError: - irc.error('%r is not a valid command.' % command) - return - if command in conf.supybot.defaultCapabilities(): - conf.supybot.defaultCapabilities().remove(command) - conf.supybot.defaultCapabilities().add(capability) - irc.replySuccess() - - def enable(self, irc, msg, args): - """ - - Re-enables the command for all non-owner users. - """ - command = privmsgs.getArgs(args) - command = command.lower() - L = [] - for capability in conf.supybot.defaultCapabilities(): - if ircdb.isAntiCapability(capability): - nonAntiCapability = ircdb.unAntiCapability(capability) - if nonAntiCapability.lower() == command: - L.append(capability) - if L: - for capability in L: - conf.supybot.defaultCapabilities().remove(capability) - irc.replySuccess() - else: - irc.error('That command wasn\'t disabled.') - def addcapability(self, irc, msg, args): """ diff --git a/src/Owner.py b/src/Owner.py index 478ab61fb..d05120617 100644 --- a/src/Owner.py +++ b/src/Owner.py @@ -115,7 +115,11 @@ def loadPluginClass(irc, module, register=None): conf.registerPlugin('Owner', True) conf.supybot.plugins.Owner.register('public', registry.Boolean(True, """Determines whether this plugin is publically visible.""")) -conf.registerGroup(conf.supybot, 'commands') + +### +# supybot.commands. +### + conf.registerGroup(conf.supybot.commands, 'defaultPlugins') conf.supybot.commands.defaultPlugins.help = utils.normalizeWhitespace(""" Determines what commands have default plugins set, and which plugins are set to @@ -555,6 +559,44 @@ class Owner(privmsgs.CapabilityCheckingPrivmsg): except AttributeError: # There's a cleaner way to do this, but I'm lazy. irc.error('I couldn\'t reconnect. You should restart me instead.') + def disable(self, irc, msg, args): + """[] + + Disables the command for all non-owner users. If + is given, only disables the from . + """ + (plugin, command) = privmsgs.getArgs(args, optional=1) + if not command: + (plugin, command) = (None, plugin) + conf.supybot.commands.disabled().add(command) + else: + conf.supybot.commands.disabled().add('%s.%s' % (plugin, command)) + if command in ('enable', 'identify'): + irc.error('You can\'t disable %s.' % command) + else: + self._disabled.add(command, plugin) + irc.replySuccess() + + def enable(self, irc, msg, args): + """[] + + Enables the command for all non-owner users. If + if given, only enables the from . + """ + (plugin, command) = privmsgs.getArgs(args, optional=1) + try: + if not command: + (plugin, command) = (None, plugin) + conf.supybot.commands.disabled().remove(command) + else: + name = '%s.%s' % (plugin, command) + conf.supybot.commands.disabled().remove(name) + self._disabled.remove(command, plugin) + irc.replySuccess() + except KeyError: + raise + irc.error('That command wasn\'t disabled.') + Class = Owner diff --git a/src/callbacks.py b/src/callbacks.py index 43917cd22..091f8d081 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -716,6 +716,63 @@ class CommandThread(threading.Thread): self.cb.threaded = self.originalThreaded +class CanonicalString(registry.NormalizedString): + def normalize(self, s): + return canonicalName(s) + +class CanonicalNameSet(utils.NormalizingSet): + def normalize(self, s): + return canonicalName(s) + +class Disabled(registry.SpaceSeparatedListOf): + Value = CanonicalString + List = CanonicalNameSet + +conf.registerGlobalValue(conf.supybot.commands, 'disabled', + Disabled([], """Determines what commands are currently disabled. Such + commands will not appear in command lists, etc. They will appear not even + to exist.""")) + +class DisabledCommands(object): + class CanonicalNameDict(utils.InsensitivePreservingDict): + def key(self, s): + return canonicalName(s) + + def __init__(self): + self.d = self.CanonicalNameDict() + for name in conf.supybot.commands.disabled(): + if '.' in name: + (plugin, command) = name.split('.', 1) + if command in self.d: + if self.d[command] is not None: + self.d[command].add(plugin) + else: + self.d[command] = CanonicalNameSet([plugin]) + else: + self.d[name] = None + + def disabled(self, command, plugin=None): + if command in self.d: + if self.d[command] is None: + return True + elif plugin in self.d[command]: + return True + return False + + def add(self, command, plugin=None): + if plugin is None: + self.d[command] = None + else: + if self.d[command] is not None: + self.d[command].add(plugin) + + def remove(self, command, plugin=None): + if plugin is None: + del self.d[command] + else: + if self.d[command] is not None: + self.d[command].remove(plugin) + class Privmsg(irclib.IrcCallback): """Base class for all Privmsg handlers.""" __metaclass__ = log.MetaFirewall @@ -729,8 +786,9 @@ class Privmsg(irclib.IrcCallback): errored = False Proxy = IrcObjectProxy commandArgs = ['self', 'irc', 'msg', 'args'] - # This must be class-scope, so all subclasses use the same one. + # This must be class-scope, so all plugins use the same one. _mores = ircutils.IrcDict() + _disabled = DisabledCommands() def __init__(self): self.__parent = super(Privmsg, self) myName = self.name() @@ -795,6 +853,8 @@ class Privmsg(irclib.IrcCallback): # Don't canonicalize this name: consider outFilter(self, irc, msg). # methodName = canonicalName(methodName) + if self._disabled.disabled(methodName, plugin=self.name()): + return False if hasattr(self, methodName): method = getattr(self, methodName) if inspect.ismethod(method): diff --git a/src/ircutils.py b/src/ircutils.py index 71ebf33a9..039c7d001 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -500,27 +500,11 @@ class IrcDict(utils.InsensitivePreservingDict): key = staticmethod(toLower) -class IrcSet(sets.Set): +class IrcSet(utils.NormalizingSet): """A sets.Set using IrcStrings instead of regular strings.""" - def __init__(self, seq=()): - self.__parent = super(IrcSet, self) - self.__parent.__init__() - for elt in seq: - self.add(elt) - - def add(self, s): - return self.__parent.add(IrcString(s)) - - def remove(self, s): - return self.__parent.remove(IrcString(s)) - - def discard(self, s): - return self.__parent.discard(IrcString(s)) - - def __contains__(self, s): - return self.__parent.__contains__(IrcString(s)) - has_key = __contains__ - + def normalize(self, s): + return IrcString(s) + def __reduce__(self): return (self.__class__, (list(self),)) diff --git a/src/utils.py b/src/utils.py index c09c29162..c3ecb8b36 100755 --- a/src/utils.py +++ b/src/utils.py @@ -42,6 +42,7 @@ import re import md5 import new import sha +import sets import types import socket import string @@ -49,6 +50,7 @@ import sgmllib import compiler import textwrap import UserDict +import itertools import htmlentitydefs from itertools import imap, ifilter @@ -640,6 +642,28 @@ class InsensitivePreservingDict(UserDict.DictMixin, object): def __reduce__(self): return (self.__class__, (dict(self.data.values()),)) + +class NormalizingSet(sets.Set): + def __init__(self, iterable=()): + iterable = itertools.imap(self.normalize, iterable) + super(NormalizingSet, self).__init__(iterable) + + def normalize(self, x): + return x + + def add(self, x): + return super(NormalizingSet, self).add(self.normalize(x)) + + def remove(self, x): + return super(NormalizingSet, self).remove(self.normalize(x)) + + def discard(self, x): + return super(NormalizingSet, self).discard(self.normalize(x)) + + def __contains__(self, x): + return super(NormalizingSet, self).__contains__(self.normalize(x)) + has_key = __contains__ + def mungeEmailForWeb(s): s = s.replace('@', ' AT ') s = s.replace('.', ' DOT ') diff --git a/test/test_Admin.py b/test/test_Admin.py index 42f6770f9..db1522ab1 100644 --- a/test/test_Admin.py +++ b/test/test_Admin.py @@ -76,17 +76,6 @@ class AdminTestCase(PluginTestCase, PluginDocumentation): def testRemoveCapability(self): self.assertError('removecapability alsdfkjasd foo') - def testDisable(self): - self.assertError('disable enable') - self.assertError('disable identify') - - def testEnable(self): - self.assertError('enable enable') - - def testEnableIsCaseInsensitive(self): - self.assertNotError('disable Foo') - self.assertNotError('enable foo') - def testJoin(self): m = self.getMsg('join #foo') self.assertEqual(m.command, 'JOIN') diff --git a/test/test_Owner.py b/test/test_Owner.py index 62d769618..565ff579e 100644 --- a/test/test_Owner.py +++ b/test/test_Owner.py @@ -96,6 +96,17 @@ class OwnerTestCase(PluginTestCase, PluginDocumentation): self.assertNotError('load ALIAS') self.assertNotError('unload ALIAS') + def testDisable(self): + self.assertError('disable enable') + self.assertError('disable identify') + + def testEnable(self): + self.assertError('enable enable') + + def testEnableIsCaseInsensitive(self): + self.assertNotError('disable Foo') + self.assertNotError('enable foo') + class FunctionsTestCase(SupyTestCase): def testLoadPluginModule(self):