Updated to a flatfile database.

This commit is contained in:
Jeremy Fincher 2004-02-08 03:23:30 +00:00
parent eaade69b39
commit 34f4e3412a

View File

@ -34,12 +34,13 @@ Keeps statistics on who says what words in a channel.
""" """
import os import os
import csv
import string import string
import sqlite import log
import conf import conf
import utils import utils
import world
import ircdb import ircdb
import plugins import plugins
import ircutils import ircutils
@ -47,7 +48,6 @@ import privmsgs
import registry import registry
import callbacks import callbacks
conf.registerPlugin('WordStats') conf.registerPlugin('WordStats')
conf.registerChannelValue(conf.supybot.plugins.WordStats, conf.registerChannelValue(conf.supybot.plugins.WordStats,
'rankingDisplay', 'rankingDisplay',
@ -55,67 +55,122 @@ conf.registerChannelValue(conf.supybot.plugins.WordStats,
to show for a given wordstat when someone requests the wordstats for a to show for a given wordstat when someone requests the wordstats for a
particular word.""")) particular word."""))
class WordStats(callbacks.Privmsg, plugins.ChannelDBHandler): nonAlphaNumeric = filter(lambda s: not s.isalnum(), string.ascii)
noIgnore = True
def __init__(self):
callbacks.Privmsg.__init__(self)
plugins.ChannelDBHandler.__init__(self)
def die(self): class WordStatsDB(plugins.ChannelUserDB):
callbacks.Privmsg.die(self) def __init__(self, *args, **kwargs):
plugins.ChannelDBHandler.die(self) self.channelWords = ircutils.IrcDict()
plugins.ChannelUserDB.__init__(self, *args, **kwargs)
def makeDb(self, filename): def serialize(self, v):
if os.path.exists(filename): L = []
db = sqlite.connect(filename) for (word, count) in v.iteritems():
else: L.append('%s:%s' % (word, count))
db = sqlite.connect(filename) return L
cursor = db.cursor()
cursor.execute("""CREATE TABLE words (
id INTEGER PRIMARY KEY,
word TEXT UNIQUE ON CONFLICT IGNORE
)""")
cursor.execute("""CREATE TABLE word_stats (
id INTEGER PRIMARY KEY,
word_id INTEGER,
user_id INTEGER,
count INTEGER,
UNIQUE (word_id, user_id) ON CONFLICT IGNORE
)""")
cursor.execute("""CREATE INDEX word_stats_word_id
ON word_stats (word_id)""")
cursor.execute("""CREATE INDEX word_stats_user_id
ON word_stats (user_id)""")
db.commit()
return db
_alphanumeric = string.ascii_letters + string.digits def deserialize(self, channel, id, L):
_nonAlphanumeric = string.ascii.translate(string.ascii, _alphanumeric) d = {}
def doPrivmsg(self, irc, msg): for s in L:
if not ircutils.isChannel(msg.args[0]): (word, count) = s.split(':')
return count = int(count)
d[word] = count
if channel not in self.channelWords:
self.channelWords[channel] = {}
self.channelWords[channel].setdefault(word, 0)
self.channelWords[channel][word] += count
return d
def getWordCount(self, channel, id, word):
word = word.lower()
return self[channel, id][word]
def getUserWordCounts(self, channel, id):
return self[channel, id].items()
def getWords(self, channel):
if channel not in self.channelWords:
self.channelWords[channel] = {}
L = self.channelWords[channel].keys()
L.sort()
return L
def getTotalWordCount(self, channel, word):
return self.channelWords[channel][word]
def getNumUsers(self, channel):
i = 0
for ((chan, _), _) in self.iteritems():
if chan == channel:
i += 1
return i
def getTopUsers(self, channel, word, n):
word = word.lower()
L = [(id, d[word]) for ((chan, id), d) in self.iteritems()
if channel == chan and word in d]
utils.sortBy(lambda (_, i): i, L)
L = L[-n:]
L.reverse()
return L
def getRankAndNumber(self, channel, id, word):
L = self.getTopUsers(channel, word, 0)
n = 0
for (someId, count) in L:
n += 1
if id == someId:
return (n, count)
def addWord(self, channel, word):
word = word.lower()
if channel not in self.channelWords:
self.channelWords[channel] = {}
self.channelWords[channel][word] = 0
for ((chan, id), d) in self.iteritems():
if channel == chan:
if word not in d:
d[word] = 0
def addMsg(self, msg):
assert msg.command == 'PRIVMSG'
try: try:
id = ircdb.users.getUserId(msg.prefix) id = ircdb.users.getUserId(msg.prefix)
except KeyError: except KeyError:
return return
(channel, s) = msg.args (channel, text) = msg.args
s = s.strip() text = text.strip().lower()
if not s: if not ircutils.isChannel(channel) or not text:
return return
db = self.getDb(channel) msgwords = [s.strip(nonAlphaNumeric) for s in text.split()]
cursor = db.cursor() if channel not in self.channelWords:
words = s.lower().split() self.channelWords[channel] = {}
words = [s.strip(self._nonAlphanumeric) for s in words] for word in self.channelWords[channel]:
criteria = ['word=%s'] * len(words) for msgword in msgwords:
criterion = ' OR '.join(criteria) if msgword == word:
cursor.execute("SELECT id, word FROM words WHERE %s"%criterion, *words) self.channelWords[channel][word] += 1
for (wordId, word) in cursor.fetchall(): if (channel, id) not in self:
cursor.execute("""INSERT INTO word_stats self[channel, id] = {}
VALUES(NULL, %s, %s, 0)""", wordId, id) if word not in self[channel, id]:
cursor.execute("""UPDATE word_stats SET count=count+%s self[channel, id][word] = 0
WHERE word_id=%s AND user_id=%s""", self[channel, id][word] += 1
words.count(word), wordId, id)
db.commit()
filename=os.path.join(conf.supybot.directories.data(), 'WordStats.db')
class WordStats(callbacks.Privmsg):
noIgnore = True
def __init__(self):
callbacks.Privmsg.__init__(self)
self.db = WordStatsDB(filename)
world.flushers.append(self.db.flush)
def die(self):
if self.db.flush in world.flushers:
world.flushers.remove(self.db.flush)
self.db.close()
callbacks.Privmsg.die(self)
def doPrivmsg(self, irc, msg):
self.db.addMsg(msg)
def add(self, irc, msg, args): def add(self, irc, msg, args):
"""[<channel>] <word> """[<channel>] <word>
@ -126,14 +181,11 @@ class WordStats(callbacks.Privmsg, plugins.ChannelDBHandler):
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
word = privmsgs.getArgs(args) word = privmsgs.getArgs(args)
word = word.strip() word = word.strip()
if word.strip(self._nonAlphanumeric) != word: if word.strip(nonAlphaNumeric) != word:
irc.error('<word> must not contain non-alphanumeric chars.') irc.error('<word> must not contain non-alphanumeric chars.')
return return
word = word.lower() word = word.lower()
db = self.getDb(channel) self.db.addWord(channel, word)
cursor = db.cursor()
cursor.execute("""INSERT INTO words VALUES (NULL, %s)""", word)
db.commit()
irc.replySuccess() irc.replySuccess()
def wordstats(self, irc, msg, args): def wordstats(self, irc, msg, args):
@ -149,16 +201,15 @@ class WordStats(callbacks.Privmsg, plugins.ChannelDBHandler):
""" """
channel = privmsgs.getChannel(msg, args) channel = privmsgs.getChannel(msg, args)
(arg1, arg2) = privmsgs.getArgs(args, required=0, optional=2) (arg1, arg2) = privmsgs.getArgs(args, required=0, optional=2)
db = self.getDb(channel)
cursor = db.cursor()
if not arg1 and not arg2: if not arg1 and not arg2:
cursor.execute("""SELECT word FROM words""") words = self.db.getWords(channel)
if cursor.rowcount == 0: if words:
commaAndify = utils.commaAndify
s = 'I am currently keeping stats for %s.' % commaAndify(words)
irc.reply(s)
else:
irc.reply('I am not currently keeping any word stats.') irc.reply('I am not currently keeping any word stats.')
return return
l = [repr(tup[0]) for tup in cursor.fetchall()]
s = 'Currently keeping stats for: %s' % utils.commaAndify(l)
irc.reply(s)
elif arg1 and arg2: elif arg1 and arg2:
user, word = (arg1, arg2) user, word = (arg1, arg2)
try: try:
@ -170,108 +221,60 @@ class WordStats(callbacks.Privmsg, plugins.ChannelDBHandler):
except KeyError: except KeyError:
irc.errorNoUser() irc.errorNoUser()
return return
db = self.getDb(channel) try:
cursor = db.cursor() count = self.db.getWordCount(channel, id, word)
word = word.lower() except KeyError:
cursor.execute("""SELECT word_stats.count FROM words, word_stats irc.error('I\'m not keeping stats on %r.' % word)
WHERE words.word=%s AND
word_id=words.id AND
word_stats.user_id=%s""", word, id)
if cursor.rowcount == 0:
cursor.execute("""SELECT id FROM words WHERE word=%s""", word)
if cursor.rowcount == 0:
irc.error('I\'m not keeping stats on %r.' % word)
else:
irc.error('%s has never said %r.' % (user, word))
return
count = int(cursor.fetchone()[0])
s = '%s has said %r %s.' % (user,word,utils.nItems('time', count))
irc.reply(s)
else:
# Figure out if we got a user or a word
cursor.execute("""SELECT word FROM words
WHERE word=%s""", arg1)
if cursor.rowcount == 0:
# It was a user.
try:
id = ircdb.users.getUserId(arg1)
except KeyError: # Maybe it was a nick. Check the hostmask.
try:
hostmask = irc.state.nickToHostmask(arg1)
id = ircdb.users.getUserId(hostmask)
except KeyError:
irc.error('%r doesn\'t look like a user I know '
'or a word that I\'m keeping stats '
'on' % arg1)
return
cursor.execute("""SELECT words.word, word_stats.count
FROM words, word_stats
WHERE words.id = word_stats.word_id
AND word_stats.user_id=%s
ORDER BY words.word""", id)
if cursor.rowcount == 0:
username = ircdb.users.getUser(id).name
irc.error('%r has no wordstats' % username)
return
L = [('%r: %s' % (word, count)) for
(word, count) in cursor.fetchall()]
irc.reply(utils.commaAndify(L))
return return
if count:
s = '%s has said %r %s.' % \
(user, word, utils.nItems('time', count))
irc.reply(s)
else: else:
# It's a word, not a user irc.error('%s has never said %r.' % (user, word))
word = arg1 elif arg1 in self.db.getWords(channel):
numUsers = self.registryValue('rankingDisplay', word = arg1
msg.args[0]) total = self.db.getTotalWordCount(channel, word)
cursor.execute("""SELECT word_stats.count, n = self.registryValue('rankingDisplay', channel)
word_stats.user_id try:
FROM words, word_stats id = ircdb.users.getUserId(msg.prefix)
WHERE words.word=%s AND (rank, number) = self.db.getRankAndNumber(channel, id, word)
words.id=word_stats.word_id except (KeyError, ValueError):
ORDER BY word_stats.count DESC""", id = None
word) rank = None
if cursor.rowcount == 0: number = None
irc.error('No one has said %r' % word) ers = '%rer' % word
return L = []
results = cursor.fetchall() for (userid, count) in self.db.getTopUsers(channel, word, n):
numResultsShown = min(cursor.rowcount, numUsers) if userid == id:
cursor.execute("""SELECT sum(word_stats.count) rank = None
FROM words, word_stats username = ircdb.users.getUser(userid).name
WHERE words.word=%s AND L.append('%s: %s' % (username, count))
words.id=word_stats.word_id""", ret = 'Top %s (out of a total of %s seen):' % \
word) (utils.nItems(ers, len(L)), utils.nItems(repr(word), total))
total = int(cursor.fetchone()[0]) users = self.db.getNumUsers(channel)
ers = '%rer' % word if rank is not None:
ret = 'Top %s ' % utils.nItems(ers, numResultsShown) s = ' You are ranked %s out of %s with %s.' % \
ret += '(out of a total of %s seen):' % \ (rank, utils.nItems(ers, users),
utils.nItems(repr(word), total) utils.nItems(repr(word), number))
L = [] else:
for (count, id) in results[:numResultsShown]: s = ''
username = ircdb.users.getUser(id).name ret = '%s %s.%s' % (ret, utils.commaAndify(L), s)
L.append('%s: %s' % (username, count)) irc.reply(ret)
try: else:
id = ircdb.users.getUserId(msg.prefix) user = arg1
rank = 1 try:
s = "" # Don't say anything if they show in the output id = ircdb.users.getUserId(user)
# already except KeyError:
seenUser = False irc.error('%r doesn\'t look like a word I\'m keeping stats '
for (count, userId) in results: 'on or a user in my database.' % user)
if userId == id: return
seenUser = True try:
if rank > numResultsShown: L = ['%r: %s' % (word, count)
s = 'You are ranked %s out of %s with %s.' % \ for (word,count) in self.db.getUserWordCounts(channel,id)]
(rank, utils.nItems(ers, len(results)), irc.reply(utils.commaAndify(L))
utils.nItems(repr(word), count)) except KeyError:
break irc.reply('I have no word stats for that person.')
else:
rank += 1
else:
if not seenUser:
s = 'You have not said %r' % word
ret = '%s %s. %s' % (ret, utils.commaAndify(L), s)
except KeyError:
ret = '%s %s.' % (ret, utils.commaAndify(L))
irc.reply(ret)
Class = WordStats Class = WordStats
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: