diff --git a/plugins/ChannelDB.py b/plugins/ChannelDB.py index 2a7a84cbd..2d2b98463 100644 --- a/plugins/ChannelDB.py +++ b/plugins/ChannelDB.py @@ -52,10 +52,10 @@ import utils import ircdb import ircmsgs import plugins -import privmsgs import ircutils +import privmsgs +import registry import callbacks -import configurable try: import sqlite @@ -64,51 +64,38 @@ except ImportError: 'plugin. Download it at ' # I should write/copy a generalized proxy at some point. -class ReWrapper(object): - def __init__(self, L): - self.s = utils.dqrepr(' '.join(L)) - self.r = re.compile('|'.join(imap(re.escape, L))) +class Smileys(registry.Value): + def set(self, s): + L = s.split() + self.s = s + self.setValue(L) - def findall(self, *args, **kwargs): - return self.r.findall(*args, **kwargs) + def setValue(self, v): + self.value = re.compile('|'.join(imap(re.escape, v))) def __str__(self): return self.s - __repr__ = __str__ - -def SmileyType(s): - try: - L = configurable.SpaceSeparatedStrListType(s) - return ReWrapper(L) - except configurable.Error: - raise configurable.Error, 'Value must be a space-separated list of ' \ - 'smileys or frowns.' - +conf.registerPlugin('ChannelDB') +conf.registerChannelValue(conf.supybot.plugins.ChannelDB, 'selfStats', + registry.Boolean(True, """Determines whether the bot will keep channel + statistics on itself, possibly skewing the channel stats (especially in + cases where the bot is relaying between channels on a network).""")) +conf.registerChannelValue(conf.supybot.plugins.ChannelDB, 'smileys', + 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, - configurable.Mixin, callbacks.Privmsg): 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): callbacks.Privmsg.__init__(self) - configurable.Mixin.__init__(self) plugins.ChannelDBHandler.__init__(self) self.lastmsg = None self.laststate = None @@ -116,7 +103,6 @@ class ChannelDB(plugins.ChannelDBHandler, def die(self): callbacks.Privmsg.die(self) - configurable.Mixin.die(self) plugins.ChannelDBHandler.die(self) def makeDb(self, filename): @@ -167,21 +153,6 @@ class ChannelDB(plugins.ChannelDBHandler, cursor.execute("""INSERT INTO channel_stats VALUES (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() def p(s1, s2): return int(ircutils.nickEqual(s1, s2)) @@ -199,48 +170,16 @@ class ChannelDB(plugins.ChannelDBHandler, super(ChannelDB, self).__call__(irc, msg) def doPrivmsg(self, irc, msg): - if 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: + if not ircutils.isChannel(msg.args[0]): return (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) cursor = db.cursor() chars = len(s) words = len(s.split()) isAction = ircmsgs.isAction(msg) - frowns = len(self.configurables.get('frowns', channel).findall(s)) - smileys = len(self.configurables.get('smileys', channel).findall(s)) + frowns = len(self.registryValue('frowns', channel).findall(s)) + smileys = len(self.registryValue('smileys', channel).findall(s)) s = ircmsgs.prettyPrint(msg) cursor.execute("""UPDATE channel_stats SET smileys=smileys+%s, @@ -275,9 +214,7 @@ class ChannelDB(plugins.ChannelDBHandler, def outFilter(self, irc, msg): if msg.command == 'PRIVMSG': if ircutils.isChannel(msg.args[0]): - if self.configurables.get('self-stats', msg.args[0]): - db = self.getDb(msg.args[0]) - cursor = db.cursor() + if self.registryValue('selfStats', msg.args[0]): try: self.outFiltering = True self._updatePrivmsgStats(msg) @@ -495,160 +432,6 @@ class ChannelDB(plugins.ChannelDBHandler, values.kicks, values.modes, values.topics) irc.reply(s) - def addword(self, irc, msg, args): - """[] - - Keeps stats on in . 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(' 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): - """[] [] [] - - With no arguments, returns the list of words that are being monitored - for stats. With alone, returns all the stats for that user. - With alone, returns the top users for that word. With - and , returns that user's stat for that word. is only - needed if not said in the channel. (Note: if only one of or - is given, is assumed first and only if no stats are - available for that word, do we assume it's .) - """ - 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 diff --git a/test/test_ChannelDB.py b/test/test_ChannelDB.py index 79bc57b59..ad014d464 100644 --- a/test/test_ChannelDB.py +++ b/test/test_ChannelDB.py @@ -39,8 +39,8 @@ except ImportError: sqlite = None if sqlite is not None: - class ChannelDBTestCase(ChannelPluginTestCase, PluginDocumentation): - plugins = ('ChannelDB', 'Misc', 'User') + class ChannelDBTestCase(ChannelPluginTestCase): + plugins = ('ChannelDB', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' @@ -94,115 +94,6 @@ if sqlite is not None: def testSeenNoUser(self): 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: