From d9689f818be01b60a169f7b4895f07f9730a1bbc Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Tue, 25 Jan 2005 20:04:14 +0000 Subject: [PATCH] Added the Filter plugin in the new plugin format. --- plugins/Filter/README.txt | 1 + plugins/Filter/__init__.py | 55 ++++ plugins/Filter/config.py | 57 ++++ plugins/Filter/plugin.py | 620 +++++++++++++++++++++++++++++++++++++ plugins/Filter/test.py | 169 ++++++++++ setup.py | 1 + 6 files changed, 903 insertions(+) create mode 100644 plugins/Filter/README.txt create mode 100644 plugins/Filter/__init__.py create mode 100644 plugins/Filter/config.py create mode 100644 plugins/Filter/plugin.py create mode 100644 plugins/Filter/test.py diff --git a/plugins/Filter/README.txt b/plugins/Filter/README.txt new file mode 100644 index 000000000..d60b47a97 --- /dev/null +++ b/plugins/Filter/README.txt @@ -0,0 +1 @@ +Insert a description of your plugin here, with any notes, etc. about using it. diff --git a/plugins/Filter/__init__.py b/plugins/Filter/__init__.py new file mode 100644 index 000000000..8a1b6d6ac --- /dev/null +++ b/plugins/Filter/__init__.py @@ -0,0 +1,55 @@ +### +# Copyright (c) 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. +### + +""" +Provides numerous filters, and a command (outfilter) to set them as filters on +the output of the bot. +""" + +import supybot +import supybot.world as world + +__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. + +if world.testing: + import test + +Class = plugin.Class +configure = config.configure + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Filter/config.py b/plugins/Filter/config.py new file mode 100644 index 000000000..90586e757 --- /dev/null +++ b/plugins/Filter/config.py @@ -0,0 +1,57 @@ +### +# Copyright (c) 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 supybot.conf as conf +import supybot.registry as registry + +Filter = conf.registerPlugin('Filter') +conf.registerGroup(Filter, 'spellit') +conf.registerGlobalValue(Filter.spellit, + 'replaceLetters', registry.Boolean(True, """Determines whether or not to + replace letters in the output of spellit.""")) +conf.registerGlobalValue(Filter.spellit, + 'replacePunctuation', registry.Boolean(True, """Determines whether or not + to replace punctuation in the output of spellit.""")) +conf.registerGlobalValue(Filter.spellit, + 'replaceNumbers', registry.Boolean(True, """Determines whether or not to + replace numbers in the output of spellit.""")) +conf.registerGroup(Filter, 'shrink') +conf.registerChannelValue(Filter.shrink, 'minimum', + registry.PositiveInteger(4, """Determines the minimum number of a letters + in a word before it will be shrunken by the shrink command/filter.""")) + +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('Filter', True) + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78 diff --git a/plugins/Filter/plugin.py b/plugins/Filter/plugin.py new file mode 100644 index 000000000..ffacfcb9a --- /dev/null +++ b/plugins/Filter/plugin.py @@ -0,0 +1,620 @@ +### +# Copyright (c) 2002-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.plugins as plugins + +import re +import string +import random +import itertools +from cStringIO import StringIO + +import supybot.conf as conf +import supybot.utils as utils +from supybot.commands import * +import supybot.ircmsgs as ircmsgs +import supybot.ircutils as ircutils +import supybot.registry as registry +import supybot.callbacks as callbacks + +class MyFilterProxy(object): + def reply(self, s): + self.s = s + +class Filter(callbacks.Privmsg): + """This plugin offers several commands which transform text in some way. + It also provides the capability of using such commands to 'filter' the + output of the bot -- for instance, you could make everything the bot says + be in leetspeak, or Morse code, or any number of other kinds of filters. + Not very useful, but definitely quite fun :)""" + def __init__(self): + self.outFilters = ircutils.IrcDict() + callbacks.Privmsg.__init__(self) + + def outFilter(self, irc, msg): + if msg.command == 'PRIVMSG': + if msg.args[0] in self.outFilters: + if ircmsgs.isAction(msg): + s = ircmsgs.unAction(msg) + else: + s = msg.args[1] + methods = self.outFilters[msg.args[0]] + for filtercommand in methods: + myIrc = MyFilterProxy() + filtercommand(myIrc, msg, [s]) + s = myIrc.s + if ircmsgs.isAction(msg): + msg = ircmsgs.action(msg.args[0], s, msg=msg) + else: + msg = ircmsgs.IrcMsg(msg=msg, args=(msg.args[0], s)) + return msg + + _filterCommands = ['jeffk', 'leet', 'rot13', 'hexlify', 'binary', 'lithp', + 'scramble', 'morse', 'reverse', 'colorize', 'squish', + 'supa1337', 'colorstrip', 'aol', 'rainbow', 'spellit', + 'hebrew', 'undup', 'gnu', 'shrink'] + def outfilter(self, irc, msg, args, channel, command): + """[] [] + + Sets the outFilter of this plugin to be . If no command is + given, unsets the outFilter. is only necessary if the + message isn't sent in the channel itself. + """ + if command: + if not self.isDisabled(command) and \ + command in self._filterCommands: + method = getattr(self, command) + self.outFilters.setdefault(channel, []).append(method) + irc.replySuccess() + else: + irc.error('That\'s not a valid filter command.') + else: + self.outFilters[channel] = [] + irc.replySuccess() + outfilter = wrap(outfilter, + [('checkChannelCapability', 'op'), + additional('commandName')]) + + def hebrew(self, irc, msg, args, text): + """ + + Removes all the vowels from . (If you're curious why this is + named 'hebrew' it's because I (jemfinch) thought of it in Hebrew class, + and printed Hebrew often elides the vowels.) + """ + text = filter(lambda c: c not in 'aeiou', text) + irc.reply(text) + hebrew = wrap(hebrew, ['text']) + + def squish(self, irc, msg, args, text): + """ + + Removes all the spaces from . + """ + text = ''.join(text.split()) + irc.reply(text) + squish = wrap(squish, ['text']) + + def undup(self, irc, msg, args, text): + """ + + Returns , with all consecutive duplicated letters removed. + """ + L = [text[0]] + for c in text: + if c != L[-1]: + L.append(c) + irc.reply(''.join(L)) + undup = wrap(undup, ['text']) + + def binary(self, irc, msg, args, text): + """ + + Returns the binary representation of . + """ + L = [] + for c in text: + LL = [] + i = ord(c) + counter = 8 + while i: + counter -= 1 + if i & 1: + LL.append('1') + else: + LL.append('0') + i >>= 1 + while counter: + LL.append('0') + counter -= 1 + LL.reverse() + L.extend(LL) + irc.reply(''.join(L)) + binary = wrap(binary, ['text']) + + def hexlify(self, irc, msg, args, text): + """ + + Returns a hexstring from the given string; a hexstring is a string + composed of the hexadecimal value of each character in the string + """ + irc.reply(text.encode('hex_codec')) + hexlify = wrap(hexlify, ['text']) + + def unhexlify(self, irc, msg, args, text): + """ + + Returns the string corresponding to . Obviously, + must be a string of hexadecimal digits. + """ + try: + irc.reply(text.decode('hex_codec')) + except TypeError: + irc.error('Invalid input.') + unhexlify = wrap(unhexlify, ['text']) + + def rot13(self, irc, msg, args, text): + """ + + Rotates 13 characters to the right in the alphabet. Rot13 is + commonly used for text that simply needs to be hidden from inadvertent + reading by roaming eyes, since it's easily reversible. + """ + irc.reply(text.encode('rot13')) + rot13 = wrap(rot13, ['text']) + + def lithp(self, irc, msg, args, text): + """ + + Returns the lisping version of + """ + text = text.replace('sh', 'th') + text = text.replace('SH', 'TH') + text = text.replace('Sh', 'Th') + text = text.replace('ss', 'th') + text = text.replace('SS', 'TH') + text = text.replace('s', 'th') + text = text.replace('z', 'th') + text = text.replace('S', 'Th') + text = text.replace('Z', 'Th') + text = text.replace('x', 'kth') + text = text.replace('X', 'KTH') + text = text.replace('cce', 'kth') + text = text.replace('CCE', 'KTH') + text = text.replace('tion', 'thion') + text = text.replace('TION', 'THION') + irc.reply(text) + lithp = wrap(lithp, ['text']) + + _leettrans = string.maketrans('oOaAeElBTiIts', '004433187!1+5') + _leetres = [(re.compile(r'\b(?:(?:[yY][o0O][oO0uU])|u)\b'), 'j00'), + (re.compile(r'fear'), 'ph33r'), + (re.compile(r'[aA][tT][eE]'), '8'), + (re.compile(r'[aA][tT]'), '@'), + (re.compile(r'[sS]\b'), 'z'), + (re.compile(r'x'), '><'),] + def leet(self, irc, msg, args, text): + """ + + Returns the l33tspeak version of + """ + for (r, sub) in self._leetres: + text = re.sub(r, sub, text) + text = text.translate(self._leettrans) + irc.reply(text) + leet = wrap(leet, ['text']) + + _supaleetreplacers = [('xX', '><'), ('kK', '|<'), ('rR', '|2'), + ('hH', '|-|'), ('L', '|_'), ('uU', '|_|'), + ('O', '()'), ('nN', '|\\|'), ('mM', '/\\/\\'), + ('G', '6'), ('Ss', '$'), ('i', ';'), ('aA', '/-\\'), + ('eE', '3'), ('t', '+'), ('T', '7'), ('l', '1'), + ('D', '|)'), ('B', '|3'), ('I', ']['), ('Vv', '\\/'), + ('wW', '\\/\\/'), ('d', 'c|'), ('b', '|>'), + ('c', '<'), ('h', '|n'),] + def supa1337(self, irc, msg, args, text): + """ + + Replies with an especially k-rad translation of . + """ + for (r, sub) in self._leetres: + text = re.sub(r, sub, text) + for (letters, replacement) in self._supaleetreplacers: + for letter in letters: + text = text.replace(letter, replacement) + irc.reply(text) + supa1337 = wrap(supa1337, ['text']) + + _scrambleRe = re.compile(r'(?:\b|(?![a-zA-Z]))([a-zA-Z])([a-zA-Z]*)' + r'([a-zA-Z])(?:\b|(?![a-zA-Z]))') + def scramble(self, irc, msg, args, text): + """ + + Replies with a string where each word is scrambled; i.e., each internal + letter (that is, all letters but the first and last) are shuffled. + """ + def _subber(m): + L = list(m.group(2)) + random.shuffle(L) + return '%s%s%s' % (m.group(1), ''.join(L), m.group(3)) + s = self._scrambleRe.sub(_subber, text) + irc.reply(s) + scramble = wrap(scramble, ['text']) + + _code = { + "A" : ".-", + "B" : "-...", + "C" : "-.-.", + "D" : "-..", + "E" : ".", + "F" : "..-.", + "G" : "--.", + "H" : "....", + "I" : "..", + "J" : ".---", + "K" : "-.-", + "L" : ".-..", + "M" : "--", + "N" : "-.", + "O" : "---", + "P" : ".--.", + "Q" : "--.-", + "R" : ".-.", + "S" : "...", + "T" : "-", + "U" : "..-", + "V" : "...-", + "W" : ".--", + "X" : "-..-", + "Y" : "-.--", + "Z" : "--..", + "0" : "-----", + "1" : ".----", + "2" : "..---", + "3" : "...--", + "4" : "....-", + "5" : ".....", + "6" : "-....", + "7" : "--...", + "8" : "---..", + "9" : "----.", + "." : ".-.-.-", + "," : "--..--", + ":" : "---...", + "?" : "..--..", + "'" : ".----.", + "-" : "-....-", + "/" : "-..-.", + '"' : ".-..-.", + "@" : ".--.-.", + "=" : "-...-" + } + _revcode = dict([(y, x) for (x, y) in _code.items()]) + _unmorsere = re.compile('([.-]+)') + def unmorse(self, irc, msg, args, text): + """ + + Does the reverse of the morse command. + """ + text = text.replace('_', '-') + def morseToLetter(m): + s = m.group(1) + return self._revcode.get(s, s) + text = self._unmorsere.sub(morseToLetter, text) + text = text.replace(' ', '\x00') + text = text.replace(' ', '') + text = text.replace('\x00', ' ') + irc.reply(text) + unmorse = wrap(unmorse, ['text']) + + def morse(self, irc, msg, args, text): + """ + + Gives the Morse code equivalent of a given string. + """ + L = [] + for c in text.upper(): + if c in self._code: + L.append(self._code[c]) + else: + L.append(c) + irc.reply(' '.join(L)) + morse = wrap(morse, ['text']) + + def reverse(self, irc, msg, args, text): + """ + + Reverses . + """ + irc.reply(text[::-1]) + reverse = wrap(reverse, ['text']) + + def _color(self, c, fg=None): + if c == ' ': + return c + if fg is None: + fg = str(random.randint(2, 15)).zfill(2) + return '\x03%s%s' % (fg, c) + + def colorize(self, irc, msg, args, text): + """ + + Returns with each character randomly colorized. + """ + L = [self._color(c) for c in text] + irc.reply('%s%s' % (''.join(L), '\x03')) + colorize = wrap(colorize, ['text']) + + def rainbow(self, irc, msg, args, text): + """ + + Returns colorized like a rainbow. + """ + colors = itertools.cycle([4, 7, 8, 3, 2, 12, 6]) + L = [self._color(c, fg=colors.next()) for c in text] + irc.reply(''.join(L) + '\x03') + rainbow = wrap(rainbow, ['text']) + + def stripcolor(self, irc, msg, args, text): + """ + + Returns stripped of all color codes. + """ + irc.reply(ircutils.stripColor(text)) + stripcolor = wrap(stripcolor, ['text']) + + def aol(self, irc, msg, args, text): + """ + + Returns as if an AOLuser had said it. + """ + text = text.replace(' you ', ' u ') + text = text.replace(' are ', ' r ') + text = text.replace(' love ', ' <3 ') + text = text.replace(' luv ', ' <3 ') + text = text.replace(' too ', ' 2 ') + text = text.replace(' to ', ' 2 ') + text = text.replace(' two ', ' 2 ') + text = text.replace('fore', '4') + text = text.replace(' for ', ' 4 ') + text = text.replace('be', 'b') + text = text.replace('four', ' 4 ') + text = text.replace(' their ', ' there ') + text = text.replace(', ', ' ') + text = text.replace(',', ' ') + text = text.replace("'", '') + text = text.replace('one', '1') + smiley = random.choice(['<3', ':)', ':-)', ':D', ':-D']) + text += smiley*3 + irc.reply(text) + aol = wrap(aol, ['text']) + + def jeffk(self, irc, msg, args, text): + """ + + Returns as if JeffK had said it himself. + """ + def randomlyPick(L): + return random.choice(L) + def quoteOrNothing(m): + return randomlyPick(['"', '']).join(m.groups()) + def randomlyReplace(s, probability=0.5): + def f(m): + if random.random() < probability: + return m.expand(s) + else: + return m.group(0) + return f + def randomExclaims(m): + if random.random() < 0.85: + return ('!' * random.randrange(1, 5)) + m.group(1) + else: + return '.' + m.group(1) + def randomlyShuffle(m): + L = list(m.groups()) + random.shuffle(L) + return ''.join(L) + def lessRandomlyShuffle(m): + L = list(m.groups()) + if random.random() < .4: + random.shuffle(L) + return ''.join(L) + def randomlyLaugh(text, probability=.3): + if random.random() < probability: + if random.random() < .5: + insult = random.choice([' fagot1', ' fagorts', ' jerks', + 'fagot' ' jerk', ' dumbshoes', + ' dumbshoe']) + else: + insult = '' + laugh1 = random.choice(['ha', 'hah', 'lol', 'l0l', 'ahh']) + laugh2 = random.choice(['ha', 'hah', 'lol', 'l0l', 'ahh']) + laugh1 = laugh1 * random.randrange(1, 5) + laugh2 = laugh2 * random.randrange(1, 5) + exclaim = random.choice(['!', '~', '!~', '~!!~~', + '!!~', '~~~!']) + exclaim += random.choice(['!', '~', '!~', '~!!~~', + '!!~', '~~~!']) + if random.random() < 0.5: + exclaim += random.choice(['!', '~', '!~', '~!!~~', + + '!!~', '~~~!']) + laugh = ''.join([' ', laugh1, laugh2, insult, exclaim]) + text += laugh + return text + if random.random() < .03: + irc.reply(randomlyLaugh('NO YUO', probability=1)) + return + alwaysInsertions = { + r'er\b': 'ar', + r'\bthe\b': 'teh', + r'\byou\b': 'yuo', + r'\bis\b': 'si', + r'\blike\b': 'liek', + r'[^e]ing\b': 'eing', + } + for (r, s) in alwaysInsertions.iteritems(): + text = re.sub(r, s, text) + randomInsertions = { + r'i': 'ui', + r'le\b': 'al', + r'i': 'io', + r'l': 'll', + r'to': 'too', + r'that': 'taht', + r'[^s]c([ei])': r'sci\1', + r'ed\b': r'e', + r'\band\b': 'adn', + r'\bhere\b': 'hear', + r'\bthey\'re': 'their', + r'\bthere\b': 'they\'re', + r'\btheir\b': 'there', + r'[^e]y': 'ey', + } + for (r, s) in randomInsertions.iteritems(): + text = re.sub(r, randomlyReplace(s), text) + text = re.sub(r'(\w)\'(\w)', quoteOrNothing, text) + text = re.sub(r'\.(\s+|$)', randomExclaims, text) + text = re.sub(r'([aeiou])([aeiou])', randomlyShuffle, text) + text = re.sub(r'([bcdfghkjlmnpqrstvwxyz])([bcdfghkjlmnpqrstvwxyz])', + lessRandomlyShuffle, text) + text = randomlyLaugh(text) + if random.random() < .4: + text = text.upper() + irc.reply(text) + jeffk = wrap(jeffk, ['text']) + + # Keeping these separate so people can just replace the alphabets for + # whatever their language of choice + _spellLetters = { + 'a': 'ay', 'b': 'bee', 'c': 'see', 'd': 'dee', 'e': 'ee', 'f': 'eff', + 'g': 'gee', 'h': 'aych', 'i': 'eye', 'j': 'jay', 'k': 'kay', 'l': + 'ell', 'm': 'em', 'n': 'en', 'o': 'oh', 'p': 'pee', 'q': 'cue', 'r': + 'arr', 's': 'ess', 't': 'tee', 'u': 'you', 'v': 'vee', 'w': + 'double-you', 'x': 'ecks', 'y': 'why', 'z': 'zee' + } + for (k, v) in _spellLetters.items(): + _spellLetters[k.upper()] = v + _spellPunctuation = { + '!': 'exclamation point', + '"': 'quote', + '#': 'pound', + '$': 'dollar sign', + '%': 'percent', + '&': 'ampersand', + '\'': 'single quote', + '(': 'left paren', + ')': 'right paren', + '*': 'asterisk', + '+': 'plus', + ',': 'comma', + '-': 'minus', + '.': 'period', + '/': 'slash', + ':': 'colon', + ';': 'semicolon', + '<': 'less than', + '=': 'equals', + '>': 'greater than', + '?': 'question mark', + '@': 'at', + '[': 'left bracket', + '\\': 'backslash', + ']': 'right bracket', + '^': 'caret', + '_': 'underscore', + '`': 'backtick', + '{': 'left brace', + '|': 'pipe', + '}': 'right brace', + '~': 'tilde' + } + _spellNumbers = { + '0': 'zero', '1': 'one', '2': 'two', '3': 'three', '4': 'four', + '5': 'five', '6': 'six', '7': 'seven', '8': 'eight', '9': 'nine' + } + def spellit(self, irc, msg, args, text): + """ + + Returns , phonetically spelled out. + """ + d = {} + if self.registryValue('spellit.replaceLetters'): + d.update(self._spellLetters) + if self.registryValue('spellit.replaceNumbers'): + d.update(self._spellNumbers) + if self.registryValue('spellit.replacePunctuation'): + d.update(self._spellPunctuation) +# A bug in unicode on OSX prevents me from testing this. +## dd = {} +## for (c, v) in d.iteritems(): +## dd[ord(c)] = unicode(v + ' ') +## irc.reply(unicode(text).translate(dd)) + out = StringIO() + write = out.write + for c in text: + try: + c = d[c] + write(' ') + except KeyError: + pass + write(c) + irc.reply(out.getvalue()) + spellit = wrap(spellit, ['text']) + + def gnu(self, irc, msg, args, text): + """ + + Returns as GNU/RMS would say it. + """ + irc.reply(' '.join(['GNU/' + s for s in text.split()])) + gnu = wrap(gnu, ['text']) + + def shrink(self, irc, msg, args, text): + """ + + Returns with each word longer than + supybot.plugins.Filter.shrink.minimum being shrunken (i.e., like + "internationalization" becomes "i18n"). + """ + L = [] + minimum = self.registryValue('shrink.minimum', msg.args[0]) + r = re.compile(r'[A-Za-z]{%s,}' % minimum) + def shrink(m): + s = m.group(0) + return ''.join((s[0], str(len(s)-2), s[-1])) + text = r.sub(shrink, text) + irc.reply(text) + shrink = wrap(shrink, ['text']) + + +Class = Filter + + +# vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Filter/test.py b/plugins/Filter/test.py new file mode 100644 index 000000000..a0bc02ccf --- /dev/null +++ b/plugins/Filter/test.py @@ -0,0 +1,169 @@ +### +# Copyright (c) 2002-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 * + +import re + +import supybot.utils as utils +import supybot.callbacks as callbacks + +class FilterTest(ChannelPluginTestCase): + plugins = ('Filter', 'Utilities') + def testNoErrors(self): + self.assertNotError('leet foobar') + self.assertNotError('supa1337 foobar') + self.assertNotError('lithp meghan sweeney') + self.assertNotError('aol I\'m too legit to quit.') + + def testDisabledCommandsCannotFilter(self): + self.assertNotError('outfilter rot13') + self.assertResponse('echo foo', 'sbb') + self.assertNotError('outfilter') + try: + self.assertNotError('disable rot13') + self.assertError('outfilter rot13') + self.assertNotError('enable rot13') + self.assertNotError('outfilter rot13') + finally: + try: + callbacks.Privmsg._disabled.remove('rot13') + except KeyError: + pass + + def testHebrew(self): + self.assertResponse('hebrew The quick brown fox ' + 'jumps over the lazy dog.', + 'Th qck brwn fx jmps vr th lzy dg.') + def testJeffk(self): + for i in range(100): + self.assertNotError('jeffk the quick brown fox is ghetto') + + def testSquish(self): + self.assertResponse('squish foo bar baz', 'foobarbaz') + self.assertResponse('squish "foo bar baz"', 'foobarbaz') + + def testUndup(self): + self.assertResponse('undup foo bar baz quux', 'fo bar baz qux') + self.assertResponse('undup aaaaaaaaaa', 'a') + + def testLithp(self): + self.assertResponse('lithp jamessan', 'jamethan') + self.assertResponse('lithp Shame', 'Thame') + + def testMorse(self): + self.assertResponse('unmorse [morse jemfinch]', 'JEMFINCH') + + def testReverse(self): + for s in map(str, range(1000, 1010)): + self.assertResponse('reverse %s' % s, s[::-1]) + + def testBinary(self): + self.assertResponse('binary A', '01000001') + + def testRot13(self): + for s in map(str, range(1000, 1010)): + self.assertResponse('rot13 [rot13 %s]' % s, s) + + def testRot13HandlesNonAsciiStuff(self): + self.assertNotError('rot13 \xe4') + + def testHexlifyUnhexlify(self): + for s in map(str, range(1000, 1010)): + self.assertResponse('unhexlify [hexlify %s]' % s, s) + + def testScramble(self): + s = 'the recalcitrant jamessan tests his scramble function' + self.assertNotRegexp('scramble %s' % s, s) + s = 'the recalc1trant jam3ssan tests his scramble fun>