mirror of
https://github.com/Mikaela/Limnoria.git
synced 2024-12-25 20:22:45 +01:00
Update News to use commands.wrap and switch from an SQLite db to a flatfile
mapping.
This commit is contained in:
parent
edc698e48b
commit
0242a8619d
240
plugins/News.py
240
plugins/News.py
@ -34,8 +34,6 @@ dates.
|
|||||||
|
|
||||||
__revision__ = "$Id$"
|
__revision__ = "$Id$"
|
||||||
|
|
||||||
import supybot.plugins as plugins
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -43,19 +41,24 @@ 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
|
||||||
|
import supybot.plugins as plugins
|
||||||
|
from supybot.commands import wrap
|
||||||
import supybot.ircutils as ircutils
|
import supybot.ircutils as ircutils
|
||||||
import supybot.privmsgs as privmsgs
|
import supybot.privmsgs as privmsgs
|
||||||
import supybot.callbacks as callbacks
|
import supybot.callbacks as callbacks
|
||||||
|
|
||||||
|
|
||||||
class NewsRecord(dbi.Record):
|
class DbiNewsDB(plugins.DbiChannelDB):
|
||||||
|
class DB(dbi.DB):
|
||||||
|
class Record(dbi.Record):
|
||||||
__fields__ = [
|
__fields__ = [
|
||||||
'subject',
|
'subject',
|
||||||
'text',
|
'text',
|
||||||
'at',
|
'at',
|
||||||
'expires',
|
'expires',
|
||||||
'by'
|
'by',
|
||||||
]
|
]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
format = conf.supybot.humanTimestampFormat()
|
format = conf.supybot.humanTimestampFormat()
|
||||||
try:
|
try:
|
||||||
@ -69,122 +72,63 @@ class NewsRecord(dbi.Record):
|
|||||||
(self.text, self.subject, self.by,
|
(self.text, self.subject, self.by,
|
||||||
time.strftime(format, time.localtime(int(self.at))))
|
time.strftime(format, time.localtime(int(self.at))))
|
||||||
else:
|
else:
|
||||||
s = '%s (Subject: "%s", added by %s on %s, expires at %s)' % \
|
s = '%s (Subject: "%s", added by %s on %s, expires at %s)'
|
||||||
(self.text, self.subject, self.by,
|
s = s % (self.text, self.subject, user,
|
||||||
time.strftime(format, time.localtime(int(self.at))),
|
time.strftime(format, time.localtime(int(self.at))),
|
||||||
time.strftime(format, time.localtime(int(self.expires))))
|
time.strftime(format, time.localtime(int(self.expires))))
|
||||||
return s
|
return s
|
||||||
|
|
||||||
class SqliteNewsDB(object):
|
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
self.dbs = ircutils.IrcDict()
|
# We use self.__class__ here because apparently DB isn't in our
|
||||||
self.filename = filename
|
# scope. python--
|
||||||
|
self.__parent = super(self.__class__, self)
|
||||||
|
self.__parent.__init__(filename)
|
||||||
|
|
||||||
def close(self):
|
def add(self, subject, text, at, expires, by):
|
||||||
for db in self.dbs.itervalues():
|
return self.__parent.add(self.Record(at=at, by=by, text=text,
|
||||||
db.close()
|
subject=subject, expires=expires))
|
||||||
|
|
||||||
def _getDb(self, channel):
|
def getOld(self, id=None):
|
||||||
try:
|
now = time.time()
|
||||||
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):
|
|
||||||
self.dbs[filename] = sqlite.connect(filename)
|
|
||||||
return self.dbs[filename]
|
|
||||||
db = sqlite.connect(filename)
|
|
||||||
self.dbs[filename] = db
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute("""CREATE TABLE news (
|
|
||||||
id INTEGER PRIMARY KEY,
|
|
||||||
subject TEXT,
|
|
||||||
item TEXT,
|
|
||||||
added_at TIMESTAMP,
|
|
||||||
expires_at TIMESTAMP,
|
|
||||||
added_by TEXT
|
|
||||||
)""")
|
|
||||||
db.commit()
|
|
||||||
return db
|
|
||||||
|
|
||||||
def add(self, channel, subject, text, added_at, expires, by):
|
|
||||||
db = self._getDb(channel)
|
|
||||||
cursor = db.cursor()
|
|
||||||
cursor.execute("INSERT INTO news VALUES (NULL, %s, %s, %s, %s, %s)",
|
|
||||||
subject, text, added_at, expires, by)
|
|
||||||
db.commit()
|
|
||||||
|
|
||||||
# XXX This should change only to support id, and the old functionality
|
|
||||||
# should move out to another method.
|
|
||||||
def get(self, channel, id=None, old=False):
|
|
||||||
db = self._getDb(channel)
|
|
||||||
cursor = db.cursor()
|
|
||||||
if id:
|
if id:
|
||||||
cursor.execute("""SELECT item, subject, added_at,
|
return self.get(id)
|
||||||
expires_at, added_by
|
|
||||||
FROM news
|
|
||||||
WHERE id=%s""", id)
|
|
||||||
if cursor.rowcount == 0:
|
|
||||||
raise dbi.NoRecordError, id
|
|
||||||
(text, subject, at, expires, by) = cursor.fetchone()
|
|
||||||
return NewsRecord(id, text=text, subject=subject, at=int(at),
|
|
||||||
expires=int(expires), by=by)
|
|
||||||
else:
|
else:
|
||||||
if old:
|
L = [R for R in self if R.expires < now and R.expires != 0]
|
||||||
cursor.execute("""SELECT id, subject
|
if not L:
|
||||||
FROM news
|
|
||||||
WHERE expires_at <> 0 AND expires_at < %s
|
|
||||||
ORDER BY id DESC""", int(time.time()))
|
|
||||||
else:
|
|
||||||
cursor.execute("""SELECT id, subject
|
|
||||||
FROM news
|
|
||||||
WHERE expires_at > %s OR expires_at=0""",
|
|
||||||
int(time.time()))
|
|
||||||
if cursor.rowcount == 0:
|
|
||||||
raise dbi.NoRecordError
|
raise dbi.NoRecordError
|
||||||
else:
|
else:
|
||||||
return cursor.fetchall()
|
return L
|
||||||
|
|
||||||
def remove(self, channel, id):
|
def get(self, id=None):
|
||||||
db = self._getDb(channel)
|
now = time.time()
|
||||||
cursor = db.cursor()
|
if id:
|
||||||
cursor.execute("""DELETE FROM news WHERE id = %s""", id)
|
return self.__parent.get(id)
|
||||||
db.commit()
|
else:
|
||||||
if cursor.rowcount == 0:
|
L = [R for R in self if R.expires >= now or R.expires == 0]
|
||||||
raise dbi.NoRecordError, id
|
if not L:
|
||||||
|
raise dbi.NoRecordError
|
||||||
|
return L
|
||||||
|
|
||||||
def change(self, channel, id, replacer):
|
def change(self, id, f):
|
||||||
db = self._getDb(channel)
|
news = self.get(id)
|
||||||
cursor = db.cursor()
|
s = '%s: %s' % (news.subject, news.text)
|
||||||
cursor.execute("""SELECT subject, item FROM news WHERE id=%s""", id)
|
s = f(s)
|
||||||
if cursor.rowcount == 0:
|
(news.subject, news.text) = s.split(': ', 1)
|
||||||
raise dbi.NoRecordError, id
|
self.set(id, news)
|
||||||
(subject, item) = cursor.fetchone()
|
|
||||||
s = '%s: %s' % (subject, item)
|
|
||||||
s = replacer(s)
|
|
||||||
(newSubject, newItem) = s.split(': ', 1)
|
|
||||||
cursor.execute("""UPDATE news SET subject=%s, item=%s WHERE id=%s""",
|
|
||||||
newSubject, newItem, id)
|
|
||||||
|
|
||||||
NewsDB = plugins.DB('News',
|
NewsDB = plugins.DB('News', {'flat': DbiNewsDB})
|
||||||
{'sqlite': SqliteNewsDB})
|
|
||||||
|
|
||||||
class News(callbacks.Privmsg):
|
class News(callbacks.Privmsg):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__parent = super(News, self)
|
self.__parent = super(News, self)
|
||||||
self.__parent.__init__()
|
self.__parent.__init__()
|
||||||
self.db = NewsDB()
|
self.db = NewsDB()
|
||||||
self.removeOld = False
|
|
||||||
|
|
||||||
def die(self):
|
def die(self):
|
||||||
self.__parent.die()
|
self.__parent.die()
|
||||||
self.db.close()
|
self.db.close()
|
||||||
|
|
||||||
def add(self, irc, msg, args, channel):
|
def add(self, irc, msg, args, channel, user, at, expires, news):
|
||||||
"""[<channel>] <expires> <subject>: <text>
|
"""[<channel>] <expires> <subject>: <text>
|
||||||
|
|
||||||
Adds a given news item of <text> to a channel with the given <subject>.
|
Adds a given news item of <text> to a channel with the given <subject>.
|
||||||
@ -192,24 +136,15 @@ class News(callbacks.Privmsg):
|
|||||||
now. <channel> is only necessary if the message isn't sent in the
|
now. <channel> is only necessary if the message isn't sent in the
|
||||||
channel itself.
|
channel itself.
|
||||||
"""
|
"""
|
||||||
(expire_interval, news) = privmsgs.getArgs(args, required=2)
|
|
||||||
try:
|
try:
|
||||||
expire_interval = int(expire_interval)
|
|
||||||
(subject, text) = news.split(': ', 1)
|
(subject, text) = news.split(': ', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise callbacks.ArgumentError
|
raise callbacks.ArgumentError
|
||||||
added_at = int(time.time())
|
id = self.db.add(channel, subject, text, at, expires, user.id)
|
||||||
expires = expire_interval and (added_at + expire_interval)
|
irc.replySuccess('(News item #%s added)' % id)
|
||||||
# Set the other stuff needed for the insert.
|
add = wrap(add, ['channeldb', 'user', 'now', 'expiry', 'text'])
|
||||||
try:
|
|
||||||
by = ircdb.users.getUserId(msg.prefix)
|
|
||||||
except KeyError:
|
|
||||||
by = msg.nick
|
|
||||||
self.db.add(channel, subject, text, added_at, expires, by)
|
|
||||||
irc.replySuccess()
|
|
||||||
add = privmsgs.checkChannelCapability(add, 'news')
|
|
||||||
|
|
||||||
def news(self, irc, msg, args):
|
def news(self, irc, msg, args, channel, id):
|
||||||
"""[<channel>] [<id>]
|
"""[<channel>] [<id>]
|
||||||
|
|
||||||
Display the news items for <channel> in the format of '(#id) subject'.
|
Display the news items for <channel> in the format of '(#id) subject'.
|
||||||
@ -217,81 +152,92 @@ class News(callbacks.Privmsg):
|
|||||||
news items. <channel> is only necessary if the message isn't sent in
|
news items. <channel> is only necessary if the message isn't sent in
|
||||||
the channel itself.
|
the channel itself.
|
||||||
"""
|
"""
|
||||||
channel = privmsgs.getChannel(msg, args)
|
if not id:
|
||||||
id = privmsgs.getArgs(args, required=0, optional=1)
|
|
||||||
if id:
|
|
||||||
try:
|
|
||||||
record = self.db.get(channel, int(id))
|
|
||||||
irc.reply(str(record))
|
|
||||||
except dbi.NoRecordError, id:
|
|
||||||
irc.errorInvalid('news item id', id)
|
|
||||||
else:
|
|
||||||
try:
|
try:
|
||||||
records = self.db.get(channel)
|
records = self.db.get(channel)
|
||||||
items = ['(#%s) %s' % (id, s) for (id, s) in records]
|
items = ['(#%s) %s' % (R.id, R.subject) for R in records]
|
||||||
s = 'News for %s: %s' % (channel, '; '.join(items))
|
s = 'News for %s: %s' % (channel, '; '.join(items))
|
||||||
irc.reply(s)
|
irc.reply(s)
|
||||||
except dbi.NoRecordError:
|
except dbi.NoRecordError:
|
||||||
irc.reply('No news for %s.' % channel)
|
irc.reply('No news for %s.' % channel)
|
||||||
|
else:
|
||||||
def remove(self, irc, msg, args, channel):
|
|
||||||
"""[<channel>] <number>
|
|
||||||
|
|
||||||
Removes the news item with id <number> from <channel>. <channel> is
|
|
||||||
only necessary if the message isn't sent in the channel itself.
|
|
||||||
"""
|
|
||||||
id = privmsgs.getArgs(args)
|
|
||||||
try:
|
try:
|
||||||
|
if id < 1:
|
||||||
|
raise ValueError
|
||||||
|
record = self.db.get(channel, id)
|
||||||
|
irc.reply(str(record))
|
||||||
|
except dbi.NoRecordError, id:
|
||||||
|
irc.errorInvalid('news item id', id)
|
||||||
|
except ValueError:
|
||||||
|
irc.errorInvalid('news item id', id,
|
||||||
|
'<id> must be a positive integer.')
|
||||||
|
news = wrap(news, ['channeldb', ('int?', None)])
|
||||||
|
|
||||||
|
def remove(self, irc, msg, args, channel, id):
|
||||||
|
"""[<channel>] <id>
|
||||||
|
|
||||||
|
Removes the news item with <id> from <channel>. <channel> is only
|
||||||
|
necessary if the message isn't sent in the channel itself.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if id < 1:
|
||||||
|
raise ValueError
|
||||||
self.db.remove(channel, id)
|
self.db.remove(channel, id)
|
||||||
irc.replySuccess()
|
irc.replySuccess()
|
||||||
except dbi.NoRecordError:
|
except dbi.NoRecordError:
|
||||||
irc.errorInvalid('news item id', id)
|
irc.errorInvalid('news item id', id)
|
||||||
remove = privmsgs.checkChannelCapability(remove, 'news')
|
except ValueError:
|
||||||
|
irc.errorInvalid('news item id', id,
|
||||||
|
'<id> must be a positive integer.')
|
||||||
|
remove = wrap(remove, ['channeldb', 'int'])
|
||||||
|
|
||||||
def change(self, irc, msg, args, channel):
|
def change(self, irc, msg, args, channel, id, replacer):
|
||||||
"""[<channel>] <number> <regexp>
|
"""[<channel>] <id> <regexp>
|
||||||
|
|
||||||
Changes the news item with id <number> from <channel> according to the
|
Changes the news item with <id> from <channel> according to the
|
||||||
regular expression <regexp>. <regexp> should be of the form
|
regular expression <regexp>. <regexp> should be of the form
|
||||||
s/text/replacement/flags. <channel> is only necessary if the message
|
s/text/replacement/flags. <channel> is only necessary if the message
|
||||||
isn't sent on the channel itself.
|
isn't sent on the channel itself.
|
||||||
"""
|
"""
|
||||||
(id, regexp) = privmsgs.getArgs(args, required=2)
|
|
||||||
try:
|
|
||||||
replacer = utils.perlReToReplacer(regexp)
|
|
||||||
except ValueError, e:
|
|
||||||
irc.error(str(e))
|
|
||||||
return
|
|
||||||
try:
|
try:
|
||||||
|
if id < 1:
|
||||||
|
raise ValueError
|
||||||
self.db.change(channel, id, replacer)
|
self.db.change(channel, id, replacer)
|
||||||
irc.replySuccess()
|
irc.replySuccess()
|
||||||
except dbi.NoRecordError:
|
except dbi.NoRecordError:
|
||||||
irc.errorInvalid('news item id', id)
|
irc.errorInvalid('news item id', id)
|
||||||
change = privmsgs.checkChannelCapability(change, 'news')
|
except ValueError:
|
||||||
|
irc.errorInvalid('news item id', id,
|
||||||
|
'<id> must be a positive integer.')
|
||||||
|
change = wrap(change, ['channeldb', 'int', 'regexpReplacer'])
|
||||||
|
|
||||||
def old(self, irc, msg, args):
|
def old(self, irc, msg, args, channel, id):
|
||||||
"""[<channel>] [<number>]
|
"""[<channel>] [<id>]
|
||||||
|
|
||||||
Returns the old news item for <channel> with id <number>. If no number
|
Returns the old news item for <channel> with <id>. If no number is
|
||||||
is given, returns all the old news items in reverse order. <channel>
|
given, returns all the old news items in reverse order. <channel> is
|
||||||
is only necessary if the message isn't sent in the channel itself.
|
only necessary if the message isn't sent in the channel itself.
|
||||||
"""
|
"""
|
||||||
channel = privmsgs.getChannel(msg, args)
|
|
||||||
id = privmsgs.getArgs(args, required=0, optional=1)
|
|
||||||
if id:
|
if id:
|
||||||
try:
|
try:
|
||||||
record = self.db.get(channel, id, old=True)
|
if id < 1:
|
||||||
|
raise ValueError
|
||||||
|
record = self.db.getOld(channel, id)
|
||||||
irc.reply(str(record))
|
irc.reply(str(record))
|
||||||
except dbi.NoRecordError, id:
|
except dbi.NoRecordError, id:
|
||||||
irc.errorInvalid('news item id', id)
|
irc.errorInvalid('news item id', id)
|
||||||
|
except ValueError:
|
||||||
|
irc.errorInvalid('news item id', id,
|
||||||
|
'<id> must be a positive integer.')
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
records = self.db.get(channel, old=True)
|
records = self.db.getOld(channel)
|
||||||
items = ['(#%s) %s' % (id, s) for (id, s) in records]
|
items = ['(#%s) %s' % (R.id, R.subject) for R in records]
|
||||||
s = 'Old news for %s: %s' % (channel, '; '.join(items))
|
s = 'Old news for %s: %s' % (channel, '; '.join(items))
|
||||||
irc.reply(s)
|
irc.reply(s)
|
||||||
except dbi.NoRecordError:
|
except dbi.NoRecordError:
|
||||||
irc.reply('No old news for %s.' % channel)
|
irc.reply('No old news for %s.' % channel)
|
||||||
|
old = wrap(old, ['channeldb', ('int?', None)])
|
||||||
|
|
||||||
|
|
||||||
Class = News
|
Class = News
|
||||||
|
@ -33,15 +33,16 @@ import time
|
|||||||
|
|
||||||
import supybot.utils as utils
|
import supybot.utils as utils
|
||||||
|
|
||||||
try:
|
class NewsTestCase(ChannelPluginTestCase):
|
||||||
import sqlite
|
plugins = ('News','User')
|
||||||
except ImportError:
|
def setUp(self):
|
||||||
sqlite = None
|
ChannelPluginTestCase.setUp(self)
|
||||||
|
# Create a valid user to use
|
||||||
|
self.prefix = 'news!bar@baz'
|
||||||
|
self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo',
|
||||||
|
prefix=self.prefix))
|
||||||
|
m = self.irc.takeMsg() # Response to register.
|
||||||
|
|
||||||
|
|
||||||
if sqlite is not None:
|
|
||||||
class NewsTestCase(ChannelPluginTestCase):
|
|
||||||
plugins = ('News',)
|
|
||||||
def testAddnews(self):
|
def testAddnews(self):
|
||||||
self.assertNotError('add 0 subject: foo')
|
self.assertNotError('add 0 subject: foo')
|
||||||
self.assertRegexp('news', 'subject')
|
self.assertRegexp('news', 'subject')
|
||||||
@ -84,6 +85,5 @@ if sqlite is not None:
|
|||||||
print 'Done sleeping.'
|
print 'Done sleeping.'
|
||||||
self.assertNotError('old')
|
self.assertNotError('old')
|
||||||
|
|
||||||
|
|
||||||
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78:
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user