From 3899f33d549ce17dc3d24f0aa225dcf7af579781 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 28 Oct 2004 17:20:37 +0000 Subject: [PATCH] Added Lart and Praise plugins, deprecated FunDB, converted Dunno and Success to the new plugins.ChannelIdDatabasePlugin. --- plugins/Dunno.py | 142 +--------------------------------- plugins/FunDB.py | 2 + plugins/Lart.py | 118 +++++++++++++++++++++++++++++ plugins/Praise.py | 117 ++++++++++++++++++++++++++++ plugins/Quote.py | 68 +++++++++++++++++ plugins/Quotes.py | 2 + plugins/Success.py | 123 +----------------------------- plugins/__init__.py | 180 ++++++++++++++++++++++++++++++++++++++++++-- 8 files changed, 484 insertions(+), 268 deletions(-) create mode 100644 plugins/Lart.py create mode 100644 plugins/Praise.py create mode 100644 plugins/Quote.py diff --git a/plugins/Dunno.py b/plugins/Dunno.py index 15f2cd5e3..33d9a5e75 100644 --- a/plugins/Dunno.py +++ b/plugins/Dunno.py @@ -28,9 +28,9 @@ ### """ -The Dunno module is used to spice up the 'replyWhenNotCommand' behavior with -random 'I dunno'-like responses. If you want something spicier than ' is -not a valid command'-like responses, use this plugin. +The Dunno module is used to spice up the reply when given an invalid command +with random 'I dunno'-like responses. If you want something spicier than +' is not a valid command'-like responses, use this plugin. """ import supybot @@ -64,46 +64,13 @@ conf.registerChannelValue(conf.supybot.plugins.Dunno, 'prefixNick', registry.Boolean(True, """Determines whether the bot will prefix the nick of the user giving an invalid command to the "dunno" response.""")) -class DbiDunnoDB(plugins.DbiChannelDB): - class DB(dbi.DB): - class Record(dbi.Record): - __fields__ = [ - 'at', - 'by', - 'text', - ] - def __init__(self, filename): - # We use self.__class__ here because apparently DB isn't in our - # scope. python-- - self.__parent = super(self.__class__, self) - self.__parent.__init__(filename) - - def add(self, text, by, at): - return self.__parent.add(self.Record(at=at, by=by, text=text)) - - def change(self, id, f): - dunno = self.get(id) - dunno.text = f(dunno.text) - self.set(id, dunno) - -DunnoDB = plugins.DB('Dunno', {'flat': DbiDunnoDB}) - -class Dunno(callbacks.Privmsg): +class Dunno(plugins.ChannelIdDatabasePlugin): """This plugin was written initially to work with MoobotFactoids, the two of them to provide a similar-to-moobot-and-blootbot interface for factoids. Basically, it replaces the standard 'Error: is not a valid command.' messages with messages kept in a database, able to give more personable responses.""" callAfter = ['MoobotFactoids'] - def __init__(self): - self.__parent = super(Dunno, self) - self.__parent.__init__() - self.db = DunnoDB() - - def die(self): - self.db.close() - self.__parent.die() - def invalidCommand(self, irc, msg, tokens): channel = msg.args[0] if ircutils.isChannel(channel): @@ -114,107 +81,6 @@ class Dunno(callbacks.Privmsg): dunno = ircutils.standardSubstitute(irc, msg, dunno) irc.reply(dunno, prefixName=prefixName) - def add(self, irc, msg, args, user, at, channel, dunno): - """[] - - Adds as a "dunno" to be used as a random response when no - command or factoid key matches. Can optionally contain '$who', which - will be replaced by the user's name when the dunno is displayed. - is only necessary if the message isn't sent in the channel - itself. - """ - id = self.db.add(channel, dunno, user.id, at) - irc.replySuccess('Dunno #%s added.' % id) - add = wrap(add, ['user', 'now', 'channeldb', 'text']) - - def remove(self, irc, msg, args, user, channel, id): - """[] - - Removes dunno with the given . is only necessary if the - message isn't sent in the channel itself. - """ - # Must be registered to use this - try: - dunno = self.db.get(channel, id) - if user.id != dunno.by: - # XXX We need to come up with a way to handle this capability - # checking when channel is None. It'll probably involve - # something along the lines of using admin instead of - # #channel,op. The function should be added to - # plugins/__init__.py - cap = ircdb.makeChannelCapability(channel, 'op') - if not ircdb.users.checkCapability(cap): - irc.errorNoCapability(cap) - self.db.remove(channel, id) - irc.replySuccess() - except KeyError: - irc.error('No dunno has id #%s.' % id) - remove = wrap(remove, ['user', 'channeldb', ('id', 'dunno')]) - - def search(self, irc, msg, args, channel, text): - """[] - - Search for dunno containing the given text. Returns the ids of the - dunnos with the text in them. is only necessary if the - message isn't sent in the channel itself. - """ - def p(dunno): - return text.lower() in dunno.text.lower() - ids = [str(dunno.id) for dunno in self.db.select(channel, p)] - if ids: - s = 'Dunno search for %s (%s found): %s.' % \ - (utils.quoted(text), len(ids), utils.commaAndify(ids)) - irc.reply(s) - else: - irc.reply('No dunnos found matching that search criteria.') - search = wrap(search, ['channeldb', 'text']) - - def get(self, irc, msg, args, channel, id): - """[] - - Display the text of the dunno with the given id. is only - necessary if the message isn't sent in the channel itself. - """ - try: - dunno = self.db.get(channel, id) - name = ircdb.users.getUser(dunno.by).name - at = time.localtime(dunno.at) - timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at) - irc.reply("Dunno #%s: %s (added by %s at %s)" % \ - (id, utils.quoted(dunno.text), name, timeStr)) - except KeyError: - irc.error('No dunno found with that id.') - get = wrap(get, ['channeldb', ('id', 'dunno')]) - - def change(self, irc, msg, args, channel, id, replacer): - """[] - - Alters the dunno with the given id according to the provided regexp. - is only necessary if the message isn't sent in the channel - itself. - """ - try: - # Should this check that Record.by == user.id || - # checkChannelCapability like remove() does? - self.db.change(channel, id, replacer) - except KeyError: - irc.error('There is no dunno #%s.' % id) - return - irc.replySuccess() - change = wrap(change, ['channeldb', ('id', 'dunno'), 'regexpReplacer']) - - def stats(self, irc, msg, args, channel): - """[] - - Returns the number of dunnos in the dunno database. is only - necessary if the message isn't sent in the channel itself. - """ - num = self.db.size(channel) - irc.reply('There %s %s in my database.' % - (utils.be(num), utils.nItems('dunno', num))) - stats = wrap(stats, ['channeldb']) - - Class = Dunno diff --git a/plugins/FunDB.py b/plugins/FunDB.py index 3edf87881..edfa37ab8 100755 --- a/plugins/FunDB.py +++ b/plugins/FunDB.py @@ -31,6 +31,8 @@ Provides fun commands that require a database to operate. """ +deprecated = True + __revision__ = "$Id$" import supybot.plugins as plugins diff --git a/plugins/Lart.py b/plugins/Lart.py new file mode 100644 index 000000000..3e07e046b --- /dev/null +++ b/plugins/Lart.py @@ -0,0 +1,118 @@ +### +# Copyright (c) 2004, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +This plugin keeps a database of larts, and larts with it. +""" + +import supybot + +__revision__ = "$Id$" +__author__ = supybot.authors.jemfinch +__contributors__ = {} + +import re + +import supybot.conf as conf +import supybot.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.privmsgs as privmsgs +import supybot.registry as registry +import supybot.callbacks as callbacks + + +def configure(advanced): + # This will be called by setup.py to configure this module. Advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('Lart', True) + +Lart = conf.registerPlugin('Lart') +conf.registerChannelValue(Lart, 'showIds', + registry.Boolean(False, """Determines whether the bot will show the ids of + a lart when the lart is given.""")) + +class Lart(plugins.ChannelIdDatabasePlugin): + _meRe = re.compile(r'\bme\b', re.I) + _myRe = re.compile(r'\bmy\b', re.I) + def _replaceFirstPerson(self, s, nick): + s = self._meRe.sub(nick, s) + s = self._myRe.sub('%s\'s' % nick, s) + return s + + def addValidator(self, irc, text): + if '$who' not in text: + irc.error('Larts must contain $who.', Raise=True) + + def lart(self, irc, msg, args, channel, id, text): + """[] [] [for ] + + Uses the Luser Attitude Readjustment Tool on (for , + if given). If is given, uses that specific lart. is + only necessary if the message isn't sent in the channel itself. + """ + if ' for ' in text: + (target, reason) = map(str.strip, text.split(' for ', 1)) + else: + (target, reason) = (text, '') + if ircutils.strEqual(target, irc.nick): + target = msg.nick + reason = 'trying to dis me' + if id is not None: + try: + lart = self.db.get(channel, id) + except KeyError: + irc.error('There is no lart with id #%s.' % id) + return + else: + lart = self.db.random(channel) + if not lart: + irc.error('There are no larts in my database for %s.' %channel) + return + text = self._replaceFirstPerson(lart.text, msg.nick) + reason = self._replaceFirstPerson(reason, msg.nick) + if target.endswith('.'): + target = target.rstrip('.') + target = self._replaceFirstPerson(target, msg.nick) + text = text.replace('$who', target) + if reason: + text += ' for ' + reason + if self.registryValue('showIds', channel): + text += ' (#%s)' % lart.id + irc.reply(text, action=True) + lart = wrap(lart, ['channeldb', optional('id'), 'text']) + + +Class = Lart + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Praise.py b/plugins/Praise.py new file mode 100644 index 000000000..d09cf5281 --- /dev/null +++ b/plugins/Praise.py @@ -0,0 +1,117 @@ +### +# Copyright (c) 2004, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +Add the module docstring here. This will be used by the setup.py script. +""" + +import supybot + +__revision__ = "$Id$" +__author__ = supybot.authors.unknown +__contributors__ = {} + +import re + +import supybot.conf as conf +import supybot.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.privmsgs as privmsgs +import supybot.registry as registry +import supybot.callbacks as callbacks + + +def configure(advanced): + # This will be called by setup.py to configure this module. Advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('Praise', True) + + +Praise = conf.registerPlugin('Praise') +conf.registerChannelValue(Praise, 'showIds', + registry.Boolean(False, """Determines whether the bot will show the ids of + a praise when the praise is given.""")) + +class Praise(plugins.ChannelIdDatabasePlugin): + _meRe = re.compile(r'\bme\b', re.I) + _myRe = re.compile(r'\bmy\b', re.I) + def _replaceFirstPerson(self, s, nick): + s = self._meRe.sub(nick, s) + s = self._myRe.sub('%s\'s' % nick, s) + return s + + def addValidator(self, irc, text): + if '$who' not in text: + irc.error('Praises must contain $who.', Raise=True) + + def praise(self, irc, msg, args, channel, id, text): + """[] [] [for ] + + Praises (for , if given). If is given, uses + that specific praise. is only necessary if the message isn't + sent in the channel itself. + """ + if ' for ' in text: + (target, reason) = map(str.strip, text.split(' for ', 1)) + else: + (target, reason) = (text, '') + if ircutils.strEqual(target, irc.nick): + target = 'itself' + if id is not None: + try: + praise = self.db.get(channel, id) + except KeyError: + irc.error('There is no praise with id #%s.' % id) + return + else: + praise = self.db.random(channel) + if not praise: + irc.error('There are no praise in my database for %s.' %channel) + return + text = self._replaceFirstPerson(praise.text, msg.nick) + reason = self._replaceFirstPerson(reason, msg.nick) + if target.endswith('.'): + target = target.rstrip('.') + target = self._replaceFirstPerson(target, msg.nick) + text = text.replace('$who', target) + if reason: + text += ' for ' + reason + if self.registryValue('showIds', channel): + text += ' (#%s)' % praise.id + irc.reply(text, action=True) + praise = wrap(praise, ['channeldb', optional('id'), 'text']) + +Class = Praise + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Quote.py b/plugins/Quote.py new file mode 100644 index 000000000..27567b7ee --- /dev/null +++ b/plugins/Quote.py @@ -0,0 +1,68 @@ +### +# Copyright (c) 2002-2004, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +Maintains a Quotes database for each channel. +""" + +__revision__ = "$Id$" + +import re +import time +import getopt +import os.path + +import supybot.dbi as dbi +import supybot.conf as conf +import supybot.utils as utils +import supybot.ircdb as ircdb +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircutils as ircutils +import supybot.privmsgs as privmsgs +import supybot.registry as registry +import supybot.callbacks as callbacks + +class Quotes(plugins.ChannelIdDatabasePlugin): + def random(self, irc, msg, args, channel): + """[] + + Returns a random quote from . is only necessary if + the message isn't sent in the channel itself. + """ + quote = self.db.random(channel) + if quote: + irc.reply(self.showRecord(quote)) + else: + irc.error('I have no quotes in my database for %s.' % channel) + random = wrap(random, ['channeldb']) + + +Class = Quotes +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Quotes.py b/plugins/Quotes.py index 029ad2fe4..87569204e 100644 --- a/plugins/Quotes.py +++ b/plugins/Quotes.py @@ -31,6 +31,8 @@ Maintains a Quotes database for each channel. """ +deprecated = True + __revision__ = "$Id$" import re diff --git a/plugins/Success.py b/plugins/Success.py index bafae43e1..fada5c395 100644 --- a/plugins/Success.py +++ b/plugins/Success.py @@ -54,31 +54,7 @@ conf.registerChannelValue(conf.supybot.plugins.Success, 'prefixNick', registry.Boolean(True, """Determines whether the bot will prefix the nick of the user giving an invalid command to the success response.""")) -class DbiSuccessDB(plugins.DbiChannelDB): - class DB(dbi.DB): - class Record(dbi.Record): - __fields__ = [ - 'at', - 'by', - 'text', - ] - def __init__(self, filename): - # We use self.__class__ here because apparently DB isn't in our - # scope. python-- - self.__parent = super(self.__class__, self) - self.__parent.__init__(filename) - - def add(self, text, by, at): - return self.__parent.add(self.Record(at=at, by=by, text=text)) - - def change(self, id, f): - dunno = self.get(id) - dunno.text = f(dunno.text) - self.set(id, dunno) - -SuccessDB = plugins.DB('Success', {'flat': DbiSuccessDB}) - -class Success(callbacks.Privmsg): +class Success(plugins.ChannelIdDatabasePlugin): """This plugin was written initially to work with MoobotFactoids, the two of them to provide a similar-to-moobot-and-blootbot interface for factoids. Basically, it replaces the standard 'Error: is not a valid command.' @@ -88,7 +64,6 @@ class Success(callbacks.Privmsg): self.__parent = super(Success, self) self.__parent.__init__() self.target = None - self.db = SuccessDB() pluginSelf = self self.originalClass = conf.supybot.replies.success.__class__ class MySuccessClass(self.originalClass): @@ -111,7 +86,6 @@ class Success(callbacks.Privmsg): conf.supybot.replies.success.__class__ = MySuccessClass def die(self): - self.db.close() self.__parent.die() conf.supybot.replies.success.__class__ = self.originalClass @@ -121,101 +95,6 @@ class Success(callbacks.Privmsg): self.target = msg.args[0] return msg - def add(self, irc, msg, args, user, at, channel, text): - """[] - - Adds as a "success" to be used as a random response when a - success message is needed. Can optionally contain '$who', which - will be replaced by the user's name when the dunno is displayed. - is only necessary if the message isn't sent in the channel - itself. - """ - id = self.db.add(channel, text, user.id, at) - irc.replySuccess('Success #%s added.' % id) - add = wrap(add, ['user', 'now', 'channeldb', 'text']) - - def remove(self, irc, msg, args, channel, id, user): - """[] - - Removes success with the given . is only necessary if the - message isn't sent in the channel itself. - """ - # Must be registered to use this - try: - success = self.db.get(channel, id) - if user.id != success.by: - cap = ircdb.makeChannelCapability(channel, 'op') - if not ircdb.users.checkCapability(cap): - irc.errorNoCapability(cap) - self.db.remove(channel, id) - irc.replySuccess() - except KeyError: - irc.error('No success has id #%s.' % id) - remove = wrap(remove, ['channeldb', ('id', 'success'), 'user']) - - def search(self, irc, msg, args, channel, text): - """[] - - Search for success containing the given text. Returns the ids of the - successes with the text in them. is only necessary if the - message isn't sent in the channel itself. - """ - def p(success): - return text.lower() in success.text.lower() - ids = [str(success.id) for success in self.db.select(channel, p)] - if ids: - s = 'Success search for %s (%s found): %s.' % \ - (utils.quoted(text), len(ids), utils.commaAndify(ids)) - irc.reply(s) - else: - irc.reply('No successes found matching that search criteria.') - search = wrap(search, ['channeldb', 'text']) - - def get(self, irc, msg, args, channel, id): - """[] - - Display the text of the success with the given id. is only - necessary if the message isn't sent in the channel itself. - """ - try: - success = self.db.get(channel, id) - name = ircdb.users.getUser(success.by).name - at = time.localtime(success.at) - timeStr = time.strftime(conf.supybot.humanTimestampFormat(), at) - irc.reply("Success #%s: %s (added by %s at %s)" % \ - (id, utils.quoted(success.text), name, timeStr)) - except KeyError: - irc.error('No success found with that id.') - get = wrap(get, ['channeldb', ('id', 'success')]) - - def change(self, irc, msg, args, user, channel, id, replacer): - """[] - - Alters the success with the given id according to the provided regexp. - is only necessary if the message isn't sent in the channel - itself. - """ - try: - self.db.change(channel, id, replacer) - irc.replySuccess() - except KeyError: - irc.error('There is no success #%s.' % id) - change = wrap(change, ['user', 'channeldb', - ('id', 'success'), 'regexpReplacer']) - - - def stats(self, irc, msg, args, channel): - """[] - - Returns the number of successes in the success database. is - only necessary if the message isn't sent in the channel itself. - """ - num = self.db.size(channel) - irc.reply('There %s %s in my database.' % - (utils.be(num), utils.nItems('success', num))) - stats = wrap(stats, ['channeldb']) - - Class = Success diff --git a/plugins/__init__.py b/plugins/__init__.py index dc4b941c9..01cd2ea2c 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -40,15 +40,18 @@ import math import sets import time import random +import fnmatch import os.path import UserDict import threading -import supybot.cdb as cdb import supybot.log as log +import supybot.dbi as dbi import supybot.conf as conf +import supybot.ircdb as ircdb import supybot.utils as utils import supybot.world as world +from supybot.commands import * import supybot.ircutils as ircutils import supybot.webutils as webutils import supybot.callbacks as callbacks @@ -194,7 +197,7 @@ class ChannelDBHandler(object): def makeDb(self, filename): """Override this to create your databases.""" - return cdb.shelf(filename) + raise NotImplementedError def getDb(self, channel): """Use this to get a database for a specific channel.""" @@ -352,12 +355,173 @@ class ChannelUserDB(ChannelUserDictionary): raise NotImplementedError -## class ChannelIdDatabasePlugin(callbacks.Privmsg): -## def __init__(self): -## # XXX Register configuration variables. -## self.__parent = super(ChannelIdDatabasePlugin, self) -## self.__parent.__init__(self) -## self.db = self.DB() +class ChannelIdDatabasePlugin(callbacks.Privmsg): + class DB(DbiChannelDB): + class DB(dbi.DB): + class Record(dbi.Record): + __fields__ = [ + 'at', + 'by', + 'text' + ] + def add(self, at, by, text, **kwargs): + record = self.Record(at=at, by=by, text=text, **kwargs) + return super(self.__class__, self).add(record) + + def __init__(self): + self.__parent = super(ChannelIdDatabasePlugin, self) + self.__parent.__init__() + self.db = DB(self.name(), {'flat': self.DB})() + + def die(self): + self.db.close() + self.__parent.die() + + def getCommandHelp(self, name): + help = self.__parent.getCommandHelp(name) + help = help.replace('$Types', utils.pluralize(self.name())) + help = help.replace('$Type', self.name()) + help = help.replace('$types', utils.pluralize(self.name().lower())) + help = help.replace('$type', self.name().lower()) + return help + + def noSuchRecord(self, irc, channel, id): + irc.error('There is no %s with id #%s in my database for %s.' % + (self.name(), id, channel)) + + def checkChangeAllowed(self, irc, msg, channel, user, record): + if user.id == record.by: + return True + cap = ircdb.makeChannelCapability(channel, 'op') + if ircdb.checkCapability(msg.prefix, cap): + return True + irc.errorNoCapability(cap) + + def addValidator(self, irc, text): + """This should irc.error or raise an exception if text is invalid.""" + pass + + def add(self, irc, msg, args, user, channel, text): + """[] + + Adds to the $type database for . + is only necessary if the message isn't sent in the channel + itself. + """ + at = time.time() + self.addValidator(irc, text) + if text is not None: + id = self.db.add(channel, at, user.id, text) + irc.replySuccess('%s #%s added.' % (self.name(), id)) + add = wrap(add, ['user', 'channeldb', 'text']) + + def remove(self, irc, msg, args, user, channel, id): + """[] + + Removes the $type with id from the $type database for . + is only necessary if the message isn't sent in the channel + itself. + """ + try: + record = self.db.get(channel, id) + self.checkChangeAllowed(irc, msg, channel, user, record) + self.db.remove(channel, id) + irc.replySuccess() + except KeyError: + self.noSuchRecord(irc, channel, id) + remove = wrap(remove, ['user', 'channeldb', 'id']) + + def searchSerializeRecord(self, record): + text = utils.quoted(utils.ellipsisify(record.text, 50)) + return '#%s: %s' % (record.id, text) + + def search(self, irc, msg, args, channel, optlist, glob): + """[] [--{regexp,by} ] [] + + Searches for $types matching the criteria given. XXX + """ + predicates = [] + def p(record): + for predicate in predicates: + if not predicate(record): + return False + return True + + for (opt, arg) in optlist: + if opt == 'by': + predicates.append(lambda r, arg=arg: r.by == arg.id) + elif opt == 'regexp': + predicates.append(lambda r, arg=arg: arg.search(r.text)) + if glob: + # XXX Case sensitive. + predicates.append(lambda r: fnmatch.fnmatch(r.text, glob)) + L = [] + for record in self.db.select(channel, p): + L.append(self.searchSerializeRecord(record)) + if L: + L.sort() + irc.reply(utils.commaAndify(L)) + else: + irc.reply('No matching %s were found.' % + utils.pluralize(self.name().lower())) + search = wrap(search, ['channeldb', + getopts({'by': 'otherUser', + 'regexp': 'regexpMatcher'}), + additional(rest('glob'))]) + + def showRecord(self, record): + try: + name = ircdb.users.getUser(record.by).name + except KeyError: + name = 'a user that is no longer registered' + at = time.localtime(record.at) + timeS = time.strftime(conf.supybot.humanTimestampFormat(), at) + return '%s #%S: %s (added by %s at %s)' % \ + (self.name(), record.id, utils.quoted(record.text), name, timeS) + + def get(self, irc, msg, args, channel, id): + """[] + + Gets the $type with id from the $type database for . + is only necessary if the message isn't sent in the channel + itself. + """ + try: + record = self.db.get(channel, id) + irc.reply(self.showRecord(record)) + except KeyError: + self.noSuchRecord(irc, channel, id) + get = wrap(get, ['channeldb', 'id']) + + def change(self, irc, msg, args, user, channel, id, replacer): + """[] + + Changes the $type with id according to the regular expression + . is only necessary if the message isn't sent in the + channel itself. + """ + try: + record = self.db.get(channel, id) + self.checkChangeAllowed(irc, msg, channel, user, record) + record.text = replacer(record.text) + self.db.set(channel, id, record) + irc.replySuccess() + except KeyError: + self.noSuchRecord(irc, channel, id) + change = wrap(change, ['user', 'channeldb', 'id', 'regexpReplacer']) + + def stats(self, irc, msg, args, channel): + """[] + + Returns the number of $types in the database for . + is only necessary if the message isn't sent in the channel + itself. + """ + n = self.db.size(channel) + irc.reply('There %s %s in my database.' % + (utils.be(n), utils.nItems(self.name().lower(), n))) + stats = wrap(stats, ['channeldb']) + class PeriodicFileDownloader(object): """A class to periodically download a file/files.