Update QuoteGrabs to use db abstraction. Hopefully fix the mxCrap thing

for good.  This update should also fix the problem that had existed with
QuoteGrabs and databases.plugins.channelSpecifc=False
This commit is contained in:
James Vega 2004-12-03 15:41:08 +00:00
parent ac04024f47
commit 4ba005ddf6
3 changed files with 180 additions and 121 deletions

View File

@ -37,28 +37,21 @@ as see who "grabbed" the quote in the first place.
__revision__ = "$Id$" __revision__ = "$Id$"
import supybot.plugins as plugins
import os import os
import time import time
import random import random
import supybot.registry as registry import supybot.registry as registry
import supybot.dbi as dbi
import supybot.conf as conf import supybot.conf as conf
import supybot.ircdb as ircdb import supybot.ircdb as ircdb
import supybot.utils as utils import supybot.utils as utils
from supybot.commands import *
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.plugins as plugins import supybot.plugins as plugins
import supybot.ircutils as ircutils import supybot.ircutils as ircutils
import supybot.callbacks as callbacks import supybot.callbacks as callbacks
from supybot.commands import wrap, additional
try:
import sqlite
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to use this ' \
'plugin. Download it at <http://pysqlite.sf.net/>'
conf.registerPlugin('QuoteGrabs') conf.registerPlugin('QuoteGrabs')
conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs, 'randomGrabber', conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs, 'randomGrabber',
@ -80,16 +73,47 @@ conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber,
minimum number of characters in a message for it to be considered for minimum number of characters in a message for it to be considered for
random grabbing.""")) random grabbing."""))
class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg): class QuoteGrabsRecord(dbi.Record):
def __init__(self): __fields__ = [
plugins.ChannelDBHandler.__init__(self) 'by',
callbacks.Privmsg.__init__(self) 'text',
'grabber',
'at',
'hostmask',
]
def makeDb(self, filename): def __str__(self):
at = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(float(self.at)))
grabber = plugins.getUserName(self.grabber)
return '%s (Said by: %s; grabbed by %s at %s)' % \
(self.text, self.hostmask, grabber, at)
class SqliteQuoteGrabsDB(object):
def __init__(self, filename):
self.dbs = ircutils.IrcDict()
self.filename = filename
def close(self):
for db in self.dbs.itervalues():
db.close()
def _getDb(self, channel):
try:
import sqlite
except ImportError:
raise callbacks.Error, 'You need to have PySQLite installed to ' \
'use this plugin. Download it at ' \
'<http://pysqlite.sf.net/>'
filename = plugins.makeChannelFilename(self.filename, channel)
if filename in self.dbs:
return self.dbs[filename]
if os.path.exists(filename): if os.path.exists(filename):
self.dbs[filename] = sqlite.connect(filename,
converters={'bool': bool})
return self.dbs[filename]
db = sqlite.connect(filename, converters={'bool': bool}) db = sqlite.connect(filename, converters={'bool': bool})
else: self.dbs[filename] = db
db = sqlite.connect(filename, coverters={'bool': bool})
cursor = db.cursor() cursor = db.cursor()
cursor.execute("""CREATE TABLE quotegrabs ( cursor.execute("""CREATE TABLE quotegrabs (
id INTEGER PRIMARY KEY, id INTEGER PRIMARY KEY,
@ -105,6 +129,85 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
db.commit() db.commit()
return db return db
def get(self, channel, id):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, nick, quote, hostmask, added_at, added_by
FROM quotegrabs WHERE id = %s""", id)
if cursor.rowcount == 0:
raise dbi.NoRecordError
(id, by, quote, hostmask, at, grabber) = cursor.fetchone()
return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask,
at=at, grabber=grabber)
def random(self, channel, nick):
db = self._getDb(channel)
cursor = db.cursor()
if nick:
cursor.execute("""SELECT quote FROM quotegrabs
WHERE nickeq(nick, %s)
ORDER BY random() LIMIT 1""",
nick)
else:
cursor.execute("""SELECT quote FROM quotegrabs
ORDER BY random() LIMIT 1""")
if cursor.rowcount == 0:
raise dbi.NoRecordError
return cursor.fetchone()[0]
def list(self, channel, nick):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, quote FROM quotegrabs
WHERE nickeq(nick, %s)
ORDER BY id ASC""", nick)
return [QuoteGrabsRecord(id, text=quote)
for (id, quote) in cursor.fetchall()]
def getQuote(self, channel, nick):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT quote FROM quotegrabs
WHERE nickeq(nick, %s)
ORDER BY id DESC LIMIT 1""", nick)
if cursor.rowcount == 0:
raise dbi.NoRecordError
return cursor.fetchone()[0]
def select(self, channel, nick):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT added_at FROM quotegrabs
WHERE nickeq(nick, %s)
ORDER BY id DESC LIMIT 1""", nick)
if cursor.rowcount == 0:
raise dbi.NoRecordError
def add(self, msg, by):
channel = msg.args[0]
db = self._getDb(channel)
cursor = db.cursor()
text = ircmsgs.prettyPrint(msg)
# Check to see if the latest quotegrab is identical
cursor.execute("""SELECT quote FROM quotegrabs
WHERE nick=%s
ORDER BY id DESC LIMIT 1""", msg.nick)
if cursor.rowcount != 0:
if text == cursor.fetchone()[0]:
return
cursor.execute("""INSERT INTO quotegrabs
VALUES (NULL, %s, %s, %s, %s, %s)""",
msg.nick, msg.prefix, by, int(time.time()), text)
db.commit()
QuoteGrabsDB = plugins.DB('QuoteGrabs', {'sqlite': SqliteQuoteGrabsDB})
class QuoteGrabs(callbacks.Privmsg):
def __init__(self):
self.__parent = super(QuoteGrabs, self)
self.__parent.__init__()
self.db = QuoteGrabsDB()
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
irc = callbacks.SimpleProxy(irc, msg) irc = callbacks.SimpleProxy(irc, msg)
if ircutils.isChannel(msg.args[0]): if ircutils.isChannel(msg.args[0]):
@ -118,12 +221,9 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
channel) channel)
if self.registryValue('randomGrabber', channel): if self.registryValue('randomGrabber', channel):
if len(payload) > length and len(payload.split()) > words: if len(payload) > length and len(payload.split()) > words:
db = self.getDb(channel) try:
cursor = db.cursor() self.db.select(channel, msg.nick)
cursor.execute("""SELECT added_at FROM quotegrabs except dbi.NoRecordError:
WHERE nick=%s
ORDER BY id DESC LIMIT 1""", msg.nick)
if cursor.rowcount == 0:
self._grab(irc, msg, irc.prefix) self._grab(irc, msg, irc.prefix)
self._sendGrabMsg(irc, msg) self._sendGrabMsg(irc, msg)
else: else:
@ -134,21 +234,7 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
self._sendGrabMsg(irc, msg) self._sendGrabMsg(irc, msg)
def _grab(self, irc, msg, addedBy): def _grab(self, irc, msg, addedBy):
channel = msg.args[0] self.db.add(msg, addedBy)
db = self.getDb(channel)
cursor = db.cursor()
text = ircmsgs.prettyPrint(msg)
# Check to see if the latest quotegrab is identical
cursor.execute("""SELECT quote FROM quotegrabs
WHERE nick=%s
ORDER BY id DESC LIMIT 1""", msg.nick)
if cursor.rowcount != 0:
if text == cursor.fetchone()[0]:
return
cursor.execute("""INSERT INTO quotegrabs
VALUES (NULL, %s, %s, %s, %s, %s)""",
msg.nick, msg.prefix, addedBy, int(time.time()), text)
db.commit()
def _sendGrabMsg(self, irc, msg): def _sendGrabMsg(self, irc, msg):
s = 'jots down a new quote for %s' % msg.nick s = 'jots down a new quote for %s' % msg.nick
@ -161,13 +247,17 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
<channel> is only necessary if the message isn't sent in the channel <channel> is only necessary if the message isn't sent in the channel
itself. itself.
""" """
if ircutils.strEqual(nick, msg.nick): # chan is used to make sure we know where to grab the quote from, as
irc.error('You can\'t quote grab yourself.') # opposed to channel which is used to determine which db to store the
return # quote in
chan = msg.args[0]
if chan is None:
raise callbacks.ArgumentError
if ircutils.nickEqual(nick, msg.nick):
irc.error('You can\'t quote grab yourself.', Raise=True)
for m in reversed(irc.state.history): for m in reversed(irc.state.history):
# XXX Note that the channel isn't used here. Also note that if m.command == 'PRIVMSG' and ircutils.nickEqual(m.nick, nick) \
# channel might be None since we use channeldb and ircutils.strEqual(m.args[0], chan):
if m.command == 'PRIVMSG' and ircutils.strEqual(m.nick, nick):
self._grab(irc, m, msg.prefix) self._grab(irc, m, msg.prefix)
irc.replySuccess() irc.replySuccess()
return return
@ -180,16 +270,11 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
Returns <nick>'s latest quote grab in <channel>. <channel> is only Returns <nick>'s latest quote grab in <channel>. <channel> is only
necessary if the message isn't sent in the channel itself. necessary if the message isn't sent in the channel itself.
""" """
db = self.getDb(channel) try:
cursor = db.cursor() irc.reply(self.db.getQuote(channel, nick))
cursor.execute("""SELECT quote FROM quotegrabs except dbi.NoRecordError:
WHERE nickeq(nick, %s) irc.error('I couldn\'t find a matching quotegrab for %s.' % nick,
ORDER BY id DESC LIMIT 1""", nick) Raise=True)
if cursor.rowcount == 0:
irc.error('I couldn\'t find a matching quotegrab for %s.' % nick)
else:
text = cursor.fetchone()[0]
irc.reply(text)
quote = wrap(quote, ['channeldb', 'nick']) quote = wrap(quote, ['channeldb', 'nick'])
def list(self, irc, msg, args, channel, nick): def list(self, irc, msg, args, channel, nick):
@ -200,52 +285,36 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
full quote. <channel> is only necessary if the message isn't sent in full quote. <channel> is only necessary if the message isn't sent in
the channel itself. the channel itself.
""" """
# XXX This doesn't seem to be channel-specific in practice. try:
db = self.getDb(channel) records = self.db.list(channel, nick)
cursor = db.cursor() L = []
cursor.execute("""SELECT id, quote FROM quotegrabs for record in records:
WHERE nick LIKE %s
ORDER BY id ASC""", nick)
if cursor.rowcount == 0:
irc.error('I couldn\'t find any quotegrabs for %s.' % nick)
else:
l = []
for (id, quote) in cursor.fetchall():
# strip the nick from the quote # strip the nick from the quote
quote = quote.replace('<%s> ' % nick, '', 1) quote = record.text.replace('<%s> ' % nick, '', 1)
item_str = utils.ellipsisify('#%s: %s' % (id, quote), 50) item = utils.ellipsisify('#%s: %s' % (record.id, quote), 50)
l.append(item_str) L.append(item)
irc.reply(utils.commaAndify(l)) irc.reply(utils.commaAndify(L))
except dbi.NoRecordError:
irc.error('I couldn\'t find any quotegrabs for %s.' % nick,
Raise=True)
list = wrap(list, ['channeldb', 'nick']) list = wrap(list, ['channeldb', 'nick'])
# XXX Could we rename this "random", to fit in more with the rest of def random(self, irc, msg, args, channel, nick):
# our plugins?
def randomquote(self, irc, msg, args, channel, nick):
"""[<channel>] [<nick>] """[<channel>] [<nick>]
Returns a randomly grabbed quote, optionally choosing only from those Returns a randomly grabbed quote, optionally choosing only from those
quotes grabbed for <nick>. <channel> is only necessary if the message quotes grabbed for <nick>. <channel> is only necessary if the message
isn't sent in the channel itself. isn't sent in the channel itself.
""" """
db = self.getDb(channel) try:
cursor = db.cursor() irc.reply(self.db.random(channel, nick))
if nick: except dbi.NoRecordError:
cursor.execute("""SELECT quote FROM quotegrabs
WHERE nick LIKE %s ORDER BY random() LIMIT 1""",
nick)
else:
cursor.execute("""SELECT quote FROM quotegrabs
ORDER BY random() LIMIT 1""")
if cursor.rowcount == 0:
if nick: if nick:
irc.error('Couldn\'t get a random quote for that nick.') irc.error('Couldn\'t get a random quote for that nick.')
else: else:
irc.error('Couldn\'t get a random quote. Are there any' irc.error('Couldn\'t get a random quote. Are there any'
'grabbed quotes in the database?') 'grabbed quotes in the database?')
return random = wrap(random, ['channeldb', additional('nick')])
quote = cursor.fetchone()[0]
irc.reply(quote)
randomquote = wrap(randomquote, ['channeldb', additional('nick')])
def get(self, irc, msg, args, channel, id): def get(self, irc, msg, args, channel, id):
"""[<channel>] <id> """[<channel>] <id>
@ -253,22 +322,10 @@ class QuoteGrabs(plugins.ChannelDBHandler, callbacks.Privmsg):
Return the quotegrab with the given <id>. <channel> is only necessary Return the quotegrab with the given <id>. <channel> is only necessary
if the message isn't sent in the channel itself. if the message isn't sent in the channel itself.
""" """
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT quote, hostmask, added_at, added_by
FROM quotegrabs WHERE id = %s""", id)
if cursor.rowcount == 0:
irc.error('No quotegrab for id %s' % utils.quoted(id))
return
quote, hostmask, timestamp, grabber_mask = cursor.fetchone()
time_str = time.strftime(conf.supybot.humanTimestampFormat(),
time.localtime(float(timestamp)))
try: try:
grabber = ircdb.users.getUser(grabber_mask).name irc.reply(self.db.get(channel, id))
except: except dbi.NoRecordError:
grabber = grabber_mask irc.error('No quotegrab for id %s' % utils.quoted(id), Raise=True)
irc.reply('%s (Said by: %s; grabbed by %s at %s)' % \
(quote, hostmask, grabber, time_str))
get = wrap(get, ['channeldb', 'id']) get = wrap(get, ['channeldb', 'id'])

View File

@ -65,12 +65,11 @@ try:
for (name, module) in sys.modules.items(): for (name, module) in sys.modules.items():
if name.startswith('mx'): if name.startswith('mx'):
mxCrap[name] = module mxCrap[name] = module
sys.modules[name] = None sys.modules.pop(name)
# Now that the mx crap is gone, we can import sqlite. # Now that the mx crap is gone, we can import sqlite.
import sqlite import sqlite
# And now we'll put it back, even though it sucks. # And now we'll put it back, even though it sucks.
for (name, module) in mxCrap.items(): sys.modules.update(mxCrap)
sys.modules[name] = module
# Just in case, we'll do this as well. It doesn't seem to work fine by # Just in case, we'll do this as well. It doesn't seem to work fine by
# itself, though, or else we'd just do this in the first place. # itself, though, or else we'd just do this in the first place.
sqlite.have_datetime = False sqlite.have_datetime = False

View File

@ -38,7 +38,7 @@ except ImportError:
if sqlite: if sqlite:
class QuoteGrabsTestCase(ChannelPluginTestCase, PluginDocumentation): class QuoteGrabsTestCase(ChannelPluginTestCase):
plugins = ('QuoteGrabs',) plugins = ('QuoteGrabs',)
def testQuoteGrab(self): def testQuoteGrab(self):
testPrefix = 'foo!bar@baz' testPrefix = 'foo!bar@baz'
@ -91,16 +91,16 @@ if sqlite:
self.assertNotError('quotegrabs list FOO') self.assertNotError('quotegrabs list FOO')
self.assertNotError('quotegrabs list fOo') self.assertNotError('quotegrabs list fOo')
def testRandomquote(self): def testRandom(self):
testPrefix = 'foo!bar@baz' testPrefix = 'foo!bar@baz'
self.assertError('randomquote') self.assertError('random')
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'test', self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'test',
prefix=testPrefix)) prefix=testPrefix))
self.assertError('randomquote') # still none in the db self.assertError('random') # still none in the db
self.assertNotError('grab foo') self.assertNotError('grab foo')
self.assertResponse('randomquote', '<foo> test') self.assertResponse('random', '<foo> test')
self.assertResponse('randomquote foo', '<foo> test') self.assertResponse('random foo', '<foo> test')
self.assertResponse('randomquote FOO', '<foo> test') self.assertResponse('random FOO', '<foo> test')
def testGet(self): def testGet(self):
testPrefix= 'foo!bar@baz' testPrefix= 'foo!bar@baz'
@ -111,5 +111,8 @@ if sqlite:
self.assertNotError('grab foo') self.assertNotError('grab foo')
self.assertNotError('quotegrabs get 1') self.assertNotError('quotegrabs get 1')
class QuoteGrabsNonChannelTestCase(QuoteGrabsTestCase):
config = { 'databases.plugins.channelSpecific' : False }
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: