Limnoria/plugins/Quotes.py

361 lines
13 KiB
Python
Raw Normal View History

2003-03-12 07:26:59 +01:00
###
# Copyright (c) 2002-2004, Jeremiah Fincher
2003-03-12 07:26:59 +01:00
# 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.
"""
deprecated = True
2003-11-25 09:23:47 +01:00
__revision__ = "$Id$"
2003-03-12 07:26:59 +01:00
import re
import time
2003-08-26 19:07:37 +02:00
import getopt
2003-03-12 07:26:59 +01:00
import os.path
2004-08-13 05:50:38 +02:00
import supybot.dbi as dbi
2004-07-24 07:18:26 +02:00
import supybot.conf as conf
import supybot.utils as utils
import supybot.ircdb as ircdb
import supybot.plugins as plugins
import supybot.ircutils as ircutils
2004-07-24 07:18:26 +02:00
import supybot.privmsgs as privmsgs
import supybot.registry as registry
2004-07-24 07:18:26 +02:00
import supybot.callbacks as callbacks
2003-03-12 07:26:59 +01:00
conf.registerPlugin('Quotes')
conf.registerGlobalValue(conf.supybot.plugins.Quotes, 'requireRegistration',
registry.Boolean(False, """Determines whether the bot should require people
trying to use this plugin to be registered."""))
class QuoteRecord(dbi.Record):
2004-08-13 05:50:38 +02:00
__fields__ = [
'at',
'by',
'text'
]
def __str__(self):
format = conf.supybot.reply.format.time()
2004-12-02 06:33:29 +01:00
user = plugins.getUserName(self.by)
return 'Quote %s added by %s at %s.' % \
(utils.quoted(self.text), user,
2004-08-13 05:50:38 +02:00
time.strftime(format, time.localtime(float(self.at))))
2003-03-12 07:26:59 +01:00
2004-08-13 05:50:38 +02:00
class SqliteQuotesDB(object):
def __init__(self, filename):
self.dbs = ircutils.IrcDict()
self.filename = filename
def close(self):
for db in self.dbs.itervalues():
db.close()
2004-08-13 05:50:38 +02:00
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 channel in self.dbs:
return self.dbs[channel]
2003-03-12 07:26:59 +01:00
if os.path.exists(filename):
self.dbs[channel] = sqlite.connect(db=filename, mode=0755,
converters={'bool': bool})
return self.dbs[channel]
2003-03-12 07:26:59 +01:00
#else:
db = sqlite.connect(db=filename, mode=0755, coverters={'bool': bool})
self.dbs[channel] = db
2003-03-12 07:26:59 +01:00
cursor = db.cursor()
cursor.execute("""CREATE TABLE quotes (
id INTEGER PRIMARY KEY,
2003-08-26 19:07:37 +02:00
added_by TEXT,
2003-03-12 07:26:59 +01:00
added_at TIMESTAMP,
2003-08-26 19:07:37 +02:00
quote TEXT
);""")
2003-03-12 07:26:59 +01:00
db.commit()
return db
2004-08-13 05:50:38 +02:00
def add(self, channel, by, quote):
db = self._getDb(channel)
cursor = db.cursor()
at = int(time.time())
cursor.execute("""INSERT INTO quotes VALUES (NULL, %s, %s, %s)""",
by, at, quote)
cursor.execute("""SELECT id FROM quotes
WHERE added_by=%s AND added_at=%s AND quote=%s""",
by, at, quote)
db.commit()
return int(cursor.fetchone()[0])
def size(self, channel):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT COUNT(*) FROM quotes""")
return int(cursor.fetchone()[0])
def random(self, channel):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT id, added_by, added_at, quote FROM quotes
ORDER BY random() LIMIT 1""")
if cursor.rowcount == 0:
raise dbi.NoRecordError
2004-08-13 05:50:38 +02:00
(id, by, at, text) = cursor.fetchone()
return QuoteRecord(id, by=by, at=int(at), text=text)
# XXX This needs to be modified to accept a predicate, and the creation of
# the predicate moved to the plugin. One plugin, many database
# implementations -- we don't want to burden every database implementation
# to do all this work. Yes, this means that sqlite implementations will
# be less efficient for awhile, but I have a plan to resolve that
# eventually.
2004-08-13 05:50:38 +02:00
def search(self, channel, **kwargs):
criteria = []
formats = []
predicateName = ''
db = self._getDb(channel)
for v in kwargs['id']:
criteria.append('id=%s' % v)
for v in kwargs['with']:
criteria.append('quote LIKE %s')
formats.append('%%%s%%' % v)
for v in kwargs['by']:
criteria.append('added_by=%s')
formats.append(arg)
for p in kwargs['predicate']:
2004-08-13 05:50:38 +02:00
predicateName += 'p'
db.create_function(predicateName, 1, p)
criteria.append('%s(quote)' % predicateName)
for s in kwargs['args']:
try:
i = int(s)
criteria.append('id=%s' % i)
except ValueError:
s = '%%%s%%' % s
criteria.append('quote LIKE %s')
formats.append(s)
sql = """SELECT id, added_by, added_at, quote FROM quotes
WHERE %s""" % ' AND '.join(criteria)
cursor = db.cursor()
cursor.execute(sql, *formats)
if cursor.rowcount == 0:
return None
elif cursor.rowcount == 1:
(id, by, at, text) = cursor.fetchone()
return QuoteRecord(id, by=by, at=int(at), text=text)
else:
quotes = []
for (id, by, at, text) in cursor.fetchall():
quotes.append(QuoteRecord(id, by=by, at=int(at), text=text))
return quotes
def get(self, channel, id):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT added_by, added_at, quote FROM quotes
WHERE id=%s""", id)
if cursor.rowcount == 0:
raise dbi.NoRecordError, id
2004-08-13 05:50:38 +02:00
(by, at, text) = cursor.fetchone()
return QuoteRecord(id, by=by, at=int(at), text=text)
def remove(self, channel, id):
db = self._getDb(channel)
cursor = db.cursor()
cursor.execute("""DELETE FROM quotes WHERE id=%s""", id)
if cursor.rowcount == 0:
raise dbi.NoRecordError, id
2004-08-13 05:50:38 +02:00
db.commit()
QuotesDB = plugins.DB('Quotes',
{'sqlite': SqliteQuotesDB})
2004-08-13 05:50:38 +02:00
class Quotes(callbacks.Privmsg):
def __init__(self):
2004-09-20 01:51:21 +02:00
self.__parent = super(Quotes, self)
self.__parent.__init__()
2004-08-13 05:50:38 +02:00
self.db = QuotesDB()
def die(self):
2004-09-20 01:51:21 +02:00
self.__parent.die()
self.db.close()
2004-08-13 05:50:38 +02:00
2004-10-23 23:27:17 +02:00
# XXX I was going to convert this to use commands.wrap, but we need to fix
# this so it's not the got the completely type-unsafe int-or-nick "by"
# column; chances are, that's going to be something we'll have a
# converter for.
2003-10-21 07:19:54 +02:00
def add(self, irc, msg, args):
2003-08-26 19:07:37 +02:00
"""[<channel>] <quote>
Adds <quote> to the quotes database for <channel>. <channel> is only
necessary if the message isn't sent in the channel itself.
"""
2003-03-12 07:26:59 +01:00
channel = privmsgs.getChannel(msg, args)
quote = privmsgs.getArgs(args)
if self.registryValue('requireRegistration'):
try:
by = ircdb.users.getUserId(msg.prefix)
except KeyError:
irc.errorNotRegistered()
return
else:
try:
by = ircdb.users.getUserId(msg.prefix)
except KeyError:
by = msg.nick
id = self.db.add(channel, by, quote)
2004-08-13 05:50:38 +02:00
irc.replySuccess('(Quote #%s added)' % id)
2003-03-12 07:26:59 +01:00
2004-01-18 09:19:44 +01:00
def stats(self, irc, msg, args):
2003-08-26 19:07:37 +02:00
"""[<channel>]
Returns the numbers of quotes in the quote database for <channel>.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
2003-03-12 07:26:59 +01:00
channel = privmsgs.getChannel(msg, args)
2004-08-13 05:50:38 +02:00
size = self.db.size(channel)
s = 'There %s %s in my database.' % \
2004-08-13 05:50:38 +02:00
(utils.be(size), utils.nItems('quote', size))
irc.reply(s)
2003-03-12 07:26:59 +01:00
2004-08-13 05:50:38 +02:00
def _replyQuote(self, irc, quote):
if isinstance(quote, QuoteRecord):
irc.reply('#%s: %s' % (quote.id, quote.text))
elif len(quote) > 10:
irc.reply('More than 10 quotes matched your criteria. '
'Please narrow your query.')
else:
quotes = ['#%s: %s' %
(q.id, utils.quoted(utils.ellipsisify(q.text, 30)))
2004-08-13 05:50:38 +02:00
for q in quote]
irc.reply(utils.commaAndify(quotes))
def search(self, irc, msg, args):
"""[<channel>] --{id,regexp,from,with} <value> ]
2003-08-26 19:07:37 +02:00
Returns quote(s) matching the given criteria. --from is who added the
quote; --id is the id number of the quote; --regexp is a regular
expression to search for.
"""
2003-03-12 07:26:59 +01:00
channel = privmsgs.getChannel(msg, args)
2003-09-23 00:11:05 +02:00
(optlist, rest) = getopt.getopt(args, '', ['id=', 'regexp=',
'from=', 'with='])
2003-08-26 19:07:37 +02:00
if not optlist and not rest:
raise callbacks.ArgumentError
2004-08-13 05:50:38 +02:00
kwargs = {'args': rest, 'id': [], 'with': [], 'by': [], 'predicate': []}
for (option, arg) in optlist:
2003-08-26 19:07:37 +02:00
option = option.lstrip('-')
if option == 'id':
try:
2004-08-13 05:50:38 +02:00
arg = int(arg)
kwargs[option].append(arg)
2003-08-26 19:07:37 +02:00
except ValueError:
irc.error('--id value must be an integer.')
2003-08-26 19:07:37 +02:00
return
2003-09-23 00:11:05 +02:00
elif option == 'with':
2004-08-13 05:50:38 +02:00
kwargs[option].append(arg)
2003-08-26 19:07:37 +02:00
elif option == 'from':
2004-08-13 05:50:38 +02:00
kwargs['by'].append(arg)
2003-08-26 19:07:37 +02:00
elif option == 'regexp':
try:
2004-08-13 05:50:38 +02:00
r = utils.perlReToPythonRe(arg)
except ValueError:
try:
2004-08-13 05:50:38 +02:00
r = re.compile(arg, re.I)
except re.error, e:
irc.error(str(e))
return
def p(s):
return int(bool(r.search(s)))
kwargs['predicate'].append(p)
2004-08-13 05:50:38 +02:00
quote = self.db.search(channel, **kwargs)
if quote is None:
irc.reply('No quotes matched that criteria.')
2003-08-26 19:07:37 +02:00
else:
2004-08-13 05:50:38 +02:00
self._replyQuote(irc, quote)
2003-03-12 07:26:59 +01:00
2003-10-21 07:19:54 +02:00
def random(self, irc, msg, args):
2003-08-26 19:07:37 +02:00
"""[<channel>]
Returns a random quote from <channel>. <channel> is only necessary if
the message isn't sent in the channel itself.
"""
2003-03-12 07:26:59 +01:00
channel = privmsgs.getChannel(msg, args)
try:
quote = self.db.random(channel)
2004-08-13 05:50:38 +02:00
self._replyQuote(irc, quote)
except dbi.NoRecordError:
irc.error('I have no quotes for this channel.')
2003-03-12 07:26:59 +01:00
2003-10-21 07:19:54 +02:00
def info(self, irc, msg, args):
2003-08-26 19:07:37 +02:00
"""[<channel>] <id>
Returns the metadata about the quote <id> in the quotes
database for <channel>. <channel> is only necessary if the message
isn't sent in the channel itself.
"""
2003-03-12 07:26:59 +01:00
channel = privmsgs.getChannel(msg, args)
id = privmsgs.getArgs(args)
2004-08-13 05:50:38 +02:00
try:
id = int(id)
except ValueError:
irc.errorInvalid('id' % id)
2004-09-17 07:09:14 +02:00
return
2004-08-13 05:50:38 +02:00
try:
quote = self.db.get(channel, id)
irc.reply(str(quote))
except dbi.NoRecordError, e:
irc.error('There isn\'t a quote with that id.')
2003-03-12 07:26:59 +01:00
def remove(self, irc, msg, args):
2003-08-26 19:07:37 +02:00
"""[<channel>] <id>
Removes quote <id> from the quotes database for <channel>. <channel>
is only necessary if the message isn't sent in the channel itself.
"""
channel = privmsgs.getChannel(msg, args)
2003-03-12 07:26:59 +01:00
id = privmsgs.getArgs(args)
2004-08-13 05:50:38 +02:00
try:
id = int(id)
except ValueError:
irc.errorInvalid('id' % id)
2004-08-13 05:50:38 +02:00
try:
self.db.remove(channel, id)
2004-01-08 16:24:56 +01:00
irc.replySuccess()
2004-08-13 05:50:38 +02:00
except KeyError:
irc.error('There was no such quote.')
2003-08-20 18:26:23 +02:00
2003-03-12 07:26:59 +01:00
Class = Quotes
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: