diff --git a/plugins/Dict.py b/plugins/Dict.py index d48922140..8f6dae6b9 100644 --- a/plugins/Dict.py +++ b/plugins/Dict.py @@ -43,11 +43,11 @@ import dictclient import supybot.conf as conf import supybot.utils as utils +from supybot.commands import * import supybot.plugins as plugins -import supybot.webutils as webutils -import supybot.registry as registry import supybot.ircutils as ircutils -import supybot.privmsgs as privmsgs +import supybot.registry as registry +import supybot.webutils as webutils import supybot.callbacks as callbacks def configure(advanced): @@ -84,6 +84,7 @@ class Dict(callbacks.Privmsg): irc.reply(utils.commaAndify(dbs)) except socket.error, e: irc.error(webutils.strError(e)) + dictionaries = wrap(dictionaries) def random(self, irc, msg, args): """takes no arguments @@ -97,23 +98,21 @@ class Dict(callbacks.Privmsg): irc.reply(random.choice(dbs)) except socket.error, e: irc.error(webutils.strError(e)) + random = wrap(random) - def dict(self, irc, msg, args): + def dict(self, irc, msg, args, words): """[] Looks up the definition of on dict.org's dictd server. """ - if not args: - raise callbacks.ArgumentError try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) except socket.error, e: - irc.error(webutils.strError(e)) - return + irc.error(webutils.strError(e), Raise=True) dbs = sets.Set(conn.getdbdescs()) - if args[0] in dbs: - dictionary = args.pop(0) + if words[0] in dbs: + dictionary = words.pop(0) else: default = self.registryValue('default', msg.args[0]) if default in dbs: @@ -123,9 +122,9 @@ class Dict(callbacks.Privmsg): self.log.info('Default dict for %s is not a supported ' 'dictionary: %s.', msg.args[0], default) dictionary = '*' - word = privmsgs.getArgs(args) - if not word: + if not words: irc.error('You must give a word to define.', Raise=True) + word = ' '.join(words) definitions = conn.define(dictionary, word) dbs = sets.Set() if not definitions: @@ -149,6 +148,7 @@ class Dict(callbacks.Privmsg): else: s = '; '.join(L) irc.reply(s) + dict = wrap(dict, [many('something')]) Class = Dict diff --git a/plugins/MoobotFactoids.py b/plugins/MoobotFactoids.py index 33835c269..7a1ec4e51 100644 --- a/plugins/MoobotFactoids.py +++ b/plugins/MoobotFactoids.py @@ -54,6 +54,7 @@ import supybot.registry as registry import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils +from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.privmsgs as privmsgs @@ -123,6 +124,13 @@ conf.registerChannelValue(conf.supybot.plugins.MoobotFactoids, class SqliteMoobotDB(object): def __init__(self, filename): self.filename = filename + self.dbs = ircutils.IrcDict() + + def close(self): + for db in self.dbs.itervalues(): + db.close() + self.dbs.clear() + def _getDb(self, channel): try: import sqlite @@ -130,26 +138,29 @@ class SqliteMoobotDB(object): raise callbacks.Error, \ 'You need to have PySQLite installed to use this ' \ 'plugin. Download it at ' + if channel in self.dbs: + return self.dbs[channel] filename = plugins.makeChannelFilename(self.filename, channel) if os.path.exists(filename): - db = sqlite.connect(filename) - else: - db = sqlite.connect(filename) - cursor = db.cursor() - cursor.execute("""CREATE TABLE factoids ( - key TEXT PRIMARY KEY, - created_by INTEGER, - created_at TIMESTAMP, - modified_by INTEGER, - modified_at TIMESTAMP, - locked_at TIMESTAMP, - locked_by INTEGER, - last_requested_by TEXT, - last_requested_at TIMESTAMP, - fact TEXT, - requested_count INTEGER - )""") - db.commit() + self.dbs[channel] = sqlite.connect(filename) + return self.dbs[channel] + db = sqlite.connect(filename) + self.dbs[channel] = db + cursor = db.cursor() + cursor.execute("""CREATE TABLE factoids ( + key TEXT PRIMARY KEY, + created_by INTEGER, + created_at TIMESTAMP, + modified_by INTEGER, + modified_at TIMESTAMP, + locked_at TIMESTAMP, + locked_by INTEGER, + last_requested_by TEXT, + last_requested_at TIMESTAMP, + fact TEXT, + requested_count INTEGER + )""") + db.commit() return db def getFactoid(self, channel, key): @@ -288,6 +299,7 @@ class SqliteMoobotDB(object): def getKeysByGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() + glob = '%%%s%%' % glob cursor.execute("""SELECT key FROM factoids WHERE key LIKE %s ORDER BY key""", glob) if cursor.rowcount == 0: @@ -298,6 +310,7 @@ class SqliteMoobotDB(object): def getKeysByValueGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() + glob = '%%%s%%' % glob cursor.execute("""SELECT key FROM factoids WHERE fact LIKE %s ORDER BY key""", glob) if cursor.rowcount == 0: @@ -307,21 +320,28 @@ class SqliteMoobotDB(object): MoobotDB = plugins.DB('MoobotFactoids', {'sqlite': SqliteMoobotDB}) -## We define our own getChannel so we can set raiseError=False in one place. -# Actually, we'll go ahead and raise the error and say that everything must be -# in channel. The smart users will know that they can specify the channel name -# for certain commands. +## We define our own getChannel so we can take advantage of the channeldb +# wrapper from non-command methods. This means that the bot will use +# supybot.databases.plugins.channelSpecific.channel as the default channel +# when the user is talking to the bot privately. def getChannel(irc, msg, args=()): - try: - return privmsgs.getChannel(msg, args) - except callbacks.Error: - irc.error('Command must be sent in a channel.', Raise=True) + spec = Spec(['channeldb']) + state = spec(irc, msg, args) + return state.channel class MoobotFactoids(callbacks.Privmsg): callBefore = ['Dunno'] def __init__(self): self.db = MoobotDB() - super(MoobotFactoids, self).__init__() + self.__parent = super(MoobotFactoids, self) + self.__parent.__init__() + + def die(self): + self.__parent.die() + self.db.close() + + def reset(self): + self.db.close() _replyTag = '' _actionTag = '' @@ -462,28 +482,25 @@ class MoobotFactoids(callbacks.Privmsg): self.db.addFactoid(channel, key, fact, id) irc.replySuccess() - def literal(self, irc, msg, args): + def literal(self, irc, msg, args, channel, key): """[] Returns the literal factoid for the given factoid key. No parsing of the factoid value is done as it is with normal retrieval. is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - key = privmsgs.getArgs(args) fact = self._getFactoid(irc, channel, key) fact = fact[0] irc.reply(fact) + literal = wrap(literal, ['channeldb', 'text']) - def factinfo(self, irc, msg, args): + def factinfo(self, irc, msg, args, channel, key): """[] Returns the various bits of info on the factoid for the given key. is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - key = privmsgs.getArgs(args) # Start building the response string s = key + ": " # Next, get all the info and build the response piece by piece @@ -521,17 +538,12 @@ class MoobotFactoids(callbacks.Privmsg): lock_by = ircdb.users.getUser(locked_by).name s += " Locked by %s on %s." % (lock_by, lock_at) irc.reply(s) + factinfo = wrap(factinfo, ['channeldb', 'text']) - def _lock(self, irc, msg, args, locking=True): - self.log.debug('in _lock') - try: - id = ircdb.users.getUserId(msg.prefix) - except KeyError: - irc.errorNotRegistered() - return - self.log.debug('id: %s' % id) - channel = getChannel(irc, msg, args) - key = privmsgs.getArgs(args) + def _lock(self, irc, msg, channel, user, key, locking=True): + #self.log.debug('in _lock') + #self.log.debug('id: %s' % id) + id = user.id info = self.db.getFactinfo(channel, key) if not info: irc.error('No such factoid: "%s"' % key) @@ -545,8 +557,8 @@ class MoobotFactoids(callbacks.Privmsg): irc.error('Factoid "%s" is not locked.' % key) return # Can only lock/unlock own factoids unless you're an admin - self.log.debug('admin?: %s' % ircdb.checkCapability(id, 'admin')) - self.log.debug('created_by: %s' % created_by) + #self.log.debug('admin?: %s' % ircdb.checkCapability(id, 'admin')) + #self.log.debug('created_by: %s' % created_by) if not (ircdb.checkCapability(id, 'admin') or created_by == id): if locking: s = "lock" @@ -562,25 +574,27 @@ class MoobotFactoids(callbacks.Privmsg): self.db.unlock(channel, key) irc.replySuccess() - def lock(self, irc, msg, args): + def lock(self, irc, msg, args, channel, user, key): """[] Locks the factoid with the given factoid key. Requires that the user be registered and have created the factoid originally. is only necessary if the message isn't sent in the channel itself. """ - self._lock(irc, msg, args, True) + self._lock(irc, msg, channel, user, key, True) + lock = wrap(lock, ['channeldb', 'user', 'text']) - def unlock(self, irc, msg, args): + def unlock(self, irc, msg, args, channel, user, key): """[] Unlocks the factoid with the given factoid key. Requires that the user be registered and have locked the factoid. is only necessary if the message isn't sent in the channel itself. """ - self._lock(irc, msg, args, False) + self._lock(irc, msg, channel, user, key, False) + unlock = wrap(unlock, ['channeldb', 'user', 'text']) - def most(self, irc, msg, args): + def most(self, irc, msg, args, channel, method): """[] {popular|authored|recent} Lists the most {popular|authored|recent} factoids. "popular" lists the @@ -589,14 +603,14 @@ class MoobotFactoids(callbacks.Privmsg): is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - arg = privmsgs.getArgs(args) - arg = arg.capitalize() - method = getattr(self, '_most%s' % arg, None) + method = method.capitalize() + method = getattr(self, '_most%s' % method, None) if method is None: raise callbacks.ArgumentError limit = self.registryValue('mostCount', channel) method(irc, channel, limit) + most = wrap(most, ['channeldb', + ('literal', ('popular', 'authored', 'recent'))]) def _mostAuthored(self, irc, channel, limit): results = self.db.mostAuthored(channel, limit) @@ -628,7 +642,7 @@ class MoobotFactoids(callbacks.Privmsg): else: irc.error('No factoids have been requested from my database.') - def listauth(self, irc, msg, args): + def listauth(self, irc, msg, args, channel, author): """[] Lists the keys of the factoids with the given author. Note that if an @@ -636,8 +650,6 @@ class MoobotFactoids(callbacks.Privmsg): this function (so don't use integer usernames!). is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - author = privmsgs.getArgs(args) try: id = ircdb.users.getUserId(author) except KeyError: @@ -650,18 +662,16 @@ class MoobotFactoids(callbacks.Privmsg): s = 'Author search for "%s" (%s found): %s' % \ (author, len(keys), utils.commaAndify(keys)) irc.reply(s) + listauth = wrap(listauth, ['channeldb', 'something']) - def listkeys(self, irc, msg, args): + def listkeys(self, irc, msg, args, channel, search): """[] Lists the keys of the factoids whose key contains the provided text. is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - search = privmsgs.getArgs(args) - glob = '%' + search + '%' - results = self.db.getKeysByGlob(channel, glob) + results = self.db.getKeysByGlob(channel, search) if not results: irc.reply('No keys matching "%s" found.' % search) elif len(results) == 1 and \ @@ -673,18 +683,16 @@ class MoobotFactoids(callbacks.Privmsg): s = 'Key search for "%s" (%s found): %s' % \ (search, len(keys), utils.commaAndify(keys)) irc.reply(s) + listkeys = wrap(listkeys, ['channeldb', 'text']) - def listvalues(self, irc, msg, args): + def listvalues(self, irc, msg, args, channel, search): """[] Lists the keys of the factoids whose value contains the provided text. is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - search = privmsgs.getArgs(args) - glob = '%' + search + '%' - results = self.db.getKeysByValueGlob(channel, glob) + results = self.db.getKeysByValueGlob(channel, search) if not results: irc.reply('No values matching "%s" found.' % search) return @@ -692,36 +700,35 @@ class MoobotFactoids(callbacks.Privmsg): s = 'Value search for "%s" (%s found): %s' % \ (search, len(keys), utils.commaAndify(keys)) irc.reply(s) + listvalues = wrap(listvalues, ['channeldb', 'text']) - def delete(self, irc, msg, args): + def remove(self, irc, msg, args, channel, key): """[] Deletes the factoid with the given key. is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) - key = privmsgs.getArgs(args) _ = self._getUserId(irc, msg.prefix) _ = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) self.db.removeFactoid(channel, key) irc.replySuccess() + remove = wrap(remove, ['channeldb', 'text']) - # XXX What the heck? Why are there two definitions of randomfactoid? - def randomfactoid(self, irc, msg, args): + def random(self, irc, msg, args, channel): """[] Displays a random factoid (along with its key) from the database. is only necessary if the message isn't sent in the channel itself. """ - channel = getChannel(irc, msg, args) results = self.db.randomFactoid(channel) if not results: irc.error('No factoids in the database.') return (fact, key) = results irc.reply('Random factoid: "%s" is "%s"' % (key, fact)) + random = wrap(random, ['channeldb']) Class = MoobotFactoids diff --git a/test/test_MoobotFactoids.py b/test/test_MoobotFactoids.py index c6b39c976..7b023e64d 100644 --- a/test/test_MoobotFactoids.py +++ b/test/test_MoobotFactoids.py @@ -65,7 +65,7 @@ if sqlite is not None: self._testOptions('(|a)', ['a', '']) self._testOptions('((a)|(b))', ['(a)', '(b)']) - class FactoidsTestCase(ChannelPluginTestCase, PluginDocumentation): + class FactoidsTestCase(ChannelPluginTestCase): plugins = ('MoobotFactoids', 'User', 'Utilities') config = {'reply.whenNotCommand': False} def setUp(self): @@ -116,7 +116,7 @@ if sqlite is not None: self.assertResponse('mOo', 'foo') self.assertResponse('MoO', 'foo') # Check the "_is_" ability - self.assertNotError('delete moo') + self.assertNotError('remove moo') self.assertNotError('moo _is_ foo') self.assertResponse('moo', 'foo') self.assertNotError('foo is bar _is_ baz') @@ -279,12 +279,12 @@ if sqlite is not None: self.assertRegexp('listauth tester', 'tester.*\(1 found\):.*moo') self.assertError('listauth moo') - def testDelete(self): + def testRemove(self): self.assertNotError('moo is moo') self.assertNotError('lock moo') - self.assertError('delete moo') + self.assertError('remove moo') self.assertNotError('unlock moo') - self.assertNotError('delete moo') + self.assertNotError('remove moo') def testAugmentFactoid(self): self.assertNotError('moo is foo') @@ -332,10 +332,10 @@ if sqlite is not None: self.assertResponse('listkeys foo', 'Key search for "foo" ' '(1 found): "foo"') - def testRandomFactoid(self): + def testRandom(self): self.assertNotError('foo is bar') self.assertNotError('bar is baz') - self.assertRegexp('randomfactoid', r'bar|baz') + self.assertRegexp('random', r'bar|baz') # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: