These don't work yet, but I need to be able to see my other diff.

This commit is contained in:
Jeremy Fincher 2004-01-28 19:08:47 +00:00
parent f57ceba827
commit 4e5957d8d2
2 changed files with 30 additions and 356 deletions

View File

@ -52,10 +52,10 @@ import utils
import ircdb import ircdb
import ircmsgs import ircmsgs
import plugins import plugins
import privmsgs
import ircutils import ircutils
import privmsgs
import registry
import callbacks import callbacks
import configurable
try: try:
import sqlite import sqlite
@ -64,51 +64,38 @@ except ImportError:
'plugin. Download it at <http://pysqlite.sf.net/>' 'plugin. Download it at <http://pysqlite.sf.net/>'
# I should write/copy a generalized proxy at some point. # I should write/copy a generalized proxy at some point.
class ReWrapper(object): class Smileys(registry.Value):
def __init__(self, L): def set(self, s):
self.s = utils.dqrepr(' '.join(L)) L = s.split()
self.r = re.compile('|'.join(imap(re.escape, L))) self.s = s
self.setValue(L)
def findall(self, *args, **kwargs): def setValue(self, v):
return self.r.findall(*args, **kwargs) self.value = re.compile('|'.join(imap(re.escape, v)))
def __str__(self): def __str__(self):
return self.s return self.s
__repr__ = __str__
def SmileyType(s): conf.registerPlugin('ChannelDB')
try: conf.registerChannelValue(conf.supybot.plugins.ChannelDB, 'selfStats',
L = configurable.SpaceSeparatedStrListType(s) registry.Boolean(True, """Determines whether the bot will keep channel
return ReWrapper(L) statistics on itself, possibly skewing the channel stats (especially in
except configurable.Error: cases where the bot is relaying between channels on a network)."""))
raise configurable.Error, 'Value must be a space-separated list of ' \ conf.registerChannelValue(conf.supybot.plugins.ChannelDB, 'smileys',
'smileys or frowns.' Smileys(':) ;) ;] :-) :-D :D :P :p (= =)'.split(), """Determines what
words (i.e., pieces of text with no spaces in them) are considered
'smileys' for the purposes of stats-keeping."""))
conf.registerChannelValue(conf.supybot.plugins.ChannelDB, 'frowns',
Smileys(':| :-/ :-\\ :\\ :/ :( :-( :\'('.split(), """Determines what words
(i.e., pieces of text with no spaces in them ) are considered 'frowns' for
the purposes of stats-keeping."""))
class ChannelDB(plugins.ChannelDBHandler, class ChannelDB(plugins.ChannelDBHandler,
configurable.Mixin,
callbacks.Privmsg): callbacks.Privmsg):
noIgnore = True noIgnore = True
configurables = configurable.Dictionary(
[('self-stats', configurable.BoolType, True,
"""Determines whether the bot will keep channel statistics on itself,
possibly skewing the channel stats (especially in cases where he's
relaying between channels on a network."""),
('wordstats-top-n', configurable.IntType, 3,
"""Determines the maximum number of top users to show for a given
wordstat when you request the wordstats for a particular word."""),
('smileys', SmileyType, SmileyType(':) ;) ;] :-) :-D :D :P :p (= =)'),
"""Determines what words count as smileys for the purpose of keeping
statistics on the number of smileys each user has sent to the
channel."""),
('frowns', SmileyType, SmileyType(':| :-/ :-\\ :\\ :/ :( :-( :\'('),
"""Determines what words count as frowns for the purpose of keeping
statistics on the number of frowns each user has sent ot the
channel."""),]
)
def __init__(self): def __init__(self):
callbacks.Privmsg.__init__(self) callbacks.Privmsg.__init__(self)
configurable.Mixin.__init__(self)
plugins.ChannelDBHandler.__init__(self) plugins.ChannelDBHandler.__init__(self)
self.lastmsg = None self.lastmsg = None
self.laststate = None self.laststate = None
@ -116,7 +103,6 @@ class ChannelDB(plugins.ChannelDBHandler,
def die(self): def die(self):
callbacks.Privmsg.die(self) callbacks.Privmsg.die(self)
configurable.Mixin.die(self)
plugins.ChannelDBHandler.die(self) plugins.ChannelDBHandler.die(self)
def makeDb(self, filename): def makeDb(self, filename):
@ -167,21 +153,6 @@ class ChannelDB(plugins.ChannelDBHandler,
cursor.execute("""INSERT INTO channel_stats cursor.execute("""INSERT INTO channel_stats
VALUES (0, 0, 0, 0, 0, 0, VALUES (0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0)""") 0, 0, 0, 0, 0, 0)""")
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() db.commit()
def p(s1, s2): def p(s1, s2):
return int(ircutils.nickEqual(s1, s2)) return int(ircutils.nickEqual(s1, s2))
@ -199,48 +170,16 @@ class ChannelDB(plugins.ChannelDBHandler,
super(ChannelDB, self).__call__(irc, msg) super(ChannelDB, self).__call__(irc, msg)
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
if ircutils.isChannel(msg.args[0]): if not ircutils.isChannel(msg.args[0]):
self._updatePrivmsgStats(msg)
self._updateWordStats(msg)
_alphanumeric = string.ascii_letters + string.digits
_nonAlphanumeric = string.ascii.translate(string.ascii, _alphanumeric)
def _updateWordStats(self, msg):
try:
if self.outFiltering:
id = 0
else:
id = ircdb.users.getUserId(msg.prefix)
except KeyError:
return return
(channel, s) = msg.args (channel, s) = msg.args
s = s.strip()
if not s:
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()
def _updatePrivmsgStats(self, msg):
(channel, s) = msg.args
db = self.getDb(channel) db = self.getDb(channel)
cursor = db.cursor() cursor = db.cursor()
chars = len(s) chars = len(s)
words = len(s.split()) words = len(s.split())
isAction = ircmsgs.isAction(msg) isAction = ircmsgs.isAction(msg)
frowns = len(self.configurables.get('frowns', channel).findall(s)) frowns = len(self.registryValue('frowns', channel).findall(s))
smileys = len(self.configurables.get('smileys', channel).findall(s)) smileys = len(self.registryValue('smileys', channel).findall(s))
s = ircmsgs.prettyPrint(msg) s = ircmsgs.prettyPrint(msg)
cursor.execute("""UPDATE channel_stats cursor.execute("""UPDATE channel_stats
SET smileys=smileys+%s, SET smileys=smileys+%s,
@ -275,9 +214,7 @@ class ChannelDB(plugins.ChannelDBHandler,
def outFilter(self, irc, msg): def outFilter(self, irc, msg):
if msg.command == 'PRIVMSG': if msg.command == 'PRIVMSG':
if ircutils.isChannel(msg.args[0]): if ircutils.isChannel(msg.args[0]):
if self.configurables.get('self-stats', msg.args[0]): if self.registryValue('selfStats', msg.args[0]):
db = self.getDb(msg.args[0])
cursor = db.cursor()
try: try:
self.outFiltering = True self.outFiltering = True
self._updatePrivmsgStats(msg) self._updatePrivmsgStats(msg)
@ -495,160 +432,6 @@ class ChannelDB(plugins.ChannelDBHandler,
values.kicks, values.modes, values.topics) values.kicks, values.modes, values.topics)
irc.reply(s) irc.reply(s)
def addword(self, irc, msg, args):
"""[<channel>] <word>
Keeps stats on <word> in <channel>. <channel> is only necessary if the
message isn't sent in the channel itself.
"""
channel = privmsgs.getChannel(msg, args)
word = privmsgs.getArgs(args)
word = word.strip()
if word.strip(self._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()
irc.replySuccess()
def wordstats(self, irc, msg, args):
"""[<channel>] [<user>] [<word>]
With no arguments, returns the list of words that are being monitored
for stats. With <user> alone, returns all the stats for that user.
With <word> alone, returns the top users for that word. With <user>
and <word>, returns that user's stat for that word. <channel> is only
needed if not said in the channel. (Note: if only one of <user> or
<word> is given, <word> is assumed first and only if no stats are
available for that word, do we assume it's <user>.)
"""
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:
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:
id = ircdb.users.getUserId(user)
except KeyError: # Maybe it was a nick. Check the hostmask.
try:
hostmask = irc.state.nickToHostmask(user)
id = ircdb.users.getUserId(hostmask)
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:
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
else:
# It's a word, not a user
word = arg1
numUsers = self.configurables.get('wordstats-top-n',
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))
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:
s = 'You are ranked %s out of %s with %s.' % \
(rank, utils.nItems(ers, len(results)),
utils.nItems(repr(word), count))
break
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 = ChannelDB Class = ChannelDB

View File

@ -39,8 +39,8 @@ except ImportError:
sqlite = None sqlite = None
if sqlite is not None: if sqlite is not None:
class ChannelDBTestCase(ChannelPluginTestCase, PluginDocumentation): class ChannelDBTestCase(ChannelPluginTestCase):
plugins = ('ChannelDB', 'Misc', 'User') plugins = ('ChannelDB', 'User')
def setUp(self): def setUp(self):
ChannelPluginTestCase.setUp(self) ChannelPluginTestCase.setUp(self)
self.prefix = 'foo!bar@baz' self.prefix = 'foo!bar@baz'
@ -94,115 +94,6 @@ if sqlite is not None:
def testSeenNoUser(self): def testSeenNoUser(self):
self.assertNotRegexp('seen --user alsdkfjalsdfkj', 'KeyError') self.assertNotRegexp('seen --user alsdkfjalsdfkj', 'KeyError')
def testWordStatsNoArgs(self):
self.assertResponse('wordstats', 'I am not currently keeping any '
'word stats.')
self.assertNotError('addword lol')
self.assertResponse('wordstats', 'Currently keeping stats for: '
'\'lol\'')
def testWordStatsUser(self):
self.assertNotError('addword lol')
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol',
prefix=self.prefix))
self.assertResponse('wordstats foo', '\'lol\': 2')
self.assertNotError('addword moo')
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'moo',
prefix=self.prefix))
self.assertResponse('wordstats foo', '\'lol\': 2 and \'moo\': 2')
def testWordStatsWord(self):
userPrefix1 = 'moo!bar@baz'; userNick1 = 'moo'
userPrefix2 = 'boo!bar@baz'; userNick2 = 'boo'
self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick,
'register %s bar' % userNick1,
prefix=userPrefix1))
self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick,
'register %s bar' % userNick2,
prefix=userPrefix2))
_ = self.irc.takeMsg()
_ = self.irc.takeMsg()
self.assertNotError('addword lol')
self.assertRegexp('wordstats lol', 'foo: 1')
for i in range(5):
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol',
prefix=userPrefix1))
self.assertRegexp('wordstats lol',
'2.*%s: 5.*foo: 2' % userNick1)
for i in range(10):
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol',
prefix=userPrefix2))
self.assertRegexp('wordstats lol',
'3.*%s: 10.*%s: 5.*foo: 3' %
(userNick2, userNick1))
# Check for the extra-swanky stuff too
# (note: to do so we must make sure they don't appear in the list,
# so we'll tweak the config)
self.assertNotError('channeldb config wordstats-top-n 2')
self.assertRegexp('wordstats lol',
'total.*19 \'lol\'s.*%s: 10.*%s: 5.*'
'ranked 3 out of 3 \'lol\'ers' % \
(userNick2, userNick1))
def testWordStatsUserWord(self):
self.assertNotError('addword lol')
self.assertResponse('wordstats foo lol',
'foo has said \'lol\' 1 time.')
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol',
prefix=self.prefix))
self.assertResponse('wordstats foo lol',
'foo has said \'lol\' 3 times.')
# Now check for case-insensitivity
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'LOL',
prefix=self.prefix))
self.assertResponse('wordstats foo lol',
'foo has said \'lol\' 5 times.')
# Check and make sure actions get nabbed too
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol',
prefix=self.prefix))
self.assertResponse('wordstats foo lol',
'foo has said \'lol\' 7 times.')
# Check and make sure it handles two words in one message
self.assertNotError('addword heh')
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol heh',
prefix=self.prefix))
self.assertResponse('wordstats foo lol',
'foo has said \'lol\' 9 times.')
self.assertResponse('wordstats foo heh',
'foo has said \'heh\' 2 times.')
# It should ignore punctuation around words
self.irc.feedMsg(ircmsgs.privmsg(self.channel,'lol, I said "heh"',
prefix=self.prefix))
self.assertResponse('wordstats foo lol',
'foo has said \'lol\' 11 times.')
self.assertResponse('wordstats foo heh',
'foo has said \'heh\' 4 times.')
def testAddword(self):
self.assertError('addword lol!')
self.assertNotError('addword lolz0r')
def testWordStatsTopN(self):
self.assertNotError('addword lol')
self.assertNotError('channeldb config wordstats-top-n 5')
# Create 10 users and have them each send a different number of
# 'lol's to the channel
users = []
for i in range(10):
users.append(('foo%s!bar@baz' % i, 'foo%s' % i))
self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick,
'register %s bar' % \
users[i][1],
prefix=users[i][0]))
_ = self.irc.takeMsg()
for i in range(10):
for j in range(i):
self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol',
prefix=users[i][0]))
# Make sure it shows the top 5
self.assertRegexp('wordstats lol',
'Top 5 \'lol\'ers.*foo9: 9.*foo8: 8.*'
'foo7: 7.*foo6: 6.*foo5: 5')
# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: