From e292c5d0c9fcee405fb397cbbd739ed088041d09 Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 20 Sep 2007 02:06:31 +0000 Subject: [PATCH] Added kick ability to the BadWords plugin, imported it from supybot-plugins. --- plugins/BadWords/README.txt | 3 + plugins/BadWords/__init__.py | 61 +++++++++++++++ plugins/BadWords/config.py | 101 +++++++++++++++++++++++++ plugins/BadWords/plugin.py | 141 +++++++++++++++++++++++++++++++++++ plugins/BadWords/test.py | 74 ++++++++++++++++++ 5 files changed, 380 insertions(+) create mode 100644 plugins/BadWords/README.txt create mode 100644 plugins/BadWords/__init__.py create mode 100644 plugins/BadWords/config.py create mode 100644 plugins/BadWords/plugin.py create mode 100644 plugins/BadWords/test.py diff --git a/plugins/BadWords/README.txt b/plugins/BadWords/README.txt new file mode 100644 index 000000000..8cb3e645c --- /dev/null +++ b/plugins/BadWords/README.txt @@ -0,0 +1,3 @@ +This plugin ensures that the bot won't say any words the bot owner finds +offensive. As an additional capability, it can (optionally) kick users who +use such words from channels that have that capability enabled. diff --git a/plugins/BadWords/__init__.py b/plugins/BadWords/__init__.py new file mode 100644 index 000000000..a605bcd56 --- /dev/null +++ b/plugins/BadWords/__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. +### + +""" +Filters bad words on outgoing messages from the bot, so the bot can't be made +to say bad words. +""" + +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__ = "" + +__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 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/BadWords/config.py b/plugins/BadWords/config.py new file mode 100644 index 000000000..b742ed0ae --- /dev/null +++ b/plugins/BadWords/config.py @@ -0,0 +1,101 @@ +### +# 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 time + +import supybot.conf as conf +import supybot.registry as registry + +def configure(advanced): + from supybot.questions import output, expect, anything, something, yn + conf.registerPlugin('BadWords', True) + if yn('Would you like to add some bad words?'): + words = anything('What words? (separate individual words by spaces)') + conf.supybot.plugins.BadWords.words.set(words) + +class LastModifiedSetOfStrings(registry.SpaceSeparatedSetOfStrings): + lastModified = 0 + def setValue(self, v): + self.lastModified = time.time() + registry.SpaceSeparatedListOfStrings.setValue(self, v) + +BadWords = conf.registerPlugin('BadWords') +conf.registerGlobalValue(BadWords, 'words', + LastModifiedSetOfStrings([], """Determines what words are + considered to be 'bad' so the bot won't say them.""")) +conf.registerGlobalValue(BadWords,'requireWordBoundaries', + registry.Boolean(False, """Determines whether the bot will require bad + words to be independent words, or whether it will censor them within other + words. For instance, if 'darn' is a bad word, then if this is true, 'darn' + will be censored, but 'darnit' will not. You probably want this to be + false.""")) + +class String256(registry.String): + def __call__(self): + s = registry.String.__call__(self) + return s * (1024/len(s)) + + def __str__(self): + return self.value + +conf.registerGlobalValue(BadWords, 'nastyChars', + String256('!@#&', """Determines what characters will replace bad words; a + chunk of these characters matching the size of the replaced bad word will + be used to replace the bad words you've configured.""")) + +class ReplacementMethods(registry.OnlySomeStrings): + validStrings = ('simple', 'nastyCharacters') + +conf.registerGlobalValue(BadWords, 'replaceMethod', + ReplacementMethods('nastyCharacters', """Determines the manner in which + bad words will be replaced. 'nastyCharacters' (the default) will replace a + bad word with the same number of 'nasty characters' (like those used in + comic books; configurable by supybot.plugins.BadWords.nastyChars). + 'simple' will replace a bad word with a simple strings (regardless of the + length of the bad word); this string is configurable via + supybot.plugins.BadWords.simpleReplacement.""")) +conf.registerGlobalValue(BadWords,'simpleReplacement', + registry.String('[CENSORED]', """Determines what word will replace bad + words if the replacement method is 'simple'.""")) +conf.registerGlobalValue(BadWords, 'stripFormatting', + registry.Boolean(True, """Determines whether the bot will strip + formatting characters from messages before it checks them for bad words. + If this is False, it will be relatively trivial to circumvent this plugin's + filtering. If it's True, however, it will interact poorly with other + plugins that do coloring or bolding of text.""")) + +conf.registerChannelValue(BadWords, 'kick', + registry.Boolean(False, """Determines whether the bot will kick people with + a warning when they use bad words.""")) +conf.registerChannelValue(BadWords.kick, 'message', + registry.String("""You have been kicked for using a word prohibited on this + channel. Please use more appropriate language in the future.""")) + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/BadWords/plugin.py b/plugins/BadWords/plugin.py new file mode 100644 index 000000000..d6f566f6a --- /dev/null +++ b/plugins/BadWords/plugin.py @@ -0,0 +1,141 @@ +### +# Copyright (c) 2002-2004, 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 re +import math +import time + +import supybot.conf as conf +import supybot.utils as utils +import supybot.ircdb as ircdb +import supybot.ircmsgs as ircmsgs +from supybot.commands import * +import supybot.ircutils as ircutils +import supybot.callbacks as callbacks + +class BadWords(callbacks.Privmsg): + def __init__(self, irc): + self.__parent = super(BadWords, self) + self.__parent.__init__(irc) + # This is so we can not filter certain outgoing messages (like list, + # which would be kinda useless if it were filtered). + self.filtering = True + self.lastModified = 0 + self.words = conf.supybot.plugins.BadWords.words + + def callCommand(self, name, irc, msg, *args, **kwargs): + if ircdb.checkCapability(msg.prefix, 'admin'): + self.__parent.callCommand(name, irc, msg, *args, **kwargs) + else: + irc.errorNoCapability('admin') + + def sub(self, m): + replaceMethod = self.registryValue('replaceMethod') + if replaceMethod == 'simple': + return self.registryValue('simpleReplacement') + elif replaceMethod == 'nastyCharacters': + return self.registryValue('nastyChars')[:len(m.group(1))] + + def inFilter(self, irc, msg): + self.filtering = True + return msg + + def outFilter(self, irc, msg): + if self.filtering and msg.command == 'PRIVMSG': + if self.lastModified < self.words.lastModified: + self.makeRegexp(self.words()) + self.lastModified = time.time() + s = msg.args[1] + if self.registryValue('stripFormatting'): + s = ircutils.stripFormatting(s) + s = self.regexp.sub(self.sub, s) + msg = ircmsgs.privmsg(msg.args[0], s, msg=msg) + return msg + + def doPrivmsg(self, irc, msg): + s = ircutils.stripFormatting(msg.args[1]) + channel = msg.args[0] + if ircutils.isChannel(channel) and self.registryValue('kick', channel): + if self.regexp.match(s): + if irc.nick in irc.state.channels[channel].ops: + message = self.registryValue('kick.message', channel) + irc.queueMsg(ircmsgs.kick(channel, msg.nick, message)) + else: + self.log('Should kick %s from %s, but am not opped.', + msg.nick, channel) + callbacks.Privmsg.doPrivmsg(self, irc, msg) + + def makeRegexp(self, iterable): + s = '(%s)' % '|'.join(map(re.escape, iterable)) + if self.registryValue('requireWordBoundaries'): + s = r'\b%s\b' % s + self.regexp = re.compile(s, re.I) + + def list(self, irc, msg, args): + """takes no arguments + + Returns the list of words being censored. + """ + L = list(self.words()) + if L: + self.filtering = False + utils.sortBy(str.lower, L) + irc.reply(format('%L', L)) + else: + irc.reply('I\'m not currently censoring any bad words.') + list = wrap(list, ['admin']) + + def add(self, irc, msg, args, words): + """ [ ...] + + Adds all s to the list of words the bot isn't to say. + """ + set = self.words() + set.update(words) + self.words.setValue(set) + irc.replySuccess() + add = wrap(add, ['admin', many('something')]) + + def remove(self, irc, msg, args, words): + """ [ ...] + + Removes a s from the list of words the bot isn't to say. + """ + set = self.words() + for word in words: + set.discard(word) + self.words.setValue(set) + irc.replySuccess() + remove = wrap(remove, ['admin', many('something')]) + + +Class = BadWords + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/BadWords/test.py b/plugins/BadWords/test.py new file mode 100644 index 000000000..b15ecfaa5 --- /dev/null +++ b/plugins/BadWords/test.py @@ -0,0 +1,74 @@ +### +# Copyright (c) 2002-2004, 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 * + +class BadWordsTestCase(PluginTestCase): + plugins = ('BadWords', 'Utilities', 'Format') + badwords = ('shit', 'ass') + def tearDown(self): + # .default() doesn't seem to be working for BadWords.words + #default = conf.supybot.plugins.BadWords.words.default() + #conf.supybot.plugins.BadWords.words.setValue(default) + conf.supybot.plugins.BadWords.words.setValue([]) + + def _test(self): + for word in self.badwords: + self.assertRegexp('echo %s' % word, '(?!%s)' % word) + self.assertRegexp('echo [colorize %s]' % word, '(?!%s)' % word) + self.assertRegexp('echo foo%sbar' % word, '(?!%s)' % word) + self.assertRegexp('echo [format join "" %s]' % ' '.join(word), + '(?!%s)' % word) + + def _NegTest(self): + for word in self.badwords: + self.assertRegexp('echo %s' % word, word) + self.assertRegexp('echo foo%sbar' % word, word) + self.assertRegexp('echo [format join "" %s]' % ' '.join(word),word) + + def testAddbadwords(self): + self.assertNotError('badwords add %s' % ' '.join(self.badwords)) + self._test() + + def testDefault(self): + self._NegTest() + + def testRemovebadwords(self): + self.assertNotError('badwords add %s' % ' '.join(self.badwords)) + self.assertNotError('badwords remove %s' % ' '.join(self.badwords)) + self._NegTest() + + def testList(self): + self.assertNotError('badwords list') + self.assertNotError('badwords add shit') + self.assertNotError('badwords add ass') + self.assertResponse('badwords list', 'ass and shit') + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: +