From 66fc538a3ad7614d82289b402c2cc1b6f3352dd4 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Sun, 5 Oct 2003 11:13:20 +0000 Subject: [PATCH] Changed the anti-capability prefix to - instead of !. --- docs/CAPABILITIES | 73 ++++++++++++++++++++++++++++----- src/conf.py | 2 +- src/ircdb.py | 10 ++--- test/test_ircdb.py | 100 ++++++++++++++++++++++----------------------- 4 files changed, 118 insertions(+), 67 deletions(-) diff --git a/docs/CAPABILITIES b/docs/CAPABILITIES index 8a8a25a95..e2f53d200 100644 --- a/docs/CAPABILITIES +++ b/docs/CAPABILITIES @@ -1,17 +1,68 @@ -Ok, some some explanation of the capabilities system is probably in order. +Ok, some some explanation of the capabilities system is probably in order. +With most IRC bots (including the ones I've written myself prior to this one) +"what a user can do" is set in one of two ways. On the *really* simple bots, +each user has a numeric "level" and commands check to see if a user has a "high +enough level" to perform some operation. On bots that are slightly more +complicated, users have a list of "flags" whose meanings are hardcoded, and the +bot checks to see if a user possesses the necessary flag before performing some +operation. Both methods, IMO, are rather arbitrary, and force the user and the +programmer to be unduly confined to less expressive constructs. -With most IRC bots (including the ones I've written myself prior to this one) "what a user can do" is set in one of two ways. On the *really* simple bots, each user has a numeric "level" and commands check to see if a user has a "high enough level" to perform some operation. On bots that are slightly more complicated, users have a list of "flags" whose meanings are hardcoded, and the bot checks to see if a user possesses the necessary flag before performing some operation. Both methods, IMO, are rather arbitrary, and force the user and the programmer to be unduly confined to less expressive constructs. +This bot is different. Every user has a set of "capabilities" that is +consulted every time they give the bot a command. Commands, rather than +checking for a user level of 100, or checking if the user has an "o" flag, are +instead able to check if a user has the "owner" capability. At this point such +a difference might not seem revolutionary, but at least we can already tell +that this method is self-documenting, and easier for users and developers to +understand what's truly going on. -This bot is different. Every user has a set of "capabilities" that is consulted every time they give the bot a command. Commands, rather than checking for a user level of 100, or checking if the user has an "o" flag, are instead able to check if a user has the "owner" capability. At this point such a difference might not seem revolutionary, but at least we can already tell that this method is self-documenting, and easier for users and developers to understand what's truly going on. +If that was all, well, the capability system would be "cool", but not many +people would say it was "awesome". But it *is* awesome! Several things are +happening behind the scene that make it awesome, and these are things that +couldn't happen if the bot was using numeric userlevels or single-character +flags. First, whenever a user issues the bot a command, the command dispatcher +checks to make sure the user doesn't have the "anticapability" for that +command. An anticapability is a capability that, instead of saying "what a +user can do", says what a user *cannot* do. It's formed rather simply by +adding an exclamation point ("-") to the beginning of a capability; "rot13" is +a capability, and "-rot13" is an anticapability. Anyway, when a user issues +the bot a command, perhaps "calc" or "help", the bot first checks to make sure +the user doesn't have the "-calc" or the "-help" capabilities before even +considering responding to the user. So commands can be turned on or off on a +*per user* basis, offering finegrained control not often (if at all!) seen in +other bots. -If that was all, well, the capability system would be "cool", but not many people would say it was "awesome". But it *is* awesome! Several things are happening behind the scene that make it awesome, and these are things that couldn't happen if the bot was using numeric userlevels or single-character flags. First, whenever a user issues the bot a command, the command dispatcher checks to make sure the user doesn't have the "anticapability" for that command. An anticapability is a capability that, instead of saying "what a user can do", says what a user *cannot* do. It's formed rather simply by adding an exclamation point ("!") to the beginning of a capability; "rot13" is a capability, and "!rot13" is an anticapability. Anyway, when a user issues the bot a command, perhaps "calc" or "help", the bot first checks to make sure the user doesn't have the "!calc" or the "!help" capabilities before even considering responding to the user. So commands can be turned on or off on a *per user* basis, offering finegrained control not often (if at all!) seen in other bots. +But that's not all! The capabilities system also supports *Channel* +capabilities, which are capabilities that only apply to a specific channel; +they're of the form "#channel.capability". Whenever a user issues a command to +the bot in a channel, the command dispatcher also checks to make sure the user +doesn't have the anticapability for that command *in that channel*, and if the +user does, the bot won't respond to the user in the channel. Thus now, in +addition to having the ability to turn individual commands on or off for an +individual user, we can now turn commands on or off for an individual user on +an individual channel! -But that's not all! The capabilities system also supports *Channel* capabilities, which are capabilities that only apply to a specific channel; they're of the form "#channel.capability". Whenever a user issues a command to the bot in a channel, the command dispatcher also checks to make sure the user doesn't have the anticapability for that command *in that channel*, and if the user does, the bot won't respond to the user in the channel. Thus now, in addition to having the ability to turn individual commands on or off for an individual user, we can now turn commands on or off for an individual user on an individual channel! +So when a user "foo" sends a command "bar" to the bot on channel "#baz", first +the bot checks to see if the user has the anticapability for the command by +itself, "-bar". If so, it returns right then and there, compltely ignoring the +fact that the user issued that command to it. If the user doesn't have that +anticapability, then the bot checks to see if the user issued the command over +a channel, and if so, checks to see if the user has the antichannelcapability +for that command, "#baz.-bar". If so, again, he returns right then and there +and doesn't even think about responding to the bot. If neither of these +anticapabilities are present, then the bot just responds to the user like +normal. -So when a user "foo" sends a command "bar" to the bot on channel "#baz", first the bot checks to see if the user has the anticapability for the command by itself, "!bar". If so, it returns right then and there, compltely ignoring the fact that the user issued that command to it. If the user doesn't have that anticapability, then the bot checks to see if the user issued the command over a channel, and if so, checks to see if the user has the antichannelcapability for that command, "#baz.!bar". If so, again, he returns right then and there and doesn't even think about responding to the bot. If neither of these anticapabilities are present, then the bot just responds to the user like normal. +From a programmatical perspective, capabilties are easy to use and flexible. +Any command can check if a user has any capability, even ones not thought of +when the bot was originally written. Commands/Callbacks can add their own +capabilities -- it's as easy as just checking for a capability and documenting +somewhere that a user needs that capability to do something. -From a programmatical perspective, capabilties are easy to use and flexible. Any command can check if a user has any capability, even ones not thought of when the bot was originally written. Commands/Callbacks can add their own capabilities -- it's as easy as just checking for a capability and documenting somewhere that a user needs that capability to do something. - -From an end-user perspective, capabilities remove a lot of the mystery and esotery of bot control, in addition to giving the user absolutely finegrained control over what users are allowed to do with the bot. Additionally, defaults can be set by the end-user for both individual channels and for the bot as a whole, letting an end-user set the policy he wants the bot to follow for users that haven't yet registered in his user database. - -It's really a revolution! +From an end-user perspective, capabilities remove a lot of the mystery and +esotery of bot control, in addition to giving the user absolutely finegrained +control over what users are allowed to do with the bot. Additionally, defaults +can be set by the end-user for both individual channels and for the bot as a +whole, letting an end-user set the policy he wants the bot to follow for users +that haven't yet registered in his user database. +It's really a revolution! diff --git a/src/conf.py b/src/conf.py index d67c216de..31543b0b9 100644 --- a/src/conf.py +++ b/src/conf.py @@ -117,7 +117,7 @@ enablePipeSyntax = False # defaultCapabilities: Capabilities allowed to everyone by default. You almost # certainly want to have !owner and !admin in here. ### -defaultCapabilities = sets.Set(['!owner', '!admin']) +defaultCapabilities = sets.Set(['-owner', '-admin']) ### # reply%s: Stock replies for various reasons. diff --git a/src/ircdb.py b/src/ircdb.py index bb951bfa2..d5e0ee82a 100644 --- a/src/ircdb.py +++ b/src/ircdb.py @@ -65,7 +65,7 @@ def isAntiCapability(capability): """Returns True if capability is an anticapability; False otherwise.""" if isChannelCapability(capability): (_, capability) = fromChannelCapability(capability) - return capability[0] == '!' + return capability[0] == '-' def makeAntiCapability(capability): """Returns the anticapability of a given capability.""" @@ -73,9 +73,9 @@ def makeAntiCapability(capability): 'work on anticapabilities; you probably want invertCapability.' if '.' in capability: (channel, capability) = fromChannelCapability(capability) - return '%s.!%s' % (channel, capability) + return '%s.-%s' % (channel, capability) else: - return '!' + capability + return '-' + capability def unAntiCapability(capability): """Takes an anticapability and returns the non-anti form.""" @@ -173,9 +173,9 @@ class UserCapabilitySet(CapabilitySet): return CapabilitySet.check(self, capability) def add(self, capability): - """Adds a capability to the set. Just make sure it's not ~owner.""" + """Adds a capability to the set. Just make sure it's not -owner.""" capability = ircutils.toLower(capability) - assert capability != '!owner', '"!owner" disallowed.' + assert capability != '-owner', '"-owner" disallowed.' CapabilitySet.add(self, capability) class IrcUser(object): diff --git a/test/test_ircdb.py b/test/test_ircdb.py index c1dc4398a..c54ff533a 100644 --- a/test/test_ircdb.py +++ b/test/test_ircdb.py @@ -43,9 +43,9 @@ class FunctionsTestCase(unittest.TestCase): def testIsAntiCapability(self): self.failIf(ircdb.isAntiCapability('foo')) self.failIf(ircdb.isAntiCapability('#foo.bar')) - self.failUnless(ircdb.isAntiCapability('!foo')) - self.failUnless(ircdb.isAntiCapability('#foo.!bar')) - self.failUnless(ircdb.isAntiCapability('#foo.bar.!baz')) + self.failUnless(ircdb.isAntiCapability('-foo')) + self.failUnless(ircdb.isAntiCapability('#foo.-bar')) + self.failUnless(ircdb.isAntiCapability('#foo.bar.-baz')) def testIsChannelCapability(self): self.failIf(ircdb.isChannelCapability('foo')) @@ -53,12 +53,12 @@ class FunctionsTestCase(unittest.TestCase): self.failUnless(ircdb.isChannelCapability('#foo.bar.baz')) def testMakeAntiCapability(self): - self.assertEqual(ircdb.makeAntiCapability('foo'), '!foo') - self.assertEqual(ircdb.makeAntiCapability('#foo.bar'), '#foo.!bar') + self.assertEqual(ircdb.makeAntiCapability('foo'), '-foo') + self.assertEqual(ircdb.makeAntiCapability('#foo.bar'), '#foo.-bar') def testMakeChannelCapability(self): self.assertEqual(ircdb.makeChannelCapability('#f', 'b'), '#f.b') - self.assertEqual(ircdb.makeChannelCapability('#f', '!b'), '#f.!b') + self.assertEqual(ircdb.makeChannelCapability('#f', '-b'), '#f.-b') def testFromChannelCapability(self): self.assertEqual(ircdb.fromChannelCapability('#foo.bar'), @@ -67,16 +67,16 @@ class FunctionsTestCase(unittest.TestCase): ['#foo.bar', 'baz']) def testUnAntiCapability(self): - self.assertEqual(ircdb.unAntiCapability('!bar'), 'bar') - self.assertEqual(ircdb.unAntiCapability('#foo.!bar'), '#foo.bar') - self.assertEqual(ircdb.unAntiCapability('#foo.bar.!baz'), + self.assertEqual(ircdb.unAntiCapability('-bar'), 'bar') + self.assertEqual(ircdb.unAntiCapability('#foo.-bar'), '#foo.bar') + self.assertEqual(ircdb.unAntiCapability('#foo.bar.-baz'), '#foo.bar.baz') def testInvertCapability(self): - self.assertEqual(ircdb.invertCapability('bar'), '!bar') - self.assertEqual(ircdb.invertCapability('!bar'), 'bar') - self.assertEqual(ircdb.invertCapability('#foo.bar'), '#foo.!bar') - self.assertEqual(ircdb.invertCapability('#foo.!bar'), '#foo.bar') + self.assertEqual(ircdb.invertCapability('bar'), '-bar') + self.assertEqual(ircdb.invertCapability('-bar'), 'bar') + self.assertEqual(ircdb.invertCapability('#foo.bar'), '#foo.-bar') + self.assertEqual(ircdb.invertCapability('#foo.-bar'), '#foo.bar') class CapabilitySetTestCase(unittest.TestCase): @@ -85,31 +85,31 @@ class CapabilitySetTestCase(unittest.TestCase): self.assertRaises(KeyError, d.check, 'foo') d = ircdb.CapabilitySet(('foo',)) self.failUnless(d.check('foo')) - self.failIf(d.check('!foo')) + self.failIf(d.check('-foo')) d.add('bar') self.failUnless(d.check('bar')) - self.failIf(d.check('!bar')) - d.add('!baz') + self.failIf(d.check('-bar')) + d.add('-baz') self.failIf(d.check('baz')) - self.failUnless(d.check('!baz')) - d.add('!bar') + self.failUnless(d.check('-baz')) + d.add('-bar') self.failIf(d.check('bar')) - self.failUnless(d.check('!bar')) - d.remove('!bar') - self.assertRaises(KeyError, d.check, '!bar') + self.failUnless(d.check('-bar')) + d.remove('-bar') + self.assertRaises(KeyError, d.check, '-bar') self.assertRaises(KeyError, d.check, 'bar') class UserCapabilitySetTestCase(unittest.TestCase): def testOwnerHasAll(self): d = ircdb.UserCapabilitySet(('owner',)) - self.failIf(d.check('!foo')) + self.failIf(d.check('-foo')) self.failUnless(d.check('foo')) def testOwnerIsAlwaysPresent(self): d = ircdb.UserCapabilitySet() self.failUnless('owner' in d) - self.failUnless('!owner' in d) + self.failUnless('-owner' in d) self.failIf(d.check('owner')) d.add('owner') self.failUnless(d.check('owner')) @@ -120,43 +120,43 @@ class CapabilitySetTestCase(unittest.TestCase): def testContains(self): s = ircdb.CapabilitySet() self.failIf('foo' in s) - self.failIf('!foo' in s) + self.failIf('-foo' in s) s.add('foo') self.failUnless('foo' in s) - self.failUnless('!foo' in s) + self.failUnless('-foo' in s) s.remove('foo') self.failIf('foo' in s) - self.failIf('!foo' in s) - s.add('!foo') + self.failIf('-foo' in s) + s.add('-foo') self.failUnless('foo' in s) - self.failUnless('!foo' in s) + self.failUnless('-foo' in s) def testCheck(self): s = ircdb.CapabilitySet() self.assertRaises(KeyError, s.check, 'foo') - self.assertRaises(KeyError, s.check, '!foo') + self.assertRaises(KeyError, s.check, '-foo') s.add('foo') self.failUnless(s.check('foo')) - self.failIf(s.check('!foo')) + self.failIf(s.check('-foo')) s.remove('foo') self.assertRaises(KeyError, s.check, 'foo') - self.assertRaises(KeyError, s.check, '!foo') - s.add('!foo') + self.assertRaises(KeyError, s.check, '-foo') + s.add('-foo') self.failIf(s.check('foo')) - self.failUnless(s.check('!foo')) - s.remove('!foo') + self.failUnless(s.check('-foo')) + s.remove('-foo') self.assertRaises(KeyError, s.check, 'foo') - self.assertRaises(KeyError, s.check, '!foo') + self.assertRaises(KeyError, s.check, '-foo') def testAdd(self): s = ircdb.CapabilitySet() s.add('foo') - s.add('!foo') + s.add('-foo') self.failIf(s.check('foo')) - self.failUnless(s.check('!foo')) + self.failUnless(s.check('-foo')) s.add('foo') self.failUnless(s.check('foo')) - self.failIf(s.check('!foo')) + self.failIf(s.check('-foo')) class UserCapabilitySetTestCase(unittest.TestCase): @@ -164,10 +164,10 @@ class UserCapabilitySetTestCase(unittest.TestCase): s = ircdb.UserCapabilitySet() s.add('owner') self.failUnless('foo' in s) - self.failUnless('!foo' in s) + self.failUnless('-foo' in s) self.failUnless(s.check('owner')) - self.failIf(s.check('!owner')) - self.failIf(s.check('!foo')) + self.failIf(s.check('-owner')) + self.failIf(s.check('-foo')) self.failUnless(s.check('foo')) @@ -176,20 +176,20 @@ class IrcUserTestCase(unittest.TestCase): u = ircdb.IrcUser() u.addCapability('foo') self.failUnless(u.checkCapability('foo')) - self.failIf(u.checkCapability('!foo')) - u.addCapability('!bar') - self.failUnless(u.checkCapability('!bar')) + self.failIf(u.checkCapability('-foo')) + u.addCapability('-bar') + self.failUnless(u.checkCapability('-bar')) self.failIf(u.checkCapability('bar')) u.removeCapability('foo') - u.removeCapability('!bar') + u.removeCapability('-bar') self.assertRaises(KeyError, u.checkCapability, 'foo') - self.assertRaises(KeyError, u.checkCapability, '!bar') + self.assertRaises(KeyError, u.checkCapability, '-bar') def testOwner(self): u = ircdb.IrcUser() u.addCapability('owner') self.failUnless(u.checkCapability('foo')) - self.failIf(u.checkCapability('!foo')) + self.failIf(u.checkCapability('-foo')) def testInitCapabilities(self): u = ircdb.IrcUser(capabilities=['foo']) @@ -221,7 +221,7 @@ class IrcUserTestCase(unittest.TestCase): def testIgnore(self): u = ircdb.IrcUser(ignore=True) self.failIf(u.checkCapability('foo')) - self.failUnless(u.checkCapability('!foo')) + self.failUnless(u.checkCapability('-foo')) def testRemoveCapability(self): u = ircdb.IrcUser(capabilities=('foo',)) @@ -247,10 +247,10 @@ class IrcChannelTestCase(unittest.TestCase): c = ircdb.IrcChannel() c.setDefaultCapability(False) self.failIf(c.checkCapability('foo')) - self.failUnless(c.checkCapability('!foo')) + self.failUnless(c.checkCapability('-foo')) c.setDefaultCapability(True) self.failUnless(c.checkCapability('foo')) - self.failIf(c.checkCapability('!foo')) + self.failIf(c.checkCapability('-foo')) def testLobotomized(self): c = ircdb.IrcChannel(lobotomized=True)