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