### # Copyright (c) 2007, Robert Brewer # 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.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.plugins as plugins import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks import os import random import time class Personality(callbacks.Plugin): _persona = {} def _filename(self, persona): """Return the absolute filename for the current persona.""" basedir = conf.supybot.directories.conf.dirize(self.name()) fname = os.path.join(basedir, '%s.txt' % persona) fname = os.path.normpath(fname) if not fname.startswith(basedir): raise ValueError("The given personality is not valid.") return fname def _talk(self, irc, msg, args, nick): """[] Reply with a random quote (addressed to nick, if appropriate).""" p = self._persona.get(msg.args[0], self.registryValue('default_persona')) try: corpus = open(self._filename(p), 'rb').readlines() except IOError: irc.reply("I am %s. Please insert girder." % p.title()) else: phrase = random.choice(corpus) % {'nick': nick or msg.nick} if phrase.startswith("/me"): action = True phrase = phrase[3:].lstrip() else: action = False # Sleep 2 seconds to think and then type 60 cps time.sleep(2.0 + (len(phrase) / 60)) irc.reply(phrase.strip(), action=action, prefixNick=False) talk = wrap(_talk, [additional('nick')]) def invalidCommand(self, irc, msg, tokens): tokens = [t.strip("?!.,;:") for t in tokens] tokens = [t for t in tokens if len(t) > 3] if tokens: p = self._persona.get(msg.args[0], self.registryValue('default_persona')) phrase = self._choose_reply(p, tokens) if phrase: if phrase.startswith("/me"): action = True phrase = phrase[3:].lstrip() else: action = False # Sleep 2 seconds to think and then type 60 cps time.sleep(2.0 + (len(phrase) / 60)) irc.reply(phrase % {'nick': msg.nick}, action=action, prefixNick=False) return self._talk(irc, msg, [], None) def _explain(self, irc, msg, args, text): """ Reply with a quote that contains the given text.""" if not text: irc.reply("That shut 'em up.") return p = self._persona.get(msg.args[0], self.registryValue('default_persona')) phrase = self._choose_reply(p, text) if phrase: if phrase.startswith("/me"): action = True phrase = phrase[3:].lstrip() else: action = False # Sleep 2 seconds to think and then type 60 cps time.sleep(2.0 + (len(phrase) / 60)) irc.reply(phrase % {'nick': msg.nick}, action=action, prefixNick=False) else: irc.reply("I don't know anything about %s" % text) explain = wrap(_explain, [additional('something')]) def _choose_reply(self, persona, words): """Select a random phrase from the persona which contains one of the words.""" if not isinstance(words, (tuple, list)): words = [words] try: corpus = open(self._filename(persona), 'rb').readlines() except IOError: return "I am %s. Please insert girder." % persona.title() random.shuffle(words) for word in words: lowword = word.lower() matches = [line for line in corpus if lowword in line.lower()] if matches: return random.choice(matches).strip() def dump(self, irc, msg, args, text, offset): """ [] Reply with 5 quotes that contain the given text.""" if not text: irc.reply("You must supply a search string.") return p = self._persona.get(msg.args[0], self.registryValue('default_persona')) seen = 0 lowtext = text.lower() try: corpus = open(self._filename(p), 'rb').readlines() except IOError: irc.reply("I am %s. Please insert girder." % p.title()) return for line in corpus: if lowtext in line.lower(): seen += 1 if seen > offset: line = line % {'nick': msg.nick} if line.startswith("/me"): action = True line = line[3:].lstrip() else: action = False irc.reply(line.strip(), action=action, prefixNick=False) if seen >= offset + 5: break if not seen: irc.reply("I don't know anything about %s" % text) dump = wrap(dump, [additional('something'), optional('int', 0)]) def learn(self, irc, msg, args, text): """ Learn a quote.""" if not text: irc.reply("You must supply a quote for me to learn.") return p = self._persona.get(msg.args[0], self.registryValue('default_persona')) try: corpus = open(self._filename(p), 'ab') try: corpus.write(text + '\n') finally: corpus.close() except IOError: irc.reply("I am %s. Please insert girder." % p.title()) return irc.reply('ok', prefixNick=False) learn = wrap(learn, ['something']) def forget(self, irc, msg, args, text): """ Forget a quote.""" if not text or len(text) < 5: irc.reply("You must supply a quote (of at least 5 letters) for me to forget.") return p = self._persona.get(msg.args[0], self.registryValue('default_persona')) try: corpus = open(self._filename(p), 'rb').readlines() corpus = [line for line in corpus if text not in line] open(self._filename(p), 'wb').writelines(corpus) except IOError: irc.reply("I am %s. Please insert girder." % p.title()) return irc.reply('ok', prefixNick=False) forget = wrap(forget, ['something']) def persona(self, irc, msg, args, newpersona): """ Changes the bot's personality to .""" if newpersona: basedir = conf.supybot.directories.conf.dirize(self.name()) names = [fname[:-4] for fname in os.listdir(basedir) if fname.endswith(".txt")] if len(names) >= 255: irc.reply("Too many personas already. An admin will have to remove one.") return self._persona[msg.args[0]] = newpersona text = "%s is now %s." % (irc.nick, newpersona) irc.reply("%s is now %s." % (irc.nick, newpersona)) else: p = self._persona.get(msg.args[0], self.registryValue('default_persona')) irc.reply("%s is %s." % (irc.nick, p)) persona = wrap(persona, [optional('something')]) def personas(self, irc, msg, args): """List the bot's known personalities.""" basedir = conf.supybot.directories.conf.dirize(self.name()) names = [fname[:-4] for fname in os.listdir(basedir) if fname.endswith(".txt")] irc.reply("Known personas: %s." % ", ".join(names)) personas = wrap(personas) setattr(Personality, '?', Personality.talk) Class = Personality