From 82f39456baf807f88f7e8eaabcfb58af89ed82ca Mon Sep 17 00:00:00 2001 From: Ali Afshar Date: Wed, 2 Feb 2005 13:53:18 +0000 Subject: [PATCH] Karma plugin converted to the new format --- plugins/Karma/README.txt | 1 + plugins/Karma/__init__.py | 61 ++++++ plugins/Karma/config.py | 62 ++++++ plugins/Karma/plugin.py | 405 ++++++++++++++++++++++++++++++++++++++ plugins/Karma/test.py | 211 ++++++++++++++++++++ 5 files changed, 740 insertions(+) create mode 100644 plugins/Karma/README.txt create mode 100644 plugins/Karma/__init__.py create mode 100644 plugins/Karma/config.py create mode 100644 plugins/Karma/plugin.py create mode 100644 plugins/Karma/test.py diff --git a/plugins/Karma/README.txt b/plugins/Karma/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/Karma/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/Karma/__init__.py b/plugins/Karma/__init__.py new file mode 100644 index 000000000..47820cd1a --- /dev/null +++ b/plugins/Karma/__init__.py @@ -0,0 +1,61 @@ +### +# Copyright (c) 2005, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +""" +Plugin for handling Karma stuff for a channel. +""" + +import supybot +import supybot.world as world + +# Use this for the version of this plugin. You may wish to put a CVS keyword +# in here if you're keeping the plugin in CVS or some similar system. +__version__ = "" + +# XXX Replace this with an appropriate author or supybot.Author instance. +__author__ = supybot.authors.jemfinch + +# This is a dictionary mapping supybot.Author instances to lists of +# contributions. +__contributors__ = {} + +import config +import plugin +reload(plugin) # In case we're being reloaded. +# Add more reloads here if you add third-party modules and want them to be +# reloaded when this plugin is reloaded. Don't forget to import them as well! + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Karma/config.py b/plugins/Karma/config.py new file mode 100644 index 000000000..28fe68086 --- /dev/null +++ b/plugins/Karma/config.py @@ -0,0 +1,62 @@ +### +# Copyright (c) 2005, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +import supybot.conf as conf +import supybot.registry as registry + +def configure(advanced): + # This will be called by supybot to configure this module. advanced is + # a bool that specifies whether the user identified himself as an advanced + # user or not. You should effect your configuration by manipulating the + # registry as appropriate. + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('Karma', True) + +conf.registerPlugin('Karma') + +conf.registerChannelValue(conf.supybot.plugins.Karma, 'simpleOutput', + registry.Boolean(False, """Determines whether the bot will output shorter + versions of the karma output when requesting a single thing's karma.""")) +conf.registerChannelValue(conf.supybot.plugins.Karma, 'response', + registry.Boolean(False, """Determines whether the bot will reply with a + success message when something's karma is increased or decreased.""")) +conf.registerChannelValue(conf.supybot.plugins.Karma, 'rankingDisplay', + registry.Integer(3, """Determines how many highest/lowest karma things are + shown when karma is called with no arguments.""")) +conf.registerChannelValue(conf.supybot.plugins.Karma, 'mostDisplay', + registry.Integer(25, """Determines how many karma things are shown when + the most command is called.'""")) +conf.registerChannelValue(conf.supybot.plugins.Karma, 'allowSelfRating', + registry.Boolean(False, """Determines whether users can adjust the karma + of their nick.""")) +conf.registerChannelValue(conf.supybot.plugins.Karma, 'allowUnaddressedKarma', + registry.Boolean(False, """Determines whether the bot will + increase/decrease karma without being addressed.""")) + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78 diff --git a/plugins/Karma/plugin.py b/plugins/Karma/plugin.py new file mode 100644 index 000000000..bf4bf41e2 --- /dev/null +++ b/plugins/Karma/plugin.py @@ -0,0 +1,405 @@ +### +# Copyright (c) 2005, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +import os +import csv +import sets +from itertools import imap + +import supybot.conf as conf +import supybot.utils as utils +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircmsgs as ircmsgs +import supybot.ircutils as ircutils +import supybot.registry as registry +import supybot.callbacks as callbacks + +class SqliteKarmaDB(object): + def __init__(self, filename): + self.dbs = ircutils.IrcDict() + self.filename = filename + + def close(self): + for db in self.dbs.itervalues(): + db.close() + + def _getDb(self, channel): + try: + import sqlite + except ImportError: + raise callbacks.Error, 'You need to have PySQLite installed to ' \ + 'use this plugin. Download it at ' \ + '' + filename = plugins.makeChannelFilename(self.filename, channel) + if filename in self.dbs: + return self.dbs[filename] + if os.path.exists(filename): + self.dbs[filename] = sqlite.connect(filename) + return self.dbs[filename] + db = sqlite.connect(filename) + self.dbs[filename] = db + cursor = db.cursor() + cursor.execute("""CREATE TABLE karma ( + id INTEGER PRIMARY KEY, + name TEXT, + normalized TEXT UNIQUE ON CONFLICT IGNORE, + added INTEGER, + subtracted INTEGER + )""") + db.commit() + def p(s1, s2): + return int(ircutils.nickEqual(s1, s2)) + db.create_function('nickeq', 2, p) + return db + + def get(self, channel, thing): + db = self._getDb(channel) + thing = thing.lower() + cursor = db.cursor() + cursor.execute("""SELECT added, subtracted FROM karma + WHERE normalized=%s""", thing) + if cursor.rowcount == 0: + return None + else: + return map(int, cursor.fetchone()) + + def gets(self, channel, things): + db = self._getDb(channel) + cursor = db.cursor() + normalizedThings = dict(zip(map(str.lower, things), things)) + criteria = ' OR '.join(['normalized=%s'] * len(normalizedThings)) + sql = """SELECT name, added-subtracted FROM karma + WHERE %s ORDER BY added-subtracted DESC""" % criteria + cursor.execute(sql, *normalizedThings) + L = [(name, int(karma)) for (name, karma) in cursor.fetchall()] + for (name, _) in L: + del normalizedThings[name.lower()] + neutrals = normalizedThings.values() + neutrals.sort() + return (L, neutrals) + + def top(self, channel, limit): + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT name, added-subtracted FROM karma + ORDER BY added-subtracted DESC LIMIT %s""", limit) + return [(t[0], int(t[1])) for t in cursor.fetchall()] + + def bottom(self, channel, limit): + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT name, added-subtracted FROM karma + ORDER BY added-subtracted ASC LIMIT %s""", limit) + return [(t[0], int(t[1])) for t in cursor.fetchall()] + + def rank(self, channel, thing): + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT added-subtracted FROM karma + WHERE name=%s""", thing) + if cursor.rowcount == 0: + return None + karma = int(cursor.fetchone()[0]) + cursor.execute("""SELECT COUNT(*) FROM karma + WHERE added-subtracted > %s""", karma) + rank = int(cursor.fetchone()[0]) + return rank + + def size(self, channel): + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT COUNT(*) FROM karma""") + return int(cursor.fetchone()[0]) + + def increment(self, channel, name): + db = self._getDb(channel) + cursor = db.cursor() + normalized = name.lower() + cursor.execute("""INSERT INTO karma VALUES (NULL, %s, %s, 0, 0)""", + name, normalized) + cursor.execute("""UPDATE karma SET added=added+1 + WHERE normalized=%s""", normalized) + db.commit() + + def decrement(self, channel, name): + db = self._getDb(channel) + cursor = db.cursor() + normalized = name.lower() + cursor.execute("""INSERT INTO karma VALUES (NULL, %s, %s, 0, 0)""", + name, normalized) + cursor.execute("""UPDATE karma SET subtracted=subtracted+1 + WHERE normalized=%s""", normalized) + db.commit() + + def most(self, channel, kind, limit): + if kind == 'increased': + orderby = 'added' + elif kind == 'decreased': + orderby = 'subtracted' + elif kind == 'active': + orderby = 'added+subtracted' + else: + raise ValueError, 'invalid kind' + sql = """SELECT name, %s FROM karma ORDER BY %s DESC LIMIT %s""" % \ + (orderby, orderby, limit) + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute(sql) + return [(name, int(i)) for (name, i) in cursor.fetchall()] + + def clear(self, channel, name): + db = self._getDb(channel) + cursor = db.cursor() + normalized = name.lower() + cursor.execute("""UPDATE karma SET subtracted=0, added=0 + WHERE normalized=%s""", normalized) + db.commit() + + def dump(self, channel, filename): + filename = conf.supybot.directories.data.dirize(filename) + fd = utils.transactionalFile(filename) + out = csv.writer(fd) + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute("""SELECT name, added, subtracted FROM karma""") + for (name, added, subtracted) in cursor.fetchall(): + out.writerow([name, added, subtracted]) + fd.close() + + def load(self, channel, filename): + filename = conf.supybot.directories.data.dirize(filename) + fd = file(filename) + reader = csv.reader(fd) + db = self._getDb(channel) + cursor = db.cursor() + cursor.execute("""DELETE FROM karma""") + for (name, added, subtracted) in reader: + normalized = name.lower() + cursor.execute("""INSERT INTO karma + VALUES (NULL, %s, %s, %s, %s)""", + name, normalized, added, subtracted) + db.commit() + fd.close() + +KarmaDB = plugins.DB('Karma', + {'sqlite': SqliteKarmaDB}) + +class Karma(callbacks.Privmsg): + callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') + def __init__(self, irc): + self.__parent = super(Karma, self) + self.__parent.__init__(irc) + self.db = KarmaDB() + + def die(self): + self.__parent.die() + self.db.close() + + def _normalizeThing(self, thing): + assert thing + if thing[0] == '(' and thing[-1] == ')': + thing = thing[1:-1] + return thing + + def _respond(self, irc, channel): + if self.registryValue('response', channel): + irc.replySuccess() + else: + irc.noReply() + assert irc.msg.repliedTo + + def _doKarma(self, irc, channel, thing): + assert thing[-2:] in ('++', '--') + if thing.endswith('++'): + thing = thing[:-2] + if ircutils.strEqual(thing, irc.msg.nick) and \ + not self.registryValue('allowSelfRating', channel): + irc.error('You\'re not allowed to adjust your own karma.') + elif thing: + self.db.increment(channel, self._normalizeThing(thing)) + self._respond(irc, channel) + else: + thing = thing[:-2] + if ircutils.strEqual(thing, irc.msg.nick) and \ + not self.registryValue('allowSelfRating', channel): + irc.error('You\'re not allowed to adjust your own karma.') + elif thing: + self.db.decrement(channel, self._normalizeThing(thing)) + self._respond(irc, channel) + + def tokenizedCommand(self, irc, msg, tokens): + channel = msg.args[0] + if not irc.isChannel(channel): + return + if tokens[-1][-2:] in ('++', '--'): + thing = ' '.join(tokens) + self._doKarma(irc, channel, thing) + + def doPrivmsg(self, irc, msg): + if not msg.repliedTo: + channel = msg.args[0] + if irc.isChannel(channel) and \ + self.registryValue('allowUnaddressedKarma', channel): + irc = callbacks.SimpleProxy(irc, msg) + thing = msg.args[1].rstrip() + if thing[-2:] in ('++', '--'): + self._doKarma(irc, channel, thing) + + def karma(self, irc, msg, args, channel, things): + """[] [ ...] + + Returns the karma of . If is not given, returns the top + three and bottom three karmas. If one is given, returns the + details of its karma; if more than one is given, returns the + total karma of each of the the things. is only necessary if + the message isn't sent on the channel itself. + """ + if len(things) == 1: + name = things[0] + t = self.db.get(channel, name) + if t is None: + irc.reply('%s has neutral karma.' % name) + else: + (added, subtracted) = t + total = added - subtracted + if self.registryValue('simpleOutput', channel): + s = '%s: %s' % (name, total) + else: + s = 'Karma for %s has been increased %s ' \ + 'and decreased %s for a total karma of %s.' % \ + (utils.quoted(name), utils.nItems('time', added), + utils.nItems('time', subtracted), total) + irc.reply(s) + elif len(things) > 1: + (L, neutrals) = self.db.gets(channel, things) + if L: + s = utils.commaAndify(['%s: %s' % t for t in L]) + if neutrals: + neutral = '. %s %s neutral karma' % \ + (utils.commaAndify(neutrals), + utils.has(len(neutrals))) + s += neutral + irc.reply(s + '.') + else: + irc.reply('I didn\'t know the karma for any of those things.') + else: # No name was given. Return the top/bottom N karmas. + limit = self.registryValue('rankingDisplay', channel) + top = self.db.top(channel, limit) + highest = ['%s (%s)' % (utils.quoted(s), t) + for (s, t) in self.db.top(channel, limit)] + lowest = ['%s (%s)' % (utils.quoted(s), t) + for (s, t) in self.db.bottom(channel, limit)] + if not (highest and lowest): + irc.error('I have no karma for this channel.') + return + rank = self.db.rank(channel, msg.nick) + if rank is not None: + total = self.db.size(channel) + rankS = ' You (%s) are ranked %s out of %s.' % \ + (msg.nick, rank, total) + else: + rankS = '' + s = 'Highest karma: %s. Lowest karma: %s.%s' % \ + (utils.commaAndify(highest), utils.commaAndify(lowest), rankS) + irc.reply(s) + karma = wrap(karma, ['channel', any('something')]) + + _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) + def most(self, irc, msg, args, channel, kind): + """[] {increased,decreased,active} + + Returns the most increased, the most decreased, or the most active + (the sum of increased and decreased) karma things. is only + necessary if the message isn't sent in the channel itself. + """ + L = self.db.most(channel, kind, + self.registryValue('mostDisplay', channel)) + if L: + L = ['%s: %s' % (utils.quoted(name), i) for (name, i) in L] + irc.reply(utils.commaAndify(L)) + else: + irc.error('I have no karma for this channel.') + most = wrap(most, ['channel', + ('literal', ['increased', 'decreased', 'active'])]) + + def clear(self, irc, msg, args, channel, name): + """[] + + Resets the karma of to 0. + """ + self.db.clear(channel, name) + irc.replySuccess() + clear = wrap(clear, [('checkChannelCapability', 'op'), 'text']) + + def getName(self, nick, msg, match): + addressed = callbacks.addressed(nick, msg) + name = callbacks.addressed(nick, + ircmsgs.IrcMsg(prefix='', + args=(msg.args[0], match.group(1)), + msg=msg)) + if not name: + name = match.group(1) + if not addressed: + if not self.registryValue('allowUnaddressedKarma'): + return '' + if not msg.args[1].startswith(match.group(1)): + return '' + name = match.group(1) + elif addressed: + if not addressed.startswith(name): + return '' + name = name.strip('()') + return name + + def dump(self, irc, msg, args, channel, filename): + """[] + + Dumps the Karma database for to in the bot's + data directory. is only necessary if the message isn't sent + in the channel itself. + """ + self.db.dump(channel, filename) + irc.replySuccess() + dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) + + def load(self, irc, msg, args, channel, filename): + """[] + + Loads the Karma database for from in the bot's + data directory. is only necessary if the message isn't sent + in the channel itself. + """ + self.db.load(channel, filename) + irc.replySuccess() + load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename']) + +Class = Karma + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Karma/test.py b/plugins/Karma/test.py new file mode 100644 index 000000000..f386a07bd --- /dev/null +++ b/plugins/Karma/test.py @@ -0,0 +1,211 @@ +### +# Copyright (c) 2005, Jeremiah Fincher +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +### + +from supybot.test import * + +try: + import sqlite +except ImportError: + sqlite = None + +if sqlite is not None: + class KarmaTestCase(ChannelPluginTestCase): + plugins = ('Karma',) + def testKarma(self): + self.assertError('karma') + self.assertRegexp('karma foobar', 'neutral karma') + try: + conf.replyWhenNotCommand = True + self.assertNoResponse('foobar++', 2) + finally: + conf.replyWhenNotCommand = False + self.assertRegexp('karma foobar', 'increased 1.*total.*1') + self.assertRegexp('karma FOOBAR', 'increased 1.*total.*1') + self.assertNoResponse('foobar--', 2) + self.assertRegexp('karma foobar', 'decreased 1.*total.*0') + self.assertRegexp('karma FOOBAR', 'decreased 1.*total.*0') + self.assertNoResponse('FOO++', 2) + self.assertNoResponse('BAR--', 2) + self.assertRegexp('karma foo bar foobar', '.*foo.*foobar.*bar.*') + self.assertRegexp('karma FOO BAR FOOBAR', '.*foo.*foobar.*bar.*') + self.assertRegexp('karma FOO BAR FOOBAR', + '.*FOO.*foobar.*BAR.*', flags=0) + self.assertRegexp('karma foo bar foobar asdfjkl', 'asdfjkl') + # Test case-insensitive + self.assertNoResponse('MOO++', 2) + self.assertRegexp('karma moo', + 'Karma for [\'"]moo[\'"].*increased 1.*total.*1') + self.assertRegexp('karma MoO', + 'Karma for [\'"]MoO[\'"].*increased 1.*total.*1') + + def testKarmaRankingDisplayConfigurable(self): + try: + orig = conf.supybot.plugins.Karma.response() + conf.supybot.plugins.Karma.response.setValue(True) + original = conf.supybot.plugins.Karma.rankingDisplay() + self.assertNotError('foo++') + self.assertNotError('foo++') + self.assertNotError('foo++') + self.assertNotError('foo++') + self.assertNotError('bar++') + self.assertNotError('bar++') + self.assertNotError('bar++') + self.assertNotError('baz++') + self.assertNotError('baz++') + self.assertNotError('quux++') + self.assertNotError('xuuq--') + self.assertNotError('zab--') + self.assertNotError('zab--') + self.assertNotError('rab--') + self.assertNotError('rab--') + self.assertNotError('rab--') + self.assertNotError('oof--') + self.assertNotError('oof--') + self.assertNotError('oof--') + self.assertNotError('oof--') + self.assertRegexp('karma', 'foo.*bar.*baz.*oof.*rab.*zab') + conf.supybot.plugins.Karma.rankingDisplay.setValue(4) + self.assertRegexp('karma', 'foo.*bar.*baz.*quux') + finally: + conf.supybot.plugins.Karma.response.setValue(orig) + conf.supybot.plugins.Karma.rankingDisplay.setValue(original) + + def testMost(self): + self.assertError('most increased') + self.assertError('most decreased') + self.assertError('most active') + self.assertHelp('most aldsfkj') + self.assertNoResponse('foo++', 1) + self.assertNoResponse('foo++', 1) + self.assertNoResponse('bar++', 1) + self.assertNoResponse('bar--', 1) + self.assertNoResponse('bar--', 1) + self.assertRegexp('karma most active', 'bar.*foo') + self.assertRegexp('karma most increased', 'foo.*bar') + self.assertRegexp('karma most decreased', 'bar.*foo') + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertRegexp('karma most active', 'foo.*bar') + self.assertRegexp('karma most increased', 'foo.*bar') + self.assertRegexp('karma most decreased', 'foo.*bar') + + def testSimpleOutput(self): + try: + orig = conf.supybot.plugins.Karma.simpleOutput() + conf.supybot.plugins.Karma.simpleOutput.setValue(True) + self.assertNoResponse('foo++', 2) + self.assertResponse('karma foo', 'foo: 1') + self.assertNoResponse('bar--', 2) + self.assertResponse('karma bar', 'bar: -1') + finally: + conf.supybot.plugins.Karma.simpleOutput.setValue(orig) + + def testSelfRating(self): + nick = self.nick + try: + orig = conf.supybot.plugins.Karma.allowSelfRating() + conf.supybot.plugins.Karma.allowSelfRating.setValue(False) + self.assertError('%s++' % nick) + self.assertResponse('karma %s' % nick, + '%s has neutral karma.' % nick) + conf.supybot.plugins.Karma.allowSelfRating.setValue(True) + self.assertNoResponse('%s++' % nick, 2) + self.assertRegexp('karma %s' % nick, + 'Karma for [\'"]%s[\'"].*increased 1.*total.*1' % nick) + finally: + conf.supybot.plugins.Karma.allowSelfRating.setValue(orig) + + def testKarmaOutputConfigurable(self): + self.assertNoResponse('foo++', 2) + try: + orig = conf.supybot.plugins.Karma.response() + conf.supybot.plugins.Karma.response.setValue(True) + self.assertNotError('foo++') + finally: + conf.supybot.plugins.Karma.response.setValue(orig) + + def testKarmaMostDisplayConfigurable(self): + self.assertNoResponse('foo++', 1) + self.assertNoResponse('foo++', 1) + self.assertNoResponse('bar++', 1) + self.assertNoResponse('bar--', 1) + self.assertNoResponse('bar--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + self.assertNoResponse('foo--', 1) + try: + orig = conf.supybot.plugins.Karma.mostDisplay() + conf.supybot.plugins.Karma.mostDisplay.setValue(1) + self.assertRegexp('karma most active', '(?!bar)') + conf.supybot.plugins.Karma.mostDisplay.setValue(25) + self.assertRegexp('karma most active', 'bar') + finally: + conf.supybot.plugins.Karma.mostDisplay.setValue(orig) + + + def testIncreaseKarmaWithNickNotCallingInvalidCommand(self): + self.assertSnarfNoResponse('%s: foo++' % self.irc.nick, 3) + + def testClear(self): + self.assertNoResponse('foo++', 1) + self.assertRegexp('karma foo', '1') + self.assertNotError('karma clear foo') + self.assertRegexp('karma foo', '0') + self.assertNotRegexp('karma foo', '1') + + def testNoKarmaDunno(self): + self.assertNotError('load Infobot') + self.assertNoResponse('foo++') + + def testMultiWordKarma(self): + self.assertNoResponse('(foo bar)++', 1) + self.assertRegexp('karma "foo bar"', '1') + + def testUnaddressedKarma(self): + karma = conf.supybot.plugins.Karma + resp = karma.response() + unaddressed = karma.allowUnaddressedKarma() + try: + karma.response.setValue(True) + karma.allowUnaddressedKarma.setValue(True) + for m in ('++', '--'): + self.assertRegexp('foo%s' % m, 'operation') + self.assertSnarfRegexp('foo%s' % m, 'operation') + #self.assertNoResponse('foo bar%s' % m) + #self.assertSnarfNoResponse('foo bar%s' % m) + self.assertRegexp('(foo bar)%s' % m, 'operation') + self.assertSnarfRegexp('(foo bar)%s' % m, 'operation') + finally: + karma.response.setValue(resp) + karma.allowUnaddressedKarma.setValue(unaddressed) + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: