### # 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. ### """ A module to observe things in the channel and call commands based on what is seen. """ __revision__ = "$Id$" __author__ = '' import supybot.plugins as plugins import random import supybot.conf as conf import supybot.utils as utils import supybot.privmsgs as privmsgs import supybot.registry as registry import supybot.callbacks as callbacks def configure(advanced): # This will be called by setup.py 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('Observer', True) class Probability(registry.Float): """Value must be a floating point number in the range (0, 1].""" def setValue(self, v): if not 0 < v <= 1: self.error() else: super(Probability, self).setValue(v) class Observers(registry.SpaceSeparatedListOfStrings): List = callbacks.CanonicalNameSet class ActiveObservers(registry.SpaceSeparatedListOfStrings): String = callbacks.canonicalName conf.registerPlugin('Observer') # XXX These both need to be CanonicalStrings. conf.registerGlobalValue(conf.supybot.plugins.Observer, 'observers', Observers([], """Determines what observers are available.""", orderAlphabetically=True)) conf.registerChannelValue(conf.supybot.plugins.Observer.observers, 'active', ActiveObservers([], """Determines what observers are active on a channel.""")) def registerObserver(name, regexpString='', commandString='', probability=1.0): g = conf.registerGlobalValue(conf.supybot.plugins.Observer.observers, name, registry.Regexp(regexpString, """Determines what regexp must match for this observer to be executed.""")) if regexpString: g.set(regexpString) # This is in case it's been registered. conf.registerGlobalValue(g, 'command', registry.String('', """Determines what command will be run when this observer is executed.""")) if commandString: g.command.setValue(commandString) conf.registerGlobalValue(g, 'probability', Probability(probability, """ Determines what the probability of executing this observer is if it matches.""")) g.probability.setValue(probability) conf.supybot.plugins.Observer.observers().add(name) return g class Observer(callbacks.Privmsg): commandCalled = False def _isValidObserverName(self, name): return name != 'active' and registry.isValidRegistryName(name) def callCommand(self, *args, **kwargs): self.commandCalled = True super(Observer, self).callCommand(*args, **kwargs) def doPrivmsg(self, irc, msg): if self.commandCalled: self.commandCalled = False return channel = msg.args[0] Owner = irc.getCallback('Owner') observers = self.registryValue('observers') active = self.registryValue('observers.active', channel) for name in active: if name not in observers: self.log.error('Active observers for %s include an ' 'invalid observer: %s.', channel, name) continue observer = self.registryValue('observers.%s' % name, value=False) probability = observer.probability() if random.random() > probability: continue r = observer() # The regexp. m = r.search(msg.args[1]) if m is not None: command = observer.command() groups = list(m.groups()) groups.insert(0, m.group(0)) for (i, group) in enumerate(groups): command = command.replace('$%s' % i, group) tokens = callbacks.tokenize(command, channel=channel) Owner.processTokens(irc, msg, tokens) def list(self, irc, msg, args): """[] Lists the currently available observers. If is given, returns the currently active observers on . """ if args: # We don't use getChannel here because we don't want it to # automatically pick the channel if the message is sent in # the channel itself. channel = args.pop(0) if args or not ircutils.isChannel(channel): raise callbacks.ArgumentError observers = self.registryValue('observers.active', channel) # We don't sort because order matters. else: observers = self.registryValue('observers') utils.sortBy(str.lower, observers) if observers: irc.reply(utils.commaAndify(observers)) else: irc.reply('There were no relevant observers.') def enable(self, irc, msg, args, channel): """[] Enables the observer in . is only necessary if the message isn't sent in the channel itself. """ name = privmsgs.getArgs(args) if name not in self.registryValue('observers'): irc.error('There is no observer %s.' % name, Raise=True) self.registryValue('observers.active', channel).append(name) irc.replySuccess() enable = privmsgs.checkChannelCapability(enable, 'op') def disable(self, irc, msg, args, channel): """[] Disables the observer in . is only necessary if the message isn't sent in the channel itself. """ name = privmsgs.getArgs(args) try: self.registryValue('observers.active', channel).remove(name) irc.replySuccess() except (KeyError, ValueError): irc.error('The observer %s was not active on %s.' % (name,channel)) disable = privmsgs.checkChannelCapability(disable, 'op') def info(self, irc, msg, args): """ Returns the relevant information on the observer specified by . """ name = privmsgs.getArgs(args) if name not in self.registryValue('observers'): irc.error('That\'s not a valid observer.', Raise=True) g = self.registryValue('observers.%s' % name, value=False) regexp = g() command = g.command() probability = g.probability() irc.reply('%s matches the regular expression %s and ' 'runs the command %s with a probability of %s' % (name, regexp, command, probability)) def add(self, irc, msg, args): """ [] Calls when matches a given message. Before being called, has the standard substitute applied to it, as well as having $1, $2, etc. replaced by the appropriate groups of the regexp. If is not given, it defaults to 1; otherwise it should be a floating point probability that the observer will execute if it matches. """ if len(args) < 3: raise callbacks.ArgumentError try: probability = float(args[1]) del args[1] except ValueError: probability = 1.0 (name, regexp, command) = privmsgs.getArgs(args, required=3) if not registry.isValidRegistryName(name): irc.error('That\'s not a valid observer name. Please be sure ' 'there are no spaces in the name.', Raise=True) registerObserver(name, regexp, command, probability) irc.replySuccess() def remove(self, irc, msg, args): """ Removes the observer . """ pass Class = Observer # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: