diff --git a/ChangeLog b/ChangeLog index 6d3de980e..da639c144 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ + * Added supybot.plugins.WordStats.ignoreQueries, which, when + true, makes the bot ignore queries (and not increment its word + statistics). + * Added the LogToIrc plugin, for sending logs to an IRC channel or nick. Useful for traceback notification and whatnot. diff --git a/plugins/WordStats.py b/plugins/WordStats.py index a8c8c6fde..462716223 100644 --- a/plugins/WordStats.py +++ b/plugins/WordStats.py @@ -51,11 +51,13 @@ import registry import callbacks conf.registerPlugin('WordStats') -conf.registerChannelValue(conf.supybot.plugins.WordStats, - 'rankingDisplay', +conf.registerChannelValue(conf.supybot.plugins.WordStats, 'rankingDisplay', registry.PositiveInteger(3, """Determines the maximum number of top users to show for a given wordstat when someone requests the wordstats for a particular word.""")) +conf.registerChannelValue(conf.supybot.plugins.WordStats, 'ignoreQueries', + registry.Boolean(False, """Determines whether the bot will ignore words + said in a channel if they're in a wordstats query (command).""")) nonAlphaNumeric = filter(lambda s: not s.isalnum(), string.ascii) @@ -143,14 +145,16 @@ class WordStatsDB(plugins.ChannelUserDB): def addMsg(self, msg): assert msg.command == 'PRIVMSG' + (channel, text) = msg.args + if not ircutils.isChannel(channel): + return + text = text.strip().lower() + if not text: + return try: id = ircdb.users.getUserId(msg.prefix) except KeyError: return - (channel, text) = msg.args - text = text.strip().lower() - if not ircutils.isChannel(channel) or not text: - return msgwords = [s.strip(nonAlphaNumeric) for s in text.split()] if channel not in self.channelWords: self.channelWords[channel] = {} @@ -172,6 +176,7 @@ class WordStats(callbacks.Privmsg): def __init__(self): callbacks.Privmsg.__init__(self) self.db = WordStatsDB(filename) + self.queried = False world.flushers.append(self.db.flush) def die(self): @@ -181,7 +186,17 @@ class WordStats(callbacks.Privmsg): callbacks.Privmsg.die(self) def doPrivmsg(self, irc, msg): - self.db.addMsg(msg) + # This depends on the fact that it's called after the command. + try: + channel = msg.args[0] + if ircutils.isChannel(channel): + if not (self.queried and + self.registryValue('ignoreQueries', channel)): + self.db.addMsg(msg) + else: + self.log.debug('Queried and ignoring.') + finally: + self.queried = False def add(self, irc, msg, args): """[] @@ -196,6 +211,7 @@ class WordStats(callbacks.Privmsg): irc.error(' must not contain non-alphanumeric chars.') return self.db.addWord(channel, word) + self.queried = True irc.replySuccess() def remove(self, irc, msg, args): @@ -260,12 +276,18 @@ class WordStats(callbacks.Privmsg): if count: s = '%s has said %r %s.' % \ (user, word, utils.nItems('time', count)) + self.queried = True irc.reply(s) else: irc.error('%s has never said %r.' % (user, word)) elif arg1 in WordDict.fromkeys(self.db.getWords(channel)): word = arg1 total = self.db.getTotalWordCount(channel, word) + if total == 0: + irc.reply('I\'m keeping stats on %s, but I haven\'t seen it ' + 'in this channel.' % word) + self.queried = True + return n = self.registryValue('rankingDisplay', channel) try: id = ircdb.users.getUserId(msg.prefix) @@ -296,6 +318,7 @@ class WordStats(callbacks.Privmsg): else: s = '' ret = '%s %s.%s' % (ret, utils.commaAndify(L), s) + self.queried = True irc.reply(ret) else: user = arg1 diff --git a/test/test_WordStats.py b/test/test_WordStats.py index 27b47a327..2d73fae23 100644 --- a/test/test_WordStats.py +++ b/test/test_WordStats.py @@ -33,153 +33,162 @@ from testsupport import * import ircdb -try: - import sqlite -except ImportError: - sqlite = None +class WordStatsTestCase(ChannelPluginTestCase): + plugins = ('WordStats', 'User', 'Utilities') + def setUp(self): + ChannelPluginTestCase.setUp(self) + self.prefix = 'foo!bar@baz' + self.nick = 'foo' + self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, + 'register foo bar', + prefix=self.prefix)) + _ = self.irc.takeMsg() + ircdb.users.getUser(self.nick).addCapability(self.channel + '.op') -if sqlite is not None: - class WordStatsTestCase(ChannelPluginTestCase): - plugins = ('WordStats', 'User') - def setUp(self): - ChannelPluginTestCase.setUp(self) - self.prefix = 'foo!bar@baz' - self.nick = 'foo' - self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, - 'register foo bar', - prefix=self.prefix)) - _ = self.irc.takeMsg() - ircdb.users.getUser(self.nick).addCapability(self.channel + '.op') - - def testWordStatsNoArgs(self): - self.assertResponse('wordstats', 'I am not currently keeping any ' - 'word stats.') - self.assertNotError('add lol') - self.assertResponse('wordstats', - 'I am currently keeping stats for lol.') + def testWordStatsNoArgs(self): + self.assertResponse('wordstats', 'I am not currently keeping any ' + 'word stats.') + self.assertNotError('add lol') + self.assertResponse('wordstats', + 'I am currently keeping stats for lol.') - def testWordStatsUser(self): - self.assertNotError('add lol') + def testWordStatsUser(self): + self.assertNotError('add lol') + self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol', + prefix=self.prefix)) + self.assertResponse('wordstats foo', '\'lol\': 2') + self.assertNotError('add 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('add lol') + self.assertRegexp('wordstats lol', 'foo: 1') + for i in range(5): self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'lol', - prefix=self.prefix)) - self.assertResponse('wordstats foo', '\'lol\': 2') - self.assertNotError('add 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, + 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.irc.takeMsg() - _ = self.irc.takeMsg() - self.assertNotError('add 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', + '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) + try: + orig = conf.supybot.plugins.WordStats.rankingDisplay() + conf.supybot.plugins.WordStats.rankingDisplay.setValue(2) 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' % + 'total.*19 \'lol\'s.*%s: 10.*%s: 5.*' + 'ranked 3 out of 3 \'lol\'ers' % \ (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) - try: - orig = conf.supybot.plugins.WordStats.rankingDisplay() - conf.supybot.plugins.WordStats.rankingDisplay.setValue(2) - self.assertRegexp('wordstats lol', - 'total.*19 \'lol\'s.*%s: 10.*%s: 5.*' - 'ranked 3 out of 3 \'lol\'ers' % \ - (userNick2, userNick1)) - finally: - conf.supybot.plugins.WordStats.rankingDisplay.setValue(orig) - - def testWordStatsUserWord(self): - self.assertNotError('add 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('add 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.') + finally: + conf.supybot.plugins.WordStats.rankingDisplay.setValue(orig) - def testAddword(self): - self.assertError('add lol!') - self.assertNotError('add lolz0r') - self.assertRegexp('wordstats lolz0r', r'1 \'lolz0r\' seen') + def testWordStatsUserWord(self): + self.assertNotError('add 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('add 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 testRemoveword(self): - self.assertError('wordstats remove foo') - self.assertNotError('wordstats add foo') - self.assertRegexp('wordstats foo', r'1 \'foo\' seen') - self.assertRegexp('wordstats foo', r'2 \'foo\'s seen') - self.assertNotError('wordstats remove foo') - self.assertRegexp('wordstats foo', r'doesn\'t look like a word I') - # Verify that we aren't keeping results from before - self.assertNotError('add foo') - self.assertRegexp('wordstats foo', r'1 \'foo\' seen') + def testAddword(self): + self.assertError('add lol!') + self.assertNotError('add lolz0r') + self.assertRegexp('wordstats lolz0r', r'1 \'lolz0r\' seen') - def testWordStatsRankingDisplay(self): - self.assertNotError('add lol') - try: - orig = conf.supybot.plugins.WordStats.rankingDisplay() - conf.supybot.plugins.WordStats.rankingDisplay.setValue(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], + def testRemoveword(self): + self.assertError('wordstats remove foo') + self.assertNotError('wordstats add foo') + self.assertRegexp('wordstats foo', r'1 \'foo\' seen') + self.assertRegexp('wordstats foo', r'2 \'foo\'s seen') + self.assertNotError('wordstats remove foo') + self.assertRegexp('wordstats foo', r'doesn\'t look like a word I') + # Verify that we aren't keeping results from before + self.assertNotError('add foo') + self.assertRegexp('wordstats foo', r'1 \'foo\' seen') + + def testWordStatsRankingDisplay(self): + self.assertNotError('add lol') + try: + orig = conf.supybot.plugins.WordStats.rankingDisplay() + conf.supybot.plugins.WordStats.rankingDisplay.setValue(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])) - _ = 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') - finally: - conf.supybot.plugins.WordStats.rankingDisplay.setValue(orig) + # 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') + finally: + conf.supybot.plugins.WordStats.rankingDisplay.setValue(orig) + + def testWordStatsIgnoreQueries(self): + try: + original = conf.supybot.plugins.WordStats.ignoreQueries() + conf.supybot.plugins.WordStats.ignoreQueries.setValue(True) + self.assertNotError('add lol') + self.assertNotRegexp('wordstats lol', 'foo') + self.assertNotRegexp('wordstats lol', 'foo') + self.assertNotRegexp('wordstats lol', 'foo') + self.assertNotError('echo lol') + self.assertRegexp('wordstats lol', 'foo: 1') + self.assertRegexp('wordstats lol', 'foo: 1') + self.assertRegexp('wordstats lol', 'foo: 1') + finally: + conf.supybot.plugins.WordStats.ignoreQueries.setValue(original) # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: