supybot-0.83.4.1.ds.orig/0000755000000000000000000000000011206611405011700 5ustar supybot-0.83.4.1.ds.orig/LICENSE0000644000000000000000000000322411206611405012706 0ustar Copyright (c) 2002-2009 Jeremiah Fincher and others 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 permission. 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. Portions of the included source code are copyright by its original author(s) and remain subject to its associated license. supybot-0.83.4.1.ds.orig/scripts/0000755000000000000000000000000011206611405013367 5ustar supybot-0.83.4.1.ds.orig/scripts/supybot-plugin-doc0000644000000000000000000002713111206611405017062 0ustar #!/usr/bin/env python ### # Copyright (c) 2005, Ali Afshar # Copyright (c) 2009, James Vega # 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 sys import shutil import supybot def error(s): sys.stderr.write('%s\n' % s) sys.exit(-1) # We need to do this before we import conf. if not os.path.exists('doc-conf'): os.mkdir('doc-conf') registryFilename = os.path.join('doc-conf', 'doc.conf') try: fd = file(registryFilename, 'w') fd.write(""" supybot.directories.data: doc-data supybot.directories.conf: doc-conf supybot.directories.log: doc-logs supybot.log.stdout: False supybot.log.level: DEBUG supybot.log.format: %(levelname)s %(message)s supybot.log.plugins.individualLogfiles: False supybot.databases: sqlite anydbm cdb flat pickle """) fd.close() except EnvironmentError, e: error('Unable to open %s for writing.' % registryFilename) import supybot.registry as registry registry.open(registryFilename) import supybot.log as log import supybot.conf as conf conf.supybot.flush.setValue(False) import textwrap import supybot.utils as utils import supybot.world as world import supybot.plugin as plugin import supybot.registry as registry world.documenting = True class PluginDoc(object): def __init__(self, mod): self.mod = mod self.inst = self.mod.Class(None) self.name = self.mod.Class.__name__ self.appendExtraBlankLine = False self.lines = [] def appendLine(self, line, indent=0): line = line.strip() indent = ' ' * indent lines = textwrap.wrap(line, 79, initial_indent=indent, subsequent_indent=indent) self.lines.extend(lines) if self.appendExtraBlankLine: self.lines.append('') def renderRST(self): self.appendExtraBlankLine = False s = 'Documentation for the %s plugin for Supybot' % self.name self.appendLine(s) self.appendLine('=' * len(s)) self.lines.append('') self.appendLine('Purpose') self.appendLine('-------') pdoc = getattr(self.mod, '__doc__', 'My author didn\'t give me a purpose.') self.appendLine(pdoc) self.lines.append('') cdoc = getattr(self.mod.Class, '__doc__', None) if cdoc is not None: self.appendLine('Usage') self.appendLine('-----') self.appendLine(cdoc) self.lines.append('') commands = self.inst.listCommands() if len(commands): self.appendLine('Commands') self.appendLine('--------') for command in commands: log.debug('command: %s', command) line = '%s ' % command command = command.split() doc = self.inst.getCommandHelp(command) if doc: doc = doc.replace('\x02', '') (args, help) = doc.split(')', 1) args = args.split('(', 1)[1] args = args[len(' '.join(command)):].strip() help = help.split('--', 1)[1].strip() self.appendLine(line + args) self.appendLine(help, 1) else: self.appendLine('No help associated with this command') self.lines.append('') # now the config try: confs = conf.supybot.plugins.get(self.name) self.appendLine('Configuration') self.appendLine('-------------') except registry.NonExistentRegistryEntry: log.info('No configuration for plugin %s', plugin) self.appendLine('No configuration for this plugin') else: for confValues in self.genConfig(confs, 0): (name, isChan, help, default, indent) = confValues self.appendLine('%s' % name, indent - 1) self.appendLine('This config variable defaults to %s and %s ' 'channel specific.' % (default,isChan), indent) self.lines.append('') self.appendLine(help, indent) self.lines.append('') return '\n'.join(self.lines) + '\n' def renderSTX(self): self.appendExtraBlankLine = True self.appendLine('Documentation for the %s plugin for ' 'Supybot' % self.name) self.appendLine('Purpose', 1) pdoc = getattr(self.mod, '__doc__', 'My author didn\'t give me a purpose.') self.appendLine(pdoc, 2) cdoc = getattr(self.mod.Class, '__doc__', None) if cdoc is not None: self.appendLine('Usage', 1) self.appendLine(cdoc, 2) commands = self.inst.listCommands() if len(commands): self.appendLine('Commands', 1) for command in commands: log.debug('command: %s', command) line = '* %s ' % command command = command.split() doc = self.inst.getCommandHelp(command) if doc: doc = doc.replace('\x02', '') (args, help) = doc.split(')', 1) args = args.split('(', 1)[1] args = args[len(' '.join(command)):].strip() help = help.split('--', 1)[1].strip() self.appendLine(line + args, 2) self.appendLine(help, 3) else: self.appendLine('No help associated with this command', 3) # now the config try: confs = conf.supybot.plugins.get(self.name) self.appendLine('Configuration', 1) except registry.NonExistentRegistryEntry: log.info('No configuration for plugin %s', plugin) self.appendLine('No configuration for this plugin', 2) else: for confValues in self.genConfig(confs, 2): (name, isChan, help, default, indent) = confValues self.appendLine('* %s' % name, indent - 1) self.appendLine('This config variable defaults to %s and %s ' 'channel specific.' % (default,isChan), indent) self.appendLine(help, indent) return '\n'.join(self.lines) + '\n' def genConfig(self, item, origindent): confVars = item.getValues(getChildren=False, fullNames=False) if not confVars: return for (c, v) in confVars: name = v._name indent = origindent + 1 try: default = str(v) if isinstance(v._default, basestring) or v._default is None: default = utils.str.dqrepr(default) help = v.help() channelValue = v.channelValue except registry.NonExistentRegistryEntry: pass else: if channelValue: cv = 'is' else: cv = 'is not' yield (name, cv, help, default, indent) for confValues in self.genConfig(v, indent): yield confValues def genDoc(m, options): Plugin = PluginDoc(m) print 'Generating documentation for %s...' % Plugin.name path = os.path.join(options.outputDir, '%s.%s' % (Plugin.name, options.format)) try: fd = file(path, 'w') except EnvironmentError, e: error('Unable to open %s for writing.' % path) f = getattr(Plugin, 'render%s' % options.format.upper(), None) if f is None: fd.close() error('Unknown render format: `%s\'' % options.format) try: fd.write(f()) finally: fd.close() if __name__ == '__main__': import glob import os.path import optparse import supybot.plugin as plugin parser = optparse.OptionParser(usage='Usage: %prog [options] [plugins]', version='Supybot %s' % conf.version) parser.add_option('-c', '--clean', action='store_true', default=False, dest='clean', help='Cleans the various data/conf/logs ' 'directories after generating the docs.') parser.add_option('-o', '--output-dir', dest='outputDir', default='.', help='Specifies the directory in which to write the ' 'documentation for the plugin.') parser.add_option('-f', '--format', dest='format', choices=['rst', 'stx'], default='stx', help='Specifies which output format to ' 'use.') parser.add_option('--plugins-dir', action='append', dest='pluginsDirs', default=[], help='Looks in in the given directory for plugins and ' 'generates documentation for all of them.') (options, args) = parser.parse_args() # This must go before checking for args, of course. for pluginDir in options.pluginsDirs: for name in glob.glob(os.path.join(pluginDir, '*')): if os.path.isdir(name): args.append(name) if not args: parser.print_help() sys.exit(-1) args = [s.rstrip('\\/') for s in args] pluginDirs = set([os.path.dirname(s) or '.' for s in args]) conf.supybot.directories.plugins.setValue(list(pluginDirs)) pluginNames = set([os.path.basename(s) for s in args]) plugins = set([]) for pluginName in pluginNames: if pluginName.endswith('.py'): pluginName = pluginName[:-3] try: pluginModule = plugin.loadPluginModule(pluginName) except ImportError, e: s = 'Failed to load plugin %s: %s\n' \ '%s(pluginDirs: %s)' % (pluginName, e, s, conf.supybot.directories.plugins()) error(s) plugins.add(pluginModule) for Plugin in plugins: genDoc(Plugin, options) if options.clean: shutil.rmtree(conf.supybot.directories.log()) shutil.rmtree(conf.supybot.directories.conf()) shutil.rmtree(conf.supybot.directories.data()) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/scripts/supybot-plugin-package0000644000000000000000000000445011206611405017707 0ustar #!/usr/bin/env python ### # 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 import os.path import tarfile import optparse import supybot.conf as conf if __name__ == '__main__': parser = optparse.OptionParser(usage='Usage: %prog pluginDirectory', version='Supybot %s' % conf.version) (options, args) = parser.parse_args() for dirname in args: # XXX Sanity checking (syntax errors?) # XXX Sanity checking (__attrs__ -- url, contributors, version, etc.) # XXX Documentation generation (jamessan!) basename = os.path.basename(dirname) tf = tarfile.open('%s.tar.gz' % basename, mode='w:gz') tf.add(dirname, basename, True) tf.close() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/scripts/supybot-wizard0000644000000000000000000007326411206611405016331 0ustar #!/usr/bin/env python ### # Copyright (c) 2003-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 sys def error(s): sys.stderr.write(s) if not s.endswith(os.linesep): sys.stderr.write(os.linesep) sys.exit(-1) if sys.version_info < (2, 3, 0): error('This program requires Python >= 2.3.0') import supybot import re import sets import time import pydoc import pprint import socket import logging import optparse import supybot.ansi as ansi import supybot.utils as utils import supybot.plugin as plugin import supybot.ircutils as ircutils import supybot.registry as registry import supybot.questions as questions from supybot.questions import output, yn, anything, something, expect, getpass def getPlugins(pluginDirs): plugins = set([]) join = os.path.join for pluginDir in pluginDirs: try: for filename in os.listdir(pluginDir): fname = join(pluginDir, filename) if (filename.endswith('.py') or os.path.isdir(fname)) \ and filename[0].isupper(): plugins.add(os.path.splitext(filename)[0]) except OSError: continue plugins.discard('Owner') plugins = list(plugins) plugins.sort() return plugins def loadPlugin(name): import supybot.plugin as plugin try: module = plugin.loadPluginModule(name) if hasattr(module, 'Class'): return module else: output("""That plugin loaded fine, but didn't seem to be a real Supybot plugin; there was no Class variable to tell us what class to load when we load the plugin. We'll skip over it for now, but you can always add it later.""") return None except Exception, e: output("""We encountered a bit of trouble trying to load plugin %r. Python told us %r. We'll skip over it for now, you can always add it later.""" % (name, utils.gen.exnToString(e))) return None def describePlugin(module, showUsage): if module.__doc__: output(module.__doc__, unformatted=False) elif hasattr(module.Class, '__doc__'): output(module.Class.__doc__, unformatted=False) else: output("""Unfortunately, this plugin doesn't seem to have any documentation. Sorry about that.""") if showUsage: if hasattr(module, 'example'): if yn('This plugin has a usage example. ' 'Would you like to see it?', default=False): pydoc.pager(module.example) else: output("""This plugin has no usage example.""") def clearLoadedPlugins(plugins, pluginRegistry): for plugin in plugins: try: pluginKey = pluginRegistry.get(plugin) if pluginKey(): plugins.remove(plugin) except registry.NonExistentRegistryEntry: continue _windowsVarRe = re.compile(r'%(\w+)%') def getDirectoryName(default, basedir=os.curdir, prompt=True): done = False while not done: if prompt: dir = something('What directory do you want to use?', default=os.path.join(basedir, default)) else: dir = os.path.join(basedir, default) orig_dir = dir dir = os.path.expanduser(dir) dir = _windowsVarRe.sub(r'$\1', dir) dir = os.path.expandvars(dir) dir = os.path.abspath(dir) try: os.makedirs(dir) done = True except OSError, e: if e.args[0] != 17: # File exists. output("""Sorry, I couldn't make that directory for some reason. The Operating System told me %s. You're going to have to pick someplace else.""" % e) prompt = True else: done = True return (dir, os.path.dirname(orig_dir)) def main(): import supybot.log as log import supybot.conf as conf log._stdoutHandler.setLevel(100) # *Nothing* gets through this! parser = optparse.OptionParser(usage='Usage: %prog [options]', version='Supybot %s' % conf.version) parser.add_option('', '--allow-root', action='store_true', dest='allowRoot', help='Determines whether the wizard will be allowed to ' 'run as root. You don\'t want this. Don\'t do it.' ' Even if you think you want it, you don\'t. ' 'You\'re probably dumb if you do this.') parser.add_option('', '--no-network', action='store_false', dest='network', help='Determines whether the wizard will be allowed to ' 'run without a network connection.') (options, args) = parser.parse_args() if os.name == 'posix': if (os.getuid() == 0 or os.geteuid() == 0) and not options.allowRoot: error('Please, don\'t run this as root.') filename = '' if args: parser.error('This program takes no non-option arguments.') output("""This is a wizard to help you start running supybot. What it will do is create the necessary config files based on the options you select here. So hold on tight and be ready to be interrogated :)""") output("""First of all, we can bold the questions you're asked so you can easily distinguish the mostly useless blather (like this) from the questions that you actually have to answer.""") if yn('Would you like to try this bolding?', default=True): questions.useBold = True if not yn('Do you see this in bold?'): output("""Sorry, it looks like your terminal isn't ANSI compliant. Try again some other day, on some other terminal :)""") questions.useBold = False else: output("""Great!""") ### # Preliminary questions. ### output("""We've got some preliminary things to get out of the way before we can really start asking you questions that directly relate to what your bot is going to be like.""") # Advanced? output("""We want to know if you consider yourself an advanced Supybot user because some questions are just utterly boring and useless for new users. Others might not make sense unless you've used Supybot for some time.""") advanced = yn('Are you an advanced Supybot user?', default=False) ### Directories. # We set these variables in cache because otherwise conf and log will # create directories for the default values, which might not be what the # user wants. if advanced: output("""Now we've got to ask you some questions about where some of your directories are (or, perhaps, will be :)). If you're running this wizard from the directory you'll actually be starting your bot from and don't mind creating some directories in the current directory, then just don't give answers to these questions and we'll create the directories we need right here in this directory.""") # conf.supybot.directories.log output("""Your bot will need to put his logs somewhere. Do you have any specific place you'd like them? If not, just press enter and we'll make a directory named "logs" right here.""") (logDir, basedir) = getDirectoryName('logs') conf.supybot.directories.log.setValue(logDir) # conf.supybot.directories.data output("""Your bot will need to put various data somewhere. Things like databases, downloaded files, etc. Do you have any specific place you'd like the bot to put these things? If not, just press enter and we'll make a directory named "data" right here.""") (dataDir, basedir) = getDirectoryName('data', basedir=basedir) conf.supybot.directories.data.setValue(dataDir) # conf.supybot.directories.conf output("""Your bot must know where to find his configuration files. It'll probably only make one or two, but it's gotta have some place to put them. Where should that place be? If you don't care, just press enter and we'll make a directory right here named "conf" where it'll store his stuff. """) (confDir, basedir) = getDirectoryName('conf', basedir=basedir) conf.supybot.directories.conf.setValue(confDir) # conf.supybot.directories.backup output("""Your bot must know where to place backups of its conf and data files. Where should that place be? If you don't care, just press enter and we'll make a directory right here named "backup" where it'll store his stuff.""") (backupDir, basedir) = getDirectoryName('backup', basedir=basedir) conf.supybot.directories.backup.setValue(backupDir) # pluginDirs output("""Your bot will also need to know where to find his plugins at. Of course, he already knows where the plugins that he came with are, but your own personal plugins that you write for will probably be somewhere else.""") pluginDirs = conf.supybot.directories.plugins() output("""Currently, the bot knows about the following directories:""") output(format('%L', pluginDirs + [plugin._pluginsDir])) while yn('Would you like to add another plugin directory? ' 'Adding a local plugin directory is good style.', default=True): (pluginDir, _) = getDirectoryName('plugins', basedir=basedir) if pluginDir not in pluginDirs: pluginDirs.append(pluginDir) conf.supybot.directories.plugins.setValue(pluginDirs) else: output("""Your bot needs to create some directories in order to store the various log, config, and data files.""") basedir = something("""Where would you like to create these directories?""", default=os.curdir) # conf.supybot.directories.log (logDir, basedir) = getDirectoryName('logs', prompt=False) conf.supybot.directories.log.setValue(logDir) # conf.supybot.directories.data (dataDir, basedir) = getDirectoryName('data', basedir=basedir, prompt=False) conf.supybot.directories.data.setValue(dataDir) # conf.supybot.directories.conf (confDir, basedir) = getDirectoryName('conf', basedir=basedir, prompt=False) conf.supybot.directories.conf.setValue(confDir) # conf.supybot.directories.backup (backupDir, basedir) = getDirectoryName('backup', basedir=basedir, prompt=False) conf.supybot.directories.backup.setValue(backupDir) # pluginDirs pluginDirs = conf.supybot.directories.plugins() (pluginDir, _) = getDirectoryName('plugins', basedir=basedir, prompt=False) if pluginDir not in pluginDirs: pluginDirs.append(pluginDir) conf.supybot.directories.plugins.setValue(pluginDirs) output("Good! We're done with the directory stuff.") ### # Bot stuff ### output("""Now we're going to ask you things that actually relate to the bot you'll be running.""") network = None while not network: output("""First, we need to know the name of the network you'd like to connect to. Not the server host, mind you, but the name of the network. If you plan to connect to irc.freenode.net, for instance, you should answer this question with 'freenode' (without the quotes).""") network = something('What IRC network will you be connecting to?') if '.' in network: output("""There shouldn't be a '.' in the network name. Remember, this is the network name, not the actual server you plan to connect to.""") network = None elif not registry.isValidRegistryName(network): output("""That's not a valid name for one reason or another. Please pick a simpler name, one more likely to be valid.""") network = None conf.supybot.networks.setValue([network]) network = conf.registerNetwork(network) defaultServer = None server = None ip = None while not ip: serverString = something('What server would you like to connect to?', default=defaultServer) if options.network: try: output("""Looking up %s...""" % serverString) ip = socket.gethostbyname(serverString) except: output("""Sorry, I couldn't find that server. Perhaps you misspelled it? Also, be sure not to put the port in the server's name -- we'll ask you about that later.""") else: ip = 'no network available' output("""Found %s (%s).""" % (serverString, ip)) output("""IRC Servers almost always accept connections on port 6667. They can, however, accept connections anywhere their admin feels like he wants to accept connections from.""") if yn('Does this server require connection on a non-standard port?', default=False): port = 0 while not port: port = something('What port is that?') try: i = int(port) if not (0 < i < 65536): raise ValueError except ValueError: output("""That's not a valid port.""") port = 0 else: port = 6667 server = ':'.join([serverString, str(port)]) network.servers.setValue([server]) # conf.supybot.nick # Force the user into specifying a nick if he didn't have one already while True: nick = something('What nick would you like your bot to use?', default=None) try: conf.supybot.nick.set(nick) break except registry.InvalidRegistryValue: output("""That's not a valid nick. Go ahead and pick another.""") # conf.supybot.user if advanced: output("""If you've ever done a /whois on a person, you know that IRC provides a way for users to show the world their full name. What would you like your bot's full name to be? If you don't care, just press enter and it'll be the same as your bot's nick.""") user = '' user = something('What would you like your bot\'s full name to be?', default=nick) conf.supybot.user.set(user) # conf.supybot.ident (if advanced) defaultIdent = 'supybot' if advanced: output("""IRC servers also allow you to set your ident, which they might need if they can't find your identd server. What would you like your ident to be? If you don't care, press enter and we'll use 'supybot'. In fact, we prefer that you do this, because it provides free advertising for Supybot when users /whois your bot. But, of course, it's your call.""") while True: ident = something('What would you like your bot\'s ident to be?', default=defaultIdent) try: conf.supybot.ident.set(ident) break except registry.InvalidRegistryValue: output("""That was not a valid ident. Go ahead and pick another.""") else: conf.supybot.ident.set(defaultIdent) if advanced: # conf.supybot.networks..ssl output("""Some servers allow you to use a secure connection via SSL. This requires having pyOpenSSL installed. Currently, you also need Twisted installed as only the Twisted drivers supports SSL connections.""") if yn('Do you want to use an SSL connection?', default=False): network.ssl.setValue(True) # conf.supybot.networks..password output("""Some servers require a password to connect to them. Most public servers don't. If you try to connect to a server and for some reason it just won't work, it might be that you need to set a password.""") if yn('Do you want to set such a password?', default=False): network.password.set(getpass()) # conf.supybot.networks..channels output("""Of course, having an IRC bot isn't the most useful thing in the world unless you can make that bot join some channels.""") if yn('Do you want your bot to join some channels when he connects?', default=True): defaultChannels = ' '.join(network.channels()) output("""Separate channels with spaces. If the channel is locked with a key, follow the channel name with the key separated by a comma. For example: #supybot-bots #mychannel,mykey #otherchannel"""); while True: channels = something('What channels?', default=defaultChannels) try: network.channels.set(channels) break except registry.InvalidRegistryValue, e: output(""""%s" is an invalid IRC channel. Be sure to prefix the channel with # (or +, or !, or &, but no one uses those channels, really). Be sure the channel key (if you are supplying one) does not contain a comma.""" % e.channel) else: network.channels.setValue([]) ### # Plugins ### def configurePlugin(module, advanced): if hasattr(module, 'configure'): output("""Beginning configuration for %s...""" % module.Class.__name__) module.configure(advanced) print # Blank line :) output("""Done!""") else: conf.registerPlugin(module.__name__, currentValue=True) plugins = getPlugins(pluginDirs + [plugin._pluginsDir]) for s in ('Admin', 'User', 'Channel', 'Misc', 'Config'): m = loadPlugin(s) if m is not None: configurePlugin(m, advanced) else: error('There was an error loading one of the core plugins that ' 'under almost all circumstances are loaded. Go ahead and ' 'fix that error and run this script again.') clearLoadedPlugins(plugins, conf.supybot.plugins) output("""Now we're going to run you through plugin configuration. There's a variety of plugins in supybot by default, but you can create and add your own, of course. We'll allow you to take a look at the known plugins' descriptions and configure them if you like what you see.""") # bulk addedBulk = False if advanced and yn('Would you like to add plugins en masse first?'): addedBulk = True output(format("""The available plugins are: %L.""", plugins)) output("""What plugins would you like to add? If you've changed your mind and would rather not add plugins in bulk like this, just press enter and we'll move on to the individual plugin configuration.""") massPlugins = anything('Separate plugin names by spaces or commas:') for name in re.split(r',?\s+', massPlugins): module = loadPlugin(name) if module is not None: configurePlugin(module, advanced) clearLoadedPlugins(plugins, conf.supybot.plugins) # individual if yn('Would you like to look at plugins individually?'): output("""Next comes your opportunity to learn more about the plugins that are available and select some (or all!) of them to run in your bot. Before you have to make a decision, of course, you'll be able to see a short description of the plugin and, if you choose, an example session with the plugin. Let's begin.""") # until we get example strings again, this will default to false #showUsage =yn('Would you like the option of seeing usage examples?') showUsage = False name = expect('What plugin would you like to look at?', plugins, acceptEmpty=True) while name: module = loadPlugin(name) if module is not None: describePlugin(module, showUsage) if yn('Would you like to load this plugin?', default=True): configurePlugin(module, advanced) clearLoadedPlugins(plugins, conf.supybot.plugins) if not yn('Would you like add another plugin?'): break name = expect('What plugin would you like to look at?', plugins) ### # Sundry ### output("""Although supybot offers a supybot-adduser script, with which you can add users to your bot's user database, it's *very* important that you have an owner user for you bot.""") if yn('Would you like to add an owner user for your bot?', default=True): import supybot.ircdb as ircdb name = something('What should the owner\'s username be?') try: id = ircdb.users.getUserId(name) u = ircdb.users.getUser(id) if u._checkCapability('owner'): output("""That user already exists, and has owner capabilities already. Perhaps you added it before? """) if yn('Do you want to remove its owner capability?', default=False): u.removeCapability('owner') ircdb.users.setUser(id, u) else: output("""That user already exists, but doesn't have owner capabilities.""") if yn('Do you want to add to it owner capabilities?', default=False): u.addCapability('owner') ircdb.users.setUser(id, u) except KeyError: password = getpass('What should the owner\'s password be?') u = ircdb.users.newUser() u.name = name u.setPassword(password) u.addCapability('owner') ircdb.users.setUser(u) output("""Of course, when you're in an IRC channel you can address the bot by its nick and it will respond, if you give it a valid command (it may or may not respond, depending on what your config variable replyWhenNotCommand is set to). But your bot can also respond to a short "prefix character," so instead of saying "bot: do this," you can say, "@do this" and achieve the same effect. Of course, you don't *have* to have a prefix char, but if the bot ends up participating significantly in your channel, it'll ease things.""") if yn('Would you like to set the prefix char(s) for your bot? ', default=True): output("""Enter any characters you want here, but be careful: they should be rare enough that people don't accidentally address the bot (simply because they'll probably be annoyed if they do address the bot on accident). You can even have more than one. I (jemfinch) am quite partial to @, but that's because I've been using it since my ocamlbot days.""") import supybot.callbacks as callbacks c = '' while not c: try: c = anything('What would you like your bot\'s prefix ' 'character(s) to be?') conf.supybot.reply.whenAddressedBy.chars.set(c) except registry.InvalidRegistryValue, e: output(str(e)) c = '' else: conf.supybot.reply.whenAddressedBy.chars.set('') ### # logging variables. ### if advanced: # conf.supybot.log.stdout output("""By default, your bot will log not only to files in the logs directory you gave it, but also to stdout. We find this useful for debugging, and also just for the pretty output (it's colored!)""") stdout = not yn('Would you like to turn off this logging to stdout?', default=False) conf.supybot.log.stdout.setValue(stdout) if conf.supybot.log.stdout(): # conf.something output("""Some terminals may not be able to display the pretty colors logged to stderr. By default, though, we turn the colors off for Windows machines and leave it on for *nix machines.""") if os.name is not 'nt': conf.supybot.log.stdout.colorized.setValue( not yn('Would you like to turn this colorization off?', default=False)) # conf.supybot.log.level output("""Your bot can handle debug messages at several priorities, CRITICAL, ERROR, WARNING, INFO, and DEBUG, in decreasing order of priority. By default, your bot will log all of these priorities except DEBUG. You can, however, specify that it only log messages above a certain priority level.""") priority = str(conf.supybot.log.level) logLevel = something('What would you like the minimum priority to be?' ' Just press enter to accept the default.', default=priority).lower() while logLevel not in ['debug','info','warning','error','critical']: output("""That's not a valid priority. Valid priorities include 'DEBUG', 'INFO', 'WARNING', 'ERROR', and 'CRITICAL'""") logLevel = something('What would you like the minimum priority to ' 'be? Just press enter to accept the default.', default=priority).lower() conf.supybot.log.level.set(logLevel) # conf.supybot.databases.plugins.channelSpecific output("""Many plugins in Supybot are channel-specific. Their databases, likewise, are specific to each channel the bot is in. Many people don't want this, so we have one central location in which to say that you would prefer all databases for all channels to be shared. This variable, supybot.databases.plugins.channelSpecific, is that place.""") conf.supybot.databases.plugins.channelSpecific.setValue( not yn('Would you like plugin databases to be shared by all ' 'channels, rather than specific to each channel the ' 'bot is in?')) output("""There are a lot of options we didn't ask you about simply because we'd rather you get up and running and have time left to play around with your bot. But come back and see us! When you've played around with your bot enough to know what you like, what you don't like, what you'd like to change, then take a look at your configuration file when your bot isn't running and read the comments, tweaking values to your heart's desire.""") # Let's make sure that src/ plugins are loaded. conf.registerPlugin('Admin', True) conf.registerPlugin('Channel', True) conf.registerPlugin('Config', True) conf.registerPlugin('Misc', True) conf.registerPlugin('User', True) ### # Write the registry ### # We're going to need to do a darcs predist thing here. #conf.supybot.debug.generated.setValue('...') if not filename: filename = '%s.conf' % nick registry.close(conf.supybot, filename) # Done! output("""All done! Your new bot configuration is %s. If you're running a *nix based OS, you can probably start your bot with the command line "supybot %s". If you're not running a *nix or similar machine, you'll just have to start it like you start all your other Python scripts.""" % \ (filename, filename)) if __name__ == '__main__': try: main() except KeyboardInterrupt: # We may still be using bold text when exiting during a prompt if questions.useBold: import supybot.ansi as ansi print ansi.RESET print print output("""Well, it looks like you canceled out of the wizard before it was done. Unfortunately, I didn't get to write anything to file. Please run the wizard again to completion.""") # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/scripts/supybot-plugin-create0000644000000000000000000002371111206611405017560 0ustar #!/usr/bin/env python ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 import os import sys import time import os.path import optparse def error(s): sys.stderr.write(textwrap.fill(s)) sys.stderr.write(os.linesep) sys.exit(-1) if sys.version_info < (2, 3, 0): error('This script requires Python 2.3 or newer.') import supybot.conf as conf from supybot.questions import * copyright = ''' ### # Copyright (c) %s, %%s # All rights reserved. # %%s ### ''' % time.strftime('%Y') # Here we use strip() instead of lstrip() on purpose. copyright = copyright.strip() license = ''' # 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. ''' license = license.lstrip() pluginTemplate = ''' %s import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class %s(callbacks.Plugin): """Add the help for "@plugin help %s" here This should describe *how* to use this plugin.""" %s Class = %s # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: '''.lstrip() # This removes the newlines that precede and follow the text. configTemplate = ''' %s 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(%r, True) %s = conf.registerPlugin(%r) # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(%s, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: '''.lstrip() __init__Template = ''' %s """ Add a description of the plugin (to be presented to the user inside the wizard) here. This should describe *what* the plugin does. """ 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.unknown # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} # This is a url where the most recent plugin package can be downloaded. __url__ = '' # 'http://supybot.com/Members/yourname/%s/download' 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=4 expandtab textwidth=79: '''.lstrip() testTemplate = ''' %s from supybot.test import * class %sTestCase(PluginTestCase): plugins = (%r,) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: '''.lstrip() readmeTemplate = ''' Insert a description of your plugin here, with any notes, etc. about using it. '''.lstrip() def main(): global copyright global license parser = optparse.OptionParser(usage='Usage: %prog [options]', version='Supybot %s' % conf.version) parser.add_option('-n', '--name', action='store', dest='name', help='sets the name for the plugin.') parser.add_option('-t', '--thread', action='store_true', dest='threaded', help='makes the plugin threaded.') parser.add_option('', '--real-name', action='store', dest='realName', help='Determines what real name the copyright is ' 'assigned to.') (options, args) = parser.parse_args() if options.name: name = options.name if options.threaded: threaded = True else: threaded = False if options.realName: realName = options.realName else: name = something('What should the name of the plugin be?') if name.endswith('.py'): name = name[:-3] while name[0].islower(): print 'Plugin names must begin with a capital letter.' name = something('What should the name of the plugin be?') if name.endswith('.py'): name = name[:-3] if os.path.exists(name): error('A file or directory named %s already exists; remove or ' 'rename it and run this program again.' % name) print textwrap.fill(textwrap.dedent(""" Sometimes you'll want a callback to be threaded. If its methods (command or regexp-based, either one) will take a significant amount of time to run, you'll want to thread them so they don't block the entire bot.""").strip()) print threaded = yn('Does your plugin need to be threaded?') realName = something(textwrap.dedent(""" What is your real name, so I can fill in the copyright and license appropriately? """).strip()) if not yn('Do you wish to use Supybot\'s license for your plugin?'): license = '#' if threaded: threaded = 'threaded = True' else: threaded = 'pass' if name.endswith('.py'): name = name[:-3] while name[0].islower(): print 'Plugin names must begin with a capital.' name = something('What should the name of the plugin be?') if name.endswith('.py'): name = name[:-3] copyright %= (realName, license) pathname = name # Make the directory. os.mkdir(pathname) def writeFile(filename, s): fd = file(os.path.join(pathname, filename), 'w') try: fd.write(s) finally: fd.close() writeFile('plugin.py', pluginTemplate % (copyright, name, name, threaded, name)) writeFile('config.py', configTemplate % (copyright, name, name, name, name)) writeFile('__init__.py', __init__Template % (copyright, name)) writeFile('test.py', testTemplate % (copyright, name, name)) writeFile('README.txt', readmeTemplate) pathname = os.path.join(pathname, 'local') os.mkdir(pathname) writeFile('__init__.py', '# Stub so local is a module, used for third-party modules\n') print 'Your new plugin template is in the %s directory.' % name if __name__ == '__main__': try: main() except KeyboardInterrupt: print output("""It looks like you cancelled out of this script before it was finished. Obviously, nothing was written, but just run this script again whenever you want to generate a template for a plugin.""") # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/scripts/supybot-botchk0000644000000000000000000001252711206611405016276 0ustar #!/usr/bin/env python ### # 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. ### VERBOSE = False def readPid(filename): fd = file(filename) try: return int(fd.read().strip()) finally: fd.close() def isAlive(pid): try: os.kill(pid, 0) return True except OSError: return False def debug(s): if VERBOSE: if not s.endswith(os.linesep): s += os.linesep sys.stdout.write(s) if __name__ == '__main__': # XXX I wanted this for conf.version, but this will create directories. We # really need to refactor conf so it either doesn't create directories, or # so that static information (like the version) can be imported from # somewhere else. # import supybot.conf as conf import os import sys import popen2 import optparse parser = optparse.OptionParser(usage='Usage: %prog [options]') parser.add_option('', '--verbose', action='store_true', help='Makes output verbose.') parser.add_option('', '--botdir', help='Determines what directory the bot resides in and ' 'should be started from.') parser.add_option('', '--pidfile', help='Determines what file to look in for the pid of ' 'the running bot. This should be relative to the ' 'given bot directory.') parser.add_option('', '--supybot', default='supybot', help='Determines where the supybot executable is ' 'located. If not given, assumes that supybot is ' 'in $PATH.') parser.add_option('', '--conffile', help='Determines what configuration file should be ' 'given to the supybot executable when (re)starting the ' 'bot.') (options, args) = parser.parse_args() VERBOSE = options.verbose if args: parser.error('Extra arguments given.') if not options.botdir: parser.error('No botdir given.') if not options.pidfile: parser.error('No pidfile given.') if not options.conffile: parser.error('No conffile given.') os.chdir(options.botdir) os.system('touch %s' % options.pidfile) pid = None try: pid = readPid(options.pidfile) debug('Found pidFile with proper pid contents of %s' % pid) except ValueError, e: foundBot = False if pid is not None: foundBot = isAlive(pid) if foundBot: debug('Pid %s is alive and belongs to us.' % pid) else: debug('Pid %s is not the bot.' % pid) if not foundBot: # First, we check if the pidfile is writable. If not, supybot will just exit, # so we go ahead and refuse to start it. try: file(options.pidfile, 'r+') except EnvironmentError, e: debug('pidfile (%s) is not writable: %s' % (options.pidfile, e)) sys.exit(-1) debug('Bot not found, starting.') home = os.environ['HOME'] inst = popen2.Popen4('sh') for filename in ('.login', '.bash_profile', '.profile', '.bashrc'): filename = os.path.join(home, filename) if os.path.exists(filename): debug('Found %s, sourcing.' % filename) inst.tochild.write('source %s' % filename + os.linesep) cmdline = "%s --daemon %s" % (options.supybot, options.conffile) debug('Sending cmdline to sh process.') inst.tochild.write(cmdline + os.linesep) inst.tochild.close() debug('Received from sh process: %r' % inst.fromchild.read()) ret = inst.wait() debug('Bot started, command line %r returned %s.' % (cmdline, ret)) sys.exit(ret) else: sys.exit(0) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/scripts/supybot0000644000000000000000000003340411206611405015023 0ustar #!/usr/bin/env python ### # Copyright (c) 2003-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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. ### """ This is the main program to run Supybot. """ import supybot import re import os import sys import atexit import shutil import signal import cStringIO as StringIO if sys.version_info < (2, 3, 0): sys.stderr.write('This program requires Python >= 2.3.0\n') sys.exit(-1) def _termHandler(signalNumber, stackFrame): raise SystemExit, 'Signal #%s.' % signalNumber signal.signal(signal.SIGTERM, _termHandler) import time import optparse import textwrap started = time.time() import supybot import supybot.utils as utils import supybot.registry as registry import supybot.questions as questions def main(): import supybot.conf as conf import supybot.world as world import supybot.drivers as drivers import supybot.schedule as schedule # We schedule this event rather than have it actually run because if there # is a failure between now and the time it takes the Owner plugin to load # all the various plugins, our registry file might be wiped. That's bad. interrupted = False when = conf.supybot.upkeepInterval() schedule.addPeriodicEvent(world.upkeep, when, name='upkeep', now=False) world.startedAt = started while world.ircs: try: drivers.run() except KeyboardInterrupt: if interrupted: # Interrupted while waiting for queues to clear. Let's clear # them ourselves. for irc in world.ircs: irc._reallyDie() continue else: interrupted = True log.info('Exiting due to Ctrl-C. ' 'If the bot doesn\'t exit within a few seconds, ' 'feel free to press Ctrl-C again to make it exit ' 'without flushing its message queues.') world.upkeep() for irc in world.ircs: quitmsg = conf.supybot.plugins.Owner.quitMsg() or \ 'Ctrl-C at console.' irc.queueMsg(ircmsgs.quit(quitmsg)) irc.die() except SystemExit, e: s = str(e) if s: log.info('Exiting due to %s', s) break except: try: # Ok, now we're *REALLY* paranoid! log.exception('Exception raised out of drivers.run:') except Exception, e: print 'Exception raised in log.exception. This is *really*' print 'bad. Hopefully it won\'t happen again, but tell us' print 'about it anyway, this is a significant problem.' print 'Anyway, here\'s the exception: %s' % \ utils.gen.exnToString(e) except: print 'Man, this really sucks. Not only did log.exception' print 'raise an exception, but freaking-a, it was a string' print 'exception. People who raise string exceptions should' print 'die a slow, painful death.' now = time.time() seconds = now - world.startedAt log.info('Total uptime: %s.', utils.gen.timeElapsed(seconds)) (user, system, _, _, _) = os.times() log.info('Total CPU time taken: %s seconds.', user+system) log.info('No more Irc objects, exiting.') version = '0.83.4.1' if __name__ == '__main__': ### # Options: # -p (profiling) # -n, --nick (nick) # --startup (commands to run onStart) # --connect (commands to run afterConnect) # --config (configuration values) parser = optparse.OptionParser(usage='Usage: %prog [options] configFile', version='Supybot %s' % version) parser.add_option('-P', '--profile', action='store_true', dest='profile', help='enables profiling') parser.add_option('-n', '--nick', action='store', dest='nick', default='', help='nick the bot should use') parser.add_option('-u', '--user', action='store', dest='user', default='', help='full username the bot should use') parser.add_option('-i', '--ident', action='store', dest='ident', default='', help='ident the bot should use') parser.add_option('-d', '--daemon', action='store_true', dest='daemon', help='Determines whether the bot will daemonize. ' 'This is a no-op on non-POSIX systems.') parser.add_option('', '--allow-default-owner', action='store_true', dest='allowDefaultOwner', help='Determines whether the bot will allow its ' 'defaultCapabilities not to include "-owner", thus ' 'giving all users the owner capability by default. ' ' This is dumb, hence we require a command-line ' 'option. Don\'t do this.') parser.add_option('', '--allow-root', action='store_true', dest='allowRoot', help='Determines whether the bot will be allowed to run ' 'as root. You don\'t want this. Don\'t do it. ' 'Even if you think you want it, you don\'t. ' 'You\'re probably dumb if you do this.') parser.add_option('', '--debug', action='store_true', dest='debug', help='Determines whether some extra debugging stuff ' 'will be logged in this script.') (options, args) = parser.parse_args() if os.name == 'posix': if (os.getuid() == 0 or os.geteuid() == 0) and not options.allowRoot: sys.stderr.write('Dude, don\'t even try to run this as root.\n') sys.exit(-1) if len(args) > 1: parser.error("""Only one configuration option should be specified.""") elif not args: parser.error(utils.str.normalizeWhitespace("""It seems you've given me no configuration file. If you have a configuration file, be sure to tell its filename. If you don't have a configuration file, read docs/GETTING_STARTED and follow its directions.""")) else: registryFilename = args.pop() try: # The registry *MUST* be opened before importing log or conf. registry.open(registryFilename) shutil.copy(registryFilename, registryFilename + '.bak') except registry.InvalidRegistryFile, e: s = '%s in %s. Please fix this error and start supybot again.' % \ (e, registryFilename) s = textwrap.fill(s) sys.stderr.write(s) sys.stderr.write(os.linesep) raise sys.exit(-1) except EnvironmentError, e: sys.stderr.write(str(e)) sys.stderr.write(os.linesep) sys.exit(-1) try: import supybot.log as log except supybot.registry.InvalidRegistryValue, e: # This is raised here because supybot.log imports supybot.conf. name = e.value._name errmsg = textwrap.fill('%s: %s' % (name, e), width=78, subsequent_indent=' '*len(name)) sys.stderr.write(errmsg) sys.stderr.write('\n') sys.stderr.write('Please fix this error in your configuration file ' 'and restart your bot.\n') sys.exit(-1) import supybot.conf as conf import supybot.world as world world.starting = True def closeRegistry(): # We only print if world.dying so we don't see these messages during # upkeep. logger = log.debug if world.dying: logger = log.info logger('Writing registry file to %s', registryFilename) registry.close(conf.supybot, registryFilename) logger('Finished writing registry file.') world.flushers.append(closeRegistry) world.registryFilename = registryFilename nick = options.nick or conf.supybot.nick() user = options.user or conf.supybot.user() ident = options.ident or conf.supybot.ident() networks = conf.supybot.networks() if not networks: questions.output("""No networks defined. Perhaps you should re-run the wizard?""", fd=sys.stderr) # XXX We should turn off logging here for a prettier presentation. sys.exit(-1) if os.name == 'posix' and options.daemon: def fork(): child = os.fork() if child != 0: if options.debug: print 'Parent exiting, child PID: %s' % child # We must us os._exit instead of sys.exit so atexit handlers # don't run. They shouldn't be dangerous, but they're ugly. os._exit(0) fork() os.setsid() # What the heck does this do? I wonder if it breaks anything... # ...It did. I don't know why, but it seems largely useless. It seems # to me reasonable that we should respect the user's umask. #os.umask(0) # Let's not do this for now (at least until I can make sure it works): # Actually, let's never do this -- we'll always have files open in the # bot directories, so they won't be able to be unmounted anyway. # os.chdir('/') fork() # Since this is the indicator that no writing should be done to stdout, # we'll set it to True before closing stdout et alii. conf.daemonized = True # Closing stdin shouldn't cause problems. We'll let it raise an # exception if it does. sys.stdin.close() # Closing these two might cause problems; we log writes to them as # level WARNING on upkeep. sys.stdin.close() sys.stdout.close() sys.stderr.close() sys.stdout = StringIO.StringIO() sys.stderr = StringIO.StringIO() # We have to be really methodical here. os.close(0) os.close(1) os.close(2) fd = os.open('/dev/null', os.O_RDWR) os.dup2(fd, 0) os.dup2(fd, 1) os.dup2(fd, 2) signal.signal(signal.SIGHUP, signal.SIG_IGN) log.info('Completed daemonization. Current PID: %s', os.getpid()) # Stop setting our own umask. See comment above. #os.umask(077) # Let's write the PID file. This has to go after daemonization, obviously. pidFile = conf.supybot.pidFile() if pidFile: try: fd = file(pidFile, 'w') pid = os.getpid() fd.write('%s\n' % pid) fd.close() def removePidFile(): try: os.remove(pidFile) except EnvironmentError, e: log.error('Could not remove pid file: %s', e) atexit.register(removePidFile) except EnvironmentError, e: log.fatal('Error opening/writing pid file %s: %s', pidFile, e) sys.exit(-1) conf.allowDefaultOwner = options.allowDefaultOwner if not os.path.exists(conf.supybot.directories.log()): os.mkdir(conf.supybot.directories.log()) if not os.path.exists(conf.supybot.directories.conf()): os.mkdir(conf.supybot.directories.conf()) if not os.path.exists(conf.supybot.directories.data()): os.mkdir(conf.supybot.directories.data()) if not os.path.exists(conf.supybot.directories.data.tmp()): os.mkdir(conf.supybot.directories.tmp()) userdataFilename = os.path.join(conf.supybot.directories.conf(), 'userdata.conf') # Let's open this now since we've got our directories setup. if not os.path.exists(userdataFilename): fd = file(userdataFilename, 'w') fd.write('\n') fd.close() registry.open(userdataFilename) import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.drivers as drivers import supybot.callbacks as callbacks import supybot.plugins.Owner as Owner owner = Owner.Class() if options.profile: import profile world.profiling = True profile.run('main()', '%s-%i.prof' % (nick, time.time())) else: main() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/scripts/supybot-test0000644000000000000000000001666611206611405016013 0ustar #!/usr/bin/env python ### # 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 os import time import shutil started = time.time() import supybot import logging # We need to do this before we import conf. if not os.path.exists('test-conf'): os.mkdir('test-conf') registryFilename = os.path.join('test-conf', 'test.conf') fd = file(registryFilename, 'w') fd.write(""" supybot.directories.data: test-data supybot.directories.conf: test-conf supybot.directories.log: test-logs supybot.reply.whenNotCommand: True supybot.log.stdout: False supybot.log.level: DEBUG supybot.log.format: %(levelname)s %(message)s supybot.log.plugins.individualLogfiles: False supybot.protocols.irc.throttleTime: 0 supybot.reply.whenAddressedBy.chars: @ supybot.networks.test.server: should.not.need.this supybot.nick: test supybot.databases.users.allowUnregistration: True """) fd.close() import supybot.registry as registry registry.open(registryFilename) import supybot.log as log import supybot.conf as conf conf.allowEval = True conf.supybot.flush.setValue(False) import re import sys import glob import atexit import os.path import unittest import supybot.utils as utils import supybot.world as world import supybot.callbacks as callbacks world.startedAt = started import logging class TestLogFilter(logging.Filter): bads = [ 'No callbacks in', 'Invalid channel database', 'Exact error', 'Invalid user dictionary', 'because of noFlush', 'Queuing NICK', 'Queuing USER', 'IgnoresDB.reload failed', 'Starting log for', 'Irc object for test dying', 'Last Irc,', ] def filter(self, record): for bad in self.bads: if bad in record.msg: return False return True log._logger.addFilter(TestLogFilter()) class path(str): """A class to represent platform-independent paths.""" _r = re.compile(r'[\\/]') def __hash__(self): return reduce(lambda h, s: h ^ hash(s), self._r.split(self), 0) def __eq__(self, other): return self._r.split(self) == self._r.split(other) if __name__ == '__main__': import glob import os.path import optparse import supybot.test as test import supybot.plugin as plugin parser = optparse.OptionParser(usage='Usage: %prog [options] [plugins]', version='Supybot %s' % conf.version) parser.add_option('-c', '--clean', action='store_true', default=False, dest='clean', help='Cleans the various data/conf/logs' 'directories before running tests.') parser.add_option('-t', '--timeout', action='store', type='int', dest='timeout', help='Sets the timeout for tests to return responses.') parser.add_option('-v', '--verbose', action='store_true', default=False, help='Sets the verbose flag, logging extra information ' 'about each test that runs.') parser.add_option('', '--no-network', action='store_true', default=False, dest='nonetwork', help='Causes the network-based tests ' 'not to run.') parser.add_option('', '--trace', action='store_true', default=False, help='Traces all calls made. Unless you\'re really in ' 'a pinch, you probably shouldn\'t do this; it results ' 'in copious amounts of output.') parser.add_option('', '--plugins-dir', action='append', dest='pluginsDirs', default=[], help='Looks in in the given directory for plugins and ' 'loads the tests from all of them.') (options, args) = parser.parse_args() # This must go before checking for args, of course. for pluginDir in options.pluginsDirs: for name in glob.glob(os.path.join(pluginDir, '*')): #print '***', name if os.path.isdir(name): args.append(name) if not args: parser.print_help() sys.exit(-1) if options.timeout: test.timeout = options.timeout if options.trace: traceFilename = conf.supybot.directories.log.dirize('trace.log') fd = file(traceFilename, 'w') sys.settrace(utils.gen.callTracer(fd)) atexit.register(fd.close) atexit.register(lambda : sys.settrace(None)) if options.verbose: world.myVerbose = True else: world.myVerbose = False if options.nonetwork: test.network = False log.testing = True world.testing = True args = [s.rstrip('\\/') for s in args] pluginDirs = set([os.path.dirname(s) or '.' for s in args]) conf.supybot.directories.plugins.setValue(list(pluginDirs)) pluginNames = set([os.path.basename(s) for s in args]) load = unittest.defaultTestLoader.loadTestsFromModule for pluginName in pluginNames: if pluginName.endswith('.py'): pluginName = pluginName[:-3] try: pluginModule = plugin.loadPluginModule(pluginName) except (ImportError, callbacks.Error), e: sys.stderr.write('Failed to load plugin %s: %s\n' % (pluginName,e)) sys.stderr.write('(pluginDirs: %s)\n' % conf.supybot.directories.plugins()) continue if hasattr(pluginModule, 'test'): test.suites.append(load(pluginModule.test)) suite = unittest.TestSuite(test.suites) runner = unittest.TextTestRunner(verbosity=2) print 'Testing began at %s (pid %s)' % (time.ctime(), os.getpid()) if options.clean: shutil.rmtree(conf.supybot.directories.log()) shutil.rmtree(conf.supybot.directories.conf()) shutil.rmtree(conf.supybot.directories.data()) runner.run(suite) if hasattr(unittest, 'asserts'): print 'Total asserts: %s' % unittest.asserts # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/scripts/supybot-adduser0000644000000000000000000001214011206611405016442 0ustar #!/usr/bin/env python ### # 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 supybot from supybot.questions import * import os import sys import optparse def main(): import supybot.log as log import supybot.conf as conf conf.supybot.log.stdout.setValue(False) parser = optparse.OptionParser(usage='Usage: %prog [options] ', version='supybot %s' % conf.version) parser.add_option('-u', '--username', action='store', default='', dest='name', help='username for the user.') parser.add_option('-p', '--password', action='store', default='', dest='password', help='password for the user.') parser.add_option('-c', '--capability', action='append', dest='capabilities', metavar='CAPABILITY', help='capability the user should have; ' 'this option may be given multiple times.') (options, args) = parser.parse_args() if len(args) is not 1: parser.error('Specify the users.conf file you\'d like to use. ' 'Be sure *not* to specify your registry file, generated ' 'by supybot-wizard. This is not the file you want. ' 'Instead, take a look in your conf directory (usually ' 'named "conf") and take a gander at the file ' '"users.conf". That\'s the one you want.') filename = os.path.abspath(args[0]) conf.supybot.directories.log.setValue('/') conf.supybot.directories.conf.setValue('/') conf.supybot.directories.data.setValue('/') conf.supybot.directories.plugins.setValue(['/']) conf.supybot.databases.users.filename.setValue(filename) import supybot.ircdb as ircdb if not options.name: name = '' while not name: name = something('What is the user\'s name?') try: # Check to see if the user is already in the database. _ = ircdb.users.getUser(name) # Uh oh. That user already exists; # otherwise we'd have KeyError'ed. output('That user already exists. Try another name.') name = '' except KeyError: # Good. No such user exists. We'll pass. pass else: try: # Same as above. We exit here instead. _ = ircdb.users.getUser(options.name) output('That user already exists. Try another name.') sys.exit(-1) except KeyError: name = options.name if not options.password: password = getpass('What is %s\'s password? ' % name) else: password = options.password if not options.capabilities: capabilities = [] prompt = 'Would you like to give %s a capability?' % name while yn(prompt): capabilities.append(anything('What capability?')) prompt = 'Would you like to give %s another capability?' % name else: capabilities = options.capabilities user = ircdb.users.newUser() user.name = name user.setPassword(password) for capability in capabilities: user.addCapability(capability) ircdb.users.setUser(user) ircdb.users.flush() #os.system('cat %s' % filename) # Was this here just for debugging? ircdb.users.close() print 'User %s added.' % name if __name__ == '__main__': try: main() except KeyboardInterrupt: pass # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/0000755000000000000000000000000011206611405012467 5ustar supybot-0.83.4.1.ds.orig/src/drivers/0000755000000000000000000000000011206611405014145 5ustar supybot-0.83.4.1.ds.orig/src/drivers/__init__.py0000644000000000000000000001751211206611405016264 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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. ### """ Contains various drivers (network, file, and otherwise) for using IRC objects. """ import sys import time import socket import supybot.conf as conf import supybot.utils as utils import supybot.log as supylog import supybot.ircmsgs as ircmsgs _drivers = {} _deadDrivers = [] _newDrivers = [] class IrcDriver(object): """Base class for drivers.""" def __init__(self, *args, **kwargs): add(self.name(), self) super(IrcDriver, self).__init__(*args, **kwargs) def run(self): raise NotImplementedError def die(self): # The end of any overrided die method should be # "super(Class, self).die()", in order to make # sure this (and anything else later added) is done. remove(self.name()) def reconnect(self, wait=False): raise NotImplementedError def name(self): return repr(self) class ServersMixin(object): def __init__(self, irc, servers=()): self.networkGroup = conf.supybot.networks.get(irc.network) self.servers = servers super(ServersMixin, self).__init__() def _getServers(self): # We do this, rather than utils.iter.cycle the servers in __init__, # because otherwise registry updates given as setValues or sets # wouldn't be visible until a restart. return self.networkGroup.servers()[:] # Be sure to copy! def _getNextServer(self): if not self.servers: self.servers = self._getServers() assert self.servers, 'Servers value for %s is empty.' % \ self.networkGroup._name server = self.servers.pop(0) self.currentServer = '%s:%s' % server return server def empty(): """Returns whether or not the driver loop is empty.""" return (len(_drivers) + len(_newDrivers)) == 0 def add(name, driver): """Adds a given driver the loop with the given name.""" _newDrivers.append((name, driver)) def remove(name): """Removes the driver with the given name from the loop.""" _deadDrivers.append(name) def run(): """Runs the whole driver loop.""" for (name, driver) in _drivers.iteritems(): try: if name not in _deadDrivers: driver.run() except: log.exception('Uncaught exception in in drivers.run:') _deadDrivers.append(name) for name in _deadDrivers: try: driver = _drivers[name] if hasattr(driver, 'irc') and driver.irc is not None: # The Schedule driver has no irc object, or it's None. driver.irc.driver = None driver.irc = None log.info('Removing driver %s.', name) del _drivers[name] except KeyError: pass while _newDrivers: (name, driver) = _newDrivers.pop() log.debug('Adding new driver %s.', name) if name in _drivers: log.warning('Driver %s already added, killing it.', name) _drivers[name].die() del _drivers[name] _drivers[name] = driver class Log(object): """This is used to have a nice, consistent interface for drivers to use.""" def connect(self, server): self.info('Connecting to %s.', server) def connectError(self, server, e): if isinstance(e, Exception): if isinstance(e, socket.gaierror): e = e.args[1] else: e = utils.exnToString(e) self.warning('Error connecting to %s: %s', server, e) def disconnect(self, server, e=None): if e: if isinstance(e, Exception): e = utils.exnToString(e) else: e = str(e) if not e.endswith('.'): e += '.' self.warning('Disconnect from %s: %s', server, e) else: self.info('Disconnect from %s.', server) def reconnect(self, network, when=None): s = 'Reconnecting to %s' % network if when is not None: if not isinstance(when, basestring): when = self.timestamp(when) s += ' at %s.' % when else: s += '.' self.info(s) def die(self, irc): self.info('Driver for %s dying.', irc) debug = staticmethod(supylog.debug) info = staticmethod(supylog.info) warning = staticmethod(supylog.warning) error = staticmethod(supylog.warning) critical = staticmethod(supylog.critical) timestamp = staticmethod(supylog.timestamp) exception = staticmethod(supylog.exception) log = Log() def newDriver(irc, moduleName=None): """Returns a new driver for the given server using the irc given and using conf.supybot.driverModule to determine what driver to pick.""" # XXX Eventually this should be made to load the drivers from a # configurable directory in addition to the installed one. if moduleName is None: moduleName = conf.supybot.drivers.module() if moduleName == 'default': # XXX Twisted has been causing problems lately, so we're going to use # the Socket driver by default, now. Leaving behind the code for using # Twisted by default in case someone steps up and fixes the Twisted # driver. ''' try: import supybot.drivers.Twisted moduleName = 'supybot.drivers.Twisted' except ImportError: # We formerly used 'del' here, but 2.4 fixes the bug that we added # the 'del' for, so we need to make sure we don't complain if the # module is cleaned up already. sys.modules.pop('supybot.drivers.Twisted', None) moduleName = 'supybot.drivers.Socket' ''' moduleName = 'supybot.drivers.Socket' elif not moduleName.startswith('supybot.drivers.'): moduleName = 'supybot.drivers.' + moduleName driverModule = __import__(moduleName, {}, {}, ['not empty']) log.debug('Creating new driver (%s) for %s.', moduleName, irc) driver = driverModule.Driver(irc) irc.driver = driver return driver def parseMsg(s): start = time.time() s = s.strip() if s: msg = ircmsgs.IrcMsg(s) msg.tag('receivedAt', start) return msg else: return None # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/drivers/Socket.py0000644000000000000000000002031011206611405015743 0ustar ### # 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. ### """ Contains simple socket drivers. Asyncore bugged (haha, pun!) me. """ from __future__ import division import time import select import socket import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.drivers as drivers import supybot.schedule as schedule from supybot.utils.iter import imap class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): def __init__(self, irc): self.irc = irc drivers.IrcDriver.__init__(self, irc) drivers.ServersMixin.__init__(self, irc) self.conn = None self.servers = () self.eagains = 0 self.inbuffer = '' self.outbuffer = '' self.zombie = False self.scheduled = None self.connected = False self.resetDelay() # Only connect to non-SSL servers if self.networkGroup.get('ssl').value: drivers.log.error('The Socket driver can not connect to SSL ' 'servers. Try the Twisted driver instead.') else: self.connect() def getDelay(self): ret = self.currentDelay self.currentDelay = min(self.currentDelay * 2, conf.supybot.drivers.maxReconnectWait()) return ret def resetDelay(self): self.currentDelay = 10.0 def _getNextServer(self): oldServer = getattr(self, 'currentServer', None) server = drivers.ServersMixin._getNextServer(self) if self.currentServer != oldServer: self.resetDelay() return server def _handleSocketError(self, e): # (11, 'Resource temporarily unavailable') raised if connect # hasn't finished yet. We'll keep track of how many we get. if e.args[0] != 11 or self.eagains > 120: drivers.log.disconnect(self.currentServer, e) self.scheduleReconnect() else: log.debug('Got EAGAIN, current count: %s.', self.eagains) self.eagains += 1 def _sendIfMsgs(self): if not self.zombie: msgs = [self.irc.takeMsg()] while msgs[-1] is not None: msgs.append(self.irc.takeMsg()) del msgs[-1] self.outbuffer += ''.join(imap(str, msgs)) if self.outbuffer: try: sent = self.conn.send(self.outbuffer) self.outbuffer = self.outbuffer[sent:] self.eagains = 0 except socket.error, e: self._handleSocketError(e) if self.zombie and not self.outbuffer: self._reallyDie() def run(self): if not self.connected: # We sleep here because otherwise, if we're the only driver, we'll # spin at 100% CPU while we're disconnected. time.sleep(conf.supybot.drivers.poll()) return self._sendIfMsgs() try: self.inbuffer += self.conn.recv(1024) self.eagains = 0 # If we successfully recv'ed, we can reset this. lines = self.inbuffer.split('\n') self.inbuffer = lines.pop() for line in lines: msg = drivers.parseMsg(line) if msg is not None: self.irc.feedMsg(msg) except socket.timeout: pass except socket.error, e: self._handleSocketError(e) return if not self.irc.zombie: self._sendIfMsgs() def connect(self, **kwargs): self.reconnect(reset=False, **kwargs) def reconnect(self, reset=True): self.scheduled = None if self.connected: drivers.log.reconnect(self.irc.network) self.conn.close() self.connected = False if reset: drivers.log.debug('Resetting %s.', self.irc) self.irc.reset() else: drivers.log.debug('Not resetting %s.', self.irc) server = self._getNextServer() drivers.log.connect(self.currentServer) try: self.conn = utils.net.getSocket(server[0]) vhost = conf.supybot.protocols.irc.vhost() self.conn.bind((vhost, 0)) except socket.error, e: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return # We allow more time for the connect here, since it might take longer. # At least 10 seconds. self.conn.settimeout(max(10, conf.supybot.drivers.poll()*10)) try: self.conn.connect(server) self.conn.settimeout(conf.supybot.drivers.poll()) self.connected = True self.resetDelay() except socket.error, e: if e.args[0] == 115: now = time.time() when = now + 60 whenS = log.timestamp(when) drivers.log.debug('Connection in progress, scheduling ' 'connectedness check for %s', whenS) schedule.addEvent(self._checkAndWriteOrReconnect, when) else: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return def _checkAndWriteOrReconnect(self): drivers.log.debug('Checking whether we are connected.') (_, w, _) = select.select([], [self.conn], [], 0) if w: drivers.log.debug('Socket is writable, it might be connected.') self.connected = True self.resetDelay() else: drivers.log.connectError(self.currentServer, 'Timed out') self.reconnect() def scheduleReconnect(self): when = time.time() + self.getDelay() if not world.dying: drivers.log.reconnect(self.irc.network, when) if self.scheduled: drivers.log.error('Scheduling a second reconnect when one is ' 'already scheduled. This is a bug; please ' 'report it, with an explanation of what caused ' 'this to happen.') schedule.removeEvent(self.scheduled) self.scheduled = schedule.addEvent(self.reconnect, when) def die(self): self.zombie = True if self.scheduled: schedule.removeEvent(self.scheduled) drivers.log.die(self.irc) def _reallyDie(self): if self.conn is not None: self.conn.close() drivers.IrcDriver.die(self) # self.irc.die() Kill off the ircs yourself, jerk! def name(self): return '%s(%s)' % (self.__class__.__name__, self.irc) Driver = SocketDriver # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/drivers/Twisted.py0000644000000000000000000001370311206611405016146 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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.log as log import supybot.conf as conf import supybot.drivers as drivers import supybot.ircmsgs as ircmsgs from twisted.names import client from twisted.internet import reactor, error from twisted.protocols.basic import LineReceiver from twisted.internet.protocol import ReconnectingClientFactory # This hack prevents the standard Twisted resolver from starting any # threads, which allows for a clean shut-down in Twisted>=2.0 reactor.installResolver(client.createResolver()) try: from OpenSSL import SSL from twisted.internet import ssl except ImportError: drivers.log.debug('PyOpenSSL is not available, ' 'cannot connect to SSL servers.') SSL = None class TwistedRunnerDriver(drivers.IrcDriver): def name(self): return self.__class__.__name__ def run(self): try: reactor.iterate(conf.supybot.drivers.poll()) except: drivers.log.exception('Uncaught exception outside reactor:') class SupyIrcProtocol(LineReceiver): delimiter = '\n' MAX_LENGTH = 1024 def __init__(self): self.mostRecentCall = reactor.callLater(1, self.checkIrcForMsgs) def lineReceived(self, line): msg = drivers.parseMsg(line) if msg is not None: self.irc.feedMsg(msg) def checkIrcForMsgs(self): if self.connected: msg = self.irc.takeMsg() if msg: self.transport.write(str(msg)) self.mostRecentCall = reactor.callLater(1, self.checkIrcForMsgs) def connectionLost(self, r): self.mostRecentCall.cancel() if r.check(error.ConnectionDone): drivers.log.disconnect(self.factory.currentServer) else: drivers.log.disconnect(self.factory.currentServer, errorMsg(r)) if self.irc.zombie: self.factory.stopTrying() while self.irc.takeMsg(): continue else: self.irc.reset() def connectionMade(self): self.factory.resetDelay() self.irc.driver = self def die(self): drivers.log.die(self.irc) self.factory.stopTrying() self.transport.loseConnection() def reconnect(self, wait=None): # We ignore wait here, because we handled our own waiting. drivers.log.reconnect(self.irc.network) self.transport.loseConnection() def errorMsg(reason): return reason.getErrorMessage() class SupyReconnectingFactory(ReconnectingClientFactory, drivers.ServersMixin): maxDelay = property(lambda self: conf.supybot.drivers.maxReconnectWait()) protocol = SupyIrcProtocol def __init__(self, irc): self.irc = irc drivers.ServersMixin.__init__(self, irc) (server, port) = self._getNextServer() vhost = conf.supybot.protocols.irc.vhost() if self.networkGroup.get('ssl').value: self.connectSSL(server, port, vhost) else: self.connectTCP(server, port, vhost) def connectTCP(self, server, port, vhost): """Connect to the server with a standard TCP connection.""" reactor.connectTCP(server, port, self, bindAddress=(vhost, 0)) def connectSSL(self, server, port, vhost): """Connect to the server using an SSL socket.""" drivers.log.info('Attempting an SSL connection.') if SSL: reactor.connectSSL(server, port, self, ssl.ClientContextFactory(), bindAddress=(vhost, 0)) else: drivers.log.error('PyOpenSSL is not available. Not connecting.') def clientConnectionFailed(self, connector, r): drivers.log.connectError(self.currentServer, errorMsg(r)) (connector.host, connector.port) = self._getNextServer() ReconnectingClientFactory.clientConnectionFailed(self, connector,r) def clientConnectionLost(self, connector, r): (connector.host, connector.port) = self._getNextServer() ReconnectingClientFactory.clientConnectionLost(self, connector, r) def startedConnecting(self, connector): drivers.log.connect(self.currentServer) def buildProtocol(self, addr): protocol = ReconnectingClientFactory.buildProtocol(self, addr) protocol.irc = self.irc return protocol Driver = SupyReconnectingFactory try: ignore(poller) except NameError: poller = TwistedRunnerDriver() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/world.py0000644000000000000000000001602711206611405014176 0ustar ### # 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. ### """ Module for general worldly stuff, like global variables and whatnot. """ import gc import os import sys import time import atexit import threading if sys.version_info >= (2, 5, 0): import re as sre else: import sre import supybot.log as log import supybot.conf as conf import supybot.drivers as drivers import supybot.ircutils as ircutils import supybot.registry as registry startedAt = time.time() # Just in case it doesn't get set later. starting = False mainThread = threading.currentThread() def isMainThread(): return mainThread is threading.currentThread() threadsSpawned = 1 # Starts at one for the initial "thread." class SupyThread(threading.Thread): def __init__(self, *args, **kwargs): global threadsSpawned threadsSpawned += 1 super(SupyThread, self).__init__(*args, **kwargs) log.debug('Spawning thread %q.', self.getName()) commandsProcessed = 0 ircs = [] # A list of all the IRCs. def getIrc(network): network = network.lower() for irc in ircs: if irc.network.lower() == network: return irc return None def _flushUserData(): userdataFilename = os.path.join(conf.supybot.directories.conf(), 'userdata.conf') registry.close(conf.users, userdataFilename) flushers = [_flushUserData] # A periodic function will flush all these. registryFilename = None def flush(): """Flushes all the registered flushers.""" for (i, f) in enumerate(flushers): try: f() except Exception, e: log.exception('Uncaught exception in flusher #%s (%s):', i, f) def debugFlush(s=''): if conf.supybot.debug.flushVeryOften(): if s: log.debug(s) flush() def upkeep(): """Does upkeep (like flushing, garbage collection, etc.)""" sys.exc_clear() # Just in case, let's clear the exception info. if os.name == 'nt': try: import msvcrt msvcrt.heapmin() except ImportError: pass except IOError: # Win98 sux0rs! pass if conf.daemonized: # If we're daemonized, sys.stdout has been replaced with a StringIO # object, so let's see if anything's been printed, and if so, let's # log.warning it (things shouldn't be printed, and we're more likely # to get bug reports if we make it a warning). assert not type(sys.stdout) == file, 'Not a StringIO object!' if not hasattr(sys.stdout, 'getvalue'): # Stupid twisted sometimes replaces our stdout with theirs, because # "The Twisted Way Is The Right Way" (ha!). So we're stuck simply # returning. log.warning('Expected cStringIO as stdout, got %r.', sys.stdout) return s = sys.stdout.getvalue() if s: log.warning('Printed to stdout after daemonization: %s', s) sys.stdout.reset() # Seeks to 0. sys.stdout.truncate() # Truncates to current offset. assert not type(sys.stderr) == file, 'Not a StringIO object!' s = sys.stderr.getvalue() if s: log.error('Printed to stderr after daemonization: %s', s) sys.stderr.reset() # Seeks to 0. sys.stderr.truncate() # Truncates to current offset. doFlush = conf.supybot.flush() and not starting if doFlush: flush() # This is so registry._cache gets filled. # This seems dumb, so we'll try not doing it anymore. #if registryFilename is not None: # registry.open(registryFilename) if not dying: log.debug('Regexp cache size: %s', len(sre._cache)) log.debug('Pattern cache size: %s', len(ircutils._patternCache)) log.debug('HostmaskPatternEqual cache size: %s', len(ircutils._hostmaskPatternEqualCache)) #timestamp = log.timestamp() if doFlush: log.info('Flushers flushed and garbage collected.') else: log.info('Garbage collected.') collected = gc.collect() if gc.garbage: log.warning('Noncollectable garbage (file this as a bug on SF.net): %s', gc.garbage) return collected def makeDriversDie(): """Kills drivers.""" log.info('Killing Driver objects.') for driver in drivers._drivers.itervalues(): driver.die() def makeIrcsDie(): """Kills Ircs.""" log.info('Killing Irc objects.') for irc in ircs[:]: if not irc.zombie: irc.die() else: log.debug('Not killing %s, it\'s already a zombie.', irc) def startDying(): """Starts dying.""" log.info('Shutdown initiated.') global dying dying = True def finished(): log.info('Shutdown complete.') # These are in order; don't reorder them for cosmetic purposes. The order # in which they're registered is the reverse order in which they will run. atexit.register(finished) atexit.register(upkeep) atexit.register(makeIrcsDie) atexit.register(makeDriversDie) atexit.register(startDying) ################################################## ################################################## ################################################## ## Don't even *think* about messing with these. ## ################################################## ################################################## ################################################## dying = False testing = False starting = False profiling = False documenting = False # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/test.py0000644000000000000000000004234211206611405014025 0ustar ### # 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 gc import os import re import sys import time import shutil import unittest import threading import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb import supybot.world as world import supybot.irclib as irclib import supybot.plugin as plugin import supybot.drivers as drivers import supybot.ircmsgs as ircmsgs import supybot.registry as registry import supybot.ircutils as ircutils import supybot.callbacks as callbacks network = True # This is the global list of suites that are to be run. suites = [] originalCallbacksGetHelp = callbacks.getHelp lastGetHelp = 'x'*1000 def cachingGetHelp(method, name=None, doc=None): global lastGetHelp lastGetHelp = originalCallbacksGetHelp(method, name, doc) return lastGetHelp callbacks.getHelp = cachingGetHelp def getTestIrc(): irc = irclib.Irc('test') # Gotta clear the connect messages (USER, NICK, etc.) while irc.takeMsg(): pass return irc class TimeoutError(AssertionError): def __str__(self): return '%r timed out' % self.args[0] class TestPlugin(callbacks.Plugin): def eval(self, irc, msg, args): """ This is the help for eval. Since Owner doesn't have an eval command anymore, we needed to add this so as not to invalidate any of the tests that depended on that eval command. """ try: irc.reply(repr(eval(' '.join(args)))) except callbacks.ArgumentError: raise except Exception, e: irc.reply(utils.exnToString(e)) # Since we know we don't now need the Irc object, we just give None. This # might break if callbacks.Privmsg ever *requires* the Irc object. TestInstance = TestPlugin(None) conf.registerPlugin('TestPlugin', True, public=False) class SupyTestCase(unittest.TestCase): """This class exists simply for extra logging. It's come in useful in the past.""" def setUp(self): log.critical('Beginning test case %s', self.id()) threads = [t.getName() for t in threading.enumerate()] log.critical('Threads: %L', threads) unittest.TestCase.setUp(self) def tearDown(self): for irc in world.ircs[:]: irc._reallyDie() class PluginTestCase(SupyTestCase): """Subclass this to write a test case for a plugin. See plugins/Plugin/test.py for an example. """ timeout = 10 plugins = None cleanConfDir = True cleanDataDir = True config = {} def __init__(self, methodName='runTest'): originalRunTest = getattr(self, methodName) def runTest(self): run = True if hasattr(self, 'irc') and self.irc: for cb in self.irc.callbacks: cbModule = sys.modules[cb.__class__.__module__] if hasattr(cbModule, 'deprecated') and cbModule.deprecated: print print 'Ignored, %s is deprecated.' % cb.name() run = False if run: originalRunTest() runTest = utils.python.changeFunctionName(runTest, methodName) setattr(self.__class__, methodName, runTest) SupyTestCase.__init__(self, methodName=methodName) self.originals = {} def setUp(self, nick='test'): if self.__class__ in (PluginTestCase, ChannelPluginTestCase): # Necessary because there's a test in here that shouldn\'t run. return SupyTestCase.setUp(self) # Just in case, let's do this. Too many people forget to call their # super methods. for irc in world.ircs[:]: irc._reallyDie() # Set conf variables appropriately. conf.supybot.reply.whenAddressedBy.chars.setValue('@') conf.supybot.reply.error.detailed.setValue(True) conf.supybot.reply.whenNotCommand.setValue(True) self.myVerbose = world.myVerbose def rmFiles(dir): for filename in os.listdir(dir): file = os.path.join(dir, filename) if os.path.isfile(file): os.remove(file) else: shutil.rmtree(file) if self.cleanConfDir: rmFiles(conf.supybot.directories.conf()) if self.cleanDataDir: rmFiles(conf.supybot.directories.data()) ircdb.users.reload() ircdb.ignores.reload() ircdb.channels.reload() if self.plugins is None: raise ValueError, 'PluginTestCase must have a "plugins" attribute.' self.nick = nick self.prefix = ircutils.joinHostmask(nick, 'user', 'host.domain.tld') self.irc = getTestIrc() MiscModule = plugin.loadPluginModule('Misc') OwnerModule = plugin.loadPluginModule('Owner') ConfigModule = plugin.loadPluginModule('Config') _ = plugin.loadPluginClass(self.irc, MiscModule) _ = plugin.loadPluginClass(self.irc, OwnerModule) _ = plugin.loadPluginClass(self.irc, ConfigModule) if isinstance(self.plugins, str): self.plugins = [self.plugins] else: for name in self.plugins: if name not in ('Owner', 'Misc', 'Config'): module = plugin.loadPluginModule(name, ignoreDeprecation=True) cb = plugin.loadPluginClass(self.irc, module) self.irc.addCallback(TestInstance) for (name, value) in self.config.iteritems(): group = conf.supybot parts = registry.split(name) if parts[0] == 'supybot': parts.pop(0) for part in parts: group = group.get(part) self.originals[group] = group() group.setValue(value) def tearDown(self): if self.__class__ in (PluginTestCase, ChannelPluginTestCase): # Necessary because there's a test in here that shouldn\'t run. return for (group, original) in self.originals.iteritems(): group.setValue(original) ircdb.users.close() ircdb.ignores.close() ircdb.channels.close() SupyTestCase.tearDown(self) self.irc = None gc.collect() def _feedMsg(self, query, timeout=None, to=None, frm=None, usePrefixChar=True): if to is None: to = self.irc.nick if frm is None: frm = self.prefix if timeout is None: timeout = self.timeout if self.myVerbose: print # Extra newline, so it's pretty. prefixChars = conf.supybot.reply.whenAddressedBy.chars() if not usePrefixChar and query[0] in prefixChars: query = query[1:] msg = ircmsgs.privmsg(to, query, prefix=frm) if self.myVerbose: print 'Feeding: %r' % msg self.irc.feedMsg(msg) fed = time.time() response = self.irc.takeMsg() while response is None and time.time() - fed < timeout: time.sleep(0.1) # So it doesn't suck up 100% cpu. drivers.run() response = self.irc.takeMsg() if self.myVerbose: print 'Response: %r' % response return response def getMsg(self, query, **kwargs): return self._feedMsg(query, **kwargs) def feedMsg(self, query, to=None, frm=None): """Just feeds it a message, that's all.""" if to is None: to = self.irc.nick if frm is None: frm = self.prefix self.irc.feedMsg(ircmsgs.privmsg(to, query, prefix=frm)) # These assertError/assertNoError are somewhat fragile. The proper way to # do them would be to use a proxy for the irc object and intercept .error. # But that would be hard, so I don't bother. When this breaks, it'll get # fixed, but not until then. def assertError(self, query, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query if lastGetHelp not in m.args[1]: self.failUnless(m.args[1].startswith('Error:'), '%r did not error: %s' % (query, m.args[1])) return m def assertSnarfError(self, query, **kwargs): return self.assertError(query, usePrefixChar=False, **kwargs) def assertNotError(self, query, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.failIf(m.args[1].startswith('Error:'), '%r errored: %s' % (query, m.args[1])) self.failIf(lastGetHelp in m.args[1], '%r returned the help string.' % query) return m def assertSnarfNotError(self, query, **kwargs): return self.assertNotError(query, usePrefixChar=False, **kwargs) def assertHelp(self, query, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.failUnless(lastGetHelp in m.args[1], '%s is not the help (%s)' % (m.args[1], lastGetHelp)) return m def assertNoResponse(self, query, timeout=0, **kwargs): m = self._feedMsg(query, timeout=timeout, **kwargs) self.failIf(m, 'Unexpected response: %r' % m) return m def assertSnarfNoResponse(self, query, timeout=0, **kwargs): return self.assertNoResponse(query, timeout=timeout, usePrefixChar=False, **kwargs) def assertResponse(self, query, expectedResponse, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.assertEqual(m.args[1], expectedResponse, '%r != %r' % (expectedResponse, m.args[1])) return m def assertSnarfResponse(self, query, expectedResponse, **kwargs): return self.assertResponse(query, expectedResponse, usePrefixChar=False, **kwargs) def assertRegexp(self, query, regexp, flags=re.I, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.failUnless(re.search(regexp, m.args[1], flags), '%r does not match %r' % (m.args[1], regexp)) return m def assertSnarfRegexp(self, query, regexp, flags=re.I, **kwargs): return self.assertRegexp(query, regexp, flags=re.I, usePrefixChar=False, **kwargs) def assertNotRegexp(self, query, regexp, flags=re.I, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.failUnless(re.search(regexp, m.args[1], flags) is None, '%r matched %r' % (m.args[1], regexp)) return m def assertSnarfNotRegexp(self, query, regexp, flags=re.I, **kwargs): return self.assertNotRegexp(query, regexp, flags=re.I, usePrefixChar=False, **kwargs) def assertAction(self, query, expectedResponse=None, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.failUnless(ircmsgs.isAction(m), '%r is not an action.' % m) if expectedResponse is not None: s = ircmsgs.unAction(m) self.assertEqual(s, expectedResponse, '%r != %r' % (s, expectedResponse)) return m def assertSnarfAction(self, query, expectedResponse=None, **kwargs): return self.assertAction(query, expectedResponse=None, usePrefixChar=False, **kwargs) def assertActionRegexp(self, query, regexp, flags=re.I, **kwargs): m = self._feedMsg(query, **kwargs) if m is None: raise TimeoutError, query self.failUnless(ircmsgs.isAction(m)) s = ircmsgs.unAction(m) self.failUnless(re.search(regexp, s, flags), '%r does not match %r' % (s, regexp)) def assertSnarfActionRegexp(self, query, regexp, flags=re.I, **kwargs): return self.assertActionRegexp(query, regexp, flags=re.I, usePrefixChar=False, **kwargs) _noTestDoc = ('Admin', 'Channel', 'Config', 'Misc', 'Owner', 'User', 'TestPlugin') def testDocumentation(self): if self.__class__ in (PluginTestCase, ChannelPluginTestCase): return for cb in self.irc.callbacks: name = cb.name() if ((name in self._noTestDoc) and \ not name.lower() in self.__class__.__name__.lower()): continue self.failUnless(sys.modules[cb.__class__.__name__].__doc__, '%s has no module documentation.' % name) if hasattr(cb, 'isCommandMethod'): for attr in dir(cb): if cb.isCommandMethod(attr) and \ attr == callbacks.canonicalName(attr): self.failUnless(getattr(cb, attr, None).__doc__, '%s.%s has no help.' % (name, attr)) class ChannelPluginTestCase(PluginTestCase): channel = '#test' def setUp(self): if self.__class__ in (PluginTestCase, ChannelPluginTestCase): return PluginTestCase.setUp(self) self.irc.feedMsg(ircmsgs.join(self.channel, prefix=self.prefix)) m = self.irc.takeMsg() self.failIf(m is None, 'No message back from joining channel.') self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.failIf(m is None, 'No message back from joining channel.') self.assertEqual(m.command, 'WHO') def _feedMsg(self, query, timeout=None, to=None, frm=None, private=False, usePrefixChar=True): if to is None: if private: to = self.irc.nick else: to = self.channel if frm is None: frm = self.prefix if timeout is None: timeout = self.timeout if self.myVerbose: print # Newline, just like PluginTestCase. prefixChars = conf.supybot.reply.whenAddressedBy.chars() if query[0] not in prefixChars and usePrefixChar: query = prefixChars[0] + query msg = ircmsgs.privmsg(to, query, prefix=frm) if self.myVerbose: print 'Feeding: %r' % msg self.irc.feedMsg(msg) fed = time.time() response = self.irc.takeMsg() while response is None and time.time() - fed < timeout: time.sleep(0.1) drivers.run() response = self.irc.takeMsg() if response is not None: if response.command == 'PRIVMSG': args = list(response.args) # Strip off nick: at beginning of response. if args[1].startswith(self.nick) or \ args[1].startswith(ircutils.nickFromHostmask(self.prefix)): try: args[1] = args[1].split(' ', 1)[1] except IndexError: # Odd. We'll skip this. pass ret = ircmsgs.privmsg(*args) else: ret = response else: ret = None if self.myVerbose: print 'Returning: %r' % ret return ret def feedMsg(self, query, to=None, frm=None, private=False): """Just feeds it a message, that's all.""" if to is None: if private: to = self.irc.nick else: to = self.channel if frm is None: frm = self.prefix self.irc.feedMsg(ircmsgs.privmsg(to, query, prefix=frm)) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/irclib.py0000644000000000000000000011466211206611405014317 0ustar ### # 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 re import copy import time import random import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils from utils.str import rsplit from utils.iter import imap, chain, cycle from utils.structures import queue, smallqueue, RingBuffer ### # The base class for a callback to be registered with an Irc object. Shows # the required interface for callbacks -- name(), # inFilter(irc, msg), outFilter(irc, msg), and __call__(irc, msg) [used so # functions can be used as callbacks conceivable, and so if refactoring ever # changes the nature of the callbacks from classes to functions, syntactical # changes elsewhere won't be required. ### class IrcCommandDispatcher(object): """Base class for classes that must dispatch on a command.""" def dispatchCommand(self, command): """Given a string 'command', dispatches to doCommand.""" return getattr(self, 'do' + command.capitalize(), None) class IrcCallback(IrcCommandDispatcher): """Base class for standard callbacks. Callbacks derived from this class should have methods of the form "doCommand" -- doPrivmsg, doNick, do433, etc. These will be called on matching messages. """ callAfter = () callBefore = () __metaclass__ = log.MetaFirewall __firewalled__ = {'die': None, 'reset': None, '__call__': None, 'inFilter': lambda self, irc, msg: msg, 'outFilter': lambda self, irc, msg: msg, 'name': lambda self: self.__class__.__name__, 'callPrecedence': lambda self, irc: ([], []), } def __init__(self, *args, **kwargs): #object doesn't take any args, so the buck stops here. #super(IrcCallback, self).__init__(*args, **kwargs) pass def __repr__(self): return '<%s %s %s>' % \ (self.__class__.__name__, self.name(), object.__repr__(self)) def name(self): """Returns the name of the callback.""" return self.__class__.__name__ def callPrecedence(self, irc): """Returns a pair of (callbacks to call before me, callbacks to call after me)""" after = [] before = [] for name in self.callBefore: cb = irc.getCallback(name) if cb is not None: after.append(cb) for name in self.callAfter: cb = irc.getCallback(name) if cb is not None: before.append(cb) assert self not in after, '%s was in its own after.' % self.name() assert self not in before, '%s was in its own before.' % self.name() return (before, after) def inFilter(self, irc, msg): """Used for filtering/modifying messages as they're entering. ircmsgs.IrcMsg objects are immutable, so this method is expected to return another ircmsgs.IrcMsg object. Obviously the same IrcMsg can be returned. """ return msg def outFilter(self, irc, msg): """Used for filtering/modifying messages as they're leaving. As with inFilter, an IrcMsg is returned. """ return msg def __call__(self, irc, msg): """Used for handling each message.""" method = self.dispatchCommand(msg.command) if method is not None: method(irc, msg) def reset(self): """Resets the callback. Called when reconnecting to the server.""" pass def die(self): """Makes the callback die. Called when the parent Irc object dies.""" pass ### # Basic queue for IRC messages. It doesn't presently (but should at some # later point) reorder messages based on priority or penalty calculations. ### _high = frozenset(['MODE', 'KICK', 'PONG', 'NICK', 'PASS', 'CAPAB']) _low = frozenset(['PRIVMSG', 'PING', 'WHO', 'NOTICE', 'JOIN']) class IrcMsgQueue(object): """Class for a queue of IrcMsgs. Eventually, it should be smart. Probably smarter than it is now, though it's gotten quite a bit smarter than it originally was. A method to "score" methods, and a heapq to maintain a priority queue of the messages would be the ideal way to do intelligent queuing. As it stands, however, we simple keep track of 'high priority' messages, 'low priority' messages, and normal messages, and just make sure to return the 'high priority' ones before the normal ones before the 'low priority' ones. """ __slots__ = ('msgs', 'highpriority', 'normal', 'lowpriority', 'lastJoin') def __init__(self, iterable=()): self.reset() for msg in iterable: self.enqueue(msg) def reset(self): """Clears the queue.""" self.lastJoin = 0 self.highpriority = smallqueue() self.normal = smallqueue() self.lowpriority = smallqueue() def enqueue(self, msg): """Enqueues a given message.""" if msg in self and \ conf.supybot.protocols.irc.queuing.duplicates(): s = str(msg).strip() log.info('Not adding message %q to queue, already added.', s) return False else: if msg.command in _high: self.highpriority.enqueue(msg) elif msg.command in _low: self.lowpriority.enqueue(msg) else: self.normal.enqueue(msg) return True def dequeue(self): """Dequeues a given message.""" msg = None if self.highpriority: msg = self.highpriority.dequeue() elif self.normal: msg = self.normal.dequeue() elif self.lowpriority: msg = self.lowpriority.dequeue() if msg.command == 'JOIN': limit = conf.supybot.protocols.irc.queuing.rateLimit.join() now = time.time() if self.lastJoin + limit <= now: self.lastJoin = now else: self.lowpriority.enqueue(msg) msg = None return msg def __contains__(self, msg): return msg in self.normal or \ msg in self.lowpriority or \ msg in self.highpriority def __nonzero__(self): return bool(self.highpriority or self.normal or self.lowpriority) def __len__(self): return len(self.highpriority)+len(self.lowpriority)+len(self.normal) def __repr__(self): name = self.__class__.__name__ return '%s(%r)' % (name, list(chain(self.highpriority, self.normal, self.lowpriority))) __str__ = __repr__ ### # Maintains the state of IRC connection -- the most recent messages, the # status of various modes (especially ops/halfops/voices) in channels, etc. ### class ChannelState(utils.python.Object): __slots__ = ('users', 'ops', 'halfops', 'bans', 'voices', 'topic', 'modes', 'created') def __init__(self): self.topic = '' self.created = 0 self.ops = ircutils.IrcSet() self.bans = ircutils.IrcSet() self.users = ircutils.IrcSet() self.voices = ircutils.IrcSet() self.halfops = ircutils.IrcSet() self.modes = ircutils.IrcDict() def isOp(self, nick): return nick in self.ops def isVoice(self, nick): return nick in self.voices def isHalfop(self, nick): return nick in self.halfops def addUser(self, user): "Adds a given user to the ChannelState. Power prefixes are handled." nick = user.lstrip('@%+&~') if not nick: return # & is used to denote protected users in UnrealIRCd # ~ is used to denote channel owner in UnrealIRCd while user and user[0] in '@%+&~': (marker, user) = (user[0], user[1:]) assert user, 'Looks like my caller is passing chars, not nicks.' if marker in '@&~': self.ops.add(nick) elif marker == '%': self.halfops.add(nick) elif marker == '+': self.voices.add(nick) self.users.add(nick) def replaceUser(self, oldNick, newNick): """Changes the user oldNick to newNick; used for NICK changes.""" # Note that this doesn't have to have the sigil (@%+) that users # have to have for addUser; it just changes the name of the user # without changing any of his categories. for s in (self.users, self.ops, self.halfops, self.voices): if oldNick in s: s.remove(oldNick) s.add(newNick) def removeUser(self, user): """Removes a given user from the channel.""" self.users.discard(user) self.ops.discard(user) self.halfops.discard(user) self.voices.discard(user) def setMode(self, mode, value=None): assert mode not in 'ovhbeq' self.modes[mode] = value def unsetMode(self, mode): assert mode not in 'ovhbeq' if mode in self.modes: del self.modes[mode] def doMode(self, msg): def getSet(c): if c == 'o': Set = self.ops elif c == 'v': Set = self.voices elif c == 'h': Set = self.halfops elif c == 'b': Set = self.bans else: # We don't care yet, so we'll just return an empty set. Set = set() return Set for (mode, value) in ircutils.separateModes(msg.args[1:]): (action, modeChar) = mode if modeChar in 'ovhbeq': # We don't handle e or q yet. Set = getSet(modeChar) if action == '-': Set.discard(value) elif action == '+': Set.add(value) else: if action == '+': self.setMode(modeChar, value) else: assert action == '-' self.unsetMode(modeChar) def __getstate__(self): return [getattr(self, name) for name in self.__slots__] def __setstate__(self, t): for (name, value) in zip(self.__slots__, t): setattr(self, name, value) def __eq__(self, other): ret = True for name in self.__slots__: ret = ret and getattr(self, name) == getattr(other, name) return ret class IrcState(IrcCommandDispatcher): """Maintains state of the Irc connection. Should also become smarter. """ __metaclass__ = log.MetaFirewall __firewalled__ = {'addMsg': None} def __init__(self, history=None, supported=None, nicksToHostmasks=None, channels=None): if history is None: history = RingBuffer(conf.supybot.protocols.irc.maxHistoryLength()) if supported is None: supported = utils.InsensitivePreservingDict() if nicksToHostmasks is None: nicksToHostmasks = ircutils.IrcDict() if channels is None: channels = ircutils.IrcDict() self.supported = supported self.history = history self.channels = channels self.nicksToHostmasks = nicksToHostmasks def reset(self): """Resets the state to normal, unconnected state.""" self.history.reset() self.channels.clear() self.supported.clear() self.nicksToHostmasks.clear() self.history.resize(conf.supybot.protocols.irc.maxHistoryLength()) def __reduce__(self): return (self.__class__, (self.history, self.supported, self.nicksToHostmasks, self.channels)) def __eq__(self, other): return self.history == other.history and \ self.channels == other.channels and \ self.supported == other.supported and \ self.nicksToHostmasks == other.nicksToHostmasks def __ne__(self, other): return not self == other def copy(self): ret = self.__class__() ret.history = copy.deepcopy(self.history) ret.nicksToHostmasks = copy.deepcopy(self.nicksToHostmasks) ret.channels = copy.deepcopy(self.channels) return ret def addMsg(self, irc, msg): """Updates the state based on the irc object and the message.""" self.history.append(msg) if ircutils.isUserHostmask(msg.prefix) and not msg.command == 'NICK': self.nicksToHostmasks[msg.nick] = msg.prefix method = self.dispatchCommand(msg.command) if method is not None: method(irc, msg) def getTopic(self, channel): """Returns the topic for a given channel.""" return self.channels[channel].topic def nickToHostmask(self, nick): """Returns the hostmask for a given nick.""" return self.nicksToHostmasks[nick] _005converters = utils.InsensitivePreservingDict({ 'modes': int, 'keylen': int, 'nicklen': int, 'userlen': int, 'hostlen': int, 'kicklen': int, 'awaylen': int, 'silence': int, 'topiclen': int, 'channellen': int, 'maxtargets': int, 'maxnicklen': int, 'maxchannels': int, 'watch': int, # DynastyNet, EnterTheGame }) def _prefixParser(s): if ')' in s: (left, right) = s.split(')') assert left[0] == '(', 'Odd PREFIX in 005: %s' % s left = left[1:] assert len(left) == len(right), 'Odd PREFIX in 005: %s' % s return dict(zip(left, right)) else: return dict(zip('ovh', s)) _005converters['prefix'] = _prefixParser del _prefixParser def _maxlistParser(s): modes = '' limits = [] pairs = s.split(',') for pair in pairs: (mode, limit) = pair.split(':', 1) modes += mode limits += (int(limit),) * len(mode) return dict(zip(modes, limits)) _005converters['maxlist'] = _maxlistParser del _maxlistParser def _maxbansParser(s): # IRCd using a MAXLIST style string (IRCNet) if ':' in s: modes = '' limits = [] pairs = s.split(',') for pair in pairs: (mode, limit) = pair.split(':', 1) modes += mode limits += (int(limit),) * len(mode) d = dict(zip(modes, limits)) assert 'b' in d return d['b'] else: return int(s) _005converters['maxbans'] = _maxbansParser del _maxbansParser def do005(self, irc, msg): for arg in msg.args[1:-1]: # 0 is nick, -1 is "are supported" if '=' in arg: (name, value) = arg.split('=', 1) converter = self._005converters.get(name, lambda x: x) try: self.supported[name] = converter(value) except Exception, e: log.exception('Uncaught exception in 005 converter:') log.error('Name: %s, Converter: %s', name, converter) else: self.supported[arg] = None def do352(self, irc, msg): # WHO reply. (nick, user, host) = (msg.args[5], msg.args[2], msg.args[3]) hostmask = '%s!%s@%s' % (nick, user, host) self.nicksToHostmasks[nick] = hostmask def do353(self, irc, msg): # NAMES reply. (_, type, channel, names) = msg.args if channel not in self.channels: self.channels[channel] = ChannelState() c = self.channels[channel] for name in names.split(): c.addUser(name) if type == '@': c.modes['s'] = None def doJoin(self, irc, msg): for channel in msg.args[0].split(','): if channel in self.channels: self.channels[channel].addUser(msg.nick) elif msg.nick: # It must be us. chan = ChannelState() chan.addUser(msg.nick) self.channels[channel] = chan # I don't know why this assert was here. #assert msg.nick == irc.nick, msg def doMode(self, irc, msg): channel = msg.args[0] if ircutils.isChannel(channel): # There can be user modes, as well. try: chan = self.channels[channel] except KeyError: chan = ChannelState() self.channels[channel] = chan chan.doMode(msg) def do324(self, irc, msg): channel = msg.args[1] chan = self.channels[channel] for (mode, value) in ircutils.separateModes(msg.args[2:]): modeChar = mode[1] if mode[0] == '+' and mode[1] not in 'ovh': chan.setMode(modeChar, value) elif mode[0] == '-' and mode[1] not in 'ovh': chan.unsetMode(modeChar) def do329(self, irc, msg): # This is the last part of an empty mode. channel = msg.args[1] chan = self.channels[channel] chan.created = int(msg.args[2]) def doPart(self, irc, msg): for channel in msg.args[0].split(','): try: chan = self.channels[channel] except KeyError: continue if ircutils.strEqual(msg.nick, irc.nick): del self.channels[channel] else: chan.removeUser(msg.nick) def doKick(self, irc, msg): (channel, users) = msg.args[:2] chan = self.channels[channel] for user in users.split(','): if ircutils.strEqual(user, irc.nick): del self.channels[channel] return else: chan.removeUser(user) def doQuit(self, irc, msg): for channel in self.channels.itervalues(): channel.removeUser(msg.nick) if msg.nick in self.nicksToHostmasks: # If we're quitting, it may not be. del self.nicksToHostmasks[msg.nick] def doTopic(self, irc, msg): if len(msg.args) == 1: return # Empty TOPIC for information. Does not affect state. try: chan = self.channels[msg.args[0]] chan.topic = msg.args[1] except KeyError: pass # We don't have to be in a channel to send a TOPIC. def do332(self, irc, msg): chan = self.channels[msg.args[1]] chan.topic = msg.args[2] def doNick(self, irc, msg): newNick = msg.args[0] oldNick = msg.nick try: if msg.user and msg.host: # Nick messages being handed out from the bot itself won't # have the necessary prefix to make a hostmask. newHostmask = ircutils.joinHostmask(newNick,msg.user,msg.host) self.nicksToHostmasks[newNick] = newHostmask del self.nicksToHostmasks[oldNick] except KeyError: pass for channel in self.channels.itervalues(): channel.replaceUser(oldNick, newNick) ### # The basic class for handling a connection to an IRC server. Accepts # callbacks of the IrcCallback interface. Public attributes include 'driver', # 'queue', and 'state', in addition to the standard nick/user/ident attributes. ### _callbacks = [] class Irc(IrcCommandDispatcher): """The base class for an IRC connection. Handles PING commands already. """ __metaclass__ = log.MetaFirewall __firewalled__ = {'die': None, 'feedMsg': None, 'takeMsg': None,} _nickSetters = set(['001', '002', '003', '004', '250', '251', '252', '254', '255', '265', '266', '372', '375', '376', '333', '353', '332', '366', '005']) # We specifically want these callbacks to be common between all Ircs, # that's why we don't do the normal None default with a check. def __init__(self, network, callbacks=_callbacks): self.zombie = False world.ircs.append(self) self.network = network self.callbacks = callbacks self.state = IrcState() self.queue = IrcMsgQueue() self.fastqueue = smallqueue() self.driver = None # The driver should set this later. self._setNonResettingVariables() self._queueConnectMessages() self.startedSync = ircutils.IrcDict() def isChannel(self, s): """Helper function to check whether a given string is a channel on the network this Irc object is connected to.""" kw = {} if 'chantypes' in self.state.supported: kw['chantypes'] = self.state.supported['chantypes'] if 'channellen' in self.state.supported: kw['channellen'] = self.state.supported['channellen'] return ircutils.isChannel(s, **kw) def isNick(self, s): kw = {} if 'nicklen' in self.state.supported: kw['nicklen'] = self.state.supported['nicklen'] return ircutils.isNick(s, **kw) # This *isn't* threadsafe! def addCallback(self, callback): """Adds a callback to the callbacks list.""" assert not self.getCallback(callback.name()) self.callbacks.append(callback) # This is the new list we're building, which will be tsorted. cbs = [] # The vertices are self.callbacks itself. Now we make the edges. edges = set() for cb in self.callbacks: (before, after) = cb.callPrecedence(self) assert cb not in after, 'cb was in its own after.' assert cb not in before, 'cb was in its own before.' for otherCb in before: edges.add((otherCb, cb)) for otherCb in after: edges.add((cb, otherCb)) def getFirsts(): firsts = set(self.callbacks) - set(cbs) for (before, after) in edges: firsts.discard(after) return firsts firsts = getFirsts() while firsts: # Then we add these to our list of cbs, and remove all edges that # originate with these cbs. for cb in firsts: cbs.append(cb) edgesToRemove = [] for edge in edges: if edge[0] is cb: edgesToRemove.append(edge) for edge in edgesToRemove: edges.remove(edge) firsts = getFirsts() assert len(cbs) == len(self.callbacks), \ 'cbs: %s, self.callbacks: %s' % (cbs, self.callbacks) self.callbacks[:] = cbs def getCallback(self, name): """Gets a given callback by name.""" name = name.lower() for callback in self.callbacks: if callback.name().lower() == name: return callback else: return None def removeCallback(self, name): """Removes a callback from the callback list.""" name = name.lower() def nameMatches(cb): return cb.name().lower() == name (bad, good) = utils.iter.partition(nameMatches, self.callbacks) self.callbacks[:] = good return bad def queueMsg(self, msg): """Queues a message to be sent to the server.""" if not self.zombie: return self.queue.enqueue(msg) else: log.warning('Refusing to queue %r; %s is a zombie.', msg, self) return False def sendMsg(self, msg): """Queues a message to be sent to the server *immediately*""" if not self.zombie: self.fastqueue.enqueue(msg) else: log.warning('Refusing to send %r; %s is a zombie.', msg, self) def takeMsg(self): """Called by the IrcDriver; takes a message to be sent.""" if not self.callbacks: log.critical('No callbacks in %s.', self) now = time.time() msg = None if self.fastqueue: msg = self.fastqueue.dequeue() elif self.queue: if now-self.lastTake <= conf.supybot.protocols.irc.throttleTime(): log.debug('Irc.takeMsg throttling.') else: self.lastTake = now msg = self.queue.dequeue() elif self.afterConnect and \ conf.supybot.protocols.irc.ping() and \ now > self.lastping + conf.supybot.protocols.irc.ping.interval(): if self.outstandingPing: s = 'Ping sent at %s not replied to.' % \ log.timestamp(self.lastping) log.warning(s) self.feedMsg(ircmsgs.error(s)) self.driver.reconnect() elif not self.zombie: self.lastping = now now = str(int(now)) self.outstandingPing = True self.queueMsg(ircmsgs.ping(now)) if msg: for callback in reversed(self.callbacks): msg = callback.outFilter(self, msg) if msg is None: log.debug('%s.outFilter returned None.', callback.name()) return self.takeMsg() world.debugFlush() if len(str(msg)) > 512: # Yes, this violates the contract, but at this point it doesn't # matter. That's why we gotta go munging in private attributes # # I'm changing this to a log.debug to fix a possible loop in # the LogToIrc plugin. Since users can't do anything about # this issue, there's no fundamental reason to make it a # warning. log.debug('Truncating %r, message is too long.', msg) msg._str = msg._str[:500] + '\r\n' msg._len = len(str(msg)) # I don't think we should do this. Why should it matter? If it's # something important, then the server will send it back to us, # and if it's just a privmsg/notice/etc., we don't care. # On second thought, we need this for testing. if world.testing: self.state.addMsg(self, msg) log.debug('Outgoing message: %s', str(msg).rstrip('\r\n')) return msg elif self.zombie: # We kill the driver here so it doesn't continue to try to # take messages from us. self.driver.die() self._reallyDie() else: return None _numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$') def feedMsg(self, msg): """Called by the IrcDriver; feeds a message received.""" msg.tag('receivedBy', self) msg.tag('receivedOn', self.network) if msg.args and self.isChannel(msg.args[0]): channel = msg.args[0] else: channel = None preInFilter = str(msg).rstrip('\r\n') log.debug('Incoming message: %s', preInFilter) # Yeah, so this is odd. Some networks (oftc) seem to give us certain # messages with our nick instead of our prefix. We'll fix that here. if msg.prefix == self.nick: log.debug('Got one of those odd nick-instead-of-prefix msgs.') msg = ircmsgs.IrcMsg(prefix=self.prefix, msg=msg) # This catches cases where we know our own nick (from sending it to the # server) but we don't yet know our prefix. if msg.nick == self.nick and self.prefix != msg.prefix: self.prefix = msg.prefix # This keeps our nick and server attributes updated. if msg.command in self._nickSetters: if msg.args[0] != self.nick: self.nick = msg.args[0] log.debug('Updating nick attribute to %s.', self.nick) if msg.prefix != self.server: self.server = msg.prefix log.debug('Updating server attribute to %s.', self.server) # Dispatch to specific handlers for commands. method = self.dispatchCommand(msg.command) if method is not None: method(msg) elif self._numericErrorCommandRe.search(msg.command): log.error('Unhandled error message from server: %r' % msg) # Now update the IrcState object. try: self.state.addMsg(self, msg) except: log.exception('Exception in update of IrcState object:') # Now call the callbacks. world.debugFlush() for callback in self.callbacks: try: m = callback.inFilter(self, msg) if not m: log.debug('%s.inFilter returned None', callback.name()) return msg = m except: log.exception('Uncaught exception in inFilter:') world.debugFlush() postInFilter = str(msg).rstrip('\r\n') if postInFilter != preInFilter: log.debug('Incoming message (post-inFilter): %s', postInFilter) for callback in self.callbacks: try: if callback is not None: callback(self, msg) except: log.exception('Uncaught exception in callback:') world.debugFlush() def die(self): """Makes the Irc object *promise* to die -- but it won't die (of its own volition) until all its queues are clear. Isn't that cool?""" self.zombie = True if not self.afterConnect: self._reallyDie() # This is useless because it's in world.ircs, so it won't be deleted until # the program exits. Just figured you might want to know. #def __del__(self): # self._reallyDie() def reset(self): """Resets the Irc object. Called when the driver reconnects.""" self._setNonResettingVariables() self.state.reset() self.queue.reset() self.fastqueue.reset() self.startedSync.clear() for callback in self.callbacks: callback.reset() self._queueConnectMessages() def _setNonResettingVariables(self): # Configuration stuff. self.nick = conf.supybot.nick() self.user = conf.supybot.user() self.ident = conf.supybot.ident() self.alternateNicks = conf.supybot.nick.alternates()[:] self.password = conf.supybot.networks.get(self.network).password() self.prefix = '%s!%s@%s' % (self.nick, self.ident, 'unset.domain') # The rest. self.lastTake = 0 self.server = 'unset' self.afterConnect = False self.lastping = time.time() self.outstandingPing = False def _queueConnectMessages(self): if self.zombie: self.driver.die() self._reallyDie() else: if self.password: log.info('Sending PASS command, not logging the password.') self.queueMsg(ircmsgs.password(self.password)) log.debug('Queuing NICK command, nick is %s.', self.nick) self.queueMsg(ircmsgs.nick(self.nick)) log.debug('Queuing USER command, ident is %s, user is %s.', self.ident, self.user) self.queueMsg(ircmsgs.user(self.ident, self.user)) def _getNextNick(self): if self.alternateNicks: nick = self.alternateNicks.pop(0) if '%s' in nick: nick %= conf.supybot.nick() return nick else: nick = conf.supybot.nick() ret = nick L = list(nick) while len(L) <= 3: L.append('`') while ircutils.strEqual(ret, nick): L[random.randrange(len(L))] = utils.iter.choice('0123456789') ret = ''.join(L) return ret def do002(self, msg): """Logs the ircd version.""" (beginning, version) = rsplit(msg.args[-1], maxsplit=1) log.info('Server %s has version %s', self.server, version) def doPing(self, msg): """Handles PING messages.""" self.sendMsg(ircmsgs.pong(msg.args[0])) def doPong(self, msg): """Handles PONG messages.""" self.outstandingPing = False def do376(self, msg): log.info('Got end of MOTD from %s', self.server) self.afterConnect = True # Let's reset nicks in case we had to use a weird one. self.alternateNicks = conf.supybot.nick.alternates()[:] umodes = conf.supybot.protocols.irc.umodes() if umodes: if umodes[0] not in '+-': umodes = '+' + umodes log.info('Sending user modes to %s: %s', self.network, umodes) self.sendMsg(ircmsgs.mode(self.nick, umodes)) do377 = do422 = do376 def do43x(self, msg, problem): if not self.afterConnect: newNick = self._getNextNick() assert newNick != self.nick log.info('Got 433: %s %s. Trying %s.',self.nick, problem, newNick) self.sendMsg(ircmsgs.nick(newNick)) def do433(self, msg): self.do43x(msg, 'is in use') def do432(self, msg): self.do43x(msg, 'is not a valid nickname') def doJoin(self, msg): if msg.nick == self.nick: channel = msg.args[0] self.queueMsg(ircmsgs.who(channel)) # Ends with 315. self.queueMsg(ircmsgs.mode(channel)) # Ends with 329. self.startedSync[channel] = time.time() def do315(self, msg): channel = msg.args[1] if channel in self.startedSync: now = time.time() started = self.startedSync.pop(channel) elapsed = now - started log.info('Join to %s on %s synced in %.2f seconds.', channel, self.network, elapsed) def doError(self, msg): """Handles ERROR messages.""" log.warning('Error message from %s: %s', self.network, msg.args[0]) if not self.zombie: if msg.args[0].startswith('Closing Link'): self.driver.reconnect() elif 'too fast' in msg.args[0]: # Connecting too fast. self.driver.reconnect(wait=True) def doNick(self, msg): """Handles NICK messages.""" if msg.nick == self.nick: newNick = msg.args[0] self.nick = newNick (nick, user, domain) = ircutils.splitHostmask(msg.prefix) self.prefix = ircutils.joinHostmask(self.nick, user, domain) elif conf.supybot.followIdentificationThroughNickChanges(): # We use elif here because this means it's someone else's nick # change, not our own. try: id = ircdb.users.getUserId(msg.prefix) u = ircdb.users.getUser(id) except KeyError: return if u.auth: (_, user, host) = ircutils.splitHostmask(msg.prefix) newhostmask = ircutils.joinHostmask(msg.args[0], user, host) for (i, (when, authmask)) in enumerate(u.auth[:]): if ircutils.strEqual(msg.prefix, authmask): log.info('Following identification for %s: %s -> %s', u.name, authmask, newhostmask) u.auth[i] = (u.auth[i][0], newhostmask) ircdb.users.setUser(u) def _reallyDie(self): """Makes the Irc object die. Dead.""" log.info('Irc object for %s dying.', self.network) # XXX This hasattr should be removed, I'm just putting it here because # we're so close to a release. After 0.80.0 we should remove this # and fix whatever AttributeErrors arise in the drivers themselves. if self.driver is not None and hasattr(self.driver, 'die'): self.driver.die() if self in world.ircs: world.ircs.remove(self) # Only kill the callbacks if we're the last Irc. if not world.ircs: for cb in self.callbacks: cb.die() # If we shared our list of callbacks, this ensures that # cb.die() is only called once for each callback. It's # not really necessary since we already check to make sure # we're the only Irc object, but a little robustitude never # hurt anybody. log.debug('Last Irc, clearing callbacks.') self.callbacks[:] = [] else: log.warning('Irc object killed twice: %s', utils.stackTrace()) def __hash__(self): return id(self) def __eq__(self, other): # We check isinstance here, so that if some proxy object (like those # defined in callbacks.py) has overridden __eq__, it takes precedence. if isinstance(other, self.__class__): return id(self) == id(other) else: return other == self def __ne__(self, other): return not (self == other) def __str__(self): return 'Irc object for %s' % self.network def __repr__(self): return '' % self.network # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/ansi.py0000644000000000000000000000666011206611405014003 0ustar ### # 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. ### """ ansi.py ANSI Terminal Interface Color Usage: print RED + 'this is red' + RESET print BOLD + GREEN + WHITEBG + 'this is bold green on white' + RESET def move(new_x, new_y): 'Move cursor to new_x, new_y' def moveUp(lines): 'Move cursor up # of lines' def moveDown(lines): 'Move cursor down # of lines' def moveForward(chars): 'Move cursor forward # of chars' def moveBack(chars): 'Move cursor backward # of chars' def save(): 'Saves cursor position' def restore(): 'Restores cursor position' def clear(): 'Clears screen and homes cursor' def clrtoeol(): 'Clears screen to end of line' """ ################################ # C O L O R C O N S T A N T S # ################################ BLACK = '\033[30m' RED = '\033[31m' GREEN = '\033[32m' YELLOW = '\033[33m' BLUE = '\033[34m' MAGENTA = '\033[35m' CYAN = '\033[36m' WHITE = '\033[37m' RESET = '\033[0;0m' BOLD = '\033[1m' REVERSE = '\033[2m' BLACKBG = '\033[40m' REDBG = '\033[41m' GREENBG = '\033[42m' YELLOWBG = '\033[43m' BLUEBG = '\033[44m' MAGENTABG = '\033[45m' CYANBG = '\033[46m' WHITEBG = '\033[47m' #def move(new_x, new_y): # 'Move cursor to new_x, new_y' # print '\033[' + str(new_x) + ';' + str(new_y) + 'H' # #def moveUp(lines): # 'Move cursor up # of lines' # print '\033[' + str(lines) + 'A' # #def moveDown(lines): # 'Move cursor down # of lines' # print '\033[' + str(lines) + 'B' # #def moveForward(chars): # 'Move cursor forward # of chars' # print '\033[' + str(chars) + 'C' # #def moveBack(chars): # 'Move cursor backward # of chars' # print '\033[' + str(chars) + 'D' # #def save(): # 'Saves cursor position' # print '\033[s' # #def restore(): # 'Restores cursor position' # print '\033[u' # #def clear(): # 'Clears screen and homes cursor' # print '\033[2J' # #def clrtoeol(): # 'Clears screen to end of line' # print '\033[K' # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/ircutils.py0000644000000000000000000005052611206611405014707 0ustar ### # 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. ### """ Provides a great number of useful utility functions IRC. Things to muck around with hostmasks, set bold or color on strings, IRC-case-insensitive dicts, a nick class to handle nicks (so comparisons and hashing and whatnot work in an IRC-case-insensitive fashion), and numerous other things. """ import re import time import random import string import textwrap from cStringIO import StringIO as sio import supybot.utils as utils def debug(s, *args): """Prints a debug string. Most likely replaced by our logging debug.""" print '***', s % args def isUserHostmask(s): """Returns whether or not the string s is a valid User hostmask.""" p1 = s.find('!') p2 = s.find('@') if p1 < p2-1 and p1 >= 1 and p2 >= 3 and len(s) > p2+1: return True else: return False def isServerHostmask(s): """s => bool Returns True if s is a valid server hostmask.""" return not isUserHostmask(s) def nickFromHostmask(hostmask): """hostmask => nick Returns the nick from a user hostmask.""" assert isUserHostmask(hostmask) return hostmask.split('!', 1)[0] def userFromHostmask(hostmask): """hostmask => user Returns the user from a user hostmask.""" assert isUserHostmask(hostmask) return hostmask.split('!', 1)[1].split('@', 1)[0] def hostFromHostmask(hostmask): """hostmask => host Returns the host from a user hostmask.""" assert isUserHostmask(hostmask) return hostmask.split('@', 1)[1] def splitHostmask(hostmask): """hostmask => (nick, user, host) Returns the nick, user, host of a user hostmask.""" assert isUserHostmask(hostmask) nick, rest = hostmask.split('!', 1) user, host = rest.split('@', 1) return (nick, user, host) def joinHostmask(nick, ident, host): """nick, user, host => hostmask Joins the nick, ident, host into a user hostmask.""" assert nick and ident and host return '%s!%s@%s' % (nick, ident, host) _rfc1459trans = string.maketrans(string.ascii_uppercase + r'\[]~', string.ascii_lowercase + r'|{}^') def toLower(s, casemapping=None): """s => s Returns the string s lowered according to IRC case rules.""" if casemapping is None or casemapping == 'rfc1459': return s.translate(_rfc1459trans) elif casemapping == 'ascii': # freenode return s.lower() else: raise ValueError, 'Invalid casemapping: %r' % casemapping def strEqual(nick1, nick2): """s1, s2 => bool Returns True if nick1 == nick2 according to IRC case rules.""" assert isinstance(nick1, basestring) assert isinstance(nick2, basestring) return toLower(nick1) == toLower(nick2) nickEqual = strEqual _nickchars = r'[]\`_^{|}' nickRe = re.compile(r'^[A-Za-z%s][-0-9A-Za-z%s]*$' % (re.escape(_nickchars), re.escape(_nickchars))) def isNick(s, strictRfc=True, nicklen=None): """s => bool Returns True if s is a valid IRC nick.""" if strictRfc: ret = bool(nickRe.match(s)) if ret and nicklen is not None: ret = len(s) <= nicklen return ret else: return not isChannel(s) and \ not isUserHostmask(s) and \ not ' ' in s and not '!' in s def isChannel(s, chantypes='#&+!', channellen=50): """s => bool Returns True if s is a valid IRC channel name.""" return s and \ ',' not in s and \ '\x07' not in s and \ s[0] in chantypes and \ len(s) <= channellen and \ len(s.split(None, 1)) == 1 _patternCache = utils.structures.CacheDict(1000) def _hostmaskPatternEqual(pattern, hostmask): try: return _patternCache[pattern](hostmask) is not None except KeyError: # We make our own regexps, rather than use fnmatch, because fnmatch's # case-insensitivity is not IRC's case-insensitity. fd = sio() for c in pattern: if c == '*': fd.write('.*') elif c == '?': fd.write('.') elif c in '[{': fd.write('[[{]') elif c in '}]': fd.write(r'[}\]]') elif c in '|\\': fd.write(r'[|\\]') elif c in '^~': fd.write('[~^]') else: fd.write(re.escape(c)) fd.write('$') f = re.compile(fd.getvalue(), re.I).match _patternCache[pattern] = f return f(hostmask) is not None _hostmaskPatternEqualCache = utils.structures.CacheDict(1000) def hostmaskPatternEqual(pattern, hostmask): """pattern, hostmask => bool Returns True if hostmask matches the hostmask pattern pattern.""" try: return _hostmaskPatternEqualCache[(pattern, hostmask)] except KeyError: b = _hostmaskPatternEqual(pattern, hostmask) _hostmaskPatternEqualCache[(pattern, hostmask)] = b return b def banmask(hostmask): """Returns a properly generic banning hostmask for a hostmask. >>> banmask('nick!user@host.domain.tld') '*!*@*.domain.tld' >>> banmask('nick!user@10.0.0.1') '*!*@10.0.0.*' """ assert isUserHostmask(hostmask) host = hostFromHostmask(hostmask) if utils.net.isIP(host): L = host.split('.') L[-1] = '*' return '*!*@' + '.'.join(L) elif utils.net.isIPV6(host): L = host.split(':') L[-1] = '*' return '*!*@' + ':'.join(L) else: if '.' in host: return '*!*@*%s' % host[host.find('.'):] else: return '*!*@' + host _plusRequireArguments = 'ovhblkqe' _minusRequireArguments = 'ovhbkqe' def separateModes(args): """Separates modelines into single mode change tuples. Basically, you should give it the .args of a MODE IrcMsg. Examples: >>> separateModes(['+ooo', 'jemfinch', 'StoneTable', 'philmes']) [('+o', 'jemfinch'), ('+o', 'StoneTable'), ('+o', 'philmes')] >>> separateModes(['+o-o', 'jemfinch', 'PeterB']) [('+o', 'jemfinch'), ('-o', 'PeterB')] >>> separateModes(['+s-o', 'test']) [('+s', None), ('-o', 'test')] >>> separateModes(['+sntl', '100']) [('+s', None), ('+n', None), ('+t', None), ('+l', 100)] """ if not args: return [] modes = args[0] assert modes[0] in '+-', 'Invalid args: %r' % args args = list(args[1:]) ret = [] for c in modes: if c in '+-': last = c else: if last == '+': requireArguments = _plusRequireArguments else: requireArguments = _minusRequireArguments if c in requireArguments: arg = args.pop(0) try: arg = int(arg) except ValueError: pass ret.append((last + c, arg)) else: ret.append((last + c, None)) return ret def joinModes(modes): """[(mode, targetOrNone), ...] => args Joins modes of the same form as returned by separateModes.""" args = [] modeChars = [] currentMode = '\x00' for (mode, arg) in modes: if arg is not None: args.append(arg) if not mode.startswith(currentMode): currentMode = mode[0] modeChars.append(mode[0]) modeChars.append(mode[1]) args.insert(0, ''.join(modeChars)) return args def bold(s): """Returns the string s, bolded.""" return '\x02%s\x02' % s def reverse(s): """Returns the string s, reverse-videoed.""" return '\x16%s\x16' % s def underline(s): """Returns the string s, underlined.""" return '\x1F%s\x1F' % s # Definition of mircColors dictionary moved below because it became an IrcDict. def mircColor(s, fg=None, bg=None): """Returns s with the appropriate mIRC color codes applied.""" if fg is None and bg is None: return s elif bg is None: fg = mircColors[str(fg)] return '\x03%s%s\x03' % (fg.zfill(2), s) elif fg is None: bg = mircColors[str(bg)] # According to the mirc color doc, a fg color MUST be specified if a # background color is specified. So, we'll specify 00 (white) if the # user doesn't specify one. return '\x0300,%s%s\x03' % (bg.zfill(2), s) else: fg = mircColors[str(fg)] bg = mircColors[str(bg)] # No need to zfill fg because the comma delimits. return '\x03%s,%s%s\x03' % (fg, bg.zfill(2), s) def canonicalColor(s, bg=False, shift=0): """Assigns an (fg, bg) canonical color pair to a string based on its hash value. This means it might change between Python versions. This pair can be used as a *parameter to mircColor. The shift parameter is how much to right-shift the hash value initially. """ h = hash(s) >> shift fg = h % 14 + 2 # The + 2 is to rule out black and white. if bg: bg = (h >> 4) & 3 # The 5th, 6th, and 7th least significant bits. if fg < 8: bg += 8 else: bg += 2 return (fg, bg) else: return (fg, None) def stripBold(s): """Returns the string s, with bold removed.""" return s.replace('\x02', '') _stripColorRe = re.compile(r'\x03(?:\d{1,2},\d{1,2}|\d{1,2}|,\d{1,2}|)') def stripColor(s): """Returns the string s, with color removed.""" return _stripColorRe.sub('', s) def stripReverse(s): """Returns the string s, with reverse-video removed.""" return s.replace('\x16', '') def stripUnderline(s): """Returns the string s, with underlining removed.""" return s.replace('\x1f', '').replace('\x1F', '') def stripFormatting(s): """Returns the string s, with all formatting removed.""" # stripColor has to go first because of some strings, check the tests. s = stripColor(s) s = stripBold(s) s = stripReverse(s) s = stripUnderline(s) return s.replace('\x0f', '').replace('\x0F', '') class FormatContext(object): def __init__(self): self.reset() def reset(self): self.fg = None self.bg = None self.bold = False self.reverse = False self.underline = False def start(self, s): """Given a string, starts all the formatters in this context.""" if self.bold: s = '\x02' + s if self.reverse: s = '\x16' + s if self.underline: s = '\x1f' + s if self.fg is not None or self.bg is not None: s = mircColor(s, fg=self.fg, bg=self.bg)[:-1] # Remove \x03. return s def end(self, s): """Given a string, ends all the formatters in this context.""" if self.bold or self.reverse or \ self.fg or self.bg or self.underline: # Should we individually end formatters? s += '\x0f' return s class FormatParser(object): def __init__(self, s): self.fd = sio(s) self.last = None def getChar(self): if self.last is not None: c = self.last self.last = None return c else: return self.fd.read(1) def ungetChar(self, c): self.last = c def parse(self): context = FormatContext() c = self.getChar() while c: if c == '\x02': context.bold = not context.bold elif c == '\x16': context.reverse = not context.reverse elif c == '\x1f': context.underline = not context.underline elif c == '\x0f': context.reset() elif c == '\x03': self.getColor(context) c = self.getChar() return context def getInt(self): i = 0 setI = False c = self.getChar() while c.isdigit() and i < 100: setI = True i *= 10 i += int(c) c = self.getChar() self.ungetChar(c) if setI: return i else: return None def getColor(self, context): context.fg = self.getInt() c = self.getChar() if c == ',': context.bg = self.getInt() def wrap(s, length): processed = [] chunks = textwrap.wrap(s, length) context = None for chunk in chunks: if context is not None: chunk = context.start(chunk) context = FormatParser(chunk).parse() processed.append(context.end(chunk)) return processed def isValidArgument(s): """Returns whether s is strictly a valid argument for an IRC message.""" return '\r' not in s and '\n' not in s and '\x00' not in s def safeArgument(s): """If s is unsafe for IRC, returns a safe version.""" if isinstance(s, unicode): s = s.encode('utf-8') elif not isinstance(s, basestring): debug('Got a non-string in safeArgument: %r', s) s = str(s) if isValidArgument(s): return s else: return repr(s) def replyTo(msg): """Returns the appropriate target to send responses to msg.""" if isChannel(msg.args[0]): return msg.args[0] else: return msg.nick def dccIP(ip): """Returns in IP in the proper for DCC.""" assert utils.net.isIP(ip), \ 'argument must be a string ip in xxx.yyy.zzz.www format.' i = 0 x = 256**3 for quad in ip.split('.'): i += int(quad)*x x /= 256 return i def unDccIP(i): """Takes an integer DCC IP and return a normal string IP.""" assert isinstance(i, (int, long)), '%r is not an number.' % i L = [] while len(L) < 4: L.append(i % 256) i /= 256 L.reverse() return '.'.join(utils.iter.imap(str, L)) class IrcString(str): """This class does case-insensitive comparison and hashing of nicks.""" def __new__(cls, s=''): x = super(IrcString, cls).__new__(cls, s) x.lowered = toLower(x) return x def __eq__(self, s): try: return toLower(s) == self.lowered except: return False def __ne__(self, s): return not (self == s) def __hash__(self): return hash(self.lowered) class IrcDict(utils.InsensitivePreservingDict): """Subclass of dict to make key comparison IRC-case insensitive.""" def key(self, s): if s is not None: s = toLower(s) return s class IrcSet(utils.NormalizingSet): """A sets.Set using IrcStrings instead of regular strings.""" def normalize(self, s): return IrcString(s) def __reduce__(self): return (self.__class__, (list(self),)) class FloodQueue(object): timeout = 0 def __init__(self, timeout=None, queues=None): if timeout is not None: self.timeout = timeout if queues is None: queues = IrcDict() self.queues = queues def __repr__(self): return 'FloodQueue(timeout=%r, queues=%s)' % (self.timeout, repr(self.queues)) def key(self, msg): return msg.user + '@' + msg.host def getTimeout(self): if callable(self.timeout): return self.timeout() else: return self.timeout def _getQueue(self, msg, insert=True): key = self.key(msg) try: return self.queues[key] except KeyError: if insert: # python-- # instancemethod.__repr__ calls the instance.__repr__, which # means that our __repr__ calls self.queues.__repr__, which # calls structures.TimeoutQueue.__repr__, which calls # getTimeout.__repr__, which calls our __repr__, which calls... getTimeout = lambda : self.getTimeout() q = utils.structures.TimeoutQueue(getTimeout) self.queues[key] = q return q else: return None def enqueue(self, msg, what=None): if what is None: what = msg q = self._getQueue(msg) q.enqueue(what) def len(self, msg): q = self._getQueue(msg, insert=False) if q is not None: return len(q) else: return 0 def has(self, msg, what=None): q = self._getQueue(msg, insert=False) if q is not None: if what is None: what = msg for elt in q: if elt == what: return True return False mircColors = IrcDict({ 'white': '0', 'black': '1', 'blue': '2', 'green': '3', 'red': '4', 'brown': '5', 'purple': '6', 'orange': '7', 'yellow': '8', 'light green': '9', 'teal': '10', 'light blue': '11', 'dark blue': '12', 'pink': '13', 'dark grey': '14', 'light grey': '15', 'dark gray': '14', 'light gray': '15', }) # We'll map integers to their string form so mircColor is simpler. for (k, v) in mircColors.items(): if k is not None: # Ignore empty string for None. sv = str(v) mircColors[sv] = sv def standardSubstitute(irc, msg, text, env=None): """Do the standard set of substitutions on text, and return it""" if isChannel(msg.args[0]): channel = msg.args[0] else: channel = 'somewhere' def randInt(): return str(random.randint(-1000, 1000)) def randDate(): t = pow(2,30)*random.random()+time.time()/4.0 return time.ctime(t) def randNick(): if channel != 'somewhere': L = list(irc.state.channels[channel].users) if len(L) > 1: n = msg.nick while n == msg.nick: n = utils.iter.choice(L) return n else: return msg.nick else: return 'someone' ctime = time.ctime() localtime = time.localtime() vars = IrcDict({ 'who': msg.nick, 'nick': msg.nick, 'user': msg.user, 'host': msg.host, 'channel': channel, 'botnick': irc.nick, 'now': ctime, 'ctime': ctime, 'randnick': randNick, 'randomnick': randNick, 'randdate': randDate, 'randomdate': randDate, 'rand': randInt, 'randint': randInt, 'randomint': randInt, 'today': time.strftime('%d %b %Y', localtime), 'year': localtime[0], 'month': localtime[1], 'monthname': time.strftime('%b', localtime), 'date': localtime[2], 'day': time.strftime('%A', localtime), 'h': localtime[3], 'hr': localtime[3], 'hour': localtime[3], 'm': localtime[4], 'min': localtime[4], 'minute': localtime[4], 's': localtime[5], 'sec': localtime[5], 'second': localtime[5], 'tz': time.tzname[time.daylight], }) if env is not None: vars.update(env) return utils.str.perlVariableSubstitute(vars, text) if __name__ == '__main__': import sys, doctest doctest.testmod(sys.modules['__main__']) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/cdb.py0000644000000000000000000003476311206611405013606 0ustar ### # 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. ### """ Database module, similar to dbhash. Uses a format similar to (if not entirely the same as) DJB's CDB . """ import os import sys import struct import os.path import cPickle as pickle import supybot.utils as utils def hash(s): """DJB's hash function for CDB.""" h = 5381 for c in s: h = ((h + (h << 5)) ^ ord(c)) & 0xFFFFFFFFL return h def unpack2Ints(s): """Returns two ints unpacked from the binary string s.""" return struct.unpack('%s\n' % (len(key), len(value), key, value)) def open(filename, mode='r', **kwargs): """Opens a database; used for compatibility with other database modules.""" if mode == 'r': return Reader(filename, **kwargs) elif mode == 'w': return ReaderWriter(filename, **kwargs) elif mode == 'c': if os.path.exists(filename): return ReaderWriter(filename, **kwargs) else: maker = Maker(filename) maker.finish() return ReaderWriter(filename, **kwargs) elif mode == 'n': maker = Maker(filename) maker.finish() return ReaderWriter(filename, **kwargs) else: raise ValueError, 'Invalid flag: %s' % mode def shelf(filename, *args, **kwargs): """Opens a new shelf database object.""" if os.path.exists(filename): return Shelf(filename, *args, **kwargs) else: maker = Maker(filename) maker.finish() return Shelf(filename, *args, **kwargs) def _readKeyValue(fd): klen = 0 dlen = 0 s = initchar = fd.read(1) if s == '': return (None, None, None) s = fd.read(1) while s != ',': klen = 10 * klen + int(s) s = fd.read(1) s = fd.read(1) while s != ':': dlen = 10 * dlen + int(s) s = fd.read(1) key = fd.read(klen) assert fd.read(2) == '->' value = fd.read(dlen) assert fd.read(1) == '\n' return (initchar, key, value) def make(dbFilename, readFilename=None): """Makes a database from the filename, otherwise uses stdin.""" if readFilename is None: readfd = sys.stdin else: readfd = file(readFilename, 'r') maker = Maker(dbFilename) while 1: (initchar, key, value) = _readKeyValue(readfd) if initchar is None: break assert initchar == '+' maker.add(key, value) readfd.close() maker.finish() class Maker(object): """Class for making CDB databases.""" def __init__(self, filename): self.fd = utils.file.AtomicFile(filename) self.filename = filename self.fd.seek(2048) self.hashPointers = [(0, 0)] * 256 #self.hashes = [[]] * 256 # Can't use this, [] stays the same... self.hashes = [] for _ in xrange(256): self.hashes.append([]) def add(self, key, data): """Adds a key->value pair to the database.""" h = hash(key) hashPointer = h % 256 startPosition = self.fd.tell() self.fd.write(pack2Ints(len(key), len(data))) self.fd.write(key) self.fd.write(data) self.hashes[hashPointer].append((h, startPosition)) def finish(self): """Finishes the current Maker object. Writes the remainder of the database to disk. """ for i in xrange(256): hash = self.hashes[i] self.hashPointers[i] = (self.fd.tell(), self._serializeHash(hash)) self._serializeHashPointers() self.fd.flush() self.fd.close() def _serializeHash(self, hash): hashLen = len(hash) * 2 a = [(0, 0)] * hashLen for (h, pos) in hash: i = (h / 256) % hashLen while a[i] != (0, 0): i = (i + 1) % hashLen a[i] = (h, pos) for (h, pos) in a: self.fd.write(pack2Ints(h, pos)) return hashLen def _serializeHashPointers(self): self.fd.seek(0) for (hashPos, hashLen) in self.hashPointers: self.fd.write(pack2Ints(hashPos, hashLen)) class Reader(utils.IterableMap): """Class for reading from a CDB database.""" def __init__(self, filename): self.filename = filename self.fd = file(filename, 'r') self.loop = 0 self.khash = 0 self.kpos = 0 self.hpos = 0 self.hslots = 0 self.dpos = 0 self.dlen = 0 def close(self): self.fd.close() def _read(self, len, pos): self.fd.seek(pos) return self.fd.read(len) def _match(self, key, pos): return self._read(len(key), pos) == key def iteritems(self): # uses loop/hslots in a strange, non-re-entrant manner. (self.loop,) = struct.unpack(' self.maxmods: self.flush() self.mods = 0 elif isinstance(self.maxmods, float): assert 0 <= self.maxmods if self.mods / max(len(self.cdb), 100) > self.maxmods: self.flush() self.mods = 0 def __getitem__(self, key): if key in self.removals: raise KeyError, key else: try: return self.adds[key] except KeyError: return self.cdb[key] # If this raises KeyError, we lack key. def __delitem__(self, key): if key in self.removals: raise KeyError, key else: if key in self.adds and key in self.cdb: self._journalRemoveKey(key) del self.adds[key] self.removals.add(key) elif key in self.adds: self._journalRemoveKey(key) del self.adds[key] elif key in self.cdb: self._journalRemoveKey(key) else: raise KeyError, key self.mods += 1 self._flushIfOverLimit() def __setitem__(self, key, value): if key in self.removals: self.removals.remove(key) self._journalAddKey(key, value) self.adds[key] = value self.mods += 1 self._flushIfOverLimit() def __contains__(self, key): if key in self.removals: return False else: return key in self.adds or key in self.cdb has_key = __contains__ def iteritems(self): already = set() for (key, value) in self.cdb.iteritems(): if key in self.removals or key in already: continue elif key in self.adds: already.add(key) yield (key, self.adds[key]) else: yield (key, value) for (key, value) in self.adds.iteritems(): if key not in already: yield (key, value) def setdefault(self, key, value): try: return self[key] except KeyError: self[key] = value return value def get(self, key, default=None): try: return self[key] except KeyError: return default class Shelf(ReaderWriter): """Uses pickle to mimic the shelf module.""" def __getitem__(self, key): return pickle.loads(ReaderWriter.__getitem__(self, key)) def __setitem__(self, key, value): ReaderWriter.__setitem__(self, key, pickle.dumps(value, True)) def iteritems(self): for (key, value) in ReaderWriter.iteritems(self): yield (key, pickle.loads(value)) if __name__ == '__main__': if sys.argv[0] == 'cdbdump': if len(sys.argv) == 2: fd = file(sys.argv[1], 'r') else: fd = sys.stdin db = Reader(fd) dump(db) elif sys.argv[0] == 'cdbmake': if len(sys.argv) == 2: make(sys.argv[1]) else: make(sys.argv[1], sys.argv[2]) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/callbacks.py0000644000000000000000000015463111206611405014772 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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. ### """ This module contains the basic callbacks for handling PRIVMSGs. """ import supybot import re import copy import time import shlex import getopt import inspect import operator from cStringIO import StringIO import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry from supybot.utils.iter import any, all def _addressed(nick, msg, prefixChars=None, nicks=None, prefixStrings=None, whenAddressedByNick=None, whenAddressedByNickAtEnd=None): def get(group): if ircutils.isChannel(target): group = group.get(target) return group() def stripPrefixStrings(payload): for prefixString in prefixStrings: if payload.startswith(prefixString): payload = payload[len(prefixString):].lstrip() return payload assert msg.command == 'PRIVMSG' (target, payload) = msg.args if not payload: return '' if prefixChars is None: prefixChars = get(conf.supybot.reply.whenAddressedBy.chars) if whenAddressedByNick is None: whenAddressedByNick = get(conf.supybot.reply.whenAddressedBy.nick) if whenAddressedByNickAtEnd is None: r = conf.supybot.reply.whenAddressedBy.nick.atEnd whenAddressedByNickAtEnd = get(r) if prefixStrings is None: prefixStrings = get(conf.supybot.reply.whenAddressedBy.strings) # We have to check this before nicks -- try "@google supybot" with supybot # and whenAddressedBy.nick.atEnd on to see why. if any(payload.startswith, prefixStrings): return stripPrefixStrings(payload) elif payload[0] in prefixChars: return payload[1:].strip() if nicks is None: nicks = get(conf.supybot.reply.whenAddressedBy.nicks) nicks = map(ircutils.toLower, nicks) else: nicks = list(nicks) # Just in case. nicks.insert(0, ircutils.toLower(nick)) # Ok, let's see if it's a private message. if ircutils.nickEqual(target, nick): payload = stripPrefixStrings(payload) while payload and payload[0] in prefixChars: payload = payload[1:].lstrip() return payload # Ok, not private. Does it start with our nick? elif whenAddressedByNick: for nick in nicks: lowered = ircutils.toLower(payload) if lowered.startswith(nick): try: (maybeNick, rest) = payload.split(None, 1) toContinue = False while not ircutils.isNick(maybeNick, strictRfc=True): if maybeNick[-1].isalnum(): toContinue = True break maybeNick = maybeNick[:-1] if toContinue: continue if ircutils.nickEqual(maybeNick, nick): return rest else: continue except ValueError: # split didn't work. continue elif whenAddressedByNickAtEnd and lowered.endswith(nick): rest = payload[:-len(nick)] possiblePayload = rest.rstrip(' \t,;') if possiblePayload != rest: # There should be some separator between the nick and the # previous alphanumeric character. return possiblePayload if conf.supybot.reply.whenNotAddressed(): return payload else: return '' def addressed(nick, msg, **kwargs): """If msg is addressed to 'name', returns the portion after the address. Otherwise returns the empty string. """ payload = msg.addressed if payload is not None: return payload else: payload = _addressed(nick, msg, **kwargs) msg.tag('addressed', payload) return payload def canonicalName(command): """Turn a command into its canonical form. Currently, this makes everything lowercase and removes all dashes and underscores. """ if isinstance(command, unicode): command = command.encode('utf-8') special = '\t -_' reAppend = '' while command and command[-1] in special: reAppend = command[-1] + reAppend command = command[:-1] return command.translate(utils.str.chars, special).lower() + reAppend def reply(msg, s, prefixNick=None, private=None, notice=None, to=None, action=None, error=False): msg.tag('repliedTo') # Ok, let's make the target: # XXX This isn't entirely right. Consider to=#foo, private=True. target = ircutils.replyTo(msg) if ircutils.isChannel(to): target = to if ircutils.isChannel(target): channel = target else: channel = None if notice is None: notice = conf.get(conf.supybot.reply.withNotice, channel) if private is None: private = conf.get(conf.supybot.reply.inPrivate, channel) if prefixNick is None: prefixNick = conf.get(conf.supybot.reply.withNickPrefix, channel) if error: notice =conf.get(conf.supybot.reply.error.withNotice, channel) or notice private=conf.get(conf.supybot.reply.error.inPrivate, channel) or private s = 'Error: ' + s if private: prefixNick = False if to is None: target = msg.nick else: target = to if action: prefixNick = False if to is None: to = msg.nick # Ok, now let's make the payload: s = ircutils.safeArgument(s) if not s and not action: s = 'Error: I tried to send you an empty message.' if prefixNick and ircutils.isChannel(target): # Let's may sure we don't do, "#channel: foo.". if not ircutils.isChannel(to): s = '%s: %s' % (to, s) if not ircutils.isChannel(target): if conf.supybot.reply.withNoticeWhenPrivate(): notice = True # And now, let's decide whether it's a PRIVMSG or a NOTICE. msgmaker = ircmsgs.privmsg if notice: msgmaker = ircmsgs.notice # We don't use elif here because actions can't be sent as NOTICEs. if action: msgmaker = ircmsgs.action # Finally, we'll return the actual message. ret = msgmaker(target, s) ret.tag('inReplyTo', msg) return ret def error(msg, s, **kwargs): """Makes an error reply to msg with the appropriate error payload.""" kwargs['error'] = True msg.tag('isError') return reply(msg, s, **kwargs) def getHelp(method, name=None, doc=None): if name is None: name = method.__name__ if doc is None: doclines = method.__doc__.splitlines() else: doclines = doc.splitlines() s = '%s %s' % (name, doclines.pop(0)) if doclines: help = ' '.join(doclines) s = '(%s) -- %s' % (ircutils.bold(s), help) return utils.str.normalizeWhitespace(s) def getSyntax(method, name=None, doc=None): if name is None: name = method.__name__ if doc is None: doclines = method.__doc__.splitlines() else: doclines = doc.splitlines() return '%s %s' % (name, doclines[0]) class Error(Exception): """Generic class for errors in Privmsg callbacks.""" pass class ArgumentError(Error): """The bot replies with a help message when this is raised.""" pass class Tokenizer(object): # This will be used as a global environment to evaluate strings in. # Evaluation is, of course, necessary in order to allowed escaped # characters to be properly handled. # # These are the characters valid in a token. Everything printable except # double-quote, left-bracket, and right-bracket. validChars = utils.str.chars.translate(utils.str.chars, '\x00\r\n \t') def __init__(self, brackets='', pipe=False, quotes='"'): if brackets: self.validChars=self.validChars.translate(utils.str.chars, brackets) self.left = brackets[0] self.right = brackets[1] else: self.left = '' self.right = '' self.pipe = pipe if self.pipe: self.validChars = self.validChars.translate(utils.str.chars, '|') self.quotes = quotes self.validChars = self.validChars.translate(utils.str.chars, quotes) def _handleToken(self, token): if token[0] == token[-1] and token[0] in self.quotes: token = token[1:-1] token = token.decode('string-escape') return token def _insideBrackets(self, lexer): ret = [] while True: token = lexer.get_token() if not token: raise SyntaxError, 'Missing "%s". You may want to ' \ 'quote your arguments with double ' \ 'quotes in order to prevent extra ' \ 'brackets from being evaluated ' \ 'as nested commands.' % self.right elif token == self.right: return ret elif token == self.left: ret.append(self._insideBrackets(lexer)) else: ret.append(self._handleToken(token)) firstToken = False return ret def tokenize(self, s): lexer = shlex.shlex(StringIO(s)) lexer.commenters = '' lexer.quotes = self.quotes lexer.wordchars = self.validChars args = [] ends = [] while True: token = lexer.get_token() if not token: break elif token == '|' and self.pipe: # The "and self.pipe" might seem redundant here, but it's there # for strings like 'foo | bar', where a pipe stands alone as a # token, but shouldn't be treated specially. if not args: raise SyntaxError, '"|" with nothing preceding. I ' \ 'obviously can\'t do a pipe with ' \ 'nothing before the |.' ends.append(args) args = [] elif token == self.left: args.append(self._insideBrackets(lexer)) elif token == self.right: raise SyntaxError, 'Spurious "%s". You may want to ' \ 'quote your arguments with double ' \ 'quotes in order to prevent extra ' \ 'brackets from being evaluated ' \ 'as nested commands.' % self.right else: args.append(self._handleToken(token)) if ends: if not args: raise SyntaxError, '"|" with nothing following. I ' \ 'obviously can\'t do a pipe with ' \ 'nothing before the |.' args.append(ends.pop()) while ends: args[-1].append(ends.pop()) return args def tokenize(s, channel=None): """A utility function to create a Tokenizer and tokenize a string.""" pipe = False brackets = '' nested = conf.supybot.commands.nested if nested(): brackets = conf.get(nested.brackets, channel) if conf.get(nested.pipeSyntax, channel): # No nesting, no pipe. pipe = True quotes = conf.get(conf.supybot.commands.quotes, channel) start = time.time() try: ret = Tokenizer(brackets=brackets,pipe=pipe,quotes=quotes).tokenize(s) return ret except ValueError, e: raise SyntaxError, str(e) def formatCommand(command): return ' '.join(command) def checkCommandCapability(msg, cb, commandName): assert isinstance(commandName, basestring), commandName plugin = cb.name().lower() pluginCommand = '%s.%s' % (plugin, commandName) def checkCapability(capability): assert ircdb.isAntiCapability(capability) if ircdb.checkCapability(msg.prefix, capability): log.info('Preventing %s from calling %s because of %s.', msg.prefix, pluginCommand, capability) raise RuntimeError, capability try: antiPlugin = ircdb.makeAntiCapability(plugin) antiCommand = ircdb.makeAntiCapability(commandName) antiPluginCommand = ircdb.makeAntiCapability(pluginCommand) checkCapability(antiPlugin) checkCapability(antiCommand) checkCapability(antiPluginCommand) checkAtEnd = [commandName, pluginCommand] default = conf.supybot.capabilities.default() if ircutils.isChannel(msg.args[0]): channel = msg.args[0] checkCapability(ircdb.makeChannelCapability(channel, antiCommand)) checkCapability(ircdb.makeChannelCapability(channel, antiPlugin)) checkCapability(ircdb.makeChannelCapability(channel, antiPluginCommand)) chanPlugin = ircdb.makeChannelCapability(channel, plugin) chanCommand = ircdb.makeChannelCapability(channel, commandName) chanPluginCommand = ircdb.makeChannelCapability(channel, pluginCommand) checkAtEnd += [chanCommand, chanPlugin, chanPluginCommand] default &= ircdb.channels.getChannel(channel).defaultAllow return not (default or \ any(lambda x: ircdb.checkCapability(msg.prefix, x), checkAtEnd)) except RuntimeError, e: s = ircdb.unAntiCapability(str(e)) return s class RichReplyMethods(object): """This is a mixin so these replies need only be defined once. It operates under several assumptions, including the fact that 'self' is an Irc object of some sort and there is a self.msg that is an IrcMsg.""" def __makeReply(self, prefix, s): if s: s = '%s %s' % (prefix, s) else: s = prefix return ircutils.standardSubstitute(self, self.msg, s) def _getConfig(self, wrapper): return conf.get(wrapper, self.msg.args[0]) def replySuccess(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.success) if v: s = self.__makeReply(v, s) return self.reply(s, **kwargs) else: self.noReply() def replyError(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.error) s = self.__makeReply(v, s) return self.reply(s, **kwargs) def replies(self, L, prefixer=None, joiner=None, onlyPrefixFirst=False, **kwargs): if prefixer is None: prefixer = '' if joiner is None: joiner = utils.str.commaAndify if isinstance(prefixer, basestring): prefixer = prefixer.__add__ if isinstance(joiner, basestring): joiner = joiner.join if conf.supybot.reply.oneToOne(): return self.reply(prefixer(joiner(L)), **kwargs) else: msg = None first = True for s in L: if onlyPrefixFirst: if first: first = False msg = self.reply(prefixer(s), **kwargs) else: msg = self.reply(s, **kwargs) else: msg = self.reply(prefixer(s), **kwargs) return msg def noReply(self): self.repliedTo = True def _error(self, s, Raise=False, **kwargs): if Raise: raise Error, s else: return self.error(s, **kwargs) def errorNoCapability(self, capability, s='', **kwargs): if 'Raise' not in kwargs: kwargs['Raise'] = True if isinstance(capability, basestring): # checkCommandCapability! log.warning('Denying %s for lacking %q capability.', self.msg.prefix, capability) if not self._getConfig(conf.supybot.reply.error.noCapability): v = self._getConfig(conf.supybot.replies.noCapability) s = self.__makeReply(v % capability, s) return self._error(s, **kwargs) else: log.debug('Not sending capability error, ' 'supybot.reply.error.noCapability is False.') else: log.warning('Denying %s for some unspecified capability ' '(or a default).', self.msg.prefix) v = self._getConfig(conf.supybot.replies.genericNoCapability) return self._error(self.__makeReply(v, s), **kwargs) def errorPossibleBug(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.possibleBug) if s: s += ' (%s)' % v else: s = v return self._error(s, **kwargs) def errorNotRegistered(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.notRegistered) return self._error(self.__makeReply(v, s), **kwargs) def errorNoUser(self, s='', name='that user', **kwargs): if 'Raise' not in kwargs: kwargs['Raise'] = True v = self._getConfig(conf.supybot.replies.noUser) try: v = v % name except TypeError: log.warning('supybot.replies.noUser should have one "%s" in it.') return self._error(self.__makeReply(v, s), **kwargs) def errorRequiresPrivacy(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.requiresPrivacy) return self._error(self.__makeReply(v, s), **kwargs) def errorInvalid(self, what, given=None, s='', repr=True, **kwargs): if given is not None: if repr: given = _repr(given) else: given = '"%s"' % given v = '%s is not a valid %s.' % (given, what) else: v = 'That\'s not a valid %s.' % what if 'Raise' not in kwargs: kwargs['Raise'] = True return self._error(self.__makeReply(v, s), **kwargs) _repr = repr class ReplyIrcProxy(RichReplyMethods): """This class is a thin wrapper around an irclib.Irc object that gives it the reply() and error() methods (as well as everything in RichReplyMethods, based on those two).""" def __init__(self, irc, msg): self.irc = irc self.msg = msg def getRealIrc(self): if isinstance(self.irc, irclib.Irc): return self.irc else: return self.irc.getRealIrc() # This should make us be considered equal to our irclib.Irc object for # hashing; an important thing (no more "too many open files" exceptions :)) def __hash__(self): return hash(self.getRealIrc()) def __eq__(self, other): return self.getRealIrc() == other __req__ = __eq__ def __ne__(self, other): return not (self == other) __rne__ = __ne__ def error(self, s, msg=None, **kwargs): if 'Raise' in kwargs and kwargs['Raise']: if s: raise Error, s else: raise ArgumentError if msg is None: msg = self.msg m = error(msg, s, **kwargs) self.irc.queueMsg(m) return m def reply(self, s, msg=None, **kwargs): if msg is None: msg = self.msg assert not isinstance(s, ircmsgs.IrcMsg), \ 'Old code alert: there is no longer a "msg" argument to reply.' kwargs.pop('noLengthCheck', None) m = reply(msg, s, **kwargs) self.irc.queueMsg(m) return m def __getattr__(self, attr): return getattr(self.irc, attr) SimpleProxy = ReplyIrcProxy # Backwards-compatibility class NestedCommandsIrcProxy(ReplyIrcProxy): "A proxy object to allow proper nested of commands (even threaded ones)." _mores = ircutils.IrcDict() def __init__(self, irc, msg, args, nested=0): assert isinstance(args, list), 'Args should be a list, not a string.' self.irc = irc self.msg = msg self.nested = nested self.repliedTo = False if not self.nested and isinstance(irc, self.__class__): # This means we were given an NestedCommandsIrcProxy instead of an # irclib.Irc, and so we're obviously nested. But nested wasn't # set! So we take our given Irc's nested value. self.nested += irc.nested maxNesting = conf.supybot.commands.nested.maximum() if maxNesting and self.nested > maxNesting: log.warning('%s attempted more than %s levels of nesting.', self.msg.prefix, maxNesting) return self.error('You\'ve attempted more nesting than is ' 'currently allowed on this bot.') # The deepcopy here is necessary for Scheduler; it re-runs already # tokenized commands. There's a possibility a simple copy[:] would # work, but we're being careful. self.args = copy.deepcopy(args) self.counter = 0 self._resetReplyAttributes() if not args: self.finalEvaled = True self._callInvalidCommands() else: self.finalEvaled = False world.commandsProcessed += 1 self.evalArgs() def __eq__(self, other): return other == self.getRealIrc() def __hash__(self): return hash(self.getRealIrc()) def _resetReplyAttributes(self): self.to = None self.action = None self.notice = None self.private = None self.noLengthCheck = None if ircutils.isChannel(self.msg.args[0]): self.prefixNick = conf.get(conf.supybot.reply.withNickPrefix, self.msg.args[0]) else: self.prefixNick = conf.supybot.reply.withNickPrefix() def evalArgs(self): while self.counter < len(self.args): self.repliedTo = False if isinstance(self.args[self.counter], basestring): # If it's a string, just go to the next arg. There is no # evaluation to be done for strings. If, at some point, # we decided to, say, convert every string using # ircutils.standardSubstitute, this would be where we would # probably put it. self.counter += 1 else: assert isinstance(self.args[self.counter], list) # It's a list. So we spawn another NestedCommandsIrcProxy # to evaluate its args. When that class has finished # evaluating its args, it will call our reply method, which # will subsequently call this function again, and we'll # pick up where we left off via self.counter. self.__class__(self, self.msg, self.args[self.counter], nested=self.nested+1) # We have to return here because the new NestedCommandsIrcProxy # might not have called our reply method instantly, since # its command might be threaded. So (obviously) we can't # just fall through to self.finalEval. return # Once all the list args are evaluated, we then evaluate our own # list of args, since we're assured that they're all strings now. assert all(lambda x: isinstance(x, basestring), self.args) self.finalEval() def _callInvalidCommands(self): log.debug('Calling invalidCommands.') threaded = False cbs = [] for cb in self.irc.callbacks: if hasattr(cb, 'invalidCommand'): cbs.append(cb) threaded = threaded or cb.threaded def callInvalidCommands(): self.repliedTo = False for cb in cbs: log.debug('Calling %s.invalidCommand.', cb.name()) try: cb.invalidCommand(self, self.msg, self.args) except Error, e: self.error(str(e)) except Exception, e: log.exception('Uncaught exception in %s.invalidCommand.', cb.name()) log.debug('Finished calling %s.invalidCommand.', cb.name()) if self.repliedTo: log.debug('Done calling invalidCommands: %s.',cb.name()) return if threaded: name = 'Thread #%s (for invalidCommands)' % world.threadsSpawned t = world.SupyThread(target=callInvalidCommands, name=name) t.setDaemon(True) t.start() else: callInvalidCommands() def findCallbacksForArgs(self, args): """Returns a two-tuple of (command, plugins) that has the command (a list of strings) and the plugins for which it was a command.""" assert isinstance(args, list) args = map(canonicalName, args) cbs = [] maxL = [] for cb in self.irc.callbacks: if not hasattr(cb, 'getCommand'): continue L = cb.getCommand(args) #log.debug('%s.getCommand(%r) returned %r', cb.name(), args, L) if L and L >= maxL: maxL = L cbs.append((cb, L)) assert isinstance(L, list), \ 'getCommand now returns a list, not a method.' assert utils.iter.startswith(L, args), \ 'getCommand must return a prefix of the args given. ' \ '(args given: %r, returned: %r)' % (args, L) log.debug('findCallbacksForArgs: %r', cbs) cbs = [cb for (cb, L) in cbs if L == maxL] if len(maxL) == 1: # Special case: one arg determines the callback. In this case, we # have to check, in order: # 1. Whether the arg is the same as the name of a callback. This # callback would then win. for cb in cbs: if cb.canonicalName() == maxL[0]: return (maxL, [cb]) # 2. Whether a defaultplugin is defined. defaultPlugins = conf.supybot.commands.defaultPlugins try: defaultPlugin = defaultPlugins.get(maxL[0])() log.debug('defaultPlugin: %r', defaultPlugin) if defaultPlugin: cb = self.irc.getCallback(defaultPlugin) if cb in cbs: # This is just a sanity check, but there's a small # possibility that a default plugin for a command # is configured to point to a plugin that doesn't # actually have that command. return (maxL, [cb]) except registry.NonExistentRegistryEntry: pass # 3. Whether an importantPlugin is one of the responses. important = defaultPlugins.importantPlugins() important = map(canonicalName, important) importants = [] for cb in cbs: if cb.canonicalName() in important: importants.append(cb) if len(importants) == 1: return (maxL, importants) return (maxL, cbs) def finalEval(self): # Now that we've already iterated through our args and made sure # that any list of args was evaluated (by spawning another # NestedCommandsIrcProxy to evaluated it into a string), we can finally # evaluated our own list of arguments. assert not self.finalEvaled, 'finalEval called twice.' self.finalEvaled = True # Now, the way we call a command is we iterate over the loaded pluings, # asking each one if the list of args we have interests it. The # way we do that is by calling getCommand on the plugin. # The plugin will return a list of args which it considers to be # "interesting." We will then give our args to the plugin which # has the *longest* list. The reason we pick the longest list is # that it seems reasonable that the longest the list, the more # specific the command is. That is, given a list of length X, a list # of length X+1 would be even more specific (assuming that both lists # used the same prefix. Of course, if two plugins return a list of the # same length, we'll just error out with a message about ambiguity. (command, cbs) = self.findCallbacksForArgs(self.args) if not cbs: # We used to handle addressedRegexps here, but I think we'll let # them handle themselves in getCommand. They can always just # return the full list of args as their "command". self._callInvalidCommands() elif len(cbs) > 1: names = sorted([cb.name() for cb in cbs]) command = formatCommand(command) self.error(format('The command %q is available in the %L ' 'plugins. Please specify the plugin ' 'whose command you wish to call by using ' 'its name as a command before %q.', command, names, command)) else: cb = cbs[0] args = self.args[len(command):] if world.isMainThread() and \ (cb.threaded or conf.supybot.debug.threadAllCommands()): t = CommandThread(target=cb._callCommand, args=(command, self, self.msg, args)) t.start() else: cb._callCommand(command, self, self.msg, args) def reply(self, s, noLengthCheck=False, prefixNick=None, action=None, private=None, notice=None, to=None, msg=None): """reply(s) -> replies to msg with s Keyword arguments: noLengthCheck=False: True if the length shouldn't be checked (used for 'more' handling) prefixNick=True: False if the nick shouldn't be prefixed to the reply. action=False: True if the reply should be an action. private=False: True if the reply should be in private. notice=False: True if the reply should be noticed when the bot is configured to do so. to=: The nick or channel the reply should go to. Defaults to msg.args[0] (or msg.nick if private) """ # These use and or or based on whether or not they default to True or # False. Those that default to True use and; those that default to # False use or. assert not isinstance(s, ircmsgs.IrcMsg), \ 'Old code alert: there is no longer a "msg" argument to reply.' self.repliedTo = True if msg is None: msg = self.msg if prefixNick is not None: self.prefixNick = prefixNick if action is not None: self.action = self.action or action if action: self.prefixNick = False if notice is not None: self.notice = self.notice or notice if private is not None: self.private = self.private or private if to is not None: self.to = self.to or to # action=True implies noLengthCheck=True and prefixNick=False self.noLengthCheck=noLengthCheck or self.noLengthCheck or self.action target = self.private and self.to or self.msg.args[0] s = str(s) # Allow non-string esses. if self.finalEvaled: try: if isinstance(self.irc, self.__class__): s = s[:conf.supybot.reply.maximumLength()] return self.irc.reply(s, to=self.to, notice=self.notice, action=self.action, private=self.private, prefixNick=self.prefixNick, noLengthCheck=self.noLengthCheck) elif self.noLengthCheck: # noLengthCheck only matters to NestedCommandsIrcProxy, so # it's not used here. Just in case you were wondering. m = reply(msg, s, to=self.to, notice=self.notice, action=self.action, private=self.private, prefixNick=self.prefixNick) self.irc.queueMsg(m) return m else: s = ircutils.safeArgument(s) allowedLength = conf.get(conf.supybot.reply.mores.length, target) if not allowedLength: # 0 indicates this. allowedLength = 450 - len(self.irc.prefix) allowedLength -= len(msg.nick) maximumMores = conf.get(conf.supybot.reply.mores.maximum, target) maximumLength = allowedLength * maximumMores if len(s) > maximumLength: log.warning('Truncating to %s bytes from %s bytes.', maximumLength, len(s)) s = s[:maximumLength] if len(s) < allowedLength or \ not conf.get(conf.supybot.reply.mores, target): # In case we're truncating, we add 20 to allowedLength, # because our allowedLength is shortened for the # "(XX more messages)" trailer. s = s[:allowedLength+20] # There's no need for action=self.action here because # action implies noLengthCheck, which has already been # handled. Let's stick an assert in here just in case. assert not self.action m = reply(msg, s, to=self.to, notice=self.notice, private=self.private, prefixNick=self.prefixNick) self.irc.queueMsg(m) return m msgs = ircutils.wrap(s, allowedLength) msgs.reverse() instant = conf.get(conf.supybot.reply.mores.instant,target) while instant > 1 and msgs: instant -= 1 response = msgs.pop() m = reply(msg, response, to=self.to, notice=self.notice, private=self.private, prefixNick=self.prefixNick) self.irc.queueMsg(m) # XXX We should somehow allow these to be returned, but # until someone complains, we'll be fine :) We # can't return from here, though, for obvious # reasons. # return m if not msgs: return response = msgs.pop() if msgs: n = ircutils.bold('(%s)') n %= format('%n', (len(msgs), 'more', 'message')) response = '%s %s' % (response, n) prefix = msg.prefix if self.to and ircutils.isNick(self.to): try: state = self.getRealIrc().state prefix = state.nickToHostmask(self.to) except KeyError: pass # We'll leave it as it is. mask = prefix.split('!', 1)[1] self._mores[mask] = msgs public = ircutils.isChannel(msg.args[0]) private = self.private or not public self._mores[msg.nick] = (private, msgs) m = reply(msg, response, to=self.to, action=self.action, notice=self.notice, private=self.private, prefixNick=self.prefixNick) self.irc.queueMsg(m) return m finally: self._resetReplyAttributes() else: self.args[self.counter] = s self.evalArgs() def error(self, s='', Raise=False, **kwargs): self.repliedTo = True if Raise: if s: raise Error, s else: raise ArgumentError if s: if not isinstance(self.irc, irclib.Irc): return self.irc.error(s, **kwargs) else: m = error(self.msg, s, **kwargs) self.irc.queueMsg(m) return m else: raise ArgumentError def getRealIrc(self): """Returns the real irclib.Irc object underlying this proxy chain.""" if isinstance(self.irc, irclib.Irc): return self.irc else: return self.irc.getRealIrc() def __getattr__(self, attr): return getattr(self.irc, attr) IrcObjectProxy = NestedCommandsIrcProxy class CommandThread(world.SupyThread): """Just does some extra logging and error-recovery for commands that need to run in threads. """ def __init__(self, target=None, args=(), kwargs={}): self.command = args[0] self.cb = target.im_self threadName = 'Thread #%s (for %s.%s)' % (world.threadsSpawned, self.cb.name(), self.command) log.debug('Spawning thread %s (args: %r)', threadName, args) self.__parent = super(CommandThread, self) self.__parent.__init__(target=target, name=threadName, args=args, kwargs=kwargs) self.setDaemon(True) self.originalThreaded = self.cb.threaded self.cb.threaded = True def run(self): try: self.__parent.run() finally: self.cb.threaded = self.originalThreaded class CanonicalString(registry.NormalizedString): def normalize(self, s): return canonicalName(s) class CanonicalNameSet(utils.NormalizingSet): def normalize(self, s): return canonicalName(s) class CanonicalNameDict(utils.InsensitivePreservingDict): def key(self, s): return canonicalName(s) class Disabled(registry.SpaceSeparatedListOf): sorted = True Value = CanonicalString List = CanonicalNameSet conf.registerGlobalValue(conf.supybot.commands, 'disabled', Disabled([], """Determines what commands are currently disabled. Such commands will not appear in command lists, etc. They will appear not even to exist.""")) class DisabledCommands(object): def __init__(self): self.d = CanonicalNameDict() for name in conf.supybot.commands.disabled(): if '.' in name: (plugin, command) = name.split('.', 1) if command in self.d: if self.d[command] is not None: self.d[command].add(plugin) else: self.d[command] = CanonicalNameSet([plugin]) else: self.d[name] = None def disabled(self, command, plugin=None): if command in self.d: if self.d[command] is None: return True elif plugin in self.d[command]: return True return False def add(self, command, plugin=None): if plugin is None: self.d[command] = None else: if command in self.d: if self.d[command] is not None: self.d[command].add(plugin) else: self.d[command] = CanonicalNameSet([plugin]) def remove(self, command, plugin=None): if plugin is None: del self.d[command] else: if self.d[command] is not None: self.d[command].remove(plugin) class BasePlugin(object): def __init__(self, *args, **kwargs): self.cbs = [] for attr in dir(self): if attr != canonicalName(attr): continue obj = getattr(self, attr) if isinstance(obj, type) and issubclass(obj, BasePlugin): cb = obj(*args, **kwargs) setattr(self, attr, cb) self.cbs.append(cb) cb.log = log.getPluginLogger('%s.%s' % (self.name(),cb.name())) super(BasePlugin, self).__init__() class SynchronizedAndFirewalled(log.MetaFirewall, utils.python.Synchronized): pass # Necessary for the metaclass compatibility issue. class Commands(BasePlugin): __metaclass__ = SynchronizedAndFirewalled __synchronized__ = ( '__call__', 'callCommand', 'invalidCommand', ) # For awhile, a comment stood here to say, "Eventually callCommand." But # that's wrong, because we can't do generic error handling in this # callCommand -- plugins need to be able to override callCommand and do # error handling there (see the Web plugin for an example). __firewalled__ = {'isCommand': None, '_callCommand': None} commandArgs = ['self', 'irc', 'msg', 'args'] # These must be class-scope, so all plugins use the same one. _disabled = DisabledCommands() def name(self): return self.__class__.__name__ def canonicalName(self): return canonicalName(self.name()) def isDisabled(self, command): return self._disabled.disabled(command, self.name()) def isCommandMethod(self, name): """Returns whether a given method name is a command in this plugin.""" # This function is ugly, but I don't want users to call methods like # doPrivmsg or __init__ or whatever, and this is good to stop them. # Don't canonize this name: consider outFilter(self, irc, msg). # name = canonicalName(name) if self.isDisabled(name): return False if name != canonicalName(name): return False if hasattr(self, name): method = getattr(self, name) if inspect.ismethod(method): code = method.im_func.func_code return inspect.getargs(code)[0] == self.commandArgs else: return False else: return False def isCommand(self, command): """Convenience, backwards-compatibility, semi-deprecated.""" if isinstance(command, basestring): return self.isCommandMethod(command) else: # Since we're doing a little type dispatching here, let's not be # too liberal. assert isinstance(command, list) return self.getCommand(command) == command def getCommand(self, args): assert args == map(canonicalName, args) first = args[0] for cb in self.cbs: if first == cb.canonicalName(): return cb.getCommand(args) if first == self.canonicalName() and len(args) > 1: ret = self.getCommand(args[1:]) if ret: return [first] + ret if self.isCommandMethod(first): return [first] return [] def getCommandMethod(self, command): """Gets the given command from this plugin.""" #print '*** %s.getCommandMethod(%r)' % (self.name(), command) assert not isinstance(command, basestring) assert command == map(canonicalName, command) assert self.getCommand(command) == command for cb in self.cbs: if command[0] == cb.canonicalName(): return cb.getCommandMethod(command) if len(command) > 1: assert command[0] == self.canonicalName() return self.getCommandMethod(command[1:]) else: method = getattr(self, command[0]) if inspect.ismethod(method): code = method.im_func.func_code if inspect.getargs(code)[0] == self.commandArgs: return method else: raise AttributeError def listCommands(self, pluginCommands=[]): commands = set(pluginCommands) for s in dir(self): if self.isCommandMethod(s): commands.add(s) for cb in self.cbs: name = cb.canonicalName() for command in cb.listCommands(): if command == name: commands.add(command) else: commands.add(' '.join([name, command])) L = list(commands) L.sort() return L def callCommand(self, command, irc, msg, *args, **kwargs): method = self.getCommandMethod(command) method(irc, msg, *args, **kwargs) def _callCommand(self, command, irc, msg, *args, **kwargs): self.log.info('%s called by %q.', formatCommand(command), msg.prefix) # XXX I'm being extra-special-careful here, but we need to refactor # this. try: for name in command: cap = checkCommandCapability(msg, self, name) if cap: irc.errorNoCapability(cap) return try: self.callingCommand = command self.callCommand(command, irc, msg, *args, **kwargs) finally: self.callingCommand = None except (getopt.GetoptError, ArgumentError), e: self.log.debug('Got %s, giving argument error.', utils.exnToString(e)) help = self.getCommandHelp(command) if help.endswith('command has no help.'): irc.error('Invalid arguments for %s.' % method.__name__) else: irc.reply(help) except (SyntaxError, Error), e: self.log.debug('Error return: %s', utils.exnToString(e)) irc.error(str(e)) except Exception, e: self.log.exception('Uncaught exception in %s.', command) if conf.supybot.reply.error.detailed(): irc.error(utils.exnToString(e)) else: irc.replyError() def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) help = getHelp chan = None if dynamic.msg is not None: chan = dynamic.msg.args[0] if simpleSyntax is None: simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan) if simpleSyntax: help = getSyntax if hasattr(method, '__doc__'): return help(method, name=formatCommand(command)) else: return format('The %q command has no help.',formatCommand(command)) class PluginMixin(BasePlugin, irclib.IrcCallback): public = True alwaysCall = () threaded = False noIgnore = False classModule = None Proxy = NestedCommandsIrcProxy def __init__(self, irc): myName = self.name() self.log = log.getPluginLogger(myName) self.__parent = super(PluginMixin, self) self.__parent.__init__(irc) # We can't do this because of the specialness that Owner and Misc do. # I guess plugin authors will have to get the capitalization right. # self.callAfter = map(str.lower, self.callAfter) # self.callBefore = map(str.lower, self.callBefore) def canonicalName(self): return canonicalName(self.name()) def __call__(self, irc, msg): irc = SimpleProxy(irc, msg) if msg.command == 'PRIVMSG': if self.noIgnore or \ not ircdb.checkIgnored(msg.prefix,msg.args[0]) or \ not ircutils.isUserHostmask(msg.prefix): # Some services impl. self.__parent.__call__(irc, msg) else: self.__parent.__call__(irc, msg) def registryValue(self, name, channel=None, value=True): plugin = self.name() group = conf.supybot.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) if channel is not None: if ircutils.isChannel(channel): group = group.get(channel) else: self.log.debug('registryValue got channel=%r', channel) if value: return group() else: return group def setRegistryValue(self, name, value, channel=None): plugin = self.name() group = conf.supybot.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) if channel is None: group.setValue(value) else: group.get(channel).setValue(value) def userValue(self, name, prefixOrName, default=None): try: id = str(ircdb.users.getUserId(prefixOrName)) except KeyError: return None plugin = self.name() group = conf.users.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) return group.get(id)() def setUserValue(self, name, prefixOrName, value, ignoreNoUser=True, setValue=True): try: id = str(ircdb.users.getUserId(prefixOrName)) except KeyError: if ignoreNoUser: return else: raise plugin = self.name() group = conf.users.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) group = group.get(id) if setValue: group.setValue(value) else: group.set(value) def getPluginHelp(self): if hasattr(self, '__doc__'): return self.__doc__ else: return None class Plugin(PluginMixin, Commands): pass Privmsg = Plugin # Backwards compatibility. class PluginRegexp(Plugin): """Same as Plugin, except allows the user to also include regexp-based callbacks. All regexp-based callbacks must be specified in the set (or list) attribute "regexps", "addressedRegexps", or "unaddressedRegexps" depending on whether they should always be triggered, triggered only when the bot is addressed, or triggered only when the bot isn't addressed. """ flags = re.I # 'regexps' methods are called whether the message is addressed or not. regexps = () # 'addressedRegexps' methods are called only when the message is addressed, # and then, only with the payload (i.e., what is returned from the # 'addressed' function. addressedRegexps = () # 'unaddressedRegexps' methods are called only when the message is *not* # addressed. unaddressedRegexps = () Proxy = SimpleProxy def __init__(self, irc): self.__parent = super(PluginRegexp, self) self.__parent.__init__(irc) self.res = [] self.addressedRes = [] self.unaddressedRes = [] for name in self.regexps: method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.res.append((r, name)) for name in self.addressedRegexps: method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.addressedRes.append((r, name)) for name in self.unaddressedRegexps: method = getattr(self, name) r = re.compile(method.__doc__, self.flags) self.unaddressedRes.append((r, name)) def _callRegexp(self, name, irc, msg, m): method = getattr(self, name) try: method(irc, msg, m) except Error, e: irc.error(str(e)) except Exception, e: self.log.exception('Uncaught exception in _callRegexp:') def invalidCommand(self, irc, msg, tokens): s = ' '.join(tokens) for (r, name) in self.addressedRes: for m in r.finditer(s): self._callRegexp(name, irc, msg, m) def doPrivmsg(self, irc, msg): if msg.isError: return proxy = self.Proxy(irc, msg) if not msg.addressed: for (r, name) in self.unaddressedRes: for m in r.finditer(msg.args[1]): self._callRegexp(name, proxy, msg, m) for (r, name) in self.res: for m in r.finditer(msg.args[1]): self._callRegexp(name, proxy, msg, m) PrivmsgCommandAndRegexp = PluginRegexp # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/ircmsgs.py0000644000000000000000000007352111206611405014520 0ustar ### # 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. ### """ This module provides the basic IrcMsg object used throughout the bot to represent the actual messages. It also provides several helper functions to construct such messages in an easier way than the constructor for the IrcMsg object (which, as you'll read later, is quite...full-featured :)) """ import re import time import supybot.conf as conf import supybot.utils as utils from supybot.utils.iter import all import supybot.ircutils as ircutils ### # IrcMsg class -- used for representing IRC messages acquired from a network. ### class MalformedIrcMsg(ValueError): pass class IrcMsg(object): """Class to represent an IRC message. As usual, ignore attributes that begin with an underscore. They simply don't exist. Instances of this class are *not* to be modified, since they are hashable. Public attributes of this class are .prefix, .command, .args, .nick, .user, and .host. The constructor for this class is pretty intricate. It's designed to take any of three major (sets of) arguments. Called with no keyword arguments, it takes a single string that is a raw IRC message (such as one taken straight from the network. Called with keyword arguments, it *requires* a command parameter. Args is optional, but with most commands will be necessary. Prefix is obviously optional, since clients aren't allowed (well, technically, they are, but only in a completely useless way) to send prefixes to the server. Since this class isn't to be modified, the constructor also accepts a 'msg' keyword argument representing a message from which to take all the attributes not provided otherwise as keyword arguments. So, for instance, if a programmer wanted to take a PRIVMSG he'd gotten and simply redirect it to a different source, he could do this: IrcMsg(prefix='', args=(newSource, otherMsg.args[1]), msg=otherMsg) """ # It's too useful to be able to tag IrcMsg objects with extra, unforeseen # data. Goodbye, __slots__. # On second thought, let's use methods for tagging. __slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user', '_hash', '_str', '_repr', '_len', 'tags') def __init__(self, s='', command='', args=(), prefix='', msg=None): assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg' if not s and not command and not msg: raise MalformedIrcMsg, 'IRC messages require a command.' self._str = None self._repr = None self._hash = None self._len = None self.tags = {} if s: originalString = s try: if not s.endswith('\n'): s += '\n' self._str = s if s[0] == ':': self.prefix, s = s[1:].split(None, 1) else: self.prefix = '' if ' :' in s: # Note the space: IPV6 addresses are bad w/o it. s, last = s.split(' :', 1) self.args = s.split() self.args.append(last.rstrip('\r\n')) else: self.args = s.split() self.command = self.args.pop(0) except (IndexError, ValueError): raise MalformedIrcMsg, repr(originalString) else: if msg is not None: if prefix: self.prefix = prefix else: self.prefix = msg.prefix if command: self.command = command else: self.command = msg.command if args: self.args = args else: self.args = msg.args self.tags = msg.tags.copy() else: self.prefix = prefix self.command = command assert all(ircutils.isValidArgument, args) self.args = args self.args = tuple(self.args) if isUserHostmask(self.prefix): (self.nick,self.user,self.host)=ircutils.splitHostmask(self.prefix) else: (self.nick, self.user, self.host) = (self.prefix,)*3 def __str__(self): if self._str is not None: return self._str if self.prefix: if len(self.args) > 1: self._str = ':%s %s %s :%s\r\n' % \ (self.prefix, self.command, ' '.join(self.args[:-1]), self.args[-1]) else: if self.args: self._str = ':%s %s :%s\r\n' % \ (self.prefix, self.command, self.args[0]) else: self._str = ':%s %s\r\n' % (self.prefix, self.command) else: if len(self.args) > 1: self._str = '%s %s :%s\r\n' % \ (self.command, ' '.join(self.args[:-1]), self.args[-1]) else: if self.args: self._str = '%s :%s\r\n' % (self.command, self.args[0]) else: self._str = '%s\r\n' % self.command return self._str def __len__(self): return len(str(self)) def __eq__(self, other): return isinstance(other, self.__class__) and \ hash(self) == hash(other) and \ self.command == other.command and \ self.prefix == other.prefix and \ self.args == other.args __req__ = __eq__ # I don't know exactly what this does, but it can't hurt. def __ne__(self, other): return not (self == other) __rne__ = __ne__ # Likewise as above. def __hash__(self): if self._hash is not None: return self._hash self._hash = hash(self.command) ^ \ hash(self.prefix) ^ \ hash(self.args) return self._hash def __repr__(self): if self._repr is not None: return self._repr self._repr = format('IrcMsg(prefix=%q, command=%q, args=%r)', self.prefix, self.command, self.args) return self._repr def __reduce__(self): return (self.__class__, (str(self),)) def tag(self, tag, value=True): self.tags[tag] = value def tagged(self, tag): return self.tags.get(tag) # Returns None if it's not there. def __getattr__(self, attr): return self.tagged(attr) def isCtcp(msg): """Returns whether or not msg is a CTCP message.""" return msg.command in ('PRIVMSG', 'NOTICE') and \ msg.args[1].startswith('\x01') and \ msg.args[1].endswith('\x01') and \ len(msg.args[1]) >= 2 def isAction(msg): """A predicate returning true if the PRIVMSG in question is an ACTION""" if isCtcp(msg): s = msg.args[1] payload = s[1:-1] # Chop off \x01. command = payload.split(None, 1)[0] return command == 'ACTION' else: return False def isSplit(msg): if msg.command == 'QUIT': # It's a quit. quitmsg = msg.args[0] if not quitmsg.startswith('"') and not quitmsg.endswith('"'): # It's not a user-generated quitmsg. servers = quitmsg.split() if len(servers) == 2: # We could check if domains match, or if the hostnames actually # resolve, but we're going to put that off for now. return True return False _unactionre = re.compile(r'^\x01ACTION\s+(.*)\x01$') def unAction(msg): """Returns the payload (i.e., non-ACTION text) of an ACTION msg.""" assert isAction(msg) return _unactionre.match(msg.args[1]).group(1) def _escape(s): s = s.replace('&', '&') s = s.replace('"', '"') s = s.replace('<', '<') s = s.replace('>', '>') return s def toXml(msg, pretty=True, includeTime=True): assert msg.command == _escape(msg.command) L = [] L.append('') if pretty: L.append('\n') for arg in msg.args: if pretty: L.append(' ') L.append('%s' % _escape(arg)) if pretty: L.append('\n') L.append('\n') return ''.join(L) def prettyPrint(msg, addRecipients=False, timestampFormat=None, showNick=True): """Provides a client-friendly string form for messages. IIRC, I copied BitchX's (or was it XChat's?) format for messages. """ def nickorprefix(): return msg.nick or msg.prefix def nick(): if addRecipients: return '%s/%s' % (msg.nick, msg.args[0]) else: return msg.nick if msg.command == 'PRIVMSG': m = _unactionre.match(msg.args[1]) if m: s = '* %s %s' % (nick(), m.group(1)) else: if not showNick: s = '%s' % msg.args[1] else: s = '<%s> %s' % (nick(), msg.args[1]) elif msg.command == 'NOTICE': if not showNick: s = '%s' % msg.args[1] else: s = '-%s- %s' % (nick(), msg.args[1]) elif msg.command == 'JOIN': s = '*** %s has joined %s' % (msg.nick, msg.args[0]) elif msg.command == 'PART': if len(msg.args) > 1: partmsg = ' (%s)' % msg.args[1] else: partmsg = '' s = '*** %s has parted %s%s' % (msg.nick, msg.args[0], partmsg) elif msg.command == 'KICK': if len(msg.args) > 2: kickmsg = ' (%s)' % msg.args[1] else: kickmsg = '' s = '*** %s was kicked by %s%s' % (msg.args[1], msg.nick, kickmsg) elif msg.command == 'MODE': s = '*** %s sets mode: %s' % (nickorprefix(), ' '.join(msg.args)) elif msg.command == 'QUIT': if msg.args: quitmsg = ' (%s)' % msg.args[0] else: quitmsg = '' s = '*** %s has quit IRC%s' % (msg.nick, quitmsg) elif msg.command == 'TOPIC': s = '*** %s changes topic to %s' % (nickorprefix(), msg.args[1]) elif msg.command == 'NICK': s = '*** %s is now known as %s' % (msg.nick, msg.args[0]) else: s = utils.str.format('--- Unknown command %q', ' '.join(msg.args)) at = getattr(msg, 'receivedAt', None) if timestampFormat and at: s = '%s %s' % (time.strftime(timestampFormat, time.localtime(at)), s) return s ### # Various IrcMsg functions ### isNick = ircutils.isNick isChannel = ircutils.isChannel isUserHostmask = ircutils.isUserHostmask def pong(payload, prefix='', msg=None): """Takes a payload and returns the proper PONG IrcMsg.""" if conf.supybot.protocols.irc.strictRfc(): assert payload, 'PONG requires a payload' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PONG', args=(payload,), msg=msg) def ping(payload, prefix='', msg=None): """Takes a payload and returns the proper PING IrcMsg.""" if conf.supybot.protocols.irc.strictRfc(): assert payload, 'PING requires a payload' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PING', args=(payload,), msg=msg) def op(channel, nick, prefix='', msg=None): """Returns a MODE to op nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+o', nick), msg=msg) def ops(channel, nicks, prefix='', msg=None): """Returns a MODE to op each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+' + ('o'*len(nicks))) + tuple(nicks), msg=msg) def deop(channel, nick, prefix='', msg=None): """Returns a MODE to deop nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-o', nick), msg=msg) def deops(channel, nicks, prefix='', msg=None): """Returns a MODE to deop each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('o'*len(nicks))) + tuple(nicks)) def halfop(channel, nick, prefix='', msg=None): """Returns a MODE to halfop nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+h', nick), msg=msg) def halfops(channel, nicks, prefix='', msg=None): """Returns a MODE to halfop each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '+' + ('h'*len(nicks))) + tuple(nicks)) def dehalfop(channel, nick, prefix='', msg=None): """Returns a MODE to dehalfop nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-h', nick), msg=msg) def dehalfops(channel, nicks, prefix='', msg=None): """Returns a MODE to dehalfop each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('h'*len(nicks))) + tuple(nicks)) def voice(channel, nick, prefix='', msg=None): """Returns a MODE to voice nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '+v', nick), msg=msg) def voices(channel, nicks, prefix='', msg=None): """Returns a MODE to voice each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '+' + ('v'*len(nicks))) + tuple(nicks)) def devoice(channel, nick, prefix='', msg=None): """Returns a MODE to devoice nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-v', nick), msg=msg) def devoices(channel, nicks, prefix='', msg=None): """Returns a MODE to devoice each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert nicks, 'Nicks must not be empty.' assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('v'*len(nicks))) + tuple(nicks)) def ban(channel, hostmask, exception='', prefix='', msg=None): """Returns a MODE to ban nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isUserHostmask(hostmask), repr(hostmask) modes = [('+b', hostmask)] if exception: modes.append(('+e', exception)) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=[channel] + ircutils.joinModes(modes), msg=msg) def bans(channel, hostmasks, exceptions=(), prefix='', msg=None): """Returns a MODE to ban each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert all(isUserHostmask, hostmasks), hostmasks modes = [('+b', s) for s in hostmasks] + [('+e', s) for s in exceptions] if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=[channel] + ircutils.joinModes(modes), msg=msg) def unban(channel, hostmask, prefix='', msg=None): """Returns a MODE to unban nick on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isUserHostmask(hostmask), repr(hostmask) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', args=(channel, '-b', hostmask), msg=msg) def unbans(channel, hostmasks, prefix='', msg=None): """Returns a MODE to unban each of nicks on channel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert all(isUserHostmask, hostmasks), hostmasks if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='MODE', msg=msg, args=(channel, '-' + ('b'*len(hostmasks)), hostmasks)) def kick(channel, nick, s='', prefix='', msg=None): """Returns a KICK to kick nick from channel with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix if s: return IrcMsg(prefix=prefix, command='KICK', args=(channel, nick, s), msg=msg) else: return IrcMsg(prefix=prefix, command='KICK', args=(channel, nick), msg=msg) def kicks(channel, nicks, s='', prefix='', msg=None): """Returns a KICK to kick each of nicks from channel with the message msg. """ if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) assert all(isNick, nicks), nicks if msg and not prefix: prefix = msg.prefix if s: return IrcMsg(prefix=prefix, command='KICK', args=(channel, ','.join(nicks), s), msg=msg) else: return IrcMsg(prefix=prefix, command='KICK', args=(channel, ','.join(nicks)), msg=msg) def privmsg(recipient, s, prefix='', msg=None): """Returns a PRIVMSG to recipient with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert (isChannel(recipient) or isNick(recipient)), repr(recipient) assert s, 's must not be empty.' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PRIVMSG', args=(recipient, s), msg=msg) def dcc(recipient, kind, *args, **kwargs): # Stupid Python won't allow (recipient, kind, *args, prefix=''), so we have # to use the **kwargs form. Blech. assert isNick(recipient), 'Can\'t DCC a channel.' kind = kind.upper() assert kind in ('SEND', 'CHAT', 'RESUME', 'ACCEPT'), 'Invalid DCC command.' args = (kind,) + args return IrcMsg(prefix=kwargs.get('prefix', ''), command='PRIVMSG', args=(recipient, ' '.join(args))) def action(recipient, s, prefix='', msg=None): """Returns a PRIVMSG ACTION to recipient with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert (isChannel(recipient) or isNick(recipient)), repr(recipient) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PRIVMSG', args=(recipient, '\x01ACTION %s\x01' % s), msg=msg) def notice(recipient, s, prefix='', msg=None): """Returns a NOTICE to recipient with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert (isChannel(recipient) or isNick(recipient)), repr(recipient) assert s, 'msg must not be empty.' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='NOTICE', args=(recipient, s), msg=msg) def join(channel, key=None, prefix='', msg=None): """Returns a JOIN to a channel""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) if msg and not prefix: prefix = msg.prefix if key is None: return IrcMsg(prefix=prefix, command='JOIN', args=(channel,), msg=msg) else: if conf.supybot.protocols.irc.strictRfc(): assert key.translate(utils.str.chars, utils.str.chars[128:]) == key and \ '\x00' not in key and \ '\r' not in key and \ '\n' not in key and \ '\f' not in key and \ '\t' not in key and \ '\v' not in key and \ ' ' not in key return IrcMsg(prefix=prefix, command='JOIN', args=(channel, key), msg=msg) def joins(channels, keys=None, prefix='', msg=None): """Returns a JOIN to each of channels.""" if conf.supybot.protocols.irc.strictRfc(): assert all(isChannel, channels), channels if msg and not prefix: prefix = msg.prefix if keys is None: keys = [] assert len(keys) <= len(channels), 'Got more keys than channels.' if not keys: return IrcMsg(prefix=prefix, command='JOIN', args=(','.join(channels),), msg=msg) else: for key in keys: if conf.supybot.protocols.irc.strictRfc(): assert key.translate(utils.str.chars, utils.str.chars[128:])==key and \ '\x00' not in key and \ '\r' not in key and \ '\n' not in key and \ '\f' not in key and \ '\t' not in key and \ '\v' not in key and \ ' ' not in key return IrcMsg(prefix=prefix, command='JOIN', args=(','.join(channels), ','.join(keys)), msg=msg) def part(channel, s='', prefix='', msg=None): """Returns a PART from channel with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) if msg and not prefix: prefix = msg.prefix if s: return IrcMsg(prefix=prefix, command='PART', args=(channel, s), msg=msg) else: return IrcMsg(prefix=prefix, command='PART', args=(channel,), msg=msg) def parts(channels, s='', prefix='', msg=None): """Returns a PART from each of channels with the message msg.""" if conf.supybot.protocols.irc.strictRfc(): assert all(isChannel, channels), channels if msg and not prefix: prefix = msg.prefix if s: return IrcMsg(prefix=prefix, command='PART', args=(','.join(channels), s), msg=msg) else: return IrcMsg(prefix=prefix, command='PART', args=(','.join(channels),), msg=msg) def quit(s='', prefix='', msg=None): """Returns a QUIT with the message msg.""" if msg and not prefix: prefix = msg.prefix if s: return IrcMsg(prefix=prefix, command='QUIT', args=(s,), msg=msg) else: return IrcMsg(prefix=prefix, command='QUIT', msg=msg) def topic(channel, topic=None, prefix='', msg=None): """Returns a TOPIC for channel with the topic topic.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel), repr(channel) if msg and not prefix: prefix = msg.prefix if topic is None: return IrcMsg(prefix=prefix, command='TOPIC', args=(channel,), msg=msg) else: return IrcMsg(prefix=prefix, command='TOPIC', args=(channel, topic), msg=msg) def nick(nick, prefix='', msg=None): """Returns a NICK with nick nick.""" if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='NICK', args=(nick,), msg=msg) def user(ident, user, prefix='', msg=None): """Returns a USER with ident ident and user user.""" if conf.supybot.protocols.irc.strictRfc(): assert '\x00' not in ident and \ '\r' not in ident and \ '\n' not in ident and \ ' ' not in ident and \ '@' not in ident if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='USER', args=(ident, '0', '*', user), msg=msg) def who(hostmaskOrChannel, prefix='', msg=None): """Returns a WHO for the hostmask or channel hostmaskOrChannel.""" if conf.supybot.protocols.irc.strictRfc(): assert isChannel(hostmaskOrChannel) or \ isUserHostmask(hostmaskOrChannel), repr(hostmaskOrChannel) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='WHO', args=(hostmaskOrChannel,), msg=msg) def whois(nick, mask='', prefix='', msg=None): """Returns a WHOIS for nick.""" if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='WHOIS', args=(nick, mask), msg=msg) def names(channel=None, prefix='', msg=None): if conf.supybot.protocols.irc.strictRfc(): assert isChannel(channel) if msg and not prefix: prefix = msg.prefix if channel is not None: return IrcMsg(prefix=prefix, command='NAMES', args=(channel,), msg=msg) else: return IrcMsg(prefix=prefix, command='NAMES', msg=msg) def mode(channel, args=(), prefix='', msg=None): if msg and not prefix: prefix = msg.prefix if isinstance(args, basestring): args = (args,) else: args = tuple(map(str, args)) return IrcMsg(prefix=prefix, command='MODE', args=(channel,)+args, msg=msg) def limit(channel, limit, prefix='', msg=None): return mode(channel, ['+l', limit], prefix=prefix, msg=msg) def unlimit(channel, limit, prefix='', msg=None): return mode(channel, ['-l', limit], prefix=prefix, msg=msg) def invite(nick, channel, prefix='', msg=None): """Returns an INVITE for nick.""" if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='INVITE', args=(nick, channel), msg=msg) def password(password, prefix='', msg=None): """Returns a PASS command for accessing a server.""" if conf.supybot.protocols.irc.strictRfc(): assert password, 'password must not be empty.' if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='PASS', args=(password,), msg=msg) def ison(nick, prefix='', msg=None): if conf.supybot.protocols.irc.strictRfc(): assert isNick(nick), repr(nick) if msg and not prefix: prefix = msg.prefix return IrcMsg(prefix=prefix, command='ISON', args=(nick,), msg=msg) def error(s, msg=None): return IrcMsg(command='ERROR', args=(s,), msg=msg) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/log.py0000644000000000000000000003636711206611405013641 0ustar ### # 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 os import sys import time import types import atexit import logging import operator import textwrap import traceback import supybot.ansi as ansi import supybot.conf as conf import supybot.utils as utils import supybot.registry as registry import supybot.ircutils as ircutils deadlyExceptions = [KeyboardInterrupt, SystemExit] ### # This is for testing, of course. Mostly is just disables the firewall code # so exceptions can propagate. ### testing = False class Formatter(logging.Formatter): _fmtConf = staticmethod(lambda : conf.supybot.log.format()) def formatTime(self, record, datefmt=None): return timestamp(record.created) def formatException(self, (E, e, tb)): for exn in deadlyExceptions: if issubclass(e.__class__, exn): raise return logging.Formatter.formatException(self, (E, e, tb)) def format(self, record): self._fmt = self._fmtConf() return logging.Formatter.format(self, record) class PluginFormatter(Formatter): _fmtConf = staticmethod(lambda : conf.supybot.log.plugins.format()) class Logger(logging.Logger): def exception(self, *args): (E, e, tb) = sys.exc_info() tbinfo = traceback.extract_tb(tb) path = '[%s]' % '|'.join(map(operator.itemgetter(2), tbinfo)) eStrId = '%s:%s' % (E, path) eId = hex(hash(eStrId) & 0xFFFFF) logging.Logger.exception(self, *args) self.error('Exception id: %s', eId) # The traceback should be sufficient if we want it. # self.error('Exception string: %s', eStrId) def _log(self, level, msg, args, exc_info=None): msg = format(msg, *args) logging.Logger._log(self, level, msg, (), exc_info=exc_info) class StdoutStreamHandler(logging.StreamHandler): def format(self, record): s = logging.StreamHandler.format(self, record) if record.levelname != 'ERROR' and conf.supybot.log.stdout.wrap(): # We check for ERROR there because otherwise, tracebacks (which are # already wrapped by Python itself) wrap oddly. if not isinstance(record.levelname, basestring): print record print record.levelname print utils.stackTrace() prefixLen = len(record.levelname) + 1 # ' ' s = textwrap.fill(s, width=78, subsequent_indent=' '*prefixLen) s.rstrip('\r\n') return s def emit(self, record): if conf.supybot.log.stdout() and not conf.daemonized: try: logging.StreamHandler.emit(self, record) except ValueError, e: # Raised if sys.stdout is closed. self.disable() error('Error logging to stdout. Removing stdout handler.') exception('Uncaught exception in StdoutStreamHandler:') def disable(self): self.setLevel(sys.maxint) # Just in case. _logger.removeHandler(self) logging._acquireLock() try: del logging._handlers[self] finally: logging._releaseLock() class BetterFileHandler(logging.FileHandler): def emit(self, record): msg = self.format(record) if not hasattr(types, "UnicodeType"): #if no unicode support... self.stream.write(msg) self.stream.write(os.linesep) else: try: self.stream.write(msg) self.stream.write(os.linesep) except UnicodeError: self.stream.write(msg.encode("utf8")) self.stream.write(os.linesep) self.flush() class ColorizedFormatter(Formatter): # This was necessary because these variables aren't defined until later. # The staticmethod is necessary because they get treated like methods. _fmtConf = staticmethod(lambda : conf.supybot.log.stdout.format()) def formatException(self, (E, e, tb)): if conf.supybot.log.stdout.colorized(): return ''.join([ansi.RED, Formatter.formatException(self, (E, e, tb)), ansi.RESET]) else: return Formatter.formatException(self, (E, e, tb)) def format(self, record, *args, **kwargs): if conf.supybot.log.stdout.colorized(): color = '' if record.levelno == logging.CRITICAL: color = ansi.WHITE + ansi.BOLD elif record.levelno == logging.ERROR: color = ansi.RED elif record.levelno == logging.WARNING: color = ansi.YELLOW if color: return ''.join([color, Formatter.format(self, record, *args, **kwargs), ansi.RESET]) else: return Formatter.format(self, record, *args, **kwargs) else: return Formatter.format(self, record, *args, **kwargs) conf.registerGlobalValue(conf.supybot.directories, 'log', conf.Directory('logs', """Determines what directory the bot will store its logfiles in.""")) _logDir = conf.supybot.directories.log() if not os.path.exists(_logDir): os.mkdir(_logDir, 0755) pluginLogDir = os.path.join(_logDir, 'plugins') if not os.path.exists(pluginLogDir): os.mkdir(pluginLogDir, 0755) try: messagesLogFilename = os.path.join(_logDir, 'messages.log') _handler = BetterFileHandler(messagesLogFilename) except EnvironmentError, e: raise SystemExit, \ 'Error opening messages logfile (%s). ' \ 'Generally, this is because you are running Supybot in a directory ' \ 'you don\'t have permissions to add files in, or you\'re running ' \ 'Supybot as a different user than you normal do. The original ' \ 'error was: %s' % (messagesLogFilename, utils.gen.exnToString(e)) # These are public. formatter = Formatter('NEVER SEEN; IF YOU SEE THIS, FILE A BUG!') pluginFormatter = PluginFormatter('NEVER SEEN; IF YOU SEE THIS, FILE A BUG!') # These are not. logging.setLoggerClass(Logger) _logger = logging.getLogger('supybot') _stdoutHandler = StdoutStreamHandler(sys.stdout) class ValidLogLevel(registry.String): """Invalid log level.""" handler = None minimumLevel = -1 def set(self, s): s = s.upper() try: level = logging._levelNames[s] except KeyError: try: level = int(s) except ValueError: self.error() if level < self.minimumLevel: self.error() if self.handler is not None: self.handler.setLevel(level) self.setValue(level) def __str__(self): # The str() is necessary here; apparently getLevelName returns an # integer on occasion. logging-- level = str(logging.getLevelName(self.value)) if level.startswith('Level'): level = level.split()[-1] return level class LogLevel(ValidLogLevel): """Invalid log level. Value must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL.""" handler = _handler class StdoutLogLevel(ValidLogLevel): """Invalid log level. Value must be either DEBUG, INFO, WARNING, ERROR, or CRITICAL.""" handler = _stdoutHandler conf.registerGroup(conf.supybot, 'log') conf.registerGlobalValue(conf.supybot.log, 'format', registry.String('%(levelname)s %(asctime)s %(name)s %(message)s', """Determines what the bot's logging format will be. The relevant documentation on the available formattings is Python's documentation on its logging module.""")) conf.registerGlobalValue(conf.supybot.log, 'level', LogLevel(logging.INFO, """Determines what the minimum priority level logged to file will be. Do note that this value does not affect the level logged to stdout; for that, you should set the value of supybot.log.stdout.level. Valid values are DEBUG, INFO, WARNING, ERROR, and CRITICAL, in order of increasing priority.""")) conf.registerGlobalValue(conf.supybot.log, 'timestampFormat', registry.String('%Y-%m-%dT%H:%M:%S', """Determines the format string for timestamps in logfiles. Refer to the Python documentation for the time module to see what formats are accepted. If you set this variable to the empty string, times will be logged in a simple seconds-since-epoch format.""")) class BooleanRequiredFalseOnWindows(registry.Boolean): def set(self, s): registry.Boolean.set(self, s) if self.value and os.name == 'nt': raise registry.InvalidRegistryValue, \ 'Value cannot be true on Windows.' conf.registerGlobalValue(conf.supybot.log, 'stdout', registry.Boolean(True, """Determines whether the bot will log to stdout.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'colorized', BooleanRequiredFalseOnWindows(False, """Determines whether the bot's logs to stdout (if enabled) will be colorized with ANSI color.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'wrap', registry.Boolean(True, """Determines whether the bot will wrap its logs when they're output to stdout.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'format', registry.String('%(levelname)s %(asctime)s %(message)s', """Determines what the bot's logging format will be. The relevant documentation on the available formattings is Python's documentation on its logging module.""")) conf.registerGlobalValue(conf.supybot.log.stdout, 'level', StdoutLogLevel(logging.INFO, """Determines what the minimum priority level logged will be. Valid values are DEBUG, INFO, WARNING, ERROR, and CRITICAL, in order of increasing priority.""")) conf.registerGroup(conf.supybot.log, 'plugins') conf.registerGlobalValue(conf.supybot.log.plugins, 'individualLogfiles', registry.Boolean(False, """Determines whether the bot will separate plugin logs into their own individual logfiles.""")) conf.registerGlobalValue(conf.supybot.log.plugins, 'format', registry.String('%(levelname)s %(asctime)s %(message)s', """Determines what the bot's logging format will be. The relevant documentation on the available formattings is Python's documentation on its logging module.""")) # These just make things easier. debug = _logger.debug info = _logger.info warning = _logger.warning error = _logger.error critical = _logger.critical exception = _logger.exception # These were just begging to be replaced. registry.error = error registry.exception = exception setLevel = _logger.setLevel atexit.register(logging.shutdown) # ircutils will work without this, but it's useful. ircutils.debug = debug def getPluginLogger(name): if not conf.supybot.log.plugins.individualLogfiles(): return _logger log = logging.getLogger('supybot.plugins.%s' % name) if not log.handlers: filename = os.path.join(pluginLogDir, '%s.log' % name) handler = BetterFileHandler(filename) handler.setLevel(-1) handler.setFormatter(pluginFormatter) log.addHandler(handler) if name in sys.modules: log.info('Starting log for %s.', name) return log def timestamp(when=None): if when is None: when = time.time() format = conf.supybot.log.timestampFormat() t = time.localtime(when) if format: return time.strftime(format, t) else: return str(int(time.mktime(t))) def firewall(f, errorHandler=None): def logException(self, s=None): if s is None: s = 'Uncaught exception' if hasattr(self, 'log'): self.log.exception('%s:', s) else: exception('%s in %s.%s:', s, self.__class__.__name__, f.func_name) def m(self, *args, **kwargs): try: return f(self, *args, **kwargs) except Exception, e: if testing: raise logException(self) if errorHandler is not None: try: return errorHandler(self, *args, **kwargs) except Exception, e: logException(self, 'Uncaught exception in errorHandler') m = utils.python.changeFunctionName(m, f.func_name, f.__doc__) return m class MetaFirewall(type): def __new__(cls, name, bases, classdict): firewalled = {} for base in bases: if hasattr(base, '__firewalled__'): cls.updateFirewalled(firewalled, base.__firewalled__) cls.updateFirewalled(firewalled, classdict.get('__firewalled__', [])) for (attr, errorHandler) in firewalled.iteritems(): if attr in classdict: classdict[attr] = firewall(classdict[attr], errorHandler) return super(MetaFirewall, cls).__new__(cls, name, bases, classdict) def getErrorHandler(cls, dictOrTuple, name): if isinstance(dictOrTuple, dict): return dictOrTuple[name] else: return None getErrorHandler = classmethod(getErrorHandler) def updateFirewalled(cls, firewalled, __firewalled__): for attr in __firewalled__: firewalled[attr] = cls.getErrorHandler(__firewalled__, attr) updateFirewalled = classmethod(updateFirewalled) class PluginLogFilter(logging.Filter): def filter(self, record): if conf.supybot.log.plugins.individualLogfiles(): if record.name.startswith('supybot.plugins'): return False return True _handler.setFormatter(formatter) _handler.addFilter(PluginLogFilter()) _handler.setLevel(conf.supybot.log.level()) _logger.addHandler(_handler) _logger.setLevel(-1) _stdoutFormatter = ColorizedFormatter('IF YOU SEE THIS, FILE A BUG!') _stdoutHandler.setFormatter(_stdoutFormatter) _stdoutHandler.setLevel(conf.supybot.log.stdout.level()) if not conf.daemonized: _logger.addHandler(_stdoutHandler) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/conf.py0000644000000000000000000013655611206611405014006 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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 sys import time import socket import supybot.utils as utils import supybot.registry as registry import supybot.ircutils as ircutils ### # version: This should be pretty obvious. ### version = '0.83.4.1' ### # *** The following variables are affected by command-line options. They are # not registry variables for a specific reason. Do *not* change these to # registry variables without first consulting people smarter than yourself. ### ### # daemonized: This determines whether or not the bot has been daemonized # (i.e., set to run in the background). Obviously, this defaults # to False. A command-line option for obvious reasons. ### daemonized = False ### # allowDefaultOwner: True if supybot.capabilities is allowed not to include # '-owner' -- that is, if all users should be automatically # recognized as owners. That would suck, hence we require a # command-line option to allow this stupidity. ### allowDefaultOwner = False ### # Here we replace values in other modules as appropriate. ### utils.web.defaultHeaders['User-agent'] = \ 'Mozilla/5.0 (Compatible; Supybot %s)' % version ### # The standard registry. ### supybot = registry.Group() supybot.setName('supybot') def registerGroup(Group, name, group=None, **kwargs): if kwargs: group = registry.Group(**kwargs) return Group.register(name, group) def registerGlobalValue(group, name, value): value.channelValue = False return group.register(name, value) def registerChannelValue(group, name, value): value._supplyDefault = True value.channelValue = True g = group.register(name, value) gname = g._name.lower() for name in registry._cache.iterkeys(): if name.lower().startswith(gname) and len(gname) < len(name): name = name[len(gname)+1:] # +1 for . parts = registry.split(name) if len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]): # This gets the channel values so they always persist. g.get(parts[0])() def registerPlugin(name, currentValue=None, public=True): group = registerGlobalValue(supybot.plugins, name, registry.Boolean(False, """Determines whether this plugin is loaded by default.""", showDefault=False)) supybot.plugins().add(name) registerGlobalValue(group, 'public', registry.Boolean(public, """Determines whether this plugin is publicly visible.""")) if currentValue is not None: supybot.plugins.get(name).setValue(currentValue) registerGroup(users.plugins, name) return group def get(group, channel=None): if group.channelValue and \ channel is not None and ircutils.isChannel(channel): return group.get(channel)() else: return group() ### # The user info registry. ### users = registry.Group() users.setName('users') registerGroup(users, 'plugins', orderAlphabetically=True) def registerUserValue(group, name, value): assert group._name.startswith('users') value._supplyDefault = True group.register(name, value) class ValidNick(registry.String): """Value must be a valid IRC nick.""" def setValue(self, v): if not ircutils.isNick(v): self.error() else: registry.String.setValue(self, v) class ValidNicks(registry.SpaceSeparatedListOf): Value = ValidNick class ValidNickAllowingPercentS(ValidNick): """Value must be a valid IRC nick, with the possible exception of a %s in it.""" def setValue(self, v): # If this works, it's a valid nick, aside from the %s. try: ValidNick.setValue(self, v.replace('%s', '')) # It's valid aside from the %s, we'll let it through. registry.String.setValue(self, v) except registry.InvalidRegistryValue: self.error() class ValidNicksAllowingPercentS(ValidNicks): Value = ValidNickAllowingPercentS class ValidChannel(registry.String): """Value must be a valid IRC channel name.""" def setValue(self, v): self.channel = v if ',' in v: # To prevent stupid users from: a) trying to add a channel key # with a comma in it, b) trying to add channels separated by # commas instead of spaces try: (channel, _) = v.split(',') except ValueError: self.error() else: channel = v if not ircutils.isChannel(channel): self.error() else: registry.String.setValue(self, v) def error(self): try: super(ValidChannel, self).error() except registry.InvalidRegistryValue, e: e.channel = self.channel raise e class ValidHostmask(registry.String): """Value must be a valid user hostmask.""" def setValue(self, v): if not ircutils.isUserHostmask(v): self.error() super(ValidHostmask, self).setValue(v) registerGlobalValue(supybot, 'nick', ValidNick('supybot', """Determines the bot's default nick.""")) registerGlobalValue(supybot.nick, 'alternates', ValidNicksAllowingPercentS(['%s`', '%s_'], """Determines what alternative nicks will be used if the primary nick (supybot.nick) isn't available. A %s in this nick is replaced by the value of supybot.nick when used. If no alternates are given, or if all are used, the supybot.nick will be perturbed appropriately until an unused nick is found.""")) registerGlobalValue(supybot, 'ident', ValidNick('supybot', """Determines the bot's ident string, if the server doesn't provide one by default.""")) class VersionIfEmpty(registry.String): def __call__(self): ret = registry.String.__call__(self) if not ret: ret = 'Supybot %s' % version return ret registerGlobalValue(supybot, 'user', VersionIfEmpty('', """Determines the user the bot sends to the server. A standard user using the current version of the bot will be generated if this is left empty.""")) class Networks(registry.SpaceSeparatedSetOfStrings): List = ircutils.IrcSet registerGlobalValue(supybot, 'networks', Networks([], """Determines what networks the bot will connect to.""", orderAlphabetically=True)) class Servers(registry.SpaceSeparatedListOfStrings): def normalize(self, s): if ':' not in s: s += ':6667' return s def convert(self, s): s = self.normalize(s) (server, port) = s.split(':') port = int(port) return (server, port) def __call__(self): L = registry.SpaceSeparatedListOfStrings.__call__(self) return map(self.convert, L) def __str__(self): return ' '.join(registry.SpaceSeparatedListOfStrings.__call__(self)) def append(self, s): L = registry.SpaceSeparatedListOfStrings.__call__(self) L.append(s) class SpaceSeparatedSetOfChannels(registry.SpaceSeparatedListOf): sorted = True List = ircutils.IrcSet Value = ValidChannel def join(self, channel): import ircmsgs # Don't put this globally! It's recursive. key = self.key.get(channel)() if key: return ircmsgs.join(channel, key) else: return ircmsgs.join(channel) def registerNetwork(name, password='', ssl=False): network = registerGroup(supybot.networks, name) registerGlobalValue(network, 'password', registry.String(password, """Determines what password will be used on %s. Yes, we know that technically passwords are server-specific and not network-specific, but this is the best we can do right now.""" % name, private=True)) registryServers = registerGlobalValue(network, 'servers', Servers([], """Determines what servers the bot will connect to for %s. Each will be tried in order, wrapping back to the first when the cycle is completed.""" % name)) registerGlobalValue(network, 'channels', SpaceSeparatedSetOfChannels([], """Determines what channels the bot will join only on %s.""" % name)) registerGlobalValue(network, 'ssl', registry.Boolean(ssl, """Determines whether the bot will attempt to connect with SSL sockets to %s.""" % name)) registerChannelValue(network.channels, 'key', registry.String('', """Determines what key (if any) will be used to join the channel.""")) return network # Let's fill our networks. for (name, s) in registry._cache.iteritems(): if name.startswith('supybot.networks.'): parts = name.split('.') name = parts[2] if name != 'default': registerNetwork(name) ### # Reply/error tweaking. ### registerGroup(supybot, 'reply') registerGroup(supybot.reply, 'format') registerChannelValue(supybot.reply.format, 'time', registry.String('%I:%M %p, %B %d, %Y', """Determines how timestamps printed for human reading should be formatted. Refer to the Python documentation for the time module to see valid formatting characters for time formats.""")) def timestamp(t): if t is None: t = time.time() t = time.localtime(t) format = get(supybot.reply.format.time, dynamic.channel) return time.strftime(format, t) utils.str.timestamp = timestamp registerGroup(supybot.reply.format.time, 'elapsed') registerChannelValue(supybot.reply.format.time.elapsed, 'short', registry.Boolean(False, """Determines whether elapsed times will be given as "1 day, 2 hours, 3 minutes, and 15 seconds" or as "1d 2h 3m 15s".""")) originalTimeElapsed = utils.timeElapsed def timeElapsed(*args, **kwargs): kwargs['short'] = supybot.reply.format.time.elapsed.short() return originalTimeElapsed(*args, **kwargs) utils.timeElapsed = timeElapsed registerGlobalValue(supybot.reply, 'maximumLength', registry.Integer(512*256, """Determines the absolute maximum length of the bot's reply -- no reply will be passed through the bot with a length greater than this.""")) registerChannelValue(supybot.reply, 'mores', registry.Boolean(True, """Determines whether the bot will break up long messages into chunks and allow users to use the 'more' command to get the remaining chunks.""")) registerChannelValue(supybot.reply.mores, 'maximum', registry.PositiveInteger(50, """Determines what the maximum number of chunks (for use with the 'more' command) will be.""")) registerChannelValue(supybot.reply.mores, 'length', registry.NonNegativeInteger(0, """Determines how long individual chunks will be. If set to 0, uses our super-tweaked, get-the-most-out-of-an-individual-message default.""")) registerChannelValue(supybot.reply.mores, 'instant', registry.PositiveInteger(1, """Determines how many mores will be sent instantly (i.e., without the use of the more command, immediately when they are formed). Defaults to 1, which means that a more command will be required for all but the first chunk.""")) registerGlobalValue(supybot.reply, 'oneToOne', registry.Boolean(True, """Determines whether the bot will send multi-message replies in a single message or in multiple messages. For safety purposes (so the bot is less likely to flood) it will normally send everything in a single message, using mores if necessary.""")) registerChannelValue(supybot.reply, 'whenNotCommand', registry.Boolean(True, """Determines whether the bot will reply with an error message when it is addressed but not given a valid command. If this value is False, the bot will remain silent, as long as no other plugins override the normal behavior.""")) registerGroup(supybot.reply, 'error') registerGlobalValue(supybot.reply.error, 'detailed', registry.Boolean(False, """Determines whether error messages that result from bugs in the bot will show a detailed error message (the uncaught exception) or a generic error message.""")) registerChannelValue(supybot.reply.error, 'inPrivate', registry.Boolean(False, """Determines whether the bot will send error messages to users in private. You might want to do this in order to keep channel traffic to minimum. This can be used in combination with supybot.reply.error.withNotice.""")) registerChannelValue(supybot.reply.error, 'withNotice', registry.Boolean(False, """Determines whether the bot will send error messages to users via NOTICE instead of PRIVMSG. You might want to do this so users can ignore NOTICEs from the bot and not have to see error messages; or you might want to use it in combination with supybot.reply.errorInPrivate so private errors don't open a query window in most IRC clients.""")) registerChannelValue(supybot.reply.error, 'noCapability', registry.Boolean(False, """Determines whether the bot will send an error message to users who attempt to call a command for which they do not have the necessary capability. You may wish to make this True if you don't want users to understand the underlying security system preventing them from running certain commands.""")) registerChannelValue(supybot.reply, 'inPrivate', registry.Boolean(False, """Determines whether the bot will reply privately when replying in a channel, rather than replying to the whole channel.""")) registerChannelValue(supybot.reply, 'withNotice', registry.Boolean(False, """Determines whether the bot will reply with a notice when replying in a channel, rather than replying with a privmsg as normal.""")) # XXX: User value. registerGlobalValue(supybot.reply, 'withNoticeWhenPrivate', registry.Boolean(False, """Determines whether the bot will reply with a notice when it is sending a private message, in order not to open a /query window in clients. This can be overridden by individual users via the user configuration variable reply.withNoticeWhenPrivate.""")) registerChannelValue(supybot.reply, 'withNickPrefix', registry.Boolean(True, """Determines whether the bot will always prefix the user's nick to its reply to that user's command.""")) registerChannelValue(supybot.reply, 'whenNotAddressed', registry.Boolean(False, """Determines whether the bot should attempt to reply to all messages even if they don't address it (either via its nick or a prefix character). If you set this to True, you almost certainly want to set supybot.reply.whenNotCommand to False.""")) registerChannelValue(supybot.reply, 'requireChannelCommandsToBeSentInChannel', registry.Boolean(False, """Determines whether the bot will allow you to send channel-related commands outside of that channel. Sometimes people find it confusing if a channel-related command (like Filter.outfilter) changes the behavior of the channel but was sent outside the channel itself.""")) registerGlobalValue(supybot, 'followIdentificationThroughNickChanges', registry.Boolean(False, """Determines whether the bot will unidentify someone when that person changes his or her nick. Setting this to True will cause the bot to track such changes. It defaults to False for a little greater security.""")) registerGlobalValue(supybot, 'alwaysJoinOnInvite', registry.Boolean(False, """Determines whether the bot will always join a channel when it's invited. If this value is False, the bot will only join a channel if the user inviting it has the 'admin' capability (or if it's explicitly told to join the channel using the Admin.join command)""")) registerChannelValue(supybot.reply, 'showSimpleSyntax', registry.Boolean(False, """Supybot normally replies with the full help whenever a user misuses a command. If this value is set to True, the bot will only reply with the syntax of the command (the first line of the help) rather than the full help.""")) class ValidPrefixChars(registry.String): """Value must contain only ~!@#$%^&*()_-+=[{}]\\|'\";:,<.>/?""" def setValue(self, v): if v.translate(utils.str.chars, '`~!@#$%^&*()_-+=[{}]\\|\'";:,<.>/?'): self.error() registry.String.setValue(self, v) registerGroup(supybot.reply, 'whenAddressedBy') registerChannelValue(supybot.reply.whenAddressedBy, 'chars', ValidPrefixChars('', """Determines what prefix characters the bot will reply to. A prefix character is a single character that the bot will use to determine what messages are addressed to it; when there are no prefix characters set, it just uses its nick. Each character in this string is interpreted individually; you can have multiple prefix chars simultaneously, and if any one of them is used as a prefix the bot will assume it is being addressed.""")) registerChannelValue(supybot.reply.whenAddressedBy, 'strings', registry.SpaceSeparatedSetOfStrings([], """Determines what strings the bot will reply to when they are at the beginning of the message. Whereas prefix.chars can only be one character (although there can be many of them), this variable is a space-separated list of strings, so you can set something like '@@ ??' and the bot will reply when a message is prefixed by either @@ or ??.""")) registerChannelValue(supybot.reply.whenAddressedBy, 'nick', registry.Boolean(True, """Determines whether the bot will reply when people address it by its nick, rather than with a prefix character.""")) registerChannelValue(supybot.reply.whenAddressedBy.nick, 'atEnd', registry.Boolean(False, """Determines whether the bot will reply when people address it by its nick at the end of the message, rather than at the beginning.""")) registerChannelValue(supybot.reply.whenAddressedBy, 'nicks', registry.SpaceSeparatedSetOfStrings([], """Determines what extra nicks the bot will always respond to when addressed by, even if its current nick is something else.""")) ### # Replies ### registerGroup(supybot, 'replies') registerChannelValue(supybot.replies, 'success', registry.NormalizedString("""The operation succeeded.""", """Determines what message the bot replies with when a command succeeded. If this configuration variable is empty, no success message will be sent.""")) registerChannelValue(supybot.replies, 'error', registry.NormalizedString("""An error has occurred and has been logged. Please contact this bot's administrator for more information.""", """ Determines what error message the bot gives when it wants to be ambiguous.""")) registerChannelValue(supybot.replies, 'incorrectAuthentication', registry.NormalizedString("""Your hostmask doesn't match or your password is wrong.""", """Determines what message the bot replies with when someone tries to use a command that requires being identified or having a password and neither credential is correct.""")) # XXX: This should eventually check that there's one and only one %s here. registerChannelValue(supybot.replies, 'noUser', registry.NormalizedString("""I can't find %s in my user database. If you didn't give a user name, then I might not know what your user is, and you'll need to identify before this command might work.""", """Determines what error message the bot replies with when someone tries to accessing some information on a user the bot doesn't know about.""")) registerChannelValue(supybot.replies, 'notRegistered', registry.NormalizedString("""You must be registered to use this command. If you are already registered, you must either identify (using the identify command) or add a hostmask matching your current hostmask (using the "hostmask add" command).""", """Determines what error message the bot replies with when someone tries to do something that requires them to be registered but they're not currently recognized.""")) registerChannelValue(supybot.replies, 'noCapability', registry.NormalizedString("""You don't have the %s capability. If you think that you should have this capability, be sure that you are identified before trying again. The 'whoami' command can tell you if you're identified.""", """Determines what error message is given when the bot is telling someone they aren't cool enough to use the command they tried to use.""")) registerChannelValue(supybot.replies, 'genericNoCapability', registry.NormalizedString("""You're missing some capability you need. This could be because you actually possess the anti-capability for the capability that's required of you, or because the channel provides that anti-capability by default, or because the global capabilities include that anti-capability. Or, it could be because the channel or supybot.capabilities.default is set to False, meaning that no commands are allowed unless explicitly in your capabilities. Either way, you can't do what you want to do.""", """Determines what generic error message is given when the bot is telling someone that they aren't cool enough to use the command they tried to use, and the author of the code calling errorNoCapability didn't provide an explicit capability for whatever reason.""")) registerChannelValue(supybot.replies, 'requiresPrivacy', registry.NormalizedString("""That operation cannot be done in a channel.""", """Determines what error messages the bot sends to people who try to do things in a channel that really should be done in private.""")) registerChannelValue(supybot.replies, 'possibleBug', registry.NormalizedString("""This may be a bug. If you think it is, please file a bug report at .""", """Determines what message the bot sends when it thinks you've encountered a bug that the developers don't know about.""")) ### # End supybot.replies. ### registerGlobalValue(supybot, 'snarfThrottle', registry.Float(10.0, """A floating point number of seconds to throttle snarfed URLs, in order to prevent loops between two bots snarfing the same URLs and having the snarfed URL in the output of the snarf message.""")) registerGlobalValue(supybot, 'upkeepInterval', registry.PositiveInteger(3600, """Determines the number of seconds between running the upkeep function that flushes (commits) open databases, collects garbage, and records some useful statistics at the debugging level.""")) registerGlobalValue(supybot, 'flush', registry.Boolean(True, """Determines whether the bot will periodically flush data and configuration files to disk. Generally, the only time you'll want to set this to False is when you want to modify those configuration files by hand and don't want the bot to flush its current version over your modifications. Do note that if you change this to False inside the bot, your changes won't be flushed. To make this change permanent, you must edit the registry yourself.""")) ### # supybot.commands. For stuff relating to commands. ### registerGroup(supybot, 'commands') class ValidQuotes(registry.Value): """Value must consist solely of \", ', and ` characters.""" def setValue(self, v): if [c for c in v if c not in '"`\'']: self.error() super(ValidQuotes, self).setValue(v) def __str__(self): return str(self.value) registerChannelValue(supybot.commands, 'quotes', ValidQuotes('"', """Determines what characters are valid for quoting arguments to commands in order to prevent them from being tokenized. """)) # This is a GlobalValue because bot owners should be able to say, "There will # be no nesting at all on this bot." Individual channels can just set their # brackets to the empty string. registerGlobalValue(supybot.commands, 'nested', registry.Boolean(True, """Determines whether the bot will allow nested commands, which rule. You definitely should keep this on.""")) registerGlobalValue(supybot.commands.nested, 'maximum', registry.PositiveInteger(10, """Determines what the maximum number of nested commands will be; users will receive an error if they attempt commands more nested than this.""")) class ValidBrackets(registry.OnlySomeStrings): validStrings = ('', '[]', '<>', '{}', '()') registerChannelValue(supybot.commands.nested, 'brackets', ValidBrackets('[]', """Supybot allows you to specify what brackets are used for your nested commands. Valid sets of brackets include [], <>, and {} (). [] has strong historical motivation, as well as being the brackets that don't require shift. <> or () might be slightly superior because they cannot occur in a nick. If this string is empty, nested commands will not be allowed in this channel.""")) registerChannelValue(supybot.commands.nested, 'pipeSyntax', registry.Boolean(False, """Supybot allows nested commands. Enabling this option will allow nested commands with a syntax similar to UNIX pipes, for example: 'bot: foo | bar'.""")) registerGroup(supybot.commands, 'defaultPlugins', orderAlphabetically=True, help="""Determines what commands have default plugins set, and which plugins are set to be the default for each of those commands.""") registerGlobalValue(supybot.commands.defaultPlugins, 'importantPlugins', registry.SpaceSeparatedSetOfStrings( ['Admin', 'Channel', 'Config', 'Misc', 'Owner', 'Plugin', 'User'], """Determines what plugins automatically get precedence over all other plugins when selecting a default plugin for a command. By default, this includes the standard loaded plugins. You probably shouldn't change this if you don't know what you're doing; if you do know what you're doing, then also know that this set is case-sensitive.""")) # supybot.commands.disabled moved to callbacks for canonicalName. ### # supybot.abuse. For stuff relating to abuse of the bot. ### registerGroup(supybot, 'abuse') registerGroup(supybot.abuse, 'flood') registerGlobalValue(supybot.abuse.flood, 'command', registry.Boolean(True, """Determines whether the bot will defend itself against command-flooding.""")) registerGlobalValue(supybot.abuse.flood.command, 'maximum', registry.PositiveInteger(12, """Determines how many commands users are allowed per minute. If a user sends more than this many commands in any 60 second period, he or she will be ignored for supybot.abuse.flood.command.punishment seconds.""")) registerGlobalValue(supybot.abuse.flood.command, 'punishment', registry.PositiveInteger(300, """Determines how many seconds the bot will ignore users who flood it with commands.""")) registerGlobalValue(supybot.abuse.flood.command, 'invalid', registry.Boolean(True, """Determines whether the bot will defend itself against invalid command-flooding.""")) registerGlobalValue(supybot.abuse.flood.command.invalid, 'maximum', registry.PositiveInteger(5, """Determines how many invalid commands users are allowed per minute. If a user sends more than this many invalid commands in any 60 second period, he or she will be ignored for supybot.abuse.flood.command.invalid.punishment seconds. Typically, this value is lower than supybot.abuse.flood.command.maximum, since it's far less likely (and far more annoying) for users to flood with invalid commands than for them to flood with valid commands.""")) registerGlobalValue(supybot.abuse.flood.command.invalid, 'punishment', registry.PositiveInteger(600, """Determines how many seconds the bot will ignore users who flood it with invalid commands. Typically, this value is higher than supybot.abuse.flood.command.punishment, since it's far less likely (and far more annoying) for users to flood witih invalid commands than for them to flood with valid commands.""")) ### # supybot.drivers. For stuff relating to Supybot's drivers (duh!) ### registerGroup(supybot, 'drivers') registerGlobalValue(supybot.drivers, 'poll', registry.PositiveFloat(1.0, """Determines the default length of time a driver should block waiting for input.""")) class ValidDriverModule(registry.OnlySomeStrings): validStrings = ('default', 'Socket', 'Twisted') registerGlobalValue(supybot.drivers, 'module', ValidDriverModule('default', """Determines what driver module the bot will use. Socket, a simple driver based on timeout sockets, is used by default because it's simple and stable. Twisted is very stable and simple, and if you've got Twisted installed, is probably your best bet.""")) registerGlobalValue(supybot.drivers, 'maxReconnectWait', registry.PositiveFloat(300.0, """Determines the maximum time the bot will wait before attempting to reconnect to an IRC server. The bot may, of course, reconnect earlier if possible.""")) ### # supybot.directories, for stuff relating to directories. ### # XXX This shouldn't make directories willy-nilly. As it is now, if it's # configured, it'll still make the default directories, I think. class Directory(registry.String): def __call__(self): # ??? Should we perhaps always return an absolute path here? v = super(Directory, self).__call__() if not os.path.exists(v): os.mkdir(v) return v def dirize(self, filename): myself = self() if os.path.isabs(filename): filename = os.path.abspath(filename) selfAbs = os.path.abspath(myself) commonPrefix = os.path.commonprefix([selfAbs, filename]) filename = filename[len(commonPrefix):] elif not os.path.isabs(myself): if filename.startswith(myself): filename = filename[len(myself):] filename = filename.lstrip(os.path.sep) # Stupid os.path.join! return os.path.join(myself, filename) class DataFilename(registry.String): def __call__(self): v = super(DataFilename, self).__call__() dataDir = supybot.directories.data() if not v.startswith(dataDir): v = os.path.basename(v) v = os.path.join(dataDir, v) self.setValue(v) return v class DataFilenameDirectory(DataFilename, Directory): def __call__(self): v = DataFilename.__call__(self) v = Directory.__call__(self) return v registerGroup(supybot, 'directories') registerGlobalValue(supybot.directories, 'conf', Directory('conf', """Determines what directory configuration data is put into.""")) registerGlobalValue(supybot.directories, 'data', Directory('data', """Determines what directory data is put into.""")) registerGlobalValue(supybot.directories, 'backup', Directory('backup', """Determines what directory backup data is put into.""")) registerGlobalValue(supybot.directories.data, 'tmp', DataFilenameDirectory('tmp', """Determines what directory temporary files are put into.""")) utils.file.AtomicFile.default.tmpDir = supybot.directories.data.tmp utils.file.AtomicFile.default.backupDir = supybot.directories.backup registerGlobalValue(supybot.directories, 'plugins', registry.CommaSeparatedListOfStrings([], """Determines what directories the bot will look for plugins in. Accepts a comma-separated list of strings. This means that to add another directory, you can nest the former value and add a new one. E.g. you can say: bot: 'config supybot.directories.plugins [config supybot.directories.plugins], newPluginDirectory'.""")) registerGlobalValue(supybot, 'plugins', registry.SpaceSeparatedSetOfStrings([], """Determines what plugins will be loaded.""", orderAlphabetically=True)) registerGlobalValue(supybot.plugins, 'alwaysLoadImportant', registry.Boolean(True, """Determines whether the bot will always load important plugins (Admin, Channel, Config, Misc, Owner, and User) regardless of what their configured state is. Generally, if these plugins are configured not to load, you didn't do it on purpose, and you still want them to load. Users who don't want to load these plugins are smart enough to change the value of this variable appropriately :)""")) ### # supybot.databases. For stuff relating to Supybot's databases (duh!) ### class Databases(registry.SpaceSeparatedListOfStrings): def __call__(self): v = super(Databases, self).__call__() if not v: v = ['anydbm', 'cdb', 'flat', 'pickle'] if 'sqlite' in sys.modules: v.insert(0, 'sqlite') return v def serialize(self): return ' '.join(self.value) registerGlobalValue(supybot, 'databases', Databases([], """Determines what databases are available for use. If this value is not configured (that is, if its value is empty) then sane defaults will be provided.""")) registerGroup(supybot.databases, 'users') registerGlobalValue(supybot.databases.users, 'filename', registry.String('users.conf', """Determines what filename will be used for the users database. This file will go into the directory specified by the supybot.directories.conf variable.""")) registerGlobalValue(supybot.databases.users, 'timeoutIdentification', registry.Integer(0, """Determines how long it takes identification to time out. If the value is less than or equal to zero, identification never times out.""")) registerGlobalValue(supybot.databases.users, 'allowUnregistration', registry.Boolean(False, """Determines whether the bot will allow users to unregister their users. This can wreak havoc with already-existing databases, so by default we don't allow it. Enable this at your own risk. (Do also note that this does not prevent the owner of the bot from using the unregister command.) """)) registerGroup(supybot.databases, 'ignores') registerGlobalValue(supybot.databases.ignores, 'filename', registry.String('ignores.conf', """Determines what filename will be used for the ignores database. This file will go into the directory specified by the supybot.directories.conf variable.""")) registerGroup(supybot.databases, 'channels') registerGlobalValue(supybot.databases.channels, 'filename', registry.String('channels.conf', """Determines what filename will be used for the channels database. This file will go into the directory specified by the supybot.directories.conf variable.""")) # TODO This will need to do more in the future (such as making sure link.allow # will let the link occur), but for now let's just leave it as this. class ChannelSpecific(registry.Boolean): def getChannelLink(self, channel): channelSpecific = supybot.databases.plugins.channelSpecific channels = [channel] def hasLinkChannel(channel): if not get(channelSpecific, channel): lchannel = get(channelSpecific.link, channel) if not get(channelSpecific.link.allow, lchannel): return False return channel != lchannel return False lchannel = channel while hasLinkChannel(lchannel): lchannel = get(channelSpecific.link, lchannel) if lchannel not in channels: channels.append(lchannel) else: # Found a cyclic link. We'll just use the current channel lchannel = channel break return lchannel registerGroup(supybot.databases, 'plugins') registerChannelValue(supybot.databases.plugins, 'channelSpecific', ChannelSpecific(True, """Determines whether database-based plugins that can be channel-specific will be so. This can be overridden by individual channels. Do note that the bot needs to be restarted immediately after changing this variable or your db plugins may not work for your channel; also note that you may wish to set supybot.databases.plugins.channelSpecific.link appropriately if you wish to share a certain channel's databases globally.""")) registerChannelValue(supybot.databases.plugins.channelSpecific, 'link', ValidChannel('#', """Determines what channel global (non-channel-specific) databases will be considered a part of. This is helpful if you've been running channel-specific for awhile and want to turn the databases for your primary channel into global databases. If supybot.databases.plugins.channelSpecific.link.allow prevents linking, the current channel will be used. Do note that the bot needs to be restarted immediately after changing this variable or your db plugins may not work for your channel.""")) registerChannelValue(supybot.databases.plugins.channelSpecific.link, 'allow', registry.Boolean(True, """Determines whether another channel's global (non-channel-specific) databases will be allowed to link to this channel's databases. Do note that the bot needs to be restarted immediately after changing this variable or your db plugins may not work for your channel. """)) class CDB(registry.Boolean): def connect(self, filename): import supybot.cdb as cdb basename = os.path.basename(filename) journalName = supybot.directories.data.tmp.dirize(basename+'.journal') return cdb.open(filename, 'c', journalName=journalName, maxmods=self.maximumModifications()) registerGroup(supybot.databases, 'types') registerGlobalValue(supybot.databases.types, 'cdb', CDB(True, """Determines whether CDB databases will be allowed as a database implementation.""")) registerGlobalValue(supybot.databases.types.cdb, 'maximumModifications', registry.Float(0.5, """Determines how often CDB databases will have their modifications flushed to disk. When the number of modified records is greater than this part of the number of unmodified records, the database will be entirely flushed to disk.""")) # XXX Configuration variables for dbi, sqlite, flat, mysql, etc. ### # Protocol information. ### originalIsNick = ircutils.isNick def isNick(s, strictRfc=None, **kw): if strictRfc is None: strictRfc = supybot.protocols.irc.strictRfc() return originalIsNick(s, strictRfc=strictRfc, **kw) ircutils.isNick = isNick ### # supybot.protocols ### registerGroup(supybot, 'protocols') ### # supybot.protocols.irc ### registerGroup(supybot.protocols, 'irc') class Banmask(registry.SpaceSeparatedSetOfStrings): validStrings = ('exact', 'nick', 'user', 'host') def __init__(self, *args, **kwargs): assert self.validStrings, 'There must be some valid strings. ' \ 'This is a bug.' self.__parent = super(Banmask, self) self.__parent.__init__(*args, **kwargs) self.__doc__ = format('Valid values include %L.', map(repr, self.validStrings)) def help(self): strings = [s for s in self.validStrings if s] return format('%s Valid strings: %L.', self._help, strings) def normalize(self, s): lowered = s.lower() L = list(map(str.lower, self.validStrings)) try: i = L.index(lowered) except ValueError: return s # This is handled in setValue. return self.validStrings[i] def setValue(self, v): v = map(self.normalize, v) for s in v: if s not in self.validStrings: self.error() self.__parent.setValue(self.List(v)) def makeBanmask(self, hostmask, options=None): """Create a banmask from the given hostmask. If a style of banmask isn't specified via options, the value of conf.supybot.protocols.irc.banmask is used. A variable named 'channel' (defining the channel the ban is taking place in) is expected to be in the environment of the caller of this function. options - A list specifying which parts of the hostmask should explicitly be matched: nick, user, host. If 'exact' is given, then only the exact hostmask will be used.""" assert ircutils.isChannel(dynamic.channel) (nick, user, host) = ircutils.splitHostmask(hostmask) bnick = '*' buser = '*' bhost = '*' if not options: options = get(supybot.protocols.irc.banmask, dynamic.channel) for option in options: if option == 'nick': bnick = nick elif option == 'user': buser = user elif option == 'host': bhost = host elif option == 'exact': return hostmask return ircutils.joinHostmask(bnick, buser, bhost) registerChannelValue(supybot.protocols.irc, 'banmask', Banmask(['user', 'host'], """Determines what will be used as the default banmask style.""")) registerGlobalValue(supybot.protocols.irc, 'strictRfc', registry.Boolean(False, """Determines whether the bot will strictly follow the RFC; currently this only affects what strings are considered to be nicks. If you're using a server or a network that requires you to message a nick such as services@this.network.server then you you should set this to False.""")) registerGlobalValue(supybot.protocols.irc, 'umodes', registry.String('', """Determines what user modes the bot will request from the server when it first connects. Many people might choose +i; some networks allow +x, which indicates to the auth services on those networks that you should be given a fake host.""")) registerGlobalValue(supybot.protocols.irc, 'vhost', registry.String('', """Determines what vhost the bot will bind to before connecting to the IRC server.""")) registerGlobalValue(supybot.protocols.irc, 'maxHistoryLength', registry.Integer(1000, """Determines how many old messages the bot will keep around in its history. Changing this variable will not take effect until the bot is restarted.""")) registerGlobalValue(supybot.protocols.irc, 'throttleTime', registry.Float(1.0, """A floating point number of seconds to throttle queued messages -- that is, messages will not be sent faster than once per throttleTime seconds.""")) registerGlobalValue(supybot.protocols.irc, 'ping', registry.Boolean(True, """Determines whether the bot will send PINGs to the server it's connected to in order to keep the connection alive and discover earlier when it breaks. Really, this option only exists for debugging purposes: you always should make it True unless you're testing some strange server issues.""")) registerGlobalValue(supybot.protocols.irc.ping, 'interval', registry.Integer(120, """Determines the number of seconds between sending pings to the server, if pings are being sent to the server.""")) registerGroup(supybot.protocols.irc, 'queuing') registerGlobalValue(supybot.protocols.irc.queuing, 'duplicates', registry.Boolean(False, """Determines whether the bot will refuse duplicate messages to be queued for delivery to the server. This is a safety mechanism put in place to prevent plugins from sending the same message multiple times; most of the time it doesn't matter, unless you're doing certain kinds of plugin hacking.""")) registerGroup(supybot.protocols.irc.queuing, 'rateLimit') registerGlobalValue(supybot.protocols.irc.queuing.rateLimit, 'join', registry.Float(0, """Determines how many seconds must elapse between JOINs sent to the server.""")) ### # supybot.protocols.http ### registerGroup(supybot.protocols, 'http') registerGlobalValue(supybot.protocols.http, 'peekSize', registry.PositiveInteger(4096, """Determines how many bytes the bot will 'peek' at when looking through a URL for a doctype or title or something similar. It'll give up after it reads this many bytes, even if it hasn't found what it was looking for.""")) registerGlobalValue(supybot.protocols.http, 'proxy', registry.String('', """Determines what proxy all HTTP requests should go through. The value should be of the form 'host:port'.""")) utils.web.proxy = supybot.protocols.http.proxy ### # Especially boring stuff. ### registerGlobalValue(supybot, 'defaultIgnore', registry.Boolean(False, """Determines whether the bot will ignore unregistered users by default. Of course, that'll make it particularly hard for those users to register or identify with the bot, but that's your problem to solve.""")) class IP(registry.String): """Value must be a valid IP.""" def setValue(self, v): if v and not (utils.net.isIP(v) or utils.net.isIPV6(v)): self.error() else: registry.String.setValue(self, v) registerGlobalValue(supybot, 'externalIP', IP('', """A string that is the external IP of the bot. If this is the empty string, the bot will attempt to find out its IP dynamically (though sometimes that doesn't work, hence this variable).""")) class SocketTimeout(registry.PositiveInteger): """Value must be an integer greater than supybot.drivers.poll and must be greater than or equal to 1.""" def setValue(self, v): if v < supybot.drivers.poll() or v < 1: self.error() registry.PositiveInteger.setValue(self, v) socket.setdefaulttimeout(self.value) registerGlobalValue(supybot, 'defaultSocketTimeout', SocketTimeout(10, """Determines what the default timeout for socket objects will be. This means that *all* sockets will timeout when this many seconds has gone by (unless otherwise modified by the author of the code that uses the sockets).""")) registerGlobalValue(supybot, 'pidFile', registry.String('', """Determines what file the bot should write its PID (Process ID) to, so you can kill it more easily. If it's left unset (as is the default) then no PID file will be written. A restart is required for changes to this variable to take effect.""")) ### # Debugging options. ### registerGroup(supybot, 'debug') registerGlobalValue(supybot.debug, 'threadAllCommands', registry.Boolean(False, """Determines whether the bot will automatically thread all commands.""")) registerGlobalValue(supybot.debug, 'flushVeryOften', registry.Boolean(False, """Determines whether the bot will automatically flush all flushers *very* often. Useful for debugging when you don't know what's breaking or when, but think that it might be logged.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/dbi.py0000644000000000000000000003131011206611405013575 0ustar ### # 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. ### """ Module for some slight database-independence for simple databases. """ import csv import math import supybot.cdb as cdb import supybot.utils as utils from supybot.utils.iter import ilen class Error(Exception): """General error for this module.""" class NoRecordError(KeyError): pass class InvalidDBError(Exception): pass class MappingInterface(object): """This is a class to represent the underlying representation of a map from integer keys to strings.""" def __init__(self, filename, **kwargs): """Feel free to ignore the filename.""" raise NotImplementedError def get(id): """Gets the record matching id. Raises NoRecordError otherwise.""" raise NotImplementedError def set(id, s): """Sets the record matching id to s.""" raise NotImplementedError def add(self, s): """Adds a new record, returning a new id for it.""" raise NotImplementedError def remove(self, id): "Returns and removes the record with the given id from the database." raise NotImplementedError def __iter__(self): "Return an iterator over (id, s) pairs. Not required to be ordered." raise NotImplementedError def flush(self): """Flushes current state to disk.""" raise NotImplementedError def close(self): """Flushes current state to disk and invalidates the Mapping.""" raise NotImplementedError def vacuum(self): "Cleans up in the database, if possible. Not required to do anything." pass class DirMapping(MappingInterface): def __init__(self, filename, **kwargs): self.dirname = filename if not os.path.exists(self.dirname): os.mkdir(self.dirname) if not os.path.exists(os.path.join(self.dirname, 'max')): self._setMax(1) def _setMax(self, id): fd = file(os.path.join(self.dirname, 'max'), 'w') try: fd.write(str(id)) finally: fd.close() def _getMax(self): fd = file(os.path.join(self.dirname, 'max')) try: i = int(fd.read()) return i finally: fd.close() def _makeFilename(self, id): return os.path.join(self.dirname, str(id)) def get(id): try: fd = file(self._makeFilename(id)) return fd.read() except EnvironmentError, e: exn = NoRecordError(id) exn.realException = e raise exn def set(id, s): fd = file(self._makeFilename(id), 'w') fd.write(s) fd.close() def add(self, s): id = self._getMax() fd = file(self._makeFilename(id), 'w') try: fd.write(s) return id finally: fd.close() def remove(self, id): try: os.remove(self._makeFilename(id)) except EnvironmentError, e: raise NoRecordError, id class FlatfileMapping(MappingInterface): def __init__(self, filename, maxSize=10**6): self.filename = filename try: fd = file(self.filename) strId = fd.readline().rstrip() self.maxSize = len(strId) try: self.currentId = int(strId) except ValueError: raise Error, 'Invalid file for FlatfileMapping: %s' % filename except EnvironmentError, e: # File couldn't be opened. self.maxSize = int(math.log10(maxSize)) self.currentId = 0 self._incrementCurrentId() def _canonicalId(self, id): if id is not None: return str(id).zfill(self.maxSize) else: return '-'*self.maxSize def _incrementCurrentId(self, fd=None): fdWasNone = fd is None if fdWasNone: fd = file(self.filename, 'a') fd.seek(0) self.currentId += 1 fd.write(self._canonicalId(self.currentId)) fd.write('\n') if fdWasNone: fd.close() def _splitLine(self, line): line = line.rstrip('\r\n') (id, s) = line.split(':', 1) return (id, s) def _joinLine(self, id, s): return '%s:%s\n' % (self._canonicalId(id), s) def add(self, s): line = self._joinLine(self.currentId, s) fd = file(self.filename, 'r+') try: fd.seek(0, 2) # End. fd.write(line) return self.currentId finally: self._incrementCurrentId(fd) fd.close() def get(self, id): strId = self._canonicalId(id) try: fd = file(self.filename) fd.readline() # First line, nextId. for line in fd: (lineId, s) = self._splitLine(line) if lineId == strId: return s raise NoRecordError, id finally: fd.close() # XXX This assumes it's not been given out. We should make sure that our # maximum id remains accurate if this is some value we've never given # out -- i.e., self.maxid = max(self.maxid, id) or something. def set(self, id, s): strLine = self._joinLine(id, s) try: fd = file(self.filename, 'r+') self.remove(id, fd) fd.seek(0, 2) # End. fd.write(strLine) finally: fd.close() def remove(self, id, fd=None): fdWasNone = fd is None strId = self._canonicalId(id) try: if fdWasNone: fd = file(self.filename, 'r+') fd.seek(0) fd.readline() # First line, nextId pos = fd.tell() line = fd.readline() while line: (lineId, _) = self._splitLine(line) if lineId == strId: fd.seek(pos) fd.write(self._canonicalId(None)) fd.seek(pos) fd.readline() # Same line we just rewrote the id for. pos = fd.tell() line = fd.readline() # We should be at the end. finally: if fdWasNone: fd.close() def __iter__(self): fd = file(self.filename) fd.readline() # First line, nextId. for line in fd: (id, s) = self._splitLine(line) if not id.startswith('-'): yield (int(id), s) fd.close() def vacuum(self): infd = file(self.filename) outfd = utils.file.AtomicFile(self.filename,makeBackupIfSmaller=False) outfd.write(infd.readline()) # First line, nextId. for line in infd: if not line.startswith('-'): outfd.write(line) infd.close() outfd.close() def flush(self): pass # No-op, we maintain no open files. def close(self): self.vacuum() # Should we do this? It should be fine. class CdbMapping(MappingInterface): def __init__(self, filename, **kwargs): self.filename = filename self._openCdb() # So it can be overridden later. if 'nextId' not in self.db: self.db['nextId'] = '1' def _openCdb(self, *args, **kwargs): self.db = cdb.open(self.filename, 'c', **kwargs) def _getNextId(self): i = int(self.db['nextId']) self.db['nextId'] = str(i+1) return i def get(self, id): try: return self.db[str(id)] except KeyError: raise NoRecordError, id # XXX Same as above. def set(self, id, s): self.db[str(id)] = s def add(self, s): id = self._getNextId() self.set(id, s) return id def remove(self, id): del self.db[str(id)] def __iter__(self): for (id, s) in self.db.iteritems(): if id != 'nextId': yield (int(id), s) def flush(self): self.db.flush() def close(self): self.db.close() class DB(object): Mapping = 'flat' # This is a good, sane default. Record = None def __init__(self, filename, Mapping=None, Record=None): if Record is not None: self.Record = Record if Mapping is not None: self.Mapping = Mapping if isinstance(self.Mapping, basestring): self.Mapping = Mappings[self.Mapping] self.map = self.Mapping(filename) def _newRecord(self, id, s): record = self.Record(id=id) record.deserialize(s) return record def get(self, id): s = self.map.get(id) return self._newRecord(id, s) def set(self, id, record): s = record.serialize() self.map.set(id, s) def add(self, record): s = record.serialize() id = self.map.add(s) record.id = id return id def remove(self, id): self.map.remove(id) def __iter__(self): for (id, s) in self.map: # We don't need to yield the id because it's in the record. yield self._newRecord(id, s) def select(self, p): for record in self: if p(record): yield record def random(self): try: return self._newRecord(*utils.iter.choice(self.map)) except IndexError: return None def size(self): return ilen(self.map) def flush(self): self.map.flush() def vacuum(self): self.map.vacuum() def close(self): self.map.close() Mappings = { 'cdb': CdbMapping, 'flat': FlatfileMapping, } class Record(object): def __init__(self, id=None, **kwargs): if id is not None: assert isinstance(id, int), 'id must be an integer.' self.id = id self.fields = [] self.defaults = {} self.converters = {} for name in self.__fields__: if isinstance(name, tuple): (name, spec) = name else: spec = utils.safeEval assert name != 'id' self.fields.append(name) if isinstance(spec, tuple): (converter, default) = spec else: converter = spec default = None self.defaults[name] = default self.converters[name] = converter seen = set() for (name, value) in kwargs.iteritems(): assert name in self.fields, 'name must be a record value.' seen.add(name) setattr(self, name, value) for name in self.fields: if name not in seen: default = self.defaults[name] if callable(default): default = default() setattr(self, name, default) def serialize(self): return csv.join([repr(getattr(self, name)) for name in self.fields]) def deserialize(self, s): unseenRecords = set(self.fields) for (name, strValue) in zip(self.fields, csv.split(s)): setattr(self, name, self.converters[name](strValue)) unseenRecords.remove(name) for name in unseenRecords: setattr(self, name, self.defaults[name]) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/registry.py0000644000000000000000000005110111206611405014707 0ustar ### # Copyright (c) 2004-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 re import os import time import string import textwrap import supybot.utils as utils def error(s): """Replace me with something better from another module!""" print '***', s def exception(s): """Ditto!""" print '***', s, 'A bad exception.' class RegistryException(Exception): pass class InvalidRegistryFile(RegistryException): pass class InvalidRegistryName(RegistryException): pass class InvalidRegistryValue(RegistryException): pass class NonExistentRegistryEntry(RegistryException): pass _cache = utils.InsensitivePreservingDict() _lastModified = 0 def open(filename, clear=False): """Initializes the module by loading the registry file into memory.""" global _lastModified if clear: _cache.clear() _fd = file(filename) fd = utils.file.nonCommentNonEmptyLines(_fd) acc = '' slashEnd = re.compile(r'\\*$') for line in fd: line = line.rstrip('\r\n') # XXX There should be some way to determine whether or not we're # starting a new variable or not. As it is, if there's a backslash # at the end of every line in a variable, it won't be read, and # worse, the error will pass silently. # # If the line ends in an odd number of backslashes, then there is a # line-continutation. m = slashEnd.search(line) if m and len(m.group(0)) % 2: acc += line[:-1] continue else: acc += line try: (key, value) = re.split(r'(?', _cache[name] self.set(_cache[name]) if self._supplyDefault: for (k, v) in _cache.iteritems(): if k.startswith(self._name): rest = k[len(self._name)+1:] # +1 is for . parts = split(rest) if len(parts) == 1 and parts[0] == name: try: self.__makeChild(group, v) except InvalidRegistryValue: # It's probably supposed to be registered later. pass def register(self, name, node=None): if not isValidRegistryName(name): raise InvalidRegistryName, name if node is None: node = Group() # We tried in any number of horrible ways to make it so that # re-registering something would work. It doesn't, plain and simple. # For the longest time, we had an "Is this right?" comment here, but # from experience, we now know that it most definitely *is* right. if name not in self._children: self._children[name] = node self._added.append(name) names = split(self._name) names.append(name) fullname = join(names) node.setName(fullname) else: # We do this so the return value from here is at least useful; # otherwise, we're just returning a useless, unattached node # that's simply a waste of space. node = self._children[name] return node def unregister(self, name): try: node = self._children[name] del self._children[name] # We do this because we need to remove case-insensitively. name = name.lower() for elt in reversed(self._added): if elt.lower() == name: self._added.remove(elt) if node._name in _cache: del _cache[node._name] return node except KeyError: self.__nonExistentEntry(name) def rename(self, old, new): node = self.unregister(old) self.register(new, node) def getValues(self, getChildren=False, fullNames=True): L = [] if self._orderAlphabetically: self._added.sort() for name in self._added: node = self._children[name] if hasattr(node, 'value') or hasattr(node, 'help'): if node.__class__ is not self.X: L.append((node._name, node)) if getChildren: L.extend(node.getValues(getChildren, fullNames)) if not fullNames: L = [(split(s)[-1], node) for (s, node) in L] return L class Value(Group): """Invalid registry value. If you're getting this message, report it, because we forgot to put a proper help string here.""" def __init__(self, default, help, setDefault=True, showDefault=True, **kwargs): self.__parent = super(Value, self) self.__parent.__init__(help, **kwargs) self._default = default self._showDefault = showDefault self._help = utils.str.normalizeWhitespace(help.strip()) if setDefault: self.setValue(default) def error(self): if self.__doc__: s = self.__doc__ else: s = """%s has no docstring. If you're getting this message, report it, because we forgot to put a proper help string here."""%\ self._name e = InvalidRegistryValue(utils.str.normalizeWhitespace(s)) e.value = self raise e def setName(self, *args): if self._name == 'unset': self._lastModified = 0 self.__parent.setName(*args) self._lastModified = time.time() def set(self, s): """Override this with a function to convert a string to whatever type you want, and call self.setValue to set the value.""" self.setValue(s) def setValue(self, v): """Check conditions on the actual value type here. I.e., if you're a IntegerLessThanOneHundred (all your values must be integers less than 100) convert to an integer in set() and check that the integer is less than 100 in this method. You *must* call this parent method in your own setValue.""" self._lastModified = time.time() self.value = v if self._supplyDefault: for (name, v) in self._children.items(): if v.__class__ is self.X: self.unregister(name) def __str__(self): return repr(self()) def serialize(self): return str(self).replace('\\', '\\\\') # We tried many, *many* different syntactic methods here, and this one was # simply the best -- not very intrusive, easily overridden by subclasses, # etc. def __call__(self): if _lastModified > self._lastModified: if self._name in _cache: self.set(_cache[self._name]) return self.value class Boolean(Value): """Value must be either True or False (or On or Off).""" def set(self, s): try: v = utils.str.toBool(s) except ValueError: if s.strip().lower() == 'toggle': v = not self.value else: self.error() self.setValue(v) def setValue(self, v): super(Boolean, self).setValue(bool(v)) class Integer(Value): """Value must be an integer.""" def set(self, s): try: self.setValue(int(s)) except ValueError: self.error() class NonNegativeInteger(Integer): """Value must be a non-negative integer.""" def setValue(self, v): if v < 0: self.error() super(NonNegativeInteger, self).setValue(v) class PositiveInteger(NonNegativeInteger): """Value must be positive (non-zero) integer.""" def setValue(self, v): if not v: self.error() super(PositiveInteger, self).setValue(v) class Float(Value): """Value must be a floating-point number.""" def set(self, s): try: self.setValue(float(s)) except ValueError: self.error() def setValue(self, v): try: super(Float, self).setValue(float(v)) except ValueError: self.error() class PositiveFloat(Float): """Value must be a floating-point number greater than zero.""" def setValue(self, v): if v <= 0: self.error() else: super(PositiveFloat, self).setValue(v) class Probability(Float): """Value must be a floating point number in the range [0, 1].""" def __init__(self, *args, **kwargs): self.__parent = super(Probability, self) self.__parent.__init__(*args, **kwargs) def setValue(self, v): if 0 <= v <= 1: self.__parent.setValue(v) else: self.error() class String(Value): """Value is not a valid Python string.""" def set(self, s): if not s: s = '""' elif s[0] != s[-1] or s[0] not in '\'"': s = repr(s) try: v = utils.safeEval(s) if not isinstance(v, basestring): raise ValueError self.setValue(v) except ValueError: # This catches utils.safeEval(s) errors too. self.error() _printable = string.printable[:-4] def _needsQuoting(self, s): return s.translate(utils.str.chars, self._printable) and s.strip() != s def __str__(self): s = self.value if self._needsQuoting(s): s = repr(s) return s class OnlySomeStrings(String): validStrings = () def __init__(self, *args, **kwargs): assert self.validStrings, 'There must be some valid strings. ' \ 'This is a bug.' self.__parent = super(OnlySomeStrings, self) self.__parent.__init__(*args, **kwargs) self.__doc__ = format('Valid values include %L.', map(repr, self.validStrings)) def help(self): strings = [s for s in self.validStrings if s] return format('%s Valid strings: %L.', self._help, strings) def normalize(self, s): lowered = s.lower() L = list(map(str.lower, self.validStrings)) try: i = L.index(lowered) except ValueError: return s # This is handled in setValue. return self.validStrings[i] def setValue(self, s): s = self.normalize(s) if s in self.validStrings: self.__parent.setValue(s) else: self.error() class NormalizedString(String): def __init__(self, default, *args, **kwargs): default = self.normalize(default) self.__parent = super(NormalizedString, self) self.__parent.__init__(default, *args, **kwargs) self._showDefault = False def normalize(self, s): return utils.str.normalizeWhitespace(s.strip()) def set(self, s): s = self.normalize(s) self.__parent.set(s) def setValue(self, s): s = self.normalize(s) self.__parent.setValue(s) def serialize(self): s = str(self).replace('\\', '\\\\') prefixLen = len(self._name) + 2 lines = textwrap.wrap(s, width=76-prefixLen) last = len(lines)-1 for (i, line) in enumerate(lines): if i != 0: line = ' '*prefixLen + line if i != last: line += '\\' lines[i] = line ret = os.linesep.join(lines) return ret class StringSurroundedBySpaces(String): def setValue(self, v): if v and v.lstrip() == v: v= ' ' + v if v.rstrip() == v: v += ' ' super(StringSurroundedBySpaces, self).setValue(v) class StringWithSpaceOnRight(String): def setValue(self, v): if v and v.rstrip() == v: v += ' ' super(StringWithSpaceOnRight, self).setValue(v) class Regexp(Value): """Value must be a valid regular expression.""" def __init__(self, *args, **kwargs): kwargs['setDefault'] = False self.sr = '' self.value = None self.__parent = super(Regexp, self) self.__parent.__init__(*args, **kwargs) def error(self, e): self.__parent.error('Value must be a regexp of the form %s' % e) def set(self, s): try: if s: self.setValue(utils.str.perlReToPythonRe(s), sr=s) else: self.setValue(None) except ValueError, e: self.error(e) def setValue(self, v, sr=None): parent = super(Regexp, self) if v is None: self.sr = '' parent.setValue(None) elif sr is not None: self.sr = sr parent.setValue(v) else: raise InvalidRegistryValue, \ 'Can\'t setValue a regexp, there would be an inconsistency '\ 'between the regexp and the recorded string value.' def __str__(self): self() # Gotta update if we've been reloaded. return self.sr class SeparatedListOf(Value): List = list Value = Value sorted = False def splitter(self, s): """Override this with a function that takes a string and returns a list of strings.""" raise NotImplementedError def joiner(self, L): """Override this to join the internal list for output.""" raise NotImplementedError def set(self, s): L = self.splitter(s) for (i, s) in enumerate(L): v = self.Value(s, '') L[i] = v() self.setValue(L) def setValue(self, v): super(SeparatedListOf, self).setValue(self.List(v)) def __str__(self): values = self() if self.sorted: values = sorted(values) if values: return self.joiner(values) else: # We must return *something* here, otherwise down along the road we # can run into issues showing users the value if they've disabled # nick prefixes in any of the numerous ways possible. Since the # config parser doesn't care about this space, we'll use it :) return ' ' class SpaceSeparatedListOf(SeparatedListOf): def splitter(self, s): return s.split() joiner = ' '.join class SpaceSeparatedListOfStrings(SpaceSeparatedListOf): Value = String class SpaceSeparatedSetOfStrings(SpaceSeparatedListOfStrings): List = set class CommaSeparatedListOfStrings(SeparatedListOf): Value = String def splitter(self, s): return re.split(r'\s*,\s*', s) joiner = ', '.join # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/__init__.py0000644000000000000000000000604411206611405014604 0ustar ### # 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 sys import os.path import dynamicScope import supybot.utils as utils __builtins__['format'] = utils.str.format class Author(object): def __init__(self, name=None, nick=None, email=None, **kwargs): self.__dict__.update(kwargs) self.name = name self.nick = nick self.email = email def __str__(self): return '%s (%s) <%s>' % (self.name, self.nick, utils.web.mungeEmail(self.email)) class authors(object): # This is basically a bag. jemfinch = Author('Jeremy Fincher', 'jemfinch', 'jemfinch@users.sf.net') jamessan = Author('James Vega', 'jamessan', 'jamessan@users.sf.net') strike = Author('Daniel DiPaolo', 'Strike', 'ddipaolo@users.sf.net') baggins = Author('William Robinson', 'baggins', 'airbaggins@users.sf.net') skorobeus = Author('Kevin Murphy', 'Skorobeus', 'skoro@skoroworld.com') inkedmn = Author('Brett Kelly', 'inkedmn', 'inkedmn@users.sf.net') bwp = Author('Brett Phipps', 'bwp', 'phippsb@gmail.com') bear = Author('Mike Taylor', 'bear', 'bear@code-bear.com') grantbow = Author('Grant Bowman', 'Grantbow', 'grantbow@grantbow.com') unknown = Author('Unknown author', 'unknown', 'unknown@supybot.org') # Let's be somewhat safe about this. def __getattr__(self, attr): try: return getattr(super(authors, self), attr.lower()) except AttributeError: return self.unknown # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/questions.py0000644000000000000000000001146511206611405015102 0ustar ### # 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. ### """Handles interactive questions; useful for wizards and whatnot.""" import sys import textwrap from getpass import getpass as getPass import supybot.ansi as ansi import supybot.utils as utils useBold = False def output(s, unformatted=True, fd=sys.stdout): if unformatted: s = textwrap.fill(utils.str.normalizeWhitespace(s), width=65) print >>fd, s print >>fd def expect(prompt, possibilities, recursed=False, default=None, acceptEmpty=False, fd=sys.stdout): """Prompt the user with prompt, allow them to choose from possibilities. If possibilities is empty, allow anything. """ prompt = utils.str.normalizeWhitespace(prompt) originalPrompt = prompt if recursed: output('Sorry, that response was not an option.') if useBold: choices = '[%s%%s%s]' % (ansi.RESET, ansi.BOLD) else: choices = '[%s]' if possibilities: prompt = '%s %s' % (originalPrompt, choices % '/'.join(possibilities)) if len(prompt) > 70: prompt = '%s %s' % (originalPrompt, choices % '/ '.join(possibilities)) if default is not None: if useBold: prompt = '%s %s(default: %s)' % (prompt, ansi.RESET, default) else: prompt = '%s (default: %s)' % (prompt, default) prompt = textwrap.fill(prompt) prompt = prompt.replace('/ ', '/') prompt = prompt.strip() + ' ' if useBold: prompt += ansi.RESET print >>fd, ansi.BOLD, s = raw_input(prompt) s = s.strip() print >>fd if possibilities: if s in possibilities: return s elif not s and default is not None: return default elif not s and acceptEmpty: return s else: return expect(originalPrompt, possibilities, recursed=True, default=default) else: if not s and default is not None: return default return s.strip() def anything(prompt): """Allow anything from the user.""" return expect(prompt, []) def something(prompt, default=None): """Allow anything *except* nothing from the user.""" s = expect(prompt, [], default=default) while not s: output('Sorry, you must enter a value.') s = expect(prompt, [], default=default) return s def yn(prompt, default=None): """Allow only 'y' or 'n' from the user.""" if default is not None: if default: default = 'y' else: default = 'n' s = expect(prompt, ['y', 'n'], default=default) if s is 'y': return True else: return False def getpass(prompt='Enter password: ', secondPrompt='Re-enter password: '): """Prompt the user for a password.""" password = '' secondPassword = ' ' # Note that this should be different than password. assert prompt if not prompt[-1].isspace(): prompt += ' ' while True: if useBold: prompt += ansi.RESET sys.stdout.write(ansi.BOLD) password = getPass(prompt) secondPassword = getPass(secondPrompt) if password != secondPassword: output('Passwords don\'t match.') else: break return password # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/unpreserve.py0000644000000000000000000000601011206611405015234 0ustar ### # Copyright (c) 2004-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. ### class Reader(object): def __init__(self, Creator, *args, **kwargs): self.Creator = Creator self.args = args self.kwargs = kwargs self.creator = None self.modifiedCreator = False self.indent = None def normalizeCommand(self, s): return s.lower() def readFile(self, filename): self.read(file(filename)) def read(self, fd): lineno = 0 for line in fd: lineno += 1 if not line.strip(): continue line = line.rstrip('\r\n') line = line.expandtabs() s = line.lstrip(' ') indent = len(line) - len(s) if indent != self.indent: # New indentation level. if self.creator is not None: self.creator.finish() self.creator = self.Creator(*self.args, **self.kwargs) self.modifiedCreator = False self.indent = indent (command, rest) = s.split(None, 1) command = self.normalizeCommand(command) self.modifiedCreator = True if hasattr(self.creator, command): command = getattr(self.creator, command) command(rest, lineno) else: self.creator.badCommand(command, rest, lineno) if self.modifiedCreator: self.creator.finish() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/0000755000000000000000000000000011206611405013627 5ustar supybot-0.83.4.1.ds.orig/src/utils/iter.py0000644000000000000000000001207411206611405015150 0ustar ### # 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 __future__ import division import sys import new import random from itertools import * def len(iterable): """Returns the length of an iterator.""" i = 0 for _ in iterable: i += 1 return i def trueCycle(iterable): while 1: yielded = False for x in iterable: yield x yielded = True if not yielded: raise StopIteration if sys.version_info < (2, 4, 0): def groupby(key, iterable): if key is None: key = lambda x: x it = iter(iterable) value = it.next() # If there are no items, this takes an early exit oldkey = key(value) group = [value] for value in it: newkey = key(value) if newkey != oldkey: yield group group = [] oldkey = newkey group.append(value) yield group def partition(p, iterable): """Partitions an iterable based on a predicate p. Returns a (yes,no) tuple""" no = [] yes = [] for elt in iterable: if p(elt): yes.append(elt) else: no.append(elt) return (yes, no) def any(p, iterable): """Returns true if any element in iterable satisfies predicate p.""" for elt in ifilter(p, iterable): return True else: return False def all(p, iterable): """Returns true if all elements in iterable satisfy predicate p.""" for elt in ifilterfalse(p, iterable): return False else: return True def choice(iterable): if isinstance(iterable, (list, tuple)): return random.choice(iterable) else: n = 1 m = new.module('') # Guaranteed unique value. ret = m for x in iterable: if random.random() < 1/n: ret = x n += 1 if ret is m: raise IndexError return ret def flatten(iterable, strings=False): """Flattens a list of lists into a single list. See the test for examples. """ for elt in iterable: if not strings and isinstance(elt, basestring): yield elt else: try: for x in flatten(elt): yield x except TypeError: yield elt def split(isSeparator, iterable, maxsplit=-1, yieldEmpty=False): """split(isSeparator, iterable, maxsplit=-1, yieldEmpty=False) Splits an iterator based on a predicate isSeparator.""" if isinstance(isSeparator, basestring): f = lambda s: s == isSeparator else: f = isSeparator acc = [] for element in iterable: if maxsplit == 0 or not f(element): acc.append(element) else: maxsplit -= 1 if acc or yieldEmpty: yield acc acc = [] if acc or yieldEmpty: yield acc def ilen(iterable): i = 0 for _ in iterable: i += 1 return i def startswith(long, short): longI = iter(long) shortI = iter(short) try: while True: if shortI.next() != longI.next(): return False except StopIteration: return True def limited(iterable, limit): i = limit iterable = iter(iterable) try: while i: yield iterable.next() i -= 1 except StopIteration: raise ValueError, 'Expected %s elements in iterable (%r), got %s.' % \ (limit, iterable, limit-i) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/python.py0000644000000000000000000001043411206611405015524 0ustar ### # Copyright (c) 2005-2009, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 sys import types import UserDict import threading def universalImport(*names): """Attempt to import the given modules, in order, returning the first successfully imported module. ImportError will be raised, as usual, if no imports succeed. To emulate ``from ModuleA import ModuleB'', pass the string 'ModuleA.ModuleB'""" f = sys._getframe(1) for name in names: try: # __import__ didn't gain keyword arguments until 2.5 ret = __import__(name, f.f_globals) except ImportError: continue else: if '.' in name: parts = name.split('.')[1:] while parts: ret = getattr(ret, parts[0]) del parts[0] return ret raise ImportError, ','.join(names) def changeFunctionName(f, name, doc=None): if doc is None: doc = f.__doc__ newf = types.FunctionType(f.func_code, f.func_globals, name, f.func_defaults, f.func_closure) newf.__doc__ = doc return newf class Object(object): def __ne__(self, other): return not self == other class Synchronized(type): METHODS = '__synchronized__' LOCK = '_Synchronized_rlock' def __new__(cls, name, bases, dict): sync = set() for base in bases: if hasattr(base, Synchronized.METHODS): sync.update(getattr(base, Synchronized.METHODS)) if Synchronized.METHODS in dict: sync.update(dict[Synchronized.METHODS]) if sync: def synchronized(f): def g(self, *args, **kwargs): lock = getattr(self, Synchronized.LOCK) lock.acquire() try: f(self, *args, **kwargs) finally: lock.release() return changeFunctionName(g, f.func_name, f.__doc__) for attr in sync: if attr in dict: dict[attr] = synchronized(dict[attr]) original__init__ = dict.get('__init__') def __init__(self, *args, **kwargs): if not hasattr(self, Synchronized.LOCK): setattr(self, Synchronized.LOCK, threading.RLock()) if original__init__: original__init__(self, *args, **kwargs) else: # newclass is defined below. super(newclass, self).__init__(*args, **kwargs) dict['__init__'] = __init__ newclass = super(Synchronized, cls).__new__(cls, name, bases, dict) return newclass # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/src/utils/gen.py0000644000000000000000000002373011206611405014757 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 sys import new import time import types import compiler import textwrap import UserDict import traceback from str import format from file import mktemp from iter import imap, all import crypt def abbrev(strings, d=None): """Returns a dictionary mapping unambiguous abbreviations to full forms.""" def eachSubstring(s): for i in xrange(1, len(s)+1): yield s[:i] if len(strings) != len(set(strings)): raise ValueError, \ 'strings given to utils.abbrev have duplicates: %r' % strings if d is None: d = {} for s in strings: for abbreviation in eachSubstring(s): if abbreviation not in d: d[abbreviation] = s else: if abbreviation not in strings: d[abbreviation] = None removals = [] for key in d: if d[key] is None: removals.append(key) for key in removals: del d[key] return d def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True, weeks=True, days=True, hours=True, minutes=True, seconds=True): """Given seconds, returns a string with an English description of how much time as passed. leadingZeroes determines whether 0 days, 0 hours, etc. will be printed; the others determine what larger time periods should be used. """ ret = [] def Format(s, i): if i or leadingZeroes or ret: if short: ret.append('%s%s' % (i, s[0])) else: ret.append(format('%n', (i, s))) elapsed = int(elapsed) assert years or weeks or days or \ hours or minutes or seconds, 'One flag must be True' if years: (yrs, elapsed) = (elapsed // 31536000, elapsed % 31536000) Format('year', yrs) if weeks: (wks, elapsed) = (elapsed // 604800, elapsed % 604800) Format('week', wks) if days: (ds, elapsed) = (elapsed // 86400, elapsed % 86400) Format('day', ds) if hours: (hrs, elapsed) = (elapsed // 3600, elapsed % 3600) Format('hour', hrs) if minutes or seconds: (mins, secs) = (elapsed // 60, elapsed % 60) if leadingZeroes or mins: Format('minute', mins) if seconds: leadingZeroes = True Format('second', secs) if not ret: raise ValueError, 'Time difference not great enough to be noted.' if short: return ' '.join(ret) else: return format('%L', ret) def findBinaryInPath(s): """Return full path of a binary if it's in PATH, otherwise return None.""" cmdLine = None for dir in os.getenv('PATH').split(':'): filename = os.path.join(dir, s) if os.path.exists(filename): cmdLine = filename break return cmdLine def sortBy(f, L): """Uses the decorate-sort-undecorate pattern to sort L by function f.""" for (i, elt) in enumerate(L): L[i] = (f(elt), i, elt) L.sort() for (i, elt) in enumerate(L): L[i] = L[i][2] def saltHash(password, salt=None, hash='sha'): if salt is None: salt = mktemp()[:8] if hash == 'sha': hasher = crypt.sha elif hash == 'md5': hasher = crypt.md5 return '|'.join([salt, hasher(salt + password).hexdigest()]) def safeEval(s, namespace={'True': True, 'False': False, 'None': None}): """Evaluates s, safely. Useful for turning strings into tuples/lists/etc. without unsafely using eval().""" try: node = compiler.parse(s) except SyntaxError, e: raise ValueError, 'Invalid string: %s.' % e nodes = compiler.parse(s).node.nodes if not nodes: if node.__class__ is compiler.ast.Module: return node.doc else: raise ValueError, format('Unsafe string: %q', s) node = nodes[0] if node.__class__ is not compiler.ast.Discard: raise ValueError, format('Invalid expression: %q', s) node = node.getChildNodes()[0] def checkNode(node): if node.__class__ is compiler.ast.Const: return True if node.__class__ in (compiler.ast.List, compiler.ast.Tuple, compiler.ast.Dict): return all(checkNode, node.getChildNodes()) if node.__class__ is compiler.ast.Name: if node.name in namespace: return True else: return False else: return False if checkNode(node): return eval(s, namespace, namespace) else: raise ValueError, format('Unsafe string: %q', s) def exnToString(e): """Turns a simple exception instance into a string (better than str(e))""" strE = str(e) if strE: return '%s: %s' % (e.__class__.__name__, strE) else: return e.__class__.__name__ class IterableMap(object): """Define .iteritems() in a class and subclass this to get the other iters. """ def iteritems(self): raise NotImplementedError def iterkeys(self): for (key, _) in self.iteritems(): yield key __iter__ = iterkeys def itervalues(self): for (_, value) in self.iteritems(): yield value def items(self): return list(self.iteritems()) def keys(self): return list(self.iterkeys()) def values(self): return list(self.itervalues()) def __len__(self): ret = 0 for _ in self.iteritems(): ret += 1 return ret def __nonzero__(self): for _ in self.iteritems(): return True return False class InsensitivePreservingDict(UserDict.DictMixin, object): def key(self, s): """Override this if you wish.""" if s is not None: s = s.lower() return s def __init__(self, dict=None, key=None): if key is not None: self.key = key self.data = {} if dict is not None: self.update(dict) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, super(InsensitivePreservingDict, self).__repr__()) def fromkeys(cls, keys, s=None, dict=None, key=None): d = cls(dict=dict, key=key) for key in keys: d[key] = s return d fromkeys = classmethod(fromkeys) def __getitem__(self, k): return self.data[self.key(k)][1] def __setitem__(self, k, v): self.data[self.key(k)] = (k, v) def __delitem__(self, k): del self.data[self.key(k)] def iteritems(self): return self.data.itervalues() def keys(self): L = [] for (k, _) in self.iteritems(): L.append(k) return L def __reduce__(self): return (self.__class__, (dict(self.data.values()),)) class NormalizingSet(set): def __init__(self, iterable=()): iterable = imap(self.normalize, iterable) super(NormalizingSet, self).__init__(iterable) def normalize(self, x): return x def add(self, x): return super(NormalizingSet, self).add(self.normalize(x)) def remove(self, x): return super(NormalizingSet, self).remove(self.normalize(x)) def discard(self, x): return super(NormalizingSet, self).discard(self.normalize(x)) def __contains__(self, x): return super(NormalizingSet, self).__contains__(self.normalize(x)) has_key = __contains__ def stackTrace(frame=None, compact=True): if frame is None: frame = sys._getframe() if compact: L = [] while frame: lineno = frame.f_lineno funcname = frame.f_code.co_name filename = os.path.basename(frame.f_code.co_filename) L.append('[%s|%s|%s]' % (filename, funcname, lineno)) frame = frame.f_back return textwrap.fill(' '.join(L)) else: return traceback.format_stack(frame) def callTracer(fd=None, basename=True): if fd is None: fd = sys.stdout def tracer(frame, event, _): if event == 'call': code = frame.f_code lineno = frame.f_lineno funcname = code.co_name filename = code.co_filename if basename: filename = os.path.basename(filename) print >>fd, '%s: %s(%s)' % (filename, funcname, lineno) return tracer # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/str.py0000644000000000000000000003415311206611405015017 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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. ### """ Simple utility functions related to strings. """ import re import new import sys import string import textwrap from iter import all, any from structures import TwoWayDictionary curry = new.instancemethod chars = string.maketrans('', '') def rsplit(s, sep=None, maxsplit=-1): """Equivalent to str.split, except splitting from the right.""" if sys.version_info < (2, 4, 0): if sep is not None: sep = sep[::-1] L = s[::-1].split(sep, maxsplit) L.reverse() return [s[::-1] for s in L] else: return s.rsplit(sep, maxsplit) def normalizeWhitespace(s): """Normalizes the whitespace in a string; \s+ becomes one space.""" return ' '.join(s.split()) def distance(s, t): """Returns the levenshtein edit distance between two strings.""" n = len(s) m = len(t) if n == 0: return m elif m == 0: return n d = [] for i in xrange(n+1): d.append([]) for j in xrange(m+1): d[i].append(0) d[0][j] = j d[i][0] = i for i in xrange(1, n+1): cs = s[i-1] for j in xrange(1, m+1): ct = t[j-1] cost = int(cs != ct) d[i][j] = min(d[i-1][j]+1, d[i][j-1]+1, d[i-1][j-1]+cost) return d[n][m] _soundextrans = string.maketrans(string.ascii_uppercase, '01230120022455012623010202') _notUpper = chars.translate(chars, string.ascii_uppercase) def soundex(s, length=4): """Returns the soundex hash of a given string.""" s = s.upper() # Make everything uppercase. s = s.translate(chars, _notUpper) # Delete non-letters. if not s: raise ValueError, 'Invalid string for soundex: %s' firstChar = s[0] # Save the first character. s = s.translate(_soundextrans) # Convert to soundex numbers. s = s.lstrip(s[0]) # Remove all repeated first characters. L = [firstChar] for c in s: if c != L[-1]: L.append(c) L = [c for c in L if c != '0'] + (['0']*(length-1)) s = ''.join(L) return length and s[:length] or s.rstrip('0') def dqrepr(s): """Returns a repr() of s guaranteed to be in double quotes.""" # The wankers-that-be decided not to use double-quotes anymore in 2.3. # return '"' + repr("'\x00" + s)[6:] return '"%s"' % s.encode('string_escape').replace('"', '\\"') def quoted(s): """Returns a quoted s.""" return '"%s"' % s _openers = '{[(<' _closers = '}])>' def _getSep(s, allowBraces=False): if len(s) < 2: raise ValueError, 'string given to _getSep is too short: %r' % s if allowBraces: braces = _closers else: braces = _openers + _closers if s.startswith('m') or s.startswith('s'): separator = s[1] else: separator = s[0] if separator.isalnum() or separator in braces: raise ValueError, \ 'Invalid separator: separator must not be alphanumeric or in ' \ '"%s"' % braces return separator def perlReToPythonRe(s): """Converts a string representation of a Perl regular expression (i.e., m/^foo$/i or /foo|bar/) to a Python regular expression. """ opener = closer = _getSep(s, True) if opener in '{[(<': closer = _closers[_openers.index(opener)] opener = re.escape(opener) closer = re.escape(closer) matcher = re.compile(r'm?%s((?:\\.|[^\\])*)%s(.*)' % (opener, closer)) try: (regexp, flags) = matcher.match(s).groups() except AttributeError: # Unpack list of wrong size. raise ValueError, 'Must be of the form m/.../ or /.../' regexp = regexp.replace('\\'+opener, opener) if opener != closer: regexp = regexp.replace('\\'+closer, closer) flag = 0 try: for c in flags.upper(): flag |= getattr(re, c) except AttributeError: raise ValueError, 'Invalid flag: %s' % c try: return re.compile(regexp, flag) except re.error, e: raise ValueError, str(e) def perlReToReplacer(s): """Converts a string representation of a Perl regular expression (i.e., s/foo/bar/g or s/foo/bar/i) to a Python function doing the equivalent replacement. """ sep = _getSep(s) escaped = re.escape(sep) matcher = re.compile(r's%s((?:\\.|[^\\])*)%s((?:\\.|[^\\])*)%s(.*)' % (escaped, escaped, escaped)) try: (regexp, replace, flags) = matcher.match(s).groups() except AttributeError: # Unpack list of wrong size. raise ValueError, 'Must be of the form s/.../.../' regexp = regexp.replace('\x08', r'\b') replace = replace.replace('\\'+sep, sep) for i in xrange(10): replace = replace.replace(chr(i), r'\%s' % i) g = False if 'g' in flags: g = True flags = filter('g'.__ne__, flags) r = perlReToPythonRe(sep.join(('', regexp, flags))) if g: return curry(r.sub, replace) else: return lambda s: r.sub(replace, s, 1) _perlVarSubstituteRe = re.compile(r'\$\{([^}]+)\}|\$([a-zA-Z][a-zA-Z0-9]*)') def perlVariableSubstitute(vars, text): def replacer(m): (braced, unbraced) = m.groups() var = braced or unbraced try: x = vars[var] if callable(x): return x() else: return str(x) except KeyError: if braced: return '${%s}' % braced else: return '$' + unbraced return _perlVarSubstituteRe.sub(replacer, text) def commaAndify(seq, comma=',', And='and'): """Given a a sequence, returns an English clause for that sequence. I.e., given [1, 2, 3], returns '1, 2, and 3' """ L = list(seq) if len(L) == 0: return '' elif len(L) == 1: return ''.join(L) # We need this because it raises TypeError. elif len(L) == 2: L.insert(1, And) return ' '.join(L) else: L[-1] = '%s %s' % (And, L[-1]) sep = '%s ' % comma return sep.join(L) _unCommaTheRe = re.compile(r'(.*),\s*(the)$', re.I) def unCommaThe(s): """Takes a string of the form 'foo, the' and turns it into 'the foo'.""" m = _unCommaTheRe.match(s) if m is not None: return '%s %s' % (m.group(2), m.group(1)) else: return s def ellipsisify(s, n): """Returns a shortened version of s. Produces up to the first n chars at the nearest word boundary. """ if len(s) <= n: return s else: return (textwrap.wrap(s, n-3)[0] + '...') plurals = TwoWayDictionary({}) def matchCase(s1, s2): """Matches the case of s1 in s2""" if s1.isupper(): return s2.upper() else: L = list(s2) for (i, char) in enumerate(s1[:len(s2)]): if char.isupper(): L[i] = L[i].upper() return ''.join(L) consonants = 'bcdfghjklmnpqrstvwxz' _pluralizeRegex = re.compile('[%s]y$' % consonants) def pluralize(s): """Returns the plural of s. Put any exceptions to the general English rule of appending 's' in the plurals dictionary. """ lowered = s.lower() # Exception dictionary if lowered in plurals: return matchCase(s, plurals[lowered]) # Words ending with 'ch', 'sh' or 'ss' such as 'punch(es)', 'fish(es) # and miss(es) elif any(lowered.endswith, ['x', 'ch', 'sh', 'ss']): return matchCase(s, s+'es') # Words ending with a consonant followed by a 'y' such as # 'try (tries)' or 'spy (spies)' elif _pluralizeRegex.search(lowered): return matchCase(s, s[:-1] + 'ies') # In all other cases, we simply add an 's' to the base word else: return matchCase(s, s+'s') _depluralizeRegex = re.compile('[%s]ies' % consonants) def depluralize(s): """Returns the singular of s.""" lowered = s.lower() if lowered in plurals: return matchCase(s, plurals[lowered]) elif any(lowered.endswith, ['ches', 'shes', 'sses']): return s[:-2] elif re.search(_depluralizeRegex, lowered): return s[:-3] + 'y' else: if lowered.endswith('s'): return s[:-1] # Chop off 's'. else: return s # Don't know what to do. def nItems(n, item, between=None): """Works like this: >>> nItems(1, 'clock') '1 clock' >>> nItems(10, 'clock') '10 clocks' >>> nItems(10, 'clock', between='grandfather') '10 grandfather clocks' """ assert isinstance(n, int) or isinstance(n, long), \ 'The order of the arguments to nItems changed again, sorry.' if between is None: if n != 1: return format('%s %p', n, item) else: return format('%s %s', n, item) else: if n != 1: return format('%s %s %p', n, between, item) else: return format('%s %s %s', n, between, item) def ordinal(i): """Returns i + the ordinal indicator for the number. Example: ordinal(3) => '3rd' """ i = int(i) if i % 100 in (11,12,13): return '%sth' % i ord = 'th' test = i % 10 if test == 1: ord = 'st' elif test == 2: ord = 'nd' elif test == 3: ord = 'rd' return '%s%s' % (i, ord) def be(i): """Returns the form of the verb 'to be' based on the number i.""" if i == 1: return 'is' else: return 'are' def has(i): """Returns the form of the verb 'to have' based on the number i.""" if i == 1: return 'has' else: return 'have' def toBool(s): s = s.strip().lower() if s in ('true', 'on', 'enable', 'enabled', '1'): return True elif s in ('false', 'off', 'disable', 'disabled', '0'): return False else: raise ValueError, 'Invalid string for toBool: %s' % quoted(s) # When used with Supybot, this is overriden when supybot.conf is loaded def timestamp(t): if t is None: t = time.time() return time.ctime(t) _formatRe = re.compile('%((?:\d+)?\.\d+f|[bfhiLnpqrstu%])') def format(s, *args, **kwargs): """w00t. %: literal %. i: integer s: string f: float r: repr b: form of the verb 'to be' (takes an int) h: form of the verb 'to have' (takes an int) L: commaAndify (takes a list of strings or a tuple of ([strings], and)) p: pluralize (takes a string) q: quoted (takes a string) n: nItems (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item)) t: time, formatted (takes an int) u: url, wrapped in braces (this should be configurable at some point) """ args = list(args) args.reverse() # For more efficient popping. def sub(match): char = match.group(1) if char == 's': return str(args.pop()) elif char == 'i': # XXX Improve me! return str(args.pop()) elif char.endswith('f'): return ('%'+char) % args.pop() elif char == 'b': return be(args.pop()) elif char == 'h': return has(args.pop()) elif char == 'L': t = args.pop() if isinstance(t, list): return commaAndify(t) elif isinstance(t, tuple) and len(t) == 2: if not isinstance(t[0], list): raise ValueError, \ 'Invalid list for %%L in format: %s' % t if not isinstance(t[1], basestring): raise ValueError, \ 'Invalid string for %%L in format: %s' % t return commaAndify(t[0], And=t[1]) else: raise ValueError, 'Invalid value for %%L in format: %s' % t elif char == 'p': return pluralize(args.pop()) elif char == 'q': return quoted(args.pop()) elif char == 'r': return repr(args.pop()) elif char == 'n': t = args.pop() if not isinstance(t, (tuple, list)): raise ValueError, 'Invalid value for %%n in format: %s' % t if len(t) == 2: return nItems(*t) elif len(t) == 3: return nItems(t[0], t[2], between=t[1]) else: raise ValueError, 'Invalid value for %%n in format: %s' % t elif char == 't': return timestamp(args.pop()) elif char == 'u': return '<%s>' % args.pop() elif char == '%': return '%' else: raise ValueError, 'Invalid char in sub (in format).' try: return _formatRe.sub(sub, s) except IndexError: raise ValueError, 'Extra format chars in format spec: %r' % s # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/transaction.py0000644000000000000000000002102311206611405016524 0ustar ### # 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. ### """ Defines a Transaction class for multi-file transactions. """ import os import shutil import os.path import error import python import file as File # 'txn' is used as an abbreviation for 'transaction' in the following source. class FailedAcquisition(error.Error): def __init__(self, txnDir, e=None): self.txnDir = txnDir msg = 'Could not acquire transaction directory: %s.' % self.txnDir error.Error.__init__(self, msg, e) class InProgress(error.Error): def __init__(self, inProgress, e=None): self.inProgress = inProgress msg = 'Transaction appears to be in progress already: %s exists.' % \ self.inProgress error.Error.__init__(self, msg, e) class TransactionMixin(python.Object): JOURNAL = 'journal' ORIGINALS = 'originals' INPROGRESS = '.inProgress' REPLACEMENTS = 'replacements' # expects a self.dir. used by Transaction and Rollback. def __init__(self, txnDir): self.txnDir = txnDir self.dir = self.txnDir + self.INPROGRESS self._journalName = self.dirize(self.JOURNAL) def escape(self, filename): return os.path.abspath(filename)[1:] def dirize(self, *args): return os.path.join(self.dir, *args) def _original(self, filename): return self.dirize(self.ORIGINALS, self.escape(filename)) def _replacement(self, filename): return self.dirize(self.REPLACEMENTS, self.escape(filename)) def _checkCwd(self): expected = File.contents(self.dirize('cwd')) if os.getcwd() != expected: raise InvalidCwd(expected) def _journalCommands(self): journal = file(self._journalName) for line in journal: line = line.rstrip('\n') (command, rest) = line.split(None, 1) args = rest.split() yield (command, args) class Transaction(TransactionMixin): # XXX Transaction needs to be made threadsafe. def __init__(self, *args, **kwargs): """Transaction(txnDir) -> None txnDir is the directory that will hold the transaction's working files and such. If it can't be renamed, there is probably an active transaction. """ TransactionMixin.__init__(self, *args, **kwargs) if os.path.exists(self.dir): raise InProgress(self.dir) if not os.path.exists(self.txnDir): raise FailedAcquisition(self.txnDir) try: os.rename(self.txnDir, self.dir) except EnvironmentError, e: raise FailedAcquisition(self.txnDir, e) os.mkdir(self.dirize(self.ORIGINALS)) os.mkdir(self.dirize(self.REPLACEMENTS)) self._journal = file(self._journalName, 'a') cwd = file(self.dirize('cwd'), 'w') cwd.write(os.getcwd()) cwd.close() def _journalCommand(self, command, *args): File.writeLine(self._journal, '%s %s' % (command, ' '.join(map(str, args)))) self._journal.flush() def _makeOriginal(self, filename): File.copy(filename, self._original(filename)) # XXX There needs to be a way, given a transaction, to get a # "sub-transaction", which: # # 1. Doesn't try to grab the txnDir and move it, but instead is just # given the actual directory being used and uses that. # 2. Acquires the lock of the original transaction, only releasing it # when its .commit method is called (assuming Transaction is # threadsafe). # 3. Has a no-op .commit method (i.e., doesn't commit). # # This is so that, for instance, an object with an active Transaction # can give other objects a Transaction-ish object without worrying that # the transaction will be committed, while still allowing those objects # to work properly with real transactions (i.e., they still call # as they would on a normal Transaction, it just has no effect with a # sub-transaction). # The method that returns a subtransaction should be called "child." def child(self): raise NotImplementedError # XXX create, replace, etc. return file objects. This class should keep a # list of such file descriptors and only allow a commit if all of them # are closed. Trying to commit with open file objects should raise an # exception. def create(self, filename): """ Returns a file object for a filename that should be created (with the contents as they were written to the filename) when the transaction is committed. """ raise NotImplementedError # XXX. def mkdir(self, filename): raise NotImplementedError # XXX def delete(self, filename): raise NotImplementedError # XXX def replace(self, filename): """ Returns a file object for a filename that should be replaced by the contents written to the file object when the transaction is committed. """ self._checkCwd() self._makeOriginal(filename) self._journalCommand('replace', filename) return File.open(self._replacement(filename)) def append(self, filename): self._checkCwd() length = os.stat(filename).st_size self._journalCommand('append', filename, length) replacement = self._replacement(filename) File.copy(filename, replacement) return file(replacement, 'a') def commit(self, removeWhenComplete=True): self._journal.close() self._checkCwd() File.touch(self.dirize('commit')) for (command, args) in self._journalCommands(): methodName = 'commit%s' % command.capitalize() getattr(self, methodName)(*args) File.touch(self.dirize('committed')) if removeWhenComplete: shutil.rmtree(self.dir) def commitReplace(self, filename): shutil.copy(self._replacement(filename), filename) def commitAppend(self, filename, length): shutil.copy(self._replacement(filename), filename) # XXX need to be able to rename files transactionally. (hard; especially # with renames that depend on one another. It might be easier to do # rename separate from relocate.) class Rollback(TransactionMixin): def rollback(self, removeWhenComplete=True): self._checkCwd() if not os.path.exists(self.dirize('commit')): return # No action taken; commit hadn't begun. for (command, args) in self._journalCommands(): methodName = 'rollback%s' % command.capitalize() getattr(self, methodName)(*args) if removeWhenComplete: shutil.rmtree(self.dir) def rollbackReplace(self, filename): shutil.copy(self._original(filename), filename) def rollbackAppend(self, filename, length): fd = file(filename, 'a') fd.truncate(int(length)) fd.close() # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/src/utils/crypt.py0000644000000000000000000000341311206611405015343 0ustar ### # Copyright (c) 2008, James Vega # 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 sys if sys.version_info < (2, 5, 0): from md5 import md5 from sha import sha else: from hashlib import md5 from hashlib import sha1 as sha # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/structures.py0000644000000000000000000003157411206611405016436 0ustar ### # Copyright (c) 2002-2009, 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. ### """ Data structures for Python. """ import time import types import UserDict from itertools import imap class RingBuffer(object): """Class to represent a fixed-size ring buffer.""" __slots__ = ('L', 'i', 'full', 'maxSize') def __init__(self, maxSize, seq=()): if maxSize <= 0: raise ValueError, 'maxSize must be > 0.' self.maxSize = maxSize self.reset() for elt in seq: self.append(elt) def reset(self): self.full = False self.L = [] self.i = 0 def resize(self, i): if self.full: L = list(self) self.reset() self.L = L self.maxSize = i def __len__(self): return len(self.L) def __eq__(self, other): if self.__class__ == other.__class__ and \ self.maxSize == other.maxSize and len(self) == len(other): iterator = iter(other) for elt in self: otherelt = iterator.next() if not elt == otherelt: return False return True return False def __nonzero__(self): return len(self) > 0 def __contains__(self, elt): return elt in self.L def append(self, elt): if self.full: self.L[self.i] = elt self.i += 1 self.i %= len(self.L) elif len(self) == self.maxSize: self.full = True self.append(elt) else: self.L.append(elt) def extend(self, seq): for elt in seq: self.append(elt) def __getitem__(self, idx): if self.full: oidx = idx if type(oidx) == types.SliceType: L = [] for i in xrange(*slice.indices(oidx, len(self))): L.append(self[i]) return L else: (m, idx) = divmod(oidx, len(self.L)) if m and m != -1: raise IndexError, oidx idx = (idx + self.i) % len(self.L) return self.L[idx] else: if type(idx) == types.SliceType: L = [] for i in xrange(*slice.indices(idx, len(self))): L.append(self[i]) return L else: return self.L[idx] def __setitem__(self, idx, elt): if self.full: oidx = idx if type(oidx) == types.SliceType: range = xrange(*slice.indices(oidx, len(self))) if len(range) != len(elt): raise ValueError, 'seq must be the same length as slice.' else: for (i, x) in zip(range, elt): self[i] = x else: (m, idx) = divmod(oidx, len(self.L)) if m and m != -1: raise IndexError, oidx idx = (idx + self.i) % len(self.L) self.L[idx] = elt else: if type(idx) == types.SliceType: range = xrange(*slice.indices(idx, len(self))) if len(range) != len(elt): raise ValueError, 'seq must be the same length as slice.' else: for (i, x) in zip(range, elt): self[i] = x else: self.L[idx] = elt def __repr__(self): return 'RingBuffer(%r, %r)' % (self.maxSize, list(self)) def __getstate__(self): return (self.maxSize, self.full, self.i, self.L) def __setstate__(self, (maxSize, full, i, L)): self.maxSize = maxSize self.full = full self.i = i self.L = L class queue(object): """Queue class for handling large queues. Queues smaller than 1,000 or so elements are probably better served by the smallqueue class. """ __slots__ = ('front', 'back') def __init__(self, seq=()): self.back = [] self.front = [] for elt in seq: self.enqueue(elt) def reset(self): self.back[:] = [] self.front[:] = [] def enqueue(self, elt): self.back.append(elt) def dequeue(self): try: return self.front.pop() except IndexError: self.back.reverse() self.front = self.back self.back = [] return self.front.pop() def peek(self): if self.front: return self.front[-1] else: return self.back[0] def __len__(self): return len(self.front) + len(self.back) def __nonzero__(self): return bool(self.back or self.front) def __contains__(self, elt): return elt in self.front or elt in self.back def __iter__(self): for elt in reversed(self.front): yield elt for elt in self.back: yield elt def __eq__(self, other): if len(self) == len(other): otheriter = iter(other) for elt in self: otherelt = otheriter.next() if not (elt == otherelt): return False return True else: return False def __repr__(self): return 'queue([%s])' % ', '.join(imap(repr, self)) def __getitem__(self, oidx): if len(self) == 0: raise IndexError, 'queue index out of range' if type(oidx) == types.SliceType: L = [] for i in xrange(*slice.indices(oidx, len(self))): L.append(self[i]) return L else: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError, oidx if len(self.front) > idx: return self.front[-(idx+1)] else: return self.back[(idx-len(self.front))] def __setitem__(self, oidx, value): if len(self) == 0: raise IndexError, 'queue index out of range' if type(oidx) == types.SliceType: range = xrange(*slice.indices(oidx, len(self))) if len(range) != len(value): raise ValueError, 'seq must be the same length as slice.' else: for i in range: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError, oidx for (i, x) in zip(range, value): self[i] = x else: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError, oidx if len(self.front) > idx: self.front[-(idx+1)] = value else: self.back[idx-len(self.front)] = value def __delitem__(self, oidx): if type(oidx) == types.SliceType: range = xrange(*slice.indices(oidx, len(self))) for i in range: del self[i] else: (m, idx) = divmod(oidx, len(self)) if m and m != -1: raise IndexError, oidx if len(self.front) > idx: del self.front[-(idx+1)] else: del self.back[idx-len(self.front)] def __getstate__(self): return (list(self),) def __setstate__(self, (L,)): L.reverse() self.front = L self.back = [] class smallqueue(list): __slots__ = () def enqueue(self, elt): self.append(elt) def dequeue(self): return self.pop(0) def peek(self): return self[0] def __repr__(self): return 'smallqueue([%s])' % ', '.join(imap(repr, self)) def reset(self): self[:] = [] class TimeoutQueue(object): def __init__(self, timeout, queue=None): if queue is None: queue = smallqueue() self.queue = queue self.timeout = timeout def reset(self): self.queue.reset() def __repr__(self): self._clearOldElements() return '%s(timeout=%r, queue=%r)' % (self.__class__.__name__, self.timeout, self.queue) def _getTimeout(self): if callable(self.timeout): return self.timeout() else: return self.timeout def _clearOldElements(self): now = time.time() while self.queue and now - self.queue.peek()[0] > self._getTimeout(): self.queue.dequeue() def setTimeout(self, i): self.timeout = i def enqueue(self, elt, at=None): if at is None: at = time.time() self.queue.enqueue((at, elt)) def dequeue(self): self._clearOldElements() return self.queue.dequeue()[1] def __iter__(self): # We could _clearOldElements here, but what happens if someone stores # the resulting generator and elements that should've timed out are # yielded? Hmm? What happens then, smarty-pants? for (t, elt) in self.queue: if time.time() - t < self._getTimeout(): yield elt def __len__(self): # No dependency on utils.iter # return ilen(self) self._clearOldElements() return len(self.queue) class MaxLengthQueue(queue): __slots__ = ('length',) def __init__(self, length, seq=()): self.length = length queue.__init__(self, seq) def __getstate__(self): return (self.length, queue.__getstate__(self)) def __setstate__(self, (length, q)): self.length = length queue.__setstate__(self, q) def enqueue(self, elt): queue.enqueue(self, elt) if len(self) > self.length: self.dequeue() class TwoWayDictionary(dict): __slots__ = () def __init__(self, seq=(), **kwargs): if hasattr(seq, 'iteritems'): seq = seq.iteritems() elif hasattr(seq, 'items'): seq = seq.items() for (key, value) in seq: self[key] = value self[value] = key for (key, value) in kwargs.iteritems(): self[key] = value self[value] = key def __setitem__(self, key, value): dict.__setitem__(self, key, value) dict.__setitem__(self, value, key) def __delitem__(self, key): value = self[key] dict.__delitem__(self, key) dict.__delitem__(self, value) class MultiSet(object): def __init__(self, seq=()): self.d = {} for elt in seq: self.add(elt) def add(self, elt): try: self.d[elt] += 1 except KeyError: self.d[elt] = 1 def remove(self, elt): self.d[elt] -= 1 if not self.d[elt]: del self.d[elt] def __getitem__(self, elt): return self.d[elt] def __contains__(self, elt): return elt in self.d class CacheDict(UserDict.DictMixin): def __init__(self, max, **kwargs): self.d = dict(**kwargs) self.max = max def __getitem__(self, key): return self.d[key] def __setitem__(self, key, value): if len(self.d) >= self.max: self.d.clear() self.d[key] = value def __delitem__(self, key): del self.d[key] def keys(self): return self.d.keys() def iteritems(self): return self.d.iteritems() def __iter__(self): return iter(self.d) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/__init__.py0000644000000000000000000000674611206611405015755 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 sys ### # csv.{join,split} -- useful functions that should exist. ### import csv import cStringIO as StringIO def join(L): fd = StringIO.StringIO() writer = csv.writer(fd) writer.writerow(L) return fd.getvalue().rstrip('\r\n') def split(s): fd = StringIO.StringIO(s) reader = csv.reader(fd) return reader.next() csv.join = join csv.split = split # We use this often enough that we're going to stick it in builtins. def force(x): if callable(x): return x() else: return x __builtins__['force'] = force if sys.version_info < (2, 4, 0): def reversed(L): """Iterates through a sequence in reverse.""" for i in xrange(len(L) - 1, -1, -1): yield L[i] __builtins__['reversed'] = reversed def sorted(iterable, cmp=None, key=None, reversed=False): L = list(iterable) if key is not None: assert cmp is None, 'Can\'t use both cmp and key.' sortBy(key, L) else: L.sort(cmp) if reversed: L.reverse() return L __builtins__['sorted'] = sorted import operator def itemgetter(i): return lambda x: x[i] def attrgetter(attr): return lambda x: getattr(x, attr) operator.itemgetter = itemgetter operator.attrgetter = attrgetter import sets __builtins__['set'] = sets.Set __builtins__['frozenset'] = sets.ImmutableSet import socket # Some socket modules don't have sslerror, so we'll just make it an error. if not hasattr(socket, 'sslerror'): socket.sslerror = socket.error # These imports need to happen below the block above, so things get put into # __builtins__ appropriately. from gen import * import net import seq import str import web import file import iter import crypt import error import python import transaction # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/seq.py0000644000000000000000000000401711206611405014773 0ustar ### # 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. ### def window(L, size): """list * size -> window iterable Returns a sliding 'window' through the list L of size size.""" assert not isinstance(L, int), 'Argument order swapped: window(L, size)' if size < 1: raise ValueError, 'size <= 0 disallowed.' for i in xrange(len(L) - (size-1)): yield L[i:i+size] def mapinto(f, L): for (i, x) in enumerate(L): L[i] = f(x) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/net.py0000644000000000000000000000644511206611405015000 0ustar ### # 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. ### """ Simple utility modules. """ import re import socket emailRe = re.compile(r"^(\w&.+-]+!)*[\w&.+-]+@" r"(([0-9a-z]([0-9a-z-]*[0-9a-z])?\.)[a-z]{2,6}|" r"([0-9]{1,3}\.){3}[0-9]{1,3})$", re.I) def getSocket(host): """Returns a socket of the correct AF_INET type (v4 or v6) in order to communicate with host. """ addrinfo = socket.getaddrinfo(host, None) host = addrinfo[0][4][0] if isIP(host): return socket.socket(socket.AF_INET, socket.SOCK_STREAM) elif isIPV6(host): return socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: raise socket.error, 'Something wonky happened.' def isIP(s): """Returns whether or not a given string is an IPV4 address. >>> isIP('255.255.255.255') 1 >>> isIP('abc.abc.abc.abc') 0 """ try: return bool(socket.inet_aton(s)) except socket.error: return False def bruteIsIPV6(s): if s.count('::') <= 1: L = s.split(':') if len(L) <= 8: for x in L: if x: try: int(x, 16) except ValueError: return False return True return False def isIPV6(s): """Returns whether or not a given string is an IPV6 address.""" try: if hasattr(socket, 'inet_pton'): return bool(socket.inet_pton(socket.AF_INET6, s)) else: return bruteIsIPV6(s) except socket.error: try: socket.inet_pton(socket.AF_INET6, '::') except socket.error: # We gotta fake it. return bruteIsIPV6(s) return False # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/error.py0000644000000000000000000000362511206611405015340 0ustar ### # 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 gen class Error(Exception): def __init__(self, msg, e=None): self.msg = msg self.e = e def __str__(self): if self.e is not None: return os.linesep.join([self.msg, gen.exnToString(self.e)]) else: return self.msg # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/src/utils/web.py0000644000000000000000000001265111206611405014763 0ustar ### # 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 re import socket import urllib import urllib2 import httplib import sgmllib import urlparse import htmlentitydefs from str import normalizeWhitespace Request = urllib2.Request urlquote = urllib.quote urlunquote = urllib.unquote class Error(Exception): pass octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1,2})' ipAddr = r'%s(?:\.%s){3}' % (octet, octet) # Base domain regex off RFC 1034 and 1738 label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?' domain = r'%s(?:\.%s)*\.[a-z][-0-9a-z]*[a-z]?' % (label, label) urlRe = re.compile(r'(\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (domain, ipAddr), re.I) httpUrlRe = re.compile(r'(https?://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (domain, ipAddr), re.I) REFUSED = 'Connection refused.' TIMED_OUT = 'Connection timed out.' UNKNOWN_HOST = 'Unknown host.' RESET_BY_PEER = 'Connection reset by peer.' FORBIDDEN = 'Client forbidden from accessing URL.' def strError(e): try: n = e.args[0] except Exception: return str(e) if n == 111: return REFUSED elif n in (110, 10060): return TIMED_OUT elif n == 104: return RESET_BY_PEER elif n in (8, 7, 3, 2, -2, -3): return UNKNOWN_HOST elif n == 403: return FORBIDDEN else: return str(e) defaultHeaders = { 'User-agent': 'Mozilla/5.0 (compatible; utils.web python module)' } # Other modules should feel free to replace this with an appropriate # application-specific function. Feel free to use a callable here. proxy = None def getUrlFd(url, headers=None): """Gets a file-like object for a url.""" if headers is None: headers = defaultHeaders try: if not isinstance(url, urllib2.Request): if '#' in url: url = url[:url.index('#')] request = urllib2.Request(url, headers=headers) else: request = url httpProxy = force(proxy) if httpProxy: request.set_proxy(httpProxy, 'http') fd = urllib2.urlopen(request) return fd except socket.timeout, e: raise Error, TIMED_OUT except (socket.error, socket.sslerror), e: raise Error, strError(e) except httplib.InvalidURL, e: raise Error, 'Invalid URL: %s' % e except urllib2.HTTPError, e: raise Error, strError(e) except urllib2.URLError, e: raise Error, strError(e.reason) # Raised when urllib doesn't recognize the url type except ValueError, e: raise Error, strError(e) def getUrl(url, size=None, headers=None): """Gets a page. Returns a string that is the page gotten.""" fd = getUrlFd(url, headers=headers) try: if size is None: text = fd.read() else: text = fd.read(size) except socket.timeout, e: raise Error, TIMED_OUT fd.close() return text def getDomain(url): return urlparse.urlparse(url)[1] class HtmlToText(sgmllib.SGMLParser): """Taken from some eff-bot code on c.l.p.""" entitydefs = htmlentitydefs.entitydefs.copy() entitydefs['nbsp'] = ' ' def __init__(self, tagReplace=' '): self.data = [] self.tagReplace = tagReplace sgmllib.SGMLParser.__init__(self) def unknown_starttag(self, tag, attr): self.data.append(self.tagReplace) def unknown_endtag(self, tag): self.data.append(self.tagReplace) def handle_data(self, data): self.data.append(data) def getText(self): text = ''.join(self.data).strip() return normalizeWhitespace(text) def htmlToText(s, tagReplace=' '): """Turns HTML into text. tagReplace is a string to replace HTML tags with. """ x = HtmlToText(tagReplace) x.feed(s) return x.getText() def mungeEmail(s): s = s.replace('@', ' AT ') s = s.replace('.', ' DOT ') return s # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/utils/file.py0000644000000000000000000002004311206611405015117 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 time import random import shutil import os.path from iter import ifilter import crypt def contents(filename): return file(filename).read() def open(filename, mode='wb', *args, **kwargs): """filename -> file object. Returns a file object for filename, creating as many directories as may be necessary. I.e., if the filename is ./foo/bar/baz, and . exists, and ./foo exists, but ./foo/bar does not exist, bar will be created before opening baz in it. """ if mode not in ('w', 'wb'): raise ValueError, 'utils.file.open expects to write.' (dirname, basename) = os.path.split(filename) os.makedirs(dirname) return file(filename, mode, *args, **kwargs) def copy(src, dst): """src, dst -> None Copies src to dst, using this module's 'open' function to open dst. """ srcfd = file(src) dstfd = open(dst, 'wb') shutil.copyfileobj(srcfd, dstfd) def writeLine(fd, line): fd.write(line) if not line.endswith('\n'): fd.write('\n') def readLines(filename): fd = file(filename) try: return [line.rstrip('\r\n') for line in fd.readlines()] finally: fd.close() def touch(filename): fd = file(filename, 'w') fd.close() def mktemp(suffix=''): """Gives a decent random string, suitable for a filename.""" r = random.Random() m = crypt.md5(suffix) r.seed(time.time()) s = str(r.getstate()) period = random.random() now = start = time.time() while start + period < now: time.sleep() # Induce a context switch, if possible. now = time.time() m.update(str(random.random())) m.update(s) m.update(str(now)) s = m.hexdigest() return crypt.sha(s + str(time.time())).hexdigest() + suffix def nonCommentLines(fd): for line in fd: if not line.startswith('#'): yield line def nonEmptyLines(fd): return ifilter(str.strip, fd) def nonCommentNonEmptyLines(fd): return nonEmptyLines(nonCommentLines(fd)) def chunks(fd, size): return iter(lambda : fd.read(size), '') ## chunk = fd.read(size) ## while chunk: ## yield chunk ## chunk = fd.read(size) class AtomicFile(file): """Used for files that need to be atomically written -- i.e., if there's a failure, the original file remains, unmodified. mode must be 'w' or 'wb'""" class default(object): # Holder for values. # Callables? tmpDir = None backupDir = None makeBackupIfSmaller = True allowEmptyOverwrite = True def __init__(self, filename, mode='w', allowEmptyOverwrite=None, makeBackupIfSmaller=None, tmpDir=None, backupDir=None): if tmpDir is None: tmpDir = force(self.default.tmpDir) if backupDir is None: backupDir = force(self.default.backupDir) if makeBackupIfSmaller is None: makeBackupIfSmaller = force(self.default.makeBackupIfSmaller) if allowEmptyOverwrite is None: allowEmptyOverwrite = force(self.default.allowEmptyOverwrite) if mode not in ('w', 'wb'): raise ValueError, format('Invalid mode: %q', mode) self.rolledback = False self.allowEmptyOverwrite = allowEmptyOverwrite self.makeBackupIfSmaller = makeBackupIfSmaller self.filename = filename self.backupDir = backupDir if tmpDir is None: # If not given a tmpDir, we'll just put a random token on the end # of our filename and put it in the same directory. self.tempFilename = '%s.%s' % (self.filename, mktemp()) else: # If given a tmpDir, we'll get the basename (just the filename, no # directory), put our random token on the end, and put it in tmpDir tempFilename = '%s.%s' % (os.path.basename(self.filename), mktemp()) self.tempFilename = os.path.join(tmpDir, tempFilename) # This doesn't work because of the uncollectable garbage effect. # self.__parent = super(AtomicFile, self) super(AtomicFile, self).__init__(self.tempFilename, mode) def rollback(self): if not self.closed: super(AtomicFile, self).close() if os.path.exists(self.tempFilename): os.remove(self.tempFilename) self.rolledback = True def close(self): if not self.rolledback: super(AtomicFile, self).close() # We don't mind writing an empty file if the file we're overwriting # doesn't exist. newSize = os.path.getsize(self.tempFilename) originalExists = os.path.exists(self.filename) if newSize or self.allowEmptyOverwrite or not originalExists: if originalExists: oldSize = os.path.getsize(self.filename) if self.makeBackupIfSmaller and newSize < oldSize: now = int(time.time()) backupFilename = '%s.backup.%s' % (self.filename, now) if self.backupDir is not None: backupFilename = os.path.basename(backupFilename) backupFilename = os.path.join(self.backupDir, backupFilename) shutil.copy(self.filename, backupFilename) # We use shutil.move here instead of os.rename because # the latter doesn't work on Windows when self.filename # (the target) already exists. shutil.move handles those # intricacies for us. # This raises IOError if we can't write to the file. Since # in *nix, it only takes write perms to the *directory* to # rename a file (and shutil.move will use os.rename if # possible), we first check if we have the write permission # and only then do we write. fd = file(self.filename, 'a') fd.close() shutil.move(self.tempFilename, self.filename) else: raise ValueError, 'AtomicFile.close called after rollback.' def __del__(self): # We rollback because if we're deleted without being explicitly closed, # that's bad. We really should log this here, but as of yet we've got # no logging facility in utils. I've got some ideas for this, though. self.rollback() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/commands.py0000644000000000000000000007427511206611405014661 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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. ### """ Includes wrappers for commands. """ import time import types import getopt import inspect import threading import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks ### # Non-arg wrappers -- these just change the behavior of a command without # changing the arguments given to it. ### # Thread has to be a non-arg wrapper because by the time we're parsing and # validating arguments, we're inside the function we'd want to thread. def thread(f): """Makes sure a command spawns a thread when called.""" def newf(self, irc, msg, args, *L, **kwargs): if world.isMainThread(): targetArgs = (self.callingCommand, irc, msg, args) + tuple(L) t = callbacks.CommandThread(target=self._callCommand, args=targetArgs, kwargs=kwargs) t.start() else: f(self, irc, msg, args, *L, **kwargs) return utils.python.changeFunctionName(newf, f.func_name, f.__doc__) class UrlSnarfThread(world.SupyThread): def __init__(self, *args, **kwargs): assert 'url' in kwargs kwargs['name'] = 'Thread #%s (for snarfing %s)' % \ (world.threadsSpawned, kwargs.pop('url')) super(UrlSnarfThread, self).__init__(*args, **kwargs) self.setDaemon(True) def run(self): try: super(UrlSnarfThread, self).run() except utils.web.Error, e: log.debug('Exception in urlSnarfer: %s', utils.exnToString(e)) class SnarfQueue(ircutils.FloodQueue): timeout = conf.supybot.snarfThrottle def key(self, channel): return channel _snarfed = SnarfQueue() class SnarfIrc(object): def __init__(self, irc, channel, url): self.irc = irc self.url = url self.channel = channel def __getattr__(self, attr): return getattr(self.irc, attr) def reply(self, *args, **kwargs): _snarfed.enqueue(self.channel, self.url) return self.irc.reply(*args, **kwargs) # This lock is used to serialize the calls to snarfers, so # earlier snarfers are guaranteed to beat out later snarfers. _snarfLock = threading.Lock() def urlSnarfer(f): """Protects the snarfer from loops (with other bots) and whatnot.""" def newf(self, irc, msg, match, *L, **kwargs): url = match.group(0) channel = msg.args[0] if not irc.isChannel(channel): return if ircdb.channels.getChannel(channel).lobotomized: self.log.info('Not snarfing in %s: lobotomized.', channel) return if _snarfed.has(channel, url): self.log.info('Throttling snarf of %s in %s.', url, channel) return irc = SnarfIrc(irc, channel, url) def doSnarf(): _snarfLock.acquire() try: # This has to be *after* we've acquired the lock so we can be # sure that all previous urlSnarfers have already run to # completion. if msg.repliedTo: self.log.debug('Not snarfing, msg is already repliedTo.') return f(self, irc, msg, match, *L, **kwargs) finally: _snarfLock.release() if threading.currentThread() is not world.mainThread: doSnarf() else: L = list(L) t = UrlSnarfThread(target=doSnarf, url=url) t.start() newf = utils.python.changeFunctionName(newf, f.func_name, f.__doc__) return newf ### # Converters, which take irc, msg, args, and a state object, and build up the # validated and converted args for the method in state.args. ### # This is just so we can centralize this, since it may change. def _int(s): base = 10 if s.startswith('0x'): base = 16 s = s[2:] elif s.startswith('0b'): base = 2 s = s[2:] elif s.startswith('0') and len(s) > 1: base = 8 s = s[1:] try: return int(s, base) except ValueError: if base == 10: return int(float(s)) else: raise def getInt(irc, msg, args, state, type='integer', p=None): try: i = _int(args[0]) if p is not None: if not p(i): state.errorInvalid(type, args[0]) state.args.append(i) del args[0] except ValueError: state.errorInvalid(type, args[0]) def getNonInt(irc, msg, args, state, type='non-integer value'): try: i = _int(args[0]) state.errorInvalid(type, args[0]) except ValueError: state.args.append(args.pop(0)) def getLong(irc, msg, args, state, type='long'): getInt(irc, msg, args, state, type) state.args[-1] = long(state.args[-1]) def getFloat(irc, msg, args, state, type='floating point number'): try: state.args.append(float(args[0])) del args[0] except ValueError: state.errorInvalid(type, args[0]) def getPositiveInt(irc, msg, args, state, *L): getInt(irc, msg, args, state, p=lambda i: i>0, type='positive integer', *L) def getNonNegativeInt(irc, msg, args, state, *L): getInt(irc, msg, args, state, p=lambda i: i>=0, type='non-negative integer', *L) def getIndex(irc, msg, args, state): getInt(irc, msg, args, state, type='index') if state.args[-1] > 0: state.args[-1] -= 1 def getId(irc, msg, args, state, kind=None): type = 'id' if kind is not None and not kind.endswith('id'): type = kind + ' id' original = args[0] try: args[0] = args[0].lstrip('#') getInt(irc, msg, args, state, type=type) except Exception, e: args[0] = original raise def getExpiry(irc, msg, args, state): now = int(time.time()) try: expires = _int(args[0]) if expires: expires += now state.args.append(expires) del args[0] except ValueError: state.errorInvalid('number of seconds', args[0]) def getBoolean(irc, msg, args, state): try: state.args.append(utils.str.toBool(args[0])) del args[0] except ValueError: state.errorInvalid('boolean', args[0]) def getNetworkIrc(irc, msg, args, state, errorIfNoMatch=False): if args: for otherIrc in world.ircs: if otherIrc.network.lower() == args[0].lower(): state.args.append(otherIrc) del args[0] return if errorIfNoMatch: raise callbacks.ArgumentError else: state.args.append(irc) def getHaveOp(irc, msg, args, state, action='do that'): if not state.channel: getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error('I\'m not even in %s.' % state.channel, Raise=True) if not irc.state.channels[state.channel].isOp(irc.nick): state.error('I need to be opped to %s.' % action, Raise=True) def validChannel(irc, msg, args, state): if irc.isChannel(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid('channel', args[0]) def getHostmask(irc, msg, args, state): if ircutils.isUserHostmask(args[0]): state.args.append(args.pop(0)) else: try: hostmask = irc.state.nickToHostmask(args[0]) state.args.append(hostmask) del args[0] except KeyError: state.errorInvalid('nick or hostmask', args[0]) def getBanmask(irc, msg, args, state): getHostmask(irc, msg, args, state) if not state.channel: getChannel(irc, msg, args, state) banmaskstyle = conf.supybot.protocols.irc.banmask state.args[-1] = banmaskstyle.makeBanmask(state.args[-1]) def getUser(irc, msg, args, state): try: state.args.append(ircdb.users.getUser(msg.prefix)) except KeyError: state.errorNotRegistered(Raise=True) def getOtherUser(irc, msg, args, state): if ircutils.isUserHostmask(args[0]): state.errorNoUser(args[0]) try: state.args.append(ircdb.users.getUser(args[0])) del args[0] except KeyError: try: getHostmask(irc, msg, [args[0]], state) hostmask = state.args.pop() state.args.append(ircdb.users.getUser(hostmask)) del args[0] except (KeyError, callbacks.Error): state.errorNoUser(name=args[0]) def _getRe(f): def get(irc, msg, args, state, convert=True): original = args[:] s = args.pop(0) def isRe(s): try: _ = f(s) return True except ValueError: return False try: while len(s) < 512 and not isRe(s): s += ' ' + args.pop(0) if len(s) < 512: if convert: state.args.append(f(s)) else: state.args.append(s) else: state.errorInvalid('regular expression', s) except IndexError: args[:] = original state.errorInvalid('regular expression', s) return get getMatcher = _getRe(utils.str.perlReToPythonRe) getReplacer = _getRe(utils.str.perlReToReplacer) def getNick(irc, msg, args, state): if ircutils.isNick(args[0]): if 'nicklen' in irc.state.supported: if len(args[0]) > irc.state.supported['nicklen']: state.errorInvalid('nick', args[0], 'That nick is too long for this server.') state.args.append(args.pop(0)) else: state.errorInvalid('nick', args[0]) def getSeenNick(irc, msg, args, state, errmsg=None): try: _ = irc.state.nickToHostmask(args[0]) state.args.append(args.pop(0)) except KeyError: if errmsg is None: errmsg = 'I haven\'t seen %s.' % args[0] state.error(errmsg, Raise=True) def getChannel(irc, msg, args, state): if args and irc.isChannel(args[0]): channel = args.pop(0) elif irc.isChannel(msg.args[0]): channel = msg.args[0] else: state.log.debug('Raising ArgumentError because there is no channel.') raise callbacks.ArgumentError state.channel = channel state.args.append(channel) def getChannelDb(irc, msg, args, state, **kwargs): channelSpecific = conf.supybot.databases.plugins.channelSpecific try: getChannel(irc, msg, args, state, **kwargs) channel = channelSpecific.getChannelLink(state.channel) state.channel = channel state.args[-1] = channel except (callbacks.ArgumentError, IndexError): if channelSpecific(): raise channel = channelSpecific.link() if not conf.get(channelSpecific.link.allow, channel): log.warning('channelSpecific.link is globally set to %s, but ' '%s disallowed linking to its db.', channel, channel) raise else: channel = channelSpecific.getChannelLink(channel) state.channel = channel state.args.append(channel) def inChannel(irc, msg, args, state): if not state.channel: getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error('I\'m not in %s.' % state.channel, Raise=True) def onlyInChannel(irc, msg, args, state): if not (irc.isChannel(msg.args[0]) and msg.args[0] in irc.state.channels): state.error('This command may only be given in a channel that I am in.', Raise=True) else: state.channel = msg.args[0] state.args.append(state.channel) def callerInGivenChannel(irc, msg, args, state): channel = args[0] if irc.isChannel(channel): if channel in irc.state.channels: if msg.nick in irc.state.channels[channel].users: state.args.append(args.pop(0)) else: state.error('You must be in %s.' % channel, Raise=True) else: state.error('I\'m not in %s.' % channel, Raise=True) else: state.errorInvalid('channel', args[0]) def nickInChannel(irc, msg, args, state): originalArgs = state.args[:] inChannel(irc, msg, args, state) state.args = originalArgs if args[0] not in irc.state.channels[state.channel].users: state.error('%s is not in %s.' % (args[0], state.channel), Raise=True) state.args.append(args.pop(0)) def getChannelOrNone(irc, msg, args, state): try: getChannel(irc, msg, args, state) except callbacks.ArgumentError: state.args.append(None) def checkChannelCapability(irc, msg, args, state, cap): if not state.channel: getChannel(irc, msg, args, state) cap = ircdb.canonicalCapability(cap) cap = ircdb.makeChannelCapability(state.channel, cap) if not ircdb.checkCapability(msg.prefix, cap): state.errorNoCapability(cap, Raise=True) def getOp(irc, msg, args, state): checkChannelCapability(irc, msg, args, state, 'op') def getHalfop(irc, msg, args, state): checkChannelCapability(irc, msg, args, state, 'halfop') def getVoice(irc, msg, args, state): checkChannelCapability(irc, msg, args, state, 'voice') def getLowered(irc, msg, args, state): state.args.append(ircutils.toLower(args.pop(0))) def getSomething(irc, msg, args, state, errorMsg=None, p=None): if p is None: p = lambda _: True if not args[0] or not p(args[0]): if errorMsg is None: errorMsg = 'You must not give the empty string as an argument.' state.error(errorMsg, Raise=True) else: state.args.append(args.pop(0)) def getSomethingNoSpaces(irc, msg, args, state, *L): def p(s): return len(s.split(None, 1)) == 1 getSomething(irc, msg, args, state, p=p, *L) def private(irc, msg, args, state): if irc.isChannel(msg.args[0]): state.errorRequiresPrivacy(Raise=True) def public(irc, msg, args, state, errmsg=None): if not irc.isChannel(msg.args[0]): if errmsg is None: errmsg = 'This message must be sent in a channel.' state.error(errmsg, Raise=True) def checkCapability(irc, msg, args, state, cap): cap = ircdb.canonicalCapability(cap) if not ircdb.checkCapability(msg.prefix, cap): state.errorNoCapability(cap, Raise=True) def owner(irc, msg, args, state): checkCapability(irc, msg, args, state, 'owner') def admin(irc, msg, args, state): checkCapability(irc, msg, args, state, 'admin') def anything(irc, msg, args, state): state.args.append(args.pop(0)) def getGlob(irc, msg, args, state): glob = args.pop(0) if '*' not in glob and '?' not in glob: glob = '*%s*' % glob state.args.append(glob) def getUrl(irc, msg, args, state): if utils.web.urlRe.match(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid('url', args[0]) def getEmail(irc, msg, args, state): if utils.net.emailRe.match(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid('email', args[0]) def getHttpUrl(irc, msg, args, state): if utils.web.httpUrlRe.match(args[0]): state.args.append(args.pop(0)) elif utils.web.httpUrlRe.match('http://' + args[0]): state.args.append('http://' + args.pop(0)) else: state.errorInvalid('http url', args[0]) def getNow(irc, msg, args, state): state.args.append(int(time.time())) def getCommandName(irc, msg, args, state): if ' ' in args[0]: state.errorInvalid('command name', args[0]) else: state.args.append(callbacks.canonicalName(args.pop(0))) def getIp(irc, msg, args, state): if utils.net.isIP(args[0]): state.args.append(args.pop(0)) else: state.errorInvalid('ip', args[0]) def getLetter(irc, msg, args, state): if len(args[0]) == 1: state.args.append(args.pop(0)) else: state.errorInvalid('letter', args[0]) def getMatch(irc, msg, args, state, regexp, errmsg): m = regexp.search(args[0]) if m is not None: state.args.append(m) del args[0] else: state.error(errmsg, Raise=True) def getLiteral(irc, msg, args, state, literals, errmsg=None): # ??? Should we allow abbreviations? if isinstance(literals, basestring): literals = (literals,) abbrevs = utils.abbrev(literals) if args[0] in abbrevs: state.args.append(abbrevs[args.pop(0)]) elif errmsg is not None: state.error(errmsg, Raise=True) else: raise callbacks.ArgumentError def getTo(irc, msg, args, state): if args[0].lower() == 'to': args.pop(0) def getPlugin(irc, msg, args, state, require=True): cb = irc.getCallback(args[0]) if cb is not None: state.args.append(cb) del args[0] elif require: state.errorInvalid('plugin', args[0]) else: state.args.append(None) def getIrcColor(irc, msg, args, state): if args[0] in ircutils.mircColors: state.args.append(ircutils.mircColors[args.pop(0)]) else: state.errorInvalid('irc color') def getText(irc, msg, args, state): if args: state.args.append(' '.join(args)) args[:] = [] else: raise IndexError wrappers = ircutils.IrcDict({ 'id': getId, 'ip': getIp, 'int': getInt, 'index': getIndex, 'color': getIrcColor, 'now': getNow, 'url': getUrl, 'email': getEmail, 'httpUrl': getHttpUrl, 'long': getLong, 'float': getFloat, 'nonInt': getNonInt, 'positiveInt': getPositiveInt, 'nonNegativeInt': getNonNegativeInt, 'letter': getLetter, 'haveOp': getHaveOp, 'expiry': getExpiry, 'literal': getLiteral, 'to': getTo, 'nick': getNick, 'seenNick': getSeenNick, 'channel': getChannel, 'inChannel': inChannel, 'onlyInChannel': onlyInChannel, 'nickInChannel': nickInChannel, 'networkIrc': getNetworkIrc, 'callerInGivenChannel': callerInGivenChannel, 'plugin': getPlugin, 'boolean': getBoolean, 'lowered': getLowered, 'anything': anything, 'something': getSomething, 'filename': getSomething, # XXX Check for validity. 'commandName': getCommandName, 'text': getText, 'glob': getGlob, 'somethingWithoutSpaces': getSomethingNoSpaces, 'capability': getSomethingNoSpaces, 'channelDb': getChannelDb, 'hostmask': getHostmask, 'banmask': getBanmask, 'user': getUser, 'matches': getMatch, 'public': public, 'private': private, 'otherUser': getOtherUser, 'regexpMatcher': getMatcher, 'validChannel': validChannel, 'regexpReplacer': getReplacer, 'owner': owner, 'admin': admin, 'checkCapability': checkCapability, 'checkChannelCapability': checkChannelCapability, 'op': getOp, 'halfop': getHalfop, 'voice': getVoice, }) def addConverter(name, wrapper): wrappers[name] = wrapper class UnknownConverter(KeyError): pass def getConverter(name): try: return wrappers[name] except KeyError, e: raise UnknownConverter, str(e) def callConverter(name, irc, msg, args, state, *L): getConverter(name)(irc, msg, args, state, *L) ### # Contexts. These determine what the nature of conversions is; whether they're # defaulted, or many of them are allowed, etc. Contexts should be reusable; # i.e., they should not maintain state between calls. ### def contextify(spec): if not isinstance(spec, context): spec = context(spec) return spec def setDefault(state, default): if callable(default): state.args.append(default()) else: state.args.append(default) class context(object): def __init__(self, spec): self.args = () self.spec = spec # for repr if isinstance(spec, tuple): assert spec, 'tuple spec must not be empty.' self.args = spec[1:] self.converter = getConverter(spec[0]) elif spec is None: self.converter = getConverter('anything') elif isinstance(spec, basestring): self.args = () self.converter = getConverter(spec) else: assert isinstance(spec, context) self.converter = spec def __call__(self, irc, msg, args, state): log.debug('args before %r: %r', self, args) self.converter(irc, msg, args, state, *self.args) log.debug('args after %r: %r', self, args) def __repr__(self): return '<%s for %s>' % (self.__class__.__name__, self.spec) class rest(context): def __call__(self, irc, msg, args, state): if args: original = args[:] args[:] = [' '.join(args)] try: super(rest, self).__call__(irc, msg, args, state) except Exception, e: args[:] = original else: raise IndexError # additional means: Look for this (and make sure it's of this type). If # there are no arguments for us to check, then use our default. class additional(context): def __init__(self, spec, default=None): self.__parent = super(additional, self) self.__parent.__init__(spec) self.default = default def __call__(self, irc, msg, args, state): try: self.__parent.__call__(irc, msg, args, state) except IndexError: log.debug('Got IndexError, returning default.') setDefault(state, self.default) # optional means: Look for this, but if it's not the type I'm expecting or # there are no arguments for us to check, then use the default value. class optional(additional): def __call__(self, irc, msg, args, state): try: super(optional, self).__call__(irc, msg, args, state) except (callbacks.ArgumentError, callbacks.Error), e: log.debug('Got %s, returning default.', utils.exnToString(e)) state.errored = False setDefault(state, self.default) class any(context): def __init__(self, spec, continueOnError=False): self.__parent = super(any, self) self.__parent.__init__(spec) self.continueOnError = continueOnError def __call__(self, irc, msg, args, state): st = state.essence() try: while args: self.__parent.__call__(irc, msg, args, st) except IndexError: pass except (callbacks.ArgumentError, callbacks.Error), e: if not self.continueOnError: raise else: log.debug('Got %s, returning default.', utils.exnToString(e)) pass state.args.append(st.args) class many(any): def __call__(self, irc, msg, args, state): super(many, self).__call__(irc, msg, args, state) if not state.args[-1]: state.args.pop() raise callbacks.ArgumentError class first(context): def __init__(self, *specs, **kw): if 'default' in kw: self.default = kw.pop('default') assert not kw, 'Bad kwargs for first.__init__' self.spec = specs # for __repr__ self.specs = map(contextify, specs) def __call__(self, irc, msg, args, state): errored = False for spec in self.specs: try: spec(irc, msg, args, state) return except Exception, e: errored = state.errored state.errored = False continue if hasattr(self, 'default'): state.args.append(self.default) else: state.errored = errored raise e class reverse(context): def __call__(self, irc, msg, args, state): args[:] = args[::-1] super(reverse, self).__call__(irc, msg, args, state) args[:] = args[::-1] class commalist(context): def __call__(self, irc, msg, args, state): original = args[:] st = state.essence() trailingComma = True try: while trailingComma: arg = args.pop(0) if not arg.endswith(','): trailingComma = False for part in arg.split(','): if part: # trailing commas super(commalist, self).__call__(irc, msg, [part], st) state.args.append(st.args) except Exception, e: args[:] = original raise class getopts(context): """The empty string indicates that no argument is taken; None indicates that there is no converter for the argument.""" def __init__(self, getopts): self.spec = getopts # for repr self.getopts = {} self.getoptL = [] for (name, spec) in getopts.iteritems(): if spec == '': self.getoptL.append(name) self.getopts[name] = None else: self.getoptL.append(name + '=') self.getopts[name] = contextify(spec) log.debug('getopts: %r', self.getopts) log.debug('getoptL: %r', self.getoptL) def __call__(self, irc, msg, args, state): log.debug('args before %r: %r', self, args) (optlist, rest) = getopt.getopt(args, '', self.getoptL) getopts = [] for (opt, arg) in optlist: opt = opt[2:] # Strip -- log.debug('opt: %r, arg: %r', opt, arg) context = self.getopts[opt] if context is not None: st = state.essence() context(irc, msg, [arg], st) assert len(st.args) == 1 getopts.append((opt, st.args[0])) else: getopts.append((opt, True)) state.args.append(getopts) args[:] = rest log.debug('args after %r: %r', self, args) ### # This is our state object, passed to converters along with irc, msg, and args. ### class State(object): log = log def __init__(self, types): self.args = [] self.kwargs = {} self.types = types self.channel = None self.errored = False def __getattr__(self, attr): if attr.startswith('error'): self.errored = True return getattr(dynamic.irc, attr) else: raise AttributeError, attr def essence(self): st = State(self.types) for (attr, value) in self.__dict__.iteritems(): if attr not in ('args', 'kwargs'): setattr(st, attr, value) return st def __repr__(self): return '%s(args=%r, kwargs=%r, channel=%r)' % (self.__class__.__name__, self.args, self.kwargs, self.channel) ### # This is a compiled Spec object. ### class Spec(object): def _state(self, types, attrs={}): st = State(types) st.__dict__.update(attrs) st.allowExtra = self.allowExtra return st def __init__(self, types, allowExtra=False): self.types = types self.allowExtra = allowExtra utils.seq.mapinto(contextify, self.types) def __call__(self, irc, msg, args, stateAttrs={}): state = self._state(self.types[:], stateAttrs) while state.types: context = state.types.pop(0) try: context(irc, msg, args, state) except IndexError: raise callbacks.ArgumentError if args and not state.allowExtra: log.debug('args and not self.allowExtra: %r', args) raise callbacks.ArgumentError return state def wrap(f, specList=[], name=None, **kw): name = name or f.func_name spec = Spec(specList, **kw) def newf(self, irc, msg, args, **kwargs): state = spec(irc, msg, args, stateAttrs={'cb': self, 'log': self.log}) self.log.debug('State before call: %s', state) if state.errored: self.log.debug('Refusing to call %s due to state.errored.', f) else: try: f(self, irc, msg, args, *state.args, **state.kwargs) except TypeError: self.log.error('Spec: %s', specList) self.log.error('Received args: %s', args) code = f.func_code funcArgs = inspect.getargs(code)[0][len(self.commandArgs):] self.log.error('Extra args: %s', funcArgs) raise return utils.python.changeFunctionName(newf, name, f.__doc__) __all__ = [ # Contexts. 'any', 'many', 'optional', 'additional', 'rest', 'getopts', 'first', 'reverse', 'commalist', # Converter helpers. 'getConverter', 'addConverter', 'callConverter', # Decorators. 'urlSnarfer', 'thread', # Functions. 'wrap', # Stuff for testing. 'Spec', ] # This doesn't work. Suck. ## if world.testing: ## __all__.append('Spec') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/shlex.py0000644000000000000000000001773711206611405014203 0ustar """A lexical analyzer class for simple shell-like syntaxes.""" # Module and documentation by Eric S. Raymond, 21 Dec 1998 # Input stacking and error message cleanup added by ESR, March 2000 # push_source() and pop_source() made explicit by ESR, January 2001. import os.path import sys __all__ = ["shlex"] class shlex: "A lexical analyzer class for simple shell-like syntaxes." def __init__(self, instream=None, infile=None): if instream is not None: self.instream = instream self.infile = infile else: self.instream = sys.stdin self.infile = None self.commenters = '#' self.wordchars = ('abcdfeghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_') self.whitespace = ' \t\r\n' self.quotes = '\'"' self.state = ' ' self.pushback = [] self.lineno = 1 self.debug = 0 self.token = '' self.backslash = False self.filestack = [] self.source = None if self.debug: print 'shlex: reading from %s, line %d' \ % (self.instream, self.lineno) def push_token(self, tok): "Push a token onto the stack popped by the get_token method" if self.debug >= 1: print "shlex: pushing token " + `tok` self.pushback = [tok] + self.pushback def push_source(self, newstream, newfile=None): "Push an input source onto the lexer's input source stack." self.filestack.insert(0, (self.infile, self.instream, self.lineno)) self.infile = newfile self.instream = newstream self.lineno = 1 if self.debug: if newfile is not None: print 'shlex: pushing to file %s' % (self.infile,) else: print 'shlex: pushing to stream %s' % (self.instream,) def pop_source(self): "Pop the input source stack." self.instream.close() (self.infile, self.instream, self.lineno) = self.filestack[0] self.filestack = self.filestack[1:] if self.debug: print 'shlex: popping to %s, line %d' \ % (self.instream, self.lineno) self.state = ' ' def get_token(self): "Get a token from the input stream (or from stack if it's nonempty)" if self.pushback: tok = self.pushback[0] self.pushback = self.pushback[1:] if self.debug >= 1: print "shlex: popping token " + `tok` return tok # No pushback. Get a token. raw = self.read_token() # Handle inclusions while raw == self.source: spec = self.sourcehook(self.read_token()) if spec: (newfile, newstream) = spec self.push_source(newstream, newfile) raw = self.get_token() # Maybe we got EOF instead? while raw == "": if len(self.filestack) == 0: return "" else: self.pop_source() raw = self.get_token() # Neither inclusion nor EOF if self.debug >= 1: if raw: print "shlex: token=" + `raw` else: print "shlex: token=EOF" return raw def read_token(self): "Read a token from the input stream (no pushback or inclusions)" while 1: nextchar = self.instream.read(1) if nextchar == '\n': self.lineno = self.lineno + 1 if self.debug >= 3: print "shlex: in state", repr(self.state), \ "I see character:", repr(nextchar) if self.state is None: self.token = '' # past end of file break elif self.state == ' ': if not nextchar: self.state = None # end of file break elif nextchar in self.whitespace: if self.debug >= 2: print "shlex: I see whitespace in whitespace state" if self.token: break # emit current token else: continue elif nextchar in self.commenters: self.instream.readline() self.lineno = self.lineno + 1 elif nextchar in self.wordchars: self.token = nextchar self.state = 'a' elif nextchar in self.quotes: self.token = nextchar self.state = nextchar else: self.token = nextchar if self.token: break # emit current token else: continue elif self.state in self.quotes: self.token = self.token + nextchar if nextchar == '\\': if self.backslash: self.backslash = False else: self.backslash = True else: if not self.backslash and nextchar == self.state: self.state = ' ' break elif self.backslash: self.backslash = False elif not nextchar: # end of file if self.debug >= 2: print "shlex: I see EOF in quotes state" # XXX what error should be raised here? raise ValueError, "No closing quotation" elif self.state == 'a': if not nextchar: self.state = None # end of file break elif nextchar in self.whitespace: if self.debug >= 2: print "shlex: I see whitespace in word state" self.state = ' ' if self.token: break # emit current token else: continue elif nextchar in self.commenters: self.instream.readline() self.lineno = self.lineno + 1 elif nextchar in self.wordchars or nextchar in self.quotes: self.token = self.token + nextchar else: self.pushback = [nextchar] + self.pushback if self.debug >= 2: print "shlex: I see punctuation in word state" self.state = ' ' if self.token: break # emit current token else: continue result = self.token self.token = '' if self.debug > 1: if result: print "shlex: raw token=" + `result` else: print "shlex: raw token=EOF" return result def sourcehook(self, newfile): "Hook called on a filename to be sourced." if newfile[0] == '"': newfile = newfile[1:-1] # This implements cpp-like semantics for relative-path inclusion. if type(self.infile) == type("") and not os.path.isabs(newfile): newfile = os.path.join(os.path.dirname(self.infile), newfile) return (newfile, open(newfile, "r")) def error_leader(self, infile=None, lineno=None): "Emit a C-compiler-like, Emacs-friendly error-message leader." if infile is None: infile = self.infile if lineno is None: lineno = self.lineno return "\"%s\", line %d: " % (infile, lineno) if __name__ == '__main__': if len(sys.argv) == 1: lexer = shlex() else: file = sys.argv[1] lexer = shlex(open(file), file) while 1: tt = lexer.get_token() if tt: print "Token: " + repr(tt) else: break supybot-0.83.4.1.ds.orig/src/plugin.py0000644000000000000000000001203311206611405014336 0ustar ### # 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 os import sys import imp import os.path import linecache import supybot.log as log import supybot.conf as conf import supybot.registry as registry import supybot.callbacks as callbacks installDir = os.path.dirname(sys.modules[__name__].__file__) _pluginsDir = os.path.join(installDir, 'plugins') class Deprecated(ImportError): pass def loadPluginModule(name, ignoreDeprecation=False): """Loads (and returns) the module for the plugin with the given name.""" files = [] pluginDirs = conf.supybot.directories.plugins()[:] pluginDirs.append(_pluginsDir) for dir in pluginDirs: try: files.extend(os.listdir(dir)) except EnvironmentError: # OSError, IOError superclass. log.warning('Invalid plugin directory: %s; removing.', dir) conf.supybot.directories.plugins().remove(dir) moduleInfo = imp.find_module(name, pluginDirs) try: module = imp.load_module(name, *moduleInfo) except: sys.modules.pop(name, None) raise if 'deprecated' in module.__dict__ and module.deprecated: if ignoreDeprecation: log.warning('Deprecated plugin loaded: %s', name) else: raise Deprecated, format('Attempted to load deprecated plugin %s', name) if module.__name__ in sys.modules: sys.modules[module.__name__] = module linecache.checkcache() return module def loadPluginClass(irc, module, register=None): """Loads the plugin Class from the given module into the given Irc.""" try: cb = module.Class(irc) except TypeError, e: s = str(e) if '2 given' in s and '__init__' in s: raise callbacks.Error, \ 'In our switch from CVS to Darcs (after 0.80.1), we ' \ 'changed the __init__ for callbacks.Privmsg* to also ' \ 'accept an irc argument. This plugin (%s) is overriding ' \ 'its __init__ method and needs to update its prototype ' \ 'to be \'def __init__(self, irc):\' as well as passing ' \ 'that irc object on to any calls to the plugin\'s ' \ 'parent\'s __init__.' % module.__name__ else: raise except AttributeError, e: if 'Class' in str(e): raise callbacks.Error, \ 'This plugin module doesn\'t have a "Class" ' \ 'attribute to specify which plugin should be ' \ 'instantiated. If you didn\'t write this ' \ 'plugin, but received it with Supybot, file ' \ 'a bug with us about this error.' else: raise cb.classModule = module plugin = cb.name() public = True if hasattr(cb, 'public'): public = cb.public conf.registerPlugin(plugin, register, public) assert not irc.getCallback(plugin), \ 'There is already a %r plugin registered.' % plugin try: renames = []#XXX registerRename(plugin)() if renames: for command in renames: v = registerRename(plugin, command) newName = v() assert newName renameCommand(cb, command, newName) else: conf.supybot.commands.renames.unregister(plugin) except registry.NonExistentRegistryEntry, e: pass # The plugin isn't there. irc.addCallback(cb) return cb # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/ircdb.py0000644000000000000000000012055311206611405014132 0ustar ### # Copyright (c) 2002-2009, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 __future__ import division import os import time import operator import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircutils as ircutils import supybot.registry as registry import supybot.unpreserve as unpreserve from utils.iter import imap, ilen, ifilter def isCapability(capability): return len(capability.split(None, 1)) == 1 def fromChannelCapability(capability): """Returns a (channel, capability) tuple from a channel capability.""" assert isChannelCapability(capability), 'got %s' % capability return capability.split(',', 1) def isChannelCapability(capability): """Returns True if capability is a channel capability; False otherwise.""" if ',' in capability: (channel, capability) = capability.split(',', 1) return ircutils.isChannel(channel) and isCapability(capability) else: return False def makeChannelCapability(channel, capability): """Makes a channel capability given a channel and a capability.""" assert isCapability(capability), 'got %s' % capability assert ircutils.isChannel(channel), 'got %s' % channel return '%s,%s' % (channel, capability) def isAntiCapability(capability): """Returns True if capability is an anticapability; False otherwise.""" if isChannelCapability(capability): (_, capability) = fromChannelCapability(capability) return isCapability(capability) and capability[0] == '-' def makeAntiCapability(capability): """Returns the anticapability of a given capability.""" assert isCapability(capability), 'got %s' % capability assert not isAntiCapability(capability), \ 'makeAntiCapability does not work on anticapabilities. ' \ 'You probably want invertCapability; got %s.' % capability if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) return makeChannelCapability(channel, '-' + capability) else: return '-' + capability def unAntiCapability(capability): """Takes an anticapability and returns the non-anti form.""" assert isCapability(capability), 'got %s' % capability if not isAntiCapability(capability): raise ValueError, '%s is not an anti capability' % capability if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) return ','.join((channel, capability[1:])) else: return capability[1:] def invertCapability(capability): """Make a capability into an anticapability and vice versa.""" assert isCapability(capability), 'got %s' % capability if isAntiCapability(capability): return unAntiCapability(capability) else: return makeAntiCapability(capability) def canonicalCapability(capability): if callable(capability): capability = capability() assert isCapability(capability), 'got %s' % capability return capability.lower() def unWildcardHostmask(hostmask): return hostmask.translate(utils.str.chars, '!@*?') _invert = invertCapability class CapabilitySet(set): """A subclass of set handling basic capability stuff.""" def __init__(self, capabilities=()): self.__parent = super(CapabilitySet, self) self.__parent.__init__() for capability in capabilities: self.add(capability) def add(self, capability): """Adds a capability to the set.""" capability = ircutils.toLower(capability) inverted = _invert(capability) if self.__parent.__contains__(inverted): self.__parent.remove(inverted) self.__parent.add(capability) def remove(self, capability): """Removes a capability from the set.""" capability = ircutils.toLower(capability) self.__parent.remove(capability) def __contains__(self, capability): capability = ircutils.toLower(capability) if self.__parent.__contains__(capability): return True if self.__parent.__contains__(_invert(capability)): return True else: return False def check(self, capability): """Returns the appropriate boolean for whether a given capability is 'allowed' given its (or its anticapability's) presence in the set. """ capability = ircutils.toLower(capability) if self.__parent.__contains__(capability): return True elif self.__parent.__contains__(_invert(capability)): return False else: raise KeyError, capability def __repr__(self): return '%s([%s])' % (self.__class__.__name__, ', '.join(imap(repr, self))) antiOwner = makeAntiCapability('owner') class UserCapabilitySet(CapabilitySet): """A subclass of CapabilitySet to handle the owner capability correctly.""" def __init__(self, *args, **kwargs): self.__parent = super(UserCapabilitySet, self) self.__parent.__init__(*args, **kwargs) def __contains__(self, capability): capability = ircutils.toLower(capability) if capability == 'owner' or capability == antiOwner: return True elif self.__parent.__contains__('owner'): return True else: return self.__parent.__contains__(capability) def check(self, capability): """Returns the appropriate boolean for whether a given capability is 'allowed' given its (or its anticapability's) presence in the set. Differs from CapabilitySet in that it handles the 'owner' capability appropriately. """ capability = ircutils.toLower(capability) if capability == 'owner' or capability == antiOwner: if self.__parent.__contains__('owner'): return not isAntiCapability(capability) else: return isAntiCapability(capability) elif self.__parent.__contains__('owner'): if isAntiCapability(capability): return False else: return True else: return self.__parent.check(capability) def add(self, capability): """Adds a capability to the set. Just make sure it's not -owner.""" capability = ircutils.toLower(capability) assert capability != '-owner', '"-owner" disallowed.' self.__parent.add(capability) class IrcUser(object): """This class holds the capabilities and authentications for a user.""" def __init__(self, ignore=False, password='', name='', capabilities=(), hostmasks=None, secure=False, hashed=False): self.id = None self.auth = [] # The (time, hostmask) list of auth crap. self.name = name # The name of the user. self.ignore = ignore # A boolean deciding if the person is ignored. self.secure = secure # A boolean describing if hostmasks *must* match. self.hashed = hashed # True if the password is hashed on disk. self.password = password # password (plaintext? hashed?) self.capabilities = UserCapabilitySet() for capability in capabilities: self.capabilities.add(capability) if hostmasks is None: self.hostmasks = ircutils.IrcSet() # hostmasks used for recognition else: self.hostmasks = hostmasks def __repr__(self): return format('%s(id=%s, ignore=%s, password="", name=%q, hashed=%r, ' 'capabilities=%r, hostmasks=[], secure=%r)\n', self.__class__.__name__, self.id, self.ignore, self.name, self.hashed, self.capabilities, self.secure) def __hash__(self): return hash(self.id) def addCapability(self, capability): """Gives the user the given capability.""" self.capabilities.add(capability) def removeCapability(self, capability): """Takes from the user the given capability.""" self.capabilities.remove(capability) def _checkCapability(self, capability): """Checks the user for a given capability.""" if self.ignore: if isAntiCapability(capability): return True else: return False else: return self.capabilities.check(capability) def setPassword(self, password, hashed=False): """Sets the user's password.""" if hashed or self.hashed: self.hashed = True self.password = utils.saltHash(password) else: self.password = password def checkPassword(self, password): """Checks the user's password.""" if password is None: return False if self.hashed: (salt, _) = self.password.split('|') return (self.password == utils.saltHash(password, salt=salt)) else: return (self.password == password) def checkHostmask(self, hostmask, useAuth=True): """Checks a given hostmask against the user's hostmasks or current authentication. If useAuth is False, only checks against the user's hostmasks. """ if useAuth: timeout = conf.supybot.databases.users.timeoutIdentification() removals = [] try: for (when, authmask) in self.auth: if timeout and when+timeout < time.time(): removals.append((when, authmask)) elif hostmask == authmask: return True finally: while removals: self.auth.remove(removals.pop()) for pat in self.hostmasks: if ircutils.hostmaskPatternEqual(pat, hostmask): return pat return False def addHostmask(self, hostmask): """Adds a hostmask to the user's hostmasks.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask if len(unWildcardHostmask(hostmask)) < 8: raise ValueError, \ 'Hostmask must contain at least 8 non-wildcard characters.' self.hostmasks.add(hostmask) def removeHostmask(self, hostmask): """Removes a hostmask from the user's hostmasks.""" self.hostmasks.remove(hostmask) def addAuth(self, hostmask): """Sets a user's authenticated hostmask. This times out in 1 hour.""" if self.checkHostmask(hostmask, useAuth=False) or not self.secure: self.auth.append((time.time(), hostmask)) else: raise ValueError, 'secure flag set, unmatched hostmask' def clearAuth(self): """Unsets a user's authenticated hostmask.""" for (when, hostmask) in self.auth: users.invalidateCache(hostmask=hostmask) self.auth = [] def preserve(self, fd, indent=''): def write(s): fd.write(indent) fd.write(s) fd.write(os.linesep) write('name %s' % self.name) write('ignore %s' % self.ignore) write('secure %s' % self.secure) write('hashed %s' % self.hashed) write('password %s' % self.password) for capability in self.capabilities: write('capability %s' % capability) for hostmask in self.hostmasks: write('hostmask %s' % hostmask) fd.write(os.linesep) class IrcChannel(object): """This class holds the capabilities, bans, and ignores of a channel.""" defaultOff = ('op', 'halfop', 'voice', 'protected') def __init__(self, bans=None, silences=None, exceptions=None, ignores=None, capabilities=None, lobotomized=False, defaultAllow=True): self.defaultAllow = defaultAllow self.expiredBans = [] self.bans = bans or {} self.ignores = ignores or {} self.silences = silences or [] self.exceptions = exceptions or [] self.capabilities = capabilities or CapabilitySet() for capability in self.defaultOff: if capability not in self.capabilities: self.capabilities.add(makeAntiCapability(capability)) self.lobotomized = lobotomized def __repr__(self): return '%s(bans=%r, ignores=%r, capabilities=%r, ' \ 'lobotomized=%r, defaultAllow=%s, ' \ 'silences=%r, exceptions=%r)\n' % \ (self.__class__.__name__, self.bans, self.ignores, self.capabilities, self.lobotomized, self.defaultAllow, self.silences, self.exceptions) def addBan(self, hostmask, expiration=0): """Adds a ban to the channel banlist.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask self.bans[hostmask] = int(expiration) def removeBan(self, hostmask): """Removes a ban from the channel banlist.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask return self.bans.pop(hostmask) def checkBan(self, hostmask): """Checks whether a given hostmask is banned by the channel banlist.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask now = time.time() for (pattern, expiration) in self.bans.items(): if now < expiration or not expiration: if ircutils.hostmaskPatternEqual(pattern, hostmask): return True else: self.expiredBans.append((pattern, expiration)) del self.bans[pattern] return False def addIgnore(self, hostmask, expiration=0): """Adds an ignore to the channel ignore list.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask self.ignores[hostmask] = int(expiration) def removeIgnore(self, hostmask): """Removes an ignore from the channel ignore list.""" assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask return self.ignores.pop(hostmask) def addCapability(self, capability): """Adds a capability to the channel's default capabilities.""" assert isCapability(capability), 'got %s' % capability self.capabilities.add(capability) def removeCapability(self, capability): """Removes a capability from the channel's default capabilities.""" assert isCapability(capability), 'got %s' % capability self.capabilities.remove(capability) def setDefaultCapability(self, b): """Sets the default capability in the channel.""" self.defaultAllow = b def _checkCapability(self, capability): """Checks whether a certain capability is allowed by the channel.""" assert isCapability(capability), 'got %s' % capability if capability in self.capabilities: return self.capabilities.check(capability) else: if isAntiCapability(capability): return not self.defaultAllow else: return self.defaultAllow def checkIgnored(self, hostmask): """Checks whether a given hostmask is to be ignored by the channel.""" if self.lobotomized: return True if world.testing: return False assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask if self.checkBan(hostmask): return True now = time.time() for (pattern, expiration) in self.ignores.items(): if now < expiration or not expiration: if ircutils.hostmaskPatternEqual(pattern, hostmask): return True else: del self.ignores[pattern] # Later we may wish to keep expiredIgnores, but not now. return False def preserve(self, fd, indent=''): def write(s): fd.write(indent) fd.write(s) fd.write(os.linesep) write('lobotomized %s' % self.lobotomized) write('defaultAllow %s' % self.defaultAllow) for capability in self.capabilities: write('capability ' + capability) bans = self.bans.items() utils.sortBy(operator.itemgetter(1), bans) for (ban, expiration) in bans: write('ban %s %d' % (ban, expiration)) ignores = self.ignores.items() utils.sortBy(operator.itemgetter(1), ignores) for (ignore, expiration) in ignores: write('ignore %s %d' % (ignore, expiration)) fd.write(os.linesep) class Creator(object): def badCommand(self, command, rest, lineno): raise ValueError, 'Invalid command on line %s: %s' % (lineno, command) class IrcUserCreator(Creator): u = None def __init__(self, users): if self.u is None: IrcUserCreator.u = IrcUser() self.users = users def user(self, rest, lineno): if self.u.id is not None: raise ValueError, 'Unexpected user command on line %s.' % lineno self.u.id = int(rest) def _checkId(self): if self.u.id is None: raise ValueError, 'Unexpected user description without user.' def name(self, rest, lineno): self._checkId() self.u.name = rest def ignore(self, rest, lineno): self._checkId() self.u.ignore = bool(eval(rest)) def secure(self, rest, lineno): self._checkId() self.u.secure = bool(eval(rest)) def hashed(self, rest, lineno): self._checkId() self.u.hashed = bool(eval(rest)) def password(self, rest, lineno): self._checkId() self.u.password = rest def hostmask(self, rest, lineno): self._checkId() self.u.hostmasks.add(rest) def capability(self, rest, lineno): self._checkId() self.u.capabilities.add(rest) def finish(self): if self.u.name: try: self.users.setUser(self.u) except DuplicateHostmask: log.error('Hostmasks for %s collided with another user\'s. ' 'Resetting hostmasks for %s.', self.u.name) # Some might argue that this is arbitrary, and perhaps it is. # But we've got to do *something*, so we'll show some deference # to our lower-numbered users. self.u.hostmasks.clear() self.users.setUser(self.u) IrcUserCreator.u = None class IrcChannelCreator(Creator): name = None def __init__(self, channels): self.c = IrcChannel() self.channels = channels self.hadChannel = bool(self.name) def channel(self, rest, lineno): if self.name is not None: raise ValueError, 'Unexpected channel command on line %s' % lineno IrcChannelCreator.name = rest def _checkId(self): if self.name is None: raise ValueError, 'Unexpected channel description without channel.' def lobotomized(self, rest, lineno): self._checkId() self.c.lobotomized = bool(eval(rest)) def defaultallow(self, rest, lineno): self._checkId() self.c.defaultAllow = bool(eval(rest)) def capability(self, rest, lineno): self._checkId() self.c.capabilities.add(rest) def ban(self, rest, lineno): self._checkId() (pattern, expiration) = rest.split() self.c.bans[pattern] = int(float(expiration)) def ignore(self, rest, lineno): self._checkId() (pattern, expiration) = rest.split() self.c.ignores[pattern] = int(float(expiration)) def finish(self): if self.hadChannel: self.channels.setChannel(self.name, self.c) IrcChannelCreator.name = None class DuplicateHostmask(ValueError): pass class UsersDictionary(utils.IterableMap): """A simple serialized-to-file User Database.""" def __init__(self): self.noFlush = False self.filename = None self.users = {} self.nextId = 0 self._nameCache = utils.structures.CacheDict(1000) self._hostmaskCache = utils.structures.CacheDict(1000) # This is separate because the Creator has to access our instance. def open(self, filename): self.filename = filename reader = unpreserve.Reader(IrcUserCreator, self) try: self.noFlush = True try: reader.readFile(filename) self.noFlush = False self.flush() except EnvironmentError, e: log.error('Invalid user dictionary file, resetting to empty.') log.error('Exact error: %s', utils.exnToString(e)) except Exception, e: log.exception('Exact error:') finally: self.noFlush = False def reload(self): """Reloads the database from its file.""" self.nextId = 0 self.users.clear() self._nameCache.clear() self._hostmaskCache.clear() if self.filename is not None: try: self.open(self.filename) except EnvironmentError, e: log.warning('UsersDictionary.reload failed: %s', e) else: log.error('UsersDictionary.reload called with no filename.') def flush(self): """Flushes the database to its file.""" if not self.noFlush: if self.filename is not None: L = self.users.items() L.sort() fd = utils.file.AtomicFile(self.filename) for (id, u) in L: fd.write('user %s' % id) fd.write(os.linesep) u.preserve(fd, indent=' ') fd.close() else: log.error('UsersDictionary.flush called with no filename.') else: log.debug('Not flushing UsersDictionary because of noFlush.') def close(self): self.flush() if self.flush in world.flushers: world.flushers.remove(self.flush) self.users.clear() def iteritems(self): return self.users.iteritems() def getUserId(self, s): """Returns the user ID of a given name or hostmask.""" if ircutils.isUserHostmask(s): try: return self._hostmaskCache[s] except KeyError: ids = {} for (id, user) in self.users.iteritems(): x = user.checkHostmask(s) if x: ids[id] = x if len(ids) == 1: id = ids.keys()[0] self._hostmaskCache[s] = id try: self._hostmaskCache[id].add(s) except KeyError: self._hostmaskCache[id] = set([s]) return id elif len(ids) == 0: raise KeyError, s else: log.error('Multiple matches found in user database. ' 'Removing the offending hostmasks.') for (id, hostmask) in ids.iteritems(): log.error('Removing %q from user %s.', hostmask, id) self.users[id].removeHostmask(hostmask) raise DuplicateHostmask, 'Ids %r matched.' % ids else: # Not a hostmask, must be a name. s = s.lower() try: return self._nameCache[s] except KeyError: for (id, user) in self.users.items(): if s == user.name.lower(): self._nameCache[s] = id self._nameCache[id] = s return id else: raise KeyError, s def getUser(self, id): """Returns a user given its id, name, or hostmask.""" if not isinstance(id, int): # Must be a string. Get the UserId first. id = self.getUserId(id) u = self.users[id] while isinstance(u, int): id = u u = self.users[id] u.id = id return u def hasUser(self, id): """Returns the database has a user given its id, name, or hostmask.""" try: self.getUser(id) return True except KeyError: return False def numUsers(self): return len(self.users) def invalidateCache(self, id=None, hostmask=None, name=None): if hostmask is not None: if hostmask in self._hostmaskCache: id = self._hostmaskCache.pop(hostmask) self._hostmaskCache[id].remove(hostmask) if not self._hostmaskCache[id]: del self._hostmaskCache[id] if name is not None: del self._nameCache[self._nameCache[id]] del self._nameCache[id] if id is not None: if id in self._nameCache: del self._nameCache[self._nameCache[id]] del self._nameCache[id] if id in self._hostmaskCache: for hostmask in self._hostmaskCache[id]: del self._hostmaskCache[hostmask] del self._hostmaskCache[id] def setUser(self, user, flush=True): """Sets a user (given its id) to the IrcUser given it.""" self.nextId = max(self.nextId, user.id) try: if self.getUserId(user.name) != user.id: raise DuplicateHostmask, hostmask except KeyError: pass for hostmask in user.hostmasks: for (i, u) in self.iteritems(): if i == user.id: continue elif u.checkHostmask(hostmask): # We used to remove the hostmask here, but it's not # appropriate for us both to remove the hostmask and to # raise an exception. So instead, we'll raise an # exception, but be nice and give the offending hostmask # back at the same time. raise DuplicateHostmask, hostmask for otherHostmask in u.hostmasks: if ircutils.hostmaskPatternEqual(hostmask, otherHostmask): raise DuplicateHostmask, hostmask self.invalidateCache(user.id) self.users[user.id] = user if flush: self.flush() def delUser(self, id): """Removes a user from the database.""" del self.users[id] if id in self._nameCache: del self._nameCache[self._nameCache[id]] del self._nameCache[id] if id in self._hostmaskCache: for hostmask in self._hostmaskCache[id]: del self._hostmaskCache[hostmask] del self._hostmaskCache[id] self.flush() def newUser(self): """Allocates a new user in the database and returns it and its id.""" user = IrcUser(hashed=True) self.nextId += 1 id = self.nextId self.users[id] = user self.flush() user.id = id return user class ChannelsDictionary(utils.IterableMap): def __init__(self): self.noFlush = False self.filename = None self.channels = ircutils.IrcDict() def open(self, filename): self.noFlush = True try: self.filename = filename reader = unpreserve.Reader(IrcChannelCreator, self) try: reader.readFile(filename) self.noFlush = False self.flush() except EnvironmentError, e: log.error('Invalid channel database, resetting to empty.') log.error('Exact error: %s', utils.exnToString(e)) except Exception, e: log.error('Invalid channel database, resetting to empty.') log.exception('Exact error:') finally: self.noFlush = False def flush(self): """Flushes the channel database to its file.""" if not self.noFlush: if self.filename is not None: fd = utils.file.AtomicFile(self.filename) for (channel, c) in self.channels.iteritems(): fd.write('channel %s' % channel) fd.write(os.linesep) c.preserve(fd, indent=' ') fd.close() else: log.warning('ChannelsDictionary.flush without self.filename.') else: log.debug('Not flushing ChannelsDictionary because of noFlush.') def close(self): self.flush() if self.flush in world.flushers: world.flushers.remove(self.flush) self.channels.clear() def reload(self): """Reloads the channel database from its file.""" if self.filename is not None: self.channels.clear() try: self.open(self.filename) except EnvironmentError, e: log.warning('ChannelsDictionary.reload failed: %s', e) else: log.warning('ChannelsDictionary.reload without self.filename.') def getChannel(self, channel): """Returns an IrcChannel object for the given channel.""" channel = channel.lower() if channel in self.channels: return self.channels[channel] else: c = IrcChannel() self.channels[channel] = c return c def setChannel(self, channel, ircChannel): """Sets a given channel to the IrcChannel object given.""" channel = channel.lower() self.channels[channel] = ircChannel self.flush() def iteritems(self): return self.channels.iteritems() class IgnoresDB(object): def __init__(self): self.filename = None self.hostmasks = {} def open(self, filename): self.filename = filename fd = file(self.filename) for line in utils.file.nonCommentNonEmptyLines(fd): try: line = line.rstrip('\r\n') L = line.split() hostmask = L.pop(0) if L: expiration = int(float(L.pop(0))) else: expiration = 0 self.add(hostmask, expiration) except Exception, e: log.error('Invalid line in ignores database: %q', line) fd.close() def flush(self): if self.filename is not None: fd = utils.file.AtomicFile(self.filename) now = time.time() for (hostmask, expiration) in self.hostmasks.items(): if now < expiration or not expiration: fd.write('%s %s' % (hostmask, expiration)) fd.write(os.linesep) fd.close() else: log.warning('IgnoresDB.flush called without self.filename.') def close(self): if self.flush in world.flushers: world.flushers.remove(self.flush) self.flush() self.hostmasks.clear() def reload(self): if self.filename is not None: oldhostmasks = self.hostmasks.copy() self.hostmasks.clear() try: self.open(self.filename) except EnvironmentError, e: log.warning('IgnoresDB.reload failed: %s', e) # Let's be somewhat transactional. self.hostmasks.update(oldhostmasks) else: log.warning('IgnoresDB.reload called without self.filename.') def checkIgnored(self, prefix): now = time.time() for (hostmask, expiration) in self.hostmasks.items(): if expiration and now > expiration: del self.hostmasks[hostmask] else: if ircutils.hostmaskPatternEqual(hostmask, prefix): return True return False def add(self, hostmask, expiration=0): assert ircutils.isUserHostmask(hostmask), 'got %s' % hostmask self.hostmasks[hostmask] = expiration def remove(self, hostmask): del self.hostmasks[hostmask] confDir = conf.supybot.directories.conf() try: userFile = os.path.join(confDir, conf.supybot.databases.users.filename()) users = UsersDictionary() users.open(userFile) except EnvironmentError, e: log.warning('Couldn\'t open user database: %s', e) try: channelFile = os.path.join(confDir, conf.supybot.databases.channels.filename()) channels = ChannelsDictionary() channels.open(channelFile) except EnvironmentError, e: log.warning('Couldn\'t open channel database: %s', e) try: ignoreFile = os.path.join(confDir, conf.supybot.databases.ignores.filename()) ignores = IgnoresDB() ignores.open(ignoreFile) except EnvironmentError, e: log.warning('Couldn\'t open ignore database: %s', e) world.flushers.append(users.flush) world.flushers.append(ignores.flush) world.flushers.append(channels.flush) ### # Useful functions for checking credentials. ### def checkIgnored(hostmask, recipient='', users=users, channels=channels): """checkIgnored(hostmask, recipient='') -> True/False Checks if the user is ignored by the recipient of the message. """ if ignores.checkIgnored(hostmask): log.debug('Ignoring %s due to ignore database.', hostmask) return True try: id = users.getUserId(hostmask) user = users.getUser(id) except KeyError: # If there's no user... if ircutils.isChannel(recipient): channel = channels.getChannel(recipient) if channel.checkIgnored(hostmask): log.debug('Ignoring %s due to the channel ignores.', hostmask) return True else: return False else: if conf.supybot.defaultIgnore(): log.debug('Ignoring %s due to conf.supybot.defaultIgnore', hostmask) return True else: return False if user._checkCapability('owner'): # Owners shouldn't ever be ignored. return False elif user.ignore: log.debug('Ignoring %s due to his IrcUser ignore flag.', hostmask) return True elif recipient: if ircutils.isChannel(recipient): channel = channels.getChannel(recipient) if channel.checkIgnored(hostmask): log.debug('Ignoring %s due to the channel ignores.', hostmask) return True else: return False else: return False else: return False def _x(capability, ret): if isAntiCapability(capability): return not ret else: return ret def _checkCapabilityForUnknownUser(capability, users=users, channels=channels): if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) try: c = channels.getChannel(channel) if capability in c.capabilities: return c._checkCapability(capability) else: return _x(capability, c.defaultAllow) except KeyError: pass defaultCapabilities = conf.supybot.capabilities() if capability in defaultCapabilities: return defaultCapabilities.check(capability) else: return _x(capability, conf.supybot.capabilities.default()) def checkCapability(hostmask, capability, users=users, channels=channels): """Checks that the user specified by name/hostmask has the capability given. """ if world.testing: return _x(capability, True) try: u = users.getUser(hostmask) if u.secure and not u.checkHostmask(hostmask, useAuth=False): raise KeyError except KeyError: # Raised when no hostmasks match. return _checkCapabilityForUnknownUser(capability, users=users, channels=channels) except ValueError, e: # Raised when multiple hostmasks match. log.warning('%s: %s', hostmask, e) return _checkCapabilityForUnknownUser(capability, users=users, channels=channels) if capability in u.capabilities: return u._checkCapability(capability) else: if isChannelCapability(capability): (channel, capability) = fromChannelCapability(capability) try: chanop = makeChannelCapability(channel, 'op') if u._checkCapability(chanop): return _x(capability, True) except KeyError: pass c = channels.getChannel(channel) if capability in c.capabilities: return c._checkCapability(capability) else: return _x(capability, c.defaultAllow) defaultCapabilities = conf.supybot.capabilities() if capability in defaultCapabilities: return defaultCapabilities.check(capability) else: return _x(capability, conf.supybot.capabilities.default()) def checkCapabilities(hostmask, capabilities, requireAll=False): """Checks that a user has capabilities in a list. requireAll is True if *all* capabilities in the list must be had, False if *any* of the capabilities in the list must be had. """ for capability in capabilities: if requireAll: if not checkCapability(hostmask, capability): return False else: if checkCapability(hostmask, capability): return True return requireAll ### # supybot.capabilities ### class DefaultCapabilities(registry.SpaceSeparatedListOfStrings): List = CapabilitySet # We use a keyword argument trick here to prevent eval'ing of code that # changes allowDefaultOwner from affecting this. It's not perfect, but # it's still an improvement, raising the bar for potential crackers. def setValue(self, v, allowDefaultOwner=conf.allowDefaultOwner): registry.SpaceSeparatedListOfStrings.setValue(self, v) if '-owner' not in self.value and not allowDefaultOwner: print '*** You must run supybot with the --allow-default-owner' print '*** option in order to allow a default capability of owner.' print '*** Don\'t do that, it\'s dumb.' self.value.add('-owner') conf.registerGlobalValue(conf.supybot, 'capabilities', DefaultCapabilities(['-owner', '-admin', '-trusted'], """These are the capabilities that are given to everyone by default. If they are normal capabilities, then the user will have to have the appropriate anti-capability if you want to override these capabilities; if they are anti-capabilities, then the user will have to have the actual capability to override these capabilities. See docs/CAPABILITIES if you don't understand why these default to what they do.""")) conf.registerGlobalValue(conf.supybot.capabilities, 'default', registry.Boolean(True, """Determines whether the bot by default will allow users to have a capability. If this is disabled, a user must explicitly have the capability for whatever command he wishes to run.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/dynamicScope.py0000644000000000000000000000430311206611405015457 0ustar ### # Copyright (c) 2004-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 sys class DynamicScope(object): def _getLocals(self, name): f = sys._getframe().f_back.f_back # _getLocals <- __[gs]etattr__ <- ... while f: if name in f.f_locals: return f.f_locals f = f.f_back raise NameError, name def __getattr__(self, name): try: return self._getLocals(name)[name] except (NameError, KeyError): return None def __setattr__(self, name, value): self._getLocals(name)[name] = value __builtins__['dynamic'] = DynamicScope() # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/src/schedule.py0000644000000000000000000001247411206611405014645 0ustar ### # 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. ### """ Schedule plugin with a subclass of drivers.IrcDriver in order to be run as a Supybot driver. """ import time import heapq import supybot.log as log import supybot.world as world import supybot.drivers as drivers class mytuple(tuple): def __cmp__(self, other): return cmp(self[0], other[0]) def __le__(self, other): return self[0] <= other[0] def __lt__(self, other): return self[0] < other[0] def __gt__(self, other): return self[0] > other[0] def __ge__(self, other): return self[0] >= other[0] class Schedule(drivers.IrcDriver): """An IrcDriver to handling scheduling of events. Events, in this case, are functions accepting no arguments. """ def __init__(self): drivers.IrcDriver.__init__(self) self.schedule = [] self.events = {} self.counter = 0 def reset(self): self.events.clear() self.schedule[:] = [] # We don't reset the counter here because if someone has held an id of # one of the nuked events, we don't want him removing new events with # his old id. def name(self): return 'Schedule' def addEvent(self, f, t, name=None): """Schedules an event f to run at time t. name must be hashable and not an int. """ if name is None: name = self.counter self.counter += 1 assert name not in self.events, \ 'An event with the same name has already been scheduled.' self.events[name] = f heapq.heappush(self.schedule, mytuple((t, name))) return name def removeEvent(self, name): """Removes the event with the given name from the schedule.""" f = self.events.pop(name) self.schedule = [(t, n) for (t, n) in self.schedule if n != name] # We must heapify here because the heap property may not be preserved # by the above list comprehension. We could, conceivably, just mark # the elements of the heap as removed and ignore them when we heappop, # but that would only save a constant factor (we're already linear for # the listcomp) so I'm not worried about it right now. heapq.heapify(self.schedule) return f def rescheduleEvent(self, name, t): f = self.removeEvent(name) self.addEvent(f, t, name=name) def addPeriodicEvent(self, f, t, name=None, now=True): """Adds a periodic event that is called every t seconds.""" def wrapper(): try: f() finally: # Even if it raises an exception, let's schedule it. return self.addEvent(wrapper, time.time() + t, name) if now: return wrapper() else: return self.addEvent(wrapper, time.time() + t, name) removePeriodicEvent = removeEvent def run(self): if len(drivers._drivers) == 1 and not world.testing: log.error('Schedule is the only remaining driver, ' 'why do we continue to live?') time.sleep(1) # We're the only driver; let's pause to think. while self.schedule and self.schedule[0][0] < time.time(): (t, name) = heapq.heappop(self.schedule) f = self.events[name] del self.events[name] try: f() except Exception, e: log.exception('Uncaught exception in scheduled function:') try: ignore(schedule) except NameError: schedule = Schedule() addEvent = schedule.addEvent removeEvent = schedule.removeEvent rescheduleEvent = schedule.rescheduleEvent addPeriodicEvent = schedule.addPeriodicEvent removePeriodicEvent = removeEvent run = schedule.run # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/docs/0000755000000000000000000000000011206611405012630 5ustar supybot-0.83.4.1.ds.orig/docs/PLUGIN_TUTORIAL0000644000000000000000000006717711206611405015116 0ustar WRITING YOUR FIRST SUPYBOT PLUGIN Introduction ============ First things first - what you need to do before writing a Supybot plugin. Ok, so you want to write a plugin for Supybot. Good, then this is the place to be. We're going to start from the top (the highest level, where Supybot code does the most work for you) and move lower after that. So have you used Supybot? If not, you need to go use it. This will help you understand crucial things like the way the various commands work and it is essential prior to embarking upon the plugin-development excursion detailed in the following pages. If you haven't used Supybot, come back to this document after you've used it for a while and gotten a feel for it. So, now that we know you've used Supybot, we'll start getting into details. We'll go through this tutorial by actually writing a new plugin, named Random with just a few simple commands. Caveat: you'll need to have Supybot installed on the machine you intend to develop plugins on. This will not only allow you to test the plugins with a live bot, but it will also provide you with several nice scripts which aid the development of plugins. Most notably, it provides you with the supybot-plugin-create script which we will use in the next section... Creating a minimal plugin This section describes using the 'supybot-plugin-create' script to create a minimal plugin which we will enhance in later sections. The recommended way to start writing a plugin is to use the wizard provided, 'supybot-plugin-create'. Run this from within your local plugins directory, so we will be able to load the plugin and test it out. It's very easy to follow, because basically all you have to do is answer three questions. Here's an example session: [ddipaolo@quinn ../python/supybot]% supybot-plugin-create What should the name of the plugin be? Random Sometimes you'll want a callback to be threaded. If its methods (command or regexp-based, either one) will take a significant amount of time to run, you'll want to thread them so they don't block the entire bot. Does your plugin need to be threaded? [y/n] n What is your real name, so I can fill in the copyright and license appropriately? Daniel DiPaolo Your new plugin template is in the Random directory. It's that simple! Well, that part of making the minimal plugin is that simple. You should now have a directory with a few files in it, so let's take a look at each of those files and see what they're used for. README.txt ========== Tell me about the plugin. In README.txt you put exactly what the boilerplate text says to put in there: Insert a description of your plugin here, with any notes, etc. about using it. A brief overview of exactly what the purpose of the plugin is supposed to do is really all that is needed here. Also, if this plugin requires any third-party Python modules, you should definitely mention those here. You don't have to describe individual commands or anything like that, as those are defined within the plugin code itself as you'll see later. You also don't need to acknowledge any of the developers of the plugin as those too are handled elsewhere. For our Random plugin, let's make README.txt say this: This plugin contains commands relating to random numbers, and includes: a simple random number generator, the ability to pick a random number from within a range, a command for returning a random sampling from a list of items, and a simple dice roller. And now you know what's in store for the rest of this tutorial, we'll be writing all of that in one Supybot plugin, and you'll be surprised at just how simple it is! __init__.py =========== Plugin properties and a few other bits. The next file we'll look at is __init__.py. If you're familiar with the Python import mechanism, you'll know what this file is for. If you're not, think of it as sort of the "glue" file that pulls all the files in this directory together when you load the plugin. It's also where there are a few administrative items live that you really need to maintain. Let's go through the file. For the first 30 lines or so, you'll see the copyright notice that we use for our plugins, only with your name in place (as prompted in 'supybot-plugin-create'). Feel free to use whatever license you choose, we don't feel particularly attached to the boilerplate code so it's yours to license as you see fit even if you don't modify it. For our example, we'll leave it as is. The plugin docstring immediately follows the copyright notice and it (like README.txt) tells you precisely what it should contain: Add a description of the plugin (to be presented to the user inside the wizard) here. This should describe *what* the plugin does. The "wizard" that it speaks of is the 'supybot-wizard' script that is used to create working Supybot config file. I imagine that in meeting the prerequisite of "using a Supybot" first, most readers will have already encountered this script. Basically, if the user selects to look at this plugin from the list of plugins to load, it prints out that description to let the user know what it does, so make sure to be clear on what the purpose of the plugin is. This should be an abbreviated version of what we put in our README.txt, so let's put this: Provides a number of commands for selecting random things. Next in __init__.py you see a few imports which are necessary, and then four attributes that you need to modify for your bot and preferably keep up with as you develop it: __version__, __author__, __contributors__, __url__. __version__ is just a version string representing the current working version of the plugin, and can be anything you want. If you use some sort of RCS, this would be a good place to have it automatically increment the version string for any time you edit any of the files in this directory. We'll just make ours "0.1". __author__ should be an instance of the supybot.Author class. A supybot.Author is simply created by giving it a full name, a short name (preferably IRC nick), and an e-mail address (all of these are optional, though at least the second one is expected). So, for example, to create my Author user (though I get to cheat and use supybot.authors.strike since I'm a main dev, muahaha), I would do: __author__ = supybot.Author('Daniel DiPaolo', 'Strike', 'somewhere@someplace.xxx') Keep this in mind as we get to the next item... __contributors__ is a dictionary mapping supybot.Author instances to lists of things they contributed. If someone adds a command named foo to your plugin, the list for that author should be ["foo"], or perhaps even ["added foo command"]. The main author shouldn't be referenced here, as it is assumed that everything that wasn't contributed by someone else was done by the main author. For now we have no contributors, so we'll leave it blank. Lastly, the __url__ attribute should just reference the download URL for the plugin. We encourage you to use the supybot.com website for distributing plugins and have gone to great lengths to make distributing them nice and easy to do so. Since this is just an example, we'll leave this blank, but supybot-plugin-create shows an example URL of a plugin home here on supybot.com The rest of __init__.py really shouldn't be touched unless you are using third-party modules in your plugin. If you are, then you need to take special note of the section that looks like this: 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! As the comment says, this is one place where you need to make sure you import the third-party modules, and that you call reload() on them as well. That way, if we are reloading a plugin on a running bot it will actually reload the latest code. We aren't using any third-party modules, so we can just leave this bit alone. We're almost through the "boring" part and into the guts of writing Supybot plugins, let's take a look at the next file. config.py ========= Making our plugin configurable config.py is, unsurprisingly, where all the configuration stuff related to your plugin goes. If you're not familiar with Supybot's configuration system, I recommend reading the config tutorial before going any further with this section. So, let's plow through config.py line-by-line like we did the other files. Once again, at the top is the standard copyright notice. Again, change it to how you see fit. Then, some standard imports which are necessary. Now, the first peculiar thing we get to is the configure function. This function is what is called by the supybot-wizard whenever a plugin is selected to be loaded. Since you've used the bot by now (as stated on the first page of this tutorial as a prerequisite), you've seen what this script does to configure plugins. The wizard allows the bot owner to choose something different from the default plugin config values without having to do it through the bot (which is still not difficult, but not as easy as this). Also, note that the advanced argument allows you to differentiate whether or not the person configuring this plugin considers himself an advanced Supybot user. Our plugin has no advanced features, so we won't be using it. So, what exactly do we do in this configure function for our plugin? Well, for the most part we ask questions and we set configuration values. You'll notice the import line with supybot.questions in it. That provides some nice convenience functions which are used to (you guessed it) ask questions. The other line in there is the conf.registerPlugin line which registers our plugin with the config and allows us to create configuration values for the plugin. You should leave these two lines in even if you don't have anything else to put in here. For the vast majority of plugins, you can leave this part as is, so we won't go over how to write plugin configuration functions here (that will be handled in a separate article). Our plugin won't be using much configuration, so we'll leave this as is. Next, you'll see a line that looks very similar to the one in the configure function. This line is used not only to register the plugin prior to being called in configure, but also to store a bit of an alias to the plugin's config group to make things shorter later on. So, this line should read: Random = conf.registerPlugin('Random') Now we get to the part where we define all the configuration groups and variables that our plugin is to have. Again, many plugins won't require any configuration so we won't go over it here, but in a separate article dedicated to sprucing up your config.py for more advanced plugins. Our plugin doesn't require any config variables, so we actually don't need to make any changes to this file at all. Configuration of plugins is handled in depth at the Advanced Plugin Config Tutorial plugin.py ========= The meat of the plugin Here's the moment you've been waiting for, the overview of plugin.py and how to make our plugin actually do stuff. At the top, same as always, is the standard copyright block to be used and abused at your leisure. Next, some standard imports. Not all of them are used at the moment, but you probably will use many (if not most) of them, so just let them be. Since we'll be making use of Python's standard 'random' module, you'll need to add the following line to the list of imports:: import random Now, the plugin class itself. What you're given is a skeleton: a simple subclass of callbacks.Plugin for you to start with. The only real content it has is the boilerplate docstring, which you should modify to reflect what the boilerplate text says - it should be useful so that when someone uses the plugin help command to determine how to use this plugin, they'll know what they need to do. Ours will read something like: """This plugin provides a few random number commands and some commands for getting random samples. Use the "seed" command to seed the plugin's random number generator if you like, though it is unnecessary as it gets seeded upon loading of the plugin. The "random" command is most likely what you're looking for, though there are a number of other useful commands in this plugin. Use 'list random' to check them out. """ It's basically a "guide to getting started" for the plugin. Now, to make the plugin do something. First of all, to get any random numbers we're going to need a random number generator (RNG). Pretty much everything in our plugin is going to use it, so we'll define it in the constructor of our plugin, __init__. Here we'll also seed it with the current time (standard practice for RNGs). Here's what our __init__ looks like: def __init__(self, irc): self.__parent = super(Random, self) self.__parent.__init__(irc) self.rng = random.Random() # create our rng self.rng.seed() # automatically seeds with current time Now, the first two lines may look a little daunting, but it's just administrative stuff required if you want to use a custom __init__. If we didn't want to do so, we wouldn't have to, but it's not uncommon so I decided to use an example plugin that did. For the most part you can just copy/paste those lines into any plugin you override the __init__ for and just change them to use the plugin name that you are working on instead. So, now we have a RNG in our plugin, let's write a command to get a random number. We'll start with a simple command named random that just returns a random number from our RNG and takes no arguments. Here's what that looks like: def random(self, irc, msg, args): """takes no arguments Returns the next random number from the random number generator. """ irc.reply(str(self.rng.random())) random = wrap(random) And that's it. Now here are the important points. First and foremost, all plugin commands must have all-lowercase function names. If they aren't all lowercase they won't show up in a plugin's list of commands (nor will they be useable in general). If you look through a plugin and see a function that's not in all lowercase, it is not a plugin command. Chances are it is a helper function of some sort, and in fact using capital letters is a good way of assuring that you don't accidentally expose helper functions to users as commands. You'll note the arguments to this class method are (self, irc, msg, args). This is what the argument list for all methods that are to be used as commands must start with. If you wanted additional arguments, you'd append them onto the end, but since we take no arguments we just stop there. I'll explain this in more detail with our next command, but it is very important that all plugin commands are class methods that start with those four arguments exactly as named. Next, in the docstring there are two major components. First, the very first line dictates the argument list to be displayed when someone calls the help command for this command (i.e., help random). Then you leave a blank line and start the actual help string for the function. Don't worry about the fact that it's tabbed in or anything like that, as the help command normalizes it to make it look nice. This part should be fairly brief but sufficient to explain the function and what (if any) arguments it requires. Remember that this should fit in one IRC message which is typically around a 450 character limit. Then we have the actual code body of the plugin, which consists of a single line: irc.reply(str(self.rng.random())). The irc.reply function issues a reply to wherever the PRIVMSG it received the command from with whatever text is provided. If you're not sure what I mean when I say "wherever the PRIVMSG it received the command from", basically it means: if the command is issued in a channel the response is sent in the channel, and if the command is issued in a private dialog the response is sent in a private dialog. The text we want to display is simply the next number from our RNG (self.rng). We get that number by calling the random function, and then we str it just to make sure it is a nice printable string. Lastly, all plugin commands must be 'wrap'ed. What the wrap function does is handle argument parsing for plugin commands in a very nice and very powerful way. With no arguments, we simply need to just wrap it. For more in-depth information on using wrap check out the wrap tutorial (The astute Python programmer may note that this is very much like a decorator, and that's precisely what it is. However rather than use the @decorator syntax from Python 2.4 we elect to stick with this syntax to remain compatible with Python 2.3.) Now let's create a command with some arguments and see how we use those in our plugin commands. Let's allow the user to seed our RNG with their own seed value. We'll call the command seed and take just the seed value as the argument (which we'll require be a floating point value of some sort, though technically it can be any hashable object). Here's what this command looks like: def seed(self, irc, msg, args, seed): """ Sets the internal RNG's seed value to . must be a floating point number. """ self.rng.seed(seed) irc.replySuccess() seed = wrap(seed, ['float']) You'll notice first that argument list now includes an extra argument, seed. If you read the wrap tutorial mentioned above, you should understand how this arg list gets populated with values. Thanks to wrap we don't have to worry about type-checking or value-checking or anything like that. We just specify that it must be a float in the wrap portion and we can use it in the body of the function. Of course, we modify the docstring to document this function. Note the syntax on the first line. Arguments go in <> and optional arguments should be surrounded by [] (we'll demonstrate this later as well). The body of the function should be fairly straightforward to figure out, but it introduces a new function - irc.replySuccess. This is just a generic "I succeeded" command which responds with whatever the bot owner has configured to be the success response (configured in supybot.replies.success). Note that we don't do any error-checking in the plugin, and that's because we simply don't have to. We are guaranteed that seed will be a float and so the call to our RNG's seed is guaranteed to work. Lastly, of course, the wrap call. Again, read the wrap tutorial for fuller coverage of its use, but the basic premise is that the second argument to wrap is a list of converters that handles argument validation and conversion and it then assigns values to each argument in the arg list after the first four (required) arguments. So, our seed argument gets a float, guaranteed. With this alone you'd be able to make some pretty usable plugin commands, but we'll go through two more commands to introduce a few more useful ideas. The next command we'll make is a sample command which gets a random sample of items from a list provided by the user: def sample(self, irc, msg, args, n, items): """ [ ...] Returns a sample of the taken from the remaining arguments. Obviously must be less than the number of arguments given. """ if n > len(items): irc.error(' must be less than the number ' 'of arguments.') return sample = self.rng.sample(items, n) sample.sort() irc.reply(utils.str.commaAndify(sample)) sample = wrap(sample, ['int', many('anything')]) This plugin command introduces a few new things, but the general structure should look fairly familiar by now. You may wonder why we only have two extra arguments when obviously this plugin can accept any number of arguments. Well, using wrap we collect all of the remaining arguments after the first one into the items argument. If you haven't caught on yet, wrap is really cool and extremely useful. Next of course is the updated docstring. Note the use of [] to denote the optional items after the first item. The body of the plugin should be relatively easy to read. First we check and make sure that n (the number of items the user wants to sample) is not larger than the actual number of items they gave. If it does, we call irc.error with the error message you see. irc.error is kind of like irc.replySuccess only it gives an error message using the configured error format (in supybot.replies.error). Otherwise, we use the sample function from our RNG to get a sample, then we sort it, and we reply with the 'utils.str.commaAndify'ed version. The utils.str.commaAndify function basically takes a list of strings and turns it into "item1, item2, item3, item4, and item5" for an arbitrary length. More details on using the utils module can be found in the utils tutorial. Now for the last command that we will add to our plugin.py. This last command will allow the bot users to roll an arbitrary n-sided die, with as many sides as they so choose. Here's the code for this command: def diceroll(self, irc, msg, args, n): """[] Rolls a die with sides. The default number of sides is 6. """ s = 'rolls a %s' % self.rng.randrange(1, n) irc.reply(s, action=True) diceroll = wrap(diceroll, [additional(('int', 'number of sides'), 6)]) The only new thing learned here really is that the irc.reply method accepts an optional argument action, which if set to True makes the reply an action instead. So instead of just crudely responding with the number, instead you should see something like * supybot rolls a 5. You'll also note that it uses a more advanced wrap line than we have used to this point, but to learn more about wrap, you should refer to the wrap tutorial And now that we're done adding plugin commands you should see the boilerplate stuff at the bottom, which just consists of: Class = Random And also some vim modeline stuff. Leave these as is, and we're finally done with plugin.py! test.py ======= Plugin tests go here. Now that we've gotten our plugin written, we want to make sure it works. Sure, an easy way to do a somewhat quick check is to start up a bot, load the plugin, and run a few commands on it. If all goes well there, everything's probably okay. But, we can do better than "probably okay". This is where written plugin tests come in. We can write tests that not only assure that the plugin loads and runs the commands fine, but also that it produces the expected output for given inputs. And not only that, we can use the nifty supybot-test script to test the plugin without even having to have a network connection to connect to IRC with and most certainly without running a local IRC server. The boilerplate code for test.py is a good start. It imports everything you need and sets up RandomTestCase which will contain all of our tests. Now we just need to write some test methods. I'll be moving fairly quickly here just going over very basic concepts and glossing over details, but the full plugin test authoring tutorial has much more detail to it and is recommended reading after finishing this tutorial. Since we have four commands we should have at least four test methods in our test case class. Typically you name the test methods that simply checks that a given command works by just appending the command name to test. So, we'll have testRandom, testSeed, testSample, and testDiceRoll. Any other methods you want to add are more free-form and should describe what you're testing (don't be afraid to use long names). First we'll write the testRandom method: def testRandom(self): # difficult to test, let's just make sure it works self.assertNotError('random') Since we can't predict what the output of our random number generator is going to be, it's hard to specify a response we want. So instead, we just make sure we don't get an error by calling the random command, and that's about all we can do. Next, testSeed. In this method we're just going to check that the command itself functions. In another test method later on we will check and make sure that the seed produces reproducible random numbers like we would hope it would, but for now we just test it like we did random in 'testRandom': def testSeed(self): # just make sure it works self.assertNotError('seed 20') Now for testSample. Since this one takes more arguments it makes sense that we test more scenarios in this one. Also this time we have to make sure that we hit the error that we coded in there given the right conditions: def testSample(self): self.assertError('sample 20 foo') self.assertResponse('sample 1 foo', 'foo') self.assertRegexp('sample 2 foo bar', '... and ...') self.assertRegexp('sample 3 foo bar baz', '..., ..., and ...') So first we check and make sure trying to take a 20-element sample of a 1-element list gives us an error. Next we just check and make sure we get the right number of elements and that they are formatted correctly when we give 1, 2, or 3 element lists. And for the last of our basic "check to see that it works" functions, testDiceRoll: def testDiceRoll(self): self.assertActionRegexp('diceroll', 'rolls a \d') We know that diceroll should return an action, and that with no arguments it should roll a single-digit number. And that's about all we can test reliably here, so that's all we do. Lastly, we wanted to check and make sure that seeding the RNG with seed actually took effect like it's supposed to. So, we write another test method: def testSeedActuallySeeds(self): # now to make sure things work repeatably self.assertNotError('seed 20') m1 = self.getMsg('random') self.assertNotError('seed 20') m2 = self.getMsg('random') self.failUnlessEqual(m1, m2) m3 = self.getMsg('random') self.failIfEqual(m2, m3) So we seed the RNG with 20, store the message, and then seed it at 20 again. We grab that message, and unless they are the same number when we compare the two, we fail. And then just to make sure our RNG is producing random numbers, we get another random number and make sure it is distinct from the prior one. Conclusion ========== Now you're ready to write Supybot plugins! You are now very well-prepared to write Supybot plugins. Now for a few words of wisdom with regards to Supybot plugin-writing. * Read other people's plugins, especially the included plugins and ones by the core developers. We (the Supybot dev team) can't possibly document all the awesome things that Supybot plugins can do, but we try. Nevertheless there are some really cool things that can be done that aren't very well-documented on this site. * Hack new functionality into existing plugins first if writing a new plugin is too daunting. * Come ask us questions in #supybot on Freenode or OFTC. Going back to the first point above, the developers themselves can help you even more than the docs can (though we prefer you read the docs first). * Publish your plugins on our website. We made some custom stuff on the website just to cater to publishing plugins. Use it, share your plugins with the world and make Supybot all that more attractive for other users so they will want to write their plugins for Supybot as well. * Read, read, read all the documentation on our website. I just spent a lot of time getting a bunch of these tutorials out and getting things up to date, so while Supybot documentation in the past hasn't been stellar, it certainly is very good now. * And of course, have fun writing your plugins. supybot-0.83.4.1.ds.orig/docs/CAPABILITIES0000644000000000000000000001730411206611405014411 0ustar Introduction ------------ Ok, some explanation of the capabilities system is probably in order. With most IRC bots (including the ones I've written myself prior to this one) "what a user can do" is set in one of two ways. On the *really* simple bots, each user has a numeric "level" and commands check to see if a user has a "high enough level" to perform some operation. On bots that are slightly more complicated, users have a list of "flags" whose meanings are hardcoded, and the bot checks to see if a user possesses the necessary flag before performing some operation. Both methods, IMO, are rather arbitrary, and force the user and the programmer to be unduly confined to less expressive constructs. This bot is different. Every user has a set of "capabilities" that is consulted every time they give the bot a command. Commands, rather than checking for a user level of 100, or checking if the user has an 'o' flag, are instead able to check if a user has the 'owner' capability. At this point such a difference might not seem revolutionary, but at least we can already tell that this method is self-documenting, and easier for users and developers to understand what's truly going on. User Capabilities ----------------- What the heck can these capabilities DO? If that was all, well, the capability system would be *cool*, but not many people would say it was *awesome*. But it **is** awesome! Several things are happening behind the scenes that make it awesome, and these are things that couldn't happen if the bot was using numeric userlevels or single-character flags. First, whenever a user issues the bot a command, the command dispatcher checks to make sure the user doesn't have the "anticapability" for that command. An anticapability is a capability that, instead of saying "what a user can do", says what a user *cannot* do. It's formed rather simply by adding a dash ('-') to the beginning of a capability; 'rot13' is a capability, and '-rot13' is an anticapability. Anyway, when a user issues the bot a command, perhaps 'calc' or 'help', the bot first checks to make sure the user doesn't have the '-calc' or the '-help' (anti)capabilities before even considering responding to the user. So commands can be turned on or off on a *per user* basis, offering fine-grained control not often (if at all!) seen in other bots. This can be further refined by limiting the (anti)capability to a command in a specific plugin or even an entire plugin. For example, the rot13 command is in the Filter plugin. If a user should be able to use another rot13 command, but not the one in the Format plugin, they would simply need to be given '-Format.rot13' anticapability. Similarly, if a user were to be banned from using the Filter plugin altogether, they would simply need to be given the '-Filter' anticapability. Channel Capabilities -------------------- What if #linux wants completely different capabilities from #windows? But that's not all! The capabilities system also supports *channel* capabilities, which are capabilities that only apply to a specific channel; they're of the form '#channel,capability'. Whenever a user issues a command to the bot in a channel, the command dispatcher also checks to make sure the user doesn't have the anticapability for that command *in that channel*, and if the user does, the bot won't respond to the user in the channel. Thus now, in addition to having the ability to turn individual commands on or off for an individual user, we can now turn commands on or off for an individual user on an individual channel! So when a user 'foo' sends a command 'bar' to the bot on channel '#baz', first the bot checks to see if the user has the anticapability for the command by itself, '-bar'. If so, it errors right then and there, telling the user that he lacks the 'bar' capability. If the user doesn't have that anticapability, then the bot checks to see if the user issued the command over a channel, and if so, checks to see if the user has the antichannelcapability for that command, '#baz,-bar'. If so, again, he tells the user that he lacks the 'bar' capability. If neither of these anticapabilities are present, then the bot just responds to the user like normal. Default Capabilities -------------------- So what capabilities am I dealing with already? There are several default capabilities the bot uses. The most important of these is the 'owner' capability. This capability allows the person having it to use *any* command. It's best to keep this capability reserved to people who actually have access to the shell the bot is running on. It's so important, in fact, that the bot will not allow you to add it with a command--you'll have you edit the users file directly to give it to someone. There is also the 'admin' capability for non-owners that are highly trusted to administer the bot appropriately. They can do things such as change the bot's nick, cause the bot to ignore a given user, make the bot join or part channels, etc. They generally cannot do administration related to channels, which is reserved for people with the next capability. People who are to administer channels with the bot should have the '#channel,op' capability--whatever channel they are to administrate, they should have that channel capability for 'op'. For example, since I want inkedmn to be an administrator in #supybot, I'll give him the '#supybot,op' capability. This is in addition to his 'admin' capability, since the 'admin' capability doesn't give the person having it control over channels. '#channel,op' is used for such things as giving/receiving ops, kickbanning people, lobotomizing the bot, ignoring users in the channel, and managing the channel capabilities. The '#channel,op' capability is also basically the equivalent of the 'owner' capability for capabilities involving #channel--basically anyone with the #channel,op capability is considered to have all positive capabilities and no negative capabilities for #channel. One other globally important capability exists: 'trusted'. This is a command that basically says "This user can be trusted not to try and crash the bot." It allows users to call commands like 'icalc' in the 'Math' plugin, which can cause the bot to begin a calculation that could potentially never return (a calculation like '10**10**10**10'). Another command that requires the 'trusted' capability is the 're' command in the 'Utilities' plugin, which (due to the regular expression implementation in Python (and any other language that uses NFA regular expressions, like Perl or Ruby or Lua or ...) which can allow a regular expression to take exponential time to process). Consider what would happen if someone gave the bot the command 're [format join "" s/./ [dict go] /] [dict go]' It would basically replace every character in the output of 'dict go' (14,896 characters!) with the entire output of 'dict go', resulting in 221MB of memory allocated! And that's not even the worst example! Final Word ---------- From a programmer's perspective, capabilties are flexible and easy to use. Any command can check if a user has any capability, even ones not thought of when the bot was originally written. Plugins can easily add their own capabilities--it's as easy as just checking for a capability and documenting somewhere that a user needs that capability to do something. From an user's perspective, capabilities remove a lot of the mystery and esotery of bot control, in addition to giving a bot owner absolutely finegrained control over what users are allowed to do with the bot. Additionally, defaults can be set by the bot owner for both individual channels and for the bot as a whole, letting an end-user set the policy he wants the bot to follow for users that haven't yet registered in his user database. It's really a revolution! supybot-0.83.4.1.ds.orig/docs/USING_WRAP0000644000000000000000000004177611206611405014310 0ustar Using the commands.wrap to parse your command's arguments. ---------------------------------------------------------- This document illustrates how to use the new 'wrap' function present in Supybot 0.80 to handle argument parsing and validation for your plugin's commands. Introduction ============ What is 'wrap'? To plugin developers for older (pre-0.80) versions of Supybot, one of the more annoying aspects of writing commands was handling the arguments that were passed in. In fact, many commands often had to duplicate parsing and verification code, resulting in lots of duplicated code for not a whole lot of action. So, instead of forcing plugin writers to come up with their own ways of cleaning it up, we wrote up the wrap function to handle all of it. It allows a much simpler and more flexible way of checking things than before and it doesn't require that you know the bot internals to do things like check and see if a user exists, or check if a command name exists and whatnot. If you are a plugin author this document is absolutely required reading, as it will massively ease the task of writing commands. Using Wrap ========== How can I use 'wrap'? First off, to get the wrap function, it is recommended (strongly) that you use the following import line: from supybot.commands import * This will allow you to access the wrap command (and it allows you to do it without the commands prefix). Note that this line is added to the imports of plugin templates generated by the supybot-plugin-create script. Let's write a quickie command that uses wrap to get a feel for how it makes our lives better. Let's write a command that repeats a string of text a given number of times. So you could say "repeat 3 foo" and it would say "foofoofoo". Not a very useful command, but it will serve our purpose just fine. Here's how it would be done without wrap: def repeat(self, irc, msg, args): """ Repeats times. """ (num, text) = privmsg.getArgs(args, required=2) try: num = int(num) except ValueError: raise callbacks.ArgumentError irc.reply(num * text) Note that all of the argument validation and parsing takes up 5 of the 6 lines (and you should have seen it before we had privmsg.getArgs!). Now, here's what our command will look like with wrap applied: def repeat(self, irc, msg, args, num, text): """ Repeats times. """ irc.reply(text * num) repeat = wrap(repeat, ['int', 'text']) Pretty short, eh? With wrap all of the argument parsing and validation is handled for us and we get the arguments we want, formatted how we want them, and converted into whatever types we want them to be - all in one simple function call that is used to wrap the function! So now the code inside each command really deals with how to execute the command and not how to deal with the input. So, now that you see the benefits of wrap, let's figure out what stuff we have to do to use it. Syntax Changes ============== How will I need to change my plugin commands to take advantage of 'wrap'? There are two syntax changes to the old style that are implemented. First, the definition of the command function must be changed. The basic syntax for the new definition is: def commandname(self, irc, msg, args, , , ...): Where arg1 and arg2 (up through as many as you want) are the variables that will store the parsed arguments. "Now where do these parsed arguments come from?" you ask. Well, that's where the second syntax change comes in. The second syntax change is the actual use of the wrap function itself to decorate our command names. The basic decoration syntax is: commandname = wrap(commandname, [converter1, converter2, ...]) NOTE: This should go on the line immediately following the body of the command's definition, so it can easily be located (and it obviously must go after the command's definition so that commandname is defined). Each of the converters in the above listing should be one of the converters in commands.py (I will describe each of them in detail later.) The converters are applied in order to the arguments given to the command, generally taking arguments off of the front of the argument list as they go. Note that each of the arguments is actually a string containing the NAME of the converter to use and not a reference to the actual converter itself. This way we can have converters with names like int and not have to worry about polluting the builtin namespace by overriding the builtin int. As you will find out when you look through the list of converters below, some of the converters actually take arguments. The syntax for supplying them (since we aren't actually calling the converters, but simply specifying them), is to wrap the converter name and args list into a tuple. For example: commandname = wrap(commandname, [(converterWithArgs, arg1, arg2), converterWithoutArgs1, converterWithoutArgs2]) For the most part you won't need to use an argument with the converters you use either because the defaults are satisfactory or because it doesn't even take any. Customizing Wrap ================ Is there a way to affect how I apply my wraps? We call them contexts. Converters alone are a pretty powerful tool, but for even more advanced (yet simpler!) argument handling you may want to use contexts. Contexts describe how the converters are applied to the arguments, while the converters themselves actually do the actual parsing and validation. For example, one of the contexts is "optional". By using this context, you're saying that a given argument is not required, and if the supplied converter doesn't find something it likes, we should use some default. Yet another example is the "reverse" context. This context tells the supplied converter to look at the last argument and work backwards instead of the normal first-to-last way of looking at arguments. So, that should give you a feel for the role that contexts play. They are not by any means necessary to use wrap. All of the stuff we've done to this point will work as-is. However, contexts let you do some very powerful things in very easy ways, and are a good thing to know how to use. Now, how do you use them? Well, they are in the global namespace of src/commands.py, so your previous import line will import them all, so you can call them just as you call wrap. In fact, the way you use them is you simply call the context function you want to use, passing in the converter and its arguments in as arguments. It's quite simple. Here's an example: commandname = wrap(commandname, [optional('int'), many('something')]) In this example, our command is looking for an optional integer argument first. Then, after that, any number of arguments which can be anything (as long as they are something, of course). Do note, however, that the type of the arguments that are returned can be changed if you apply a context to it. So, optional("int") may very well return None as well as something that passes the "int" converter, because after all it's an optional argument and if it is None, that signifies that nothing was there. Also, for another example, many("something") doesn't return the same thing that just "something" would return, but rather a list of "something"s. Converter List ============== What converters are available for me to use? Below is a list of all the available converters to use with wrap. If the converter accepts any arguments, they are listed after it and if they are optional, the default value is shown. "id", kind="integer" Returns something that looks like an integer ID number. Takes an optional "kind" argument for you to state what kind of ID you are looking for, though this doesn't affect the integrity-checking. Basically requires that the argument be an integer, does no other integrity-checking, and provides a nice error message with the kind in it. "ip" Checks and makes sure the argument looks like a valid IP and then returns it. "int", type="integer", p=None Gets an integer. The "type" text can be used to customize the error message received when the argument is not an integer. p is an optional predicate to test the integer with. If p(i) fails (where i is the integer arg parsed out of the argument string), the arg will not be accepted. "index" Basically ("int", "index"), but with a twist. This will take a 1-based index and turn it into a 0-based index (which is more useful in code). It doesn't transform 0, and it maintains negative indices as is (note that it does allow them!). "color" Accepts arguments that describe a text color code (e.g., "black", "light blue") and returns the mIRC color code for that color. (Note that many other IRC clients support the mIRC color code scheme, not just mIRC) "now" Simply returns the current timestamp as an arg, does not reference or modify the argument list. "url" Checks for a valid URL. "httpUrl" Checks for a valid HTTP URL. "long", type="long" Basically the same as int minus the predicate, except that it converts the argument to a long integer regardless of the size of the int. "float", type="floating point number" Basically the same as int minus the predicate, except that it converts the argument to a float. "nonInt", type="non-integer value" Accepts everything but integers, and returns them unchanged. The "type" value, as always, can be used to customize the error message that is displayed upon failure. "positiveInt" Accepts only positive integers. "nonNegativeInt" Accepts only non-negative integers. "letter" Looks for a single letter. (Technically, it looks for any one-element sequence) "haveOp", action="do that" Simply requires that the bot have ops in the channel that the command is called in. The action parameter completes the error message: "I need to be opped to ...". "expiry" Takes a number of seconds and adds it to the current time to create an expiration timestamp. "literal", literals, errmsg=None Takes a required sequence or string (literals) and any argument that uniquely matches the starting substring of one of the literals is transformed into the full literal. For example, with ("literal", ("bar", "baz", "qux")), you'd get "bar" for "bar", "baz" for "baz", and "qux" for any of "q", "qu", or "qux". "b" and "ba" would raise errors because they don't uniquely identify one of the literals in the list. You can override errmsg to provide a specific (full) error message, otherwise the default argument error message is displayed. "to" Returns the string "to" if the arg is any form of "to" (case-insensitive). "nick" Checks that the arg is a valid nick on the current IRC server. "seenNick" Checks that the arg is a nick that the bot has seen (NOTE: this is limited by the size of the history buffer that the bot has) "channel" Gets a channel to use the command in. If the channel isn't supplied, uses the channel the message was sent in. If using a different channel, does sanity-checking to make sure the channel exists on the current IRC network. "inChannel" Requires that the command be called from within any channel that the bot is currently in or with one of those channels used as an argument to the command. "onlyInChannel" Requires that the command be called from within any channel that the bot is currently in. "nickInChannel" Requires that the argument be a nick that is in the current channel, and returns that nick. "networkIrc", errorIfNoMatch=False Returns the IRC object of the specified IRC network. If one isn't specified, the IRC object of the IRC network the command was called on is returned. "callerInGivenChannel" Takes the given argument as a channel and makes sure that the caller is in that channel. "plugin", require=True Returns the plugin specified by the arg or None. If require is True, an error is raised if the plugin cannot be retrieved. "boolean" Converts the text string to a boolean value. Acceptable true values are: "1", "true", "on", "enable", or "enabled" (case-insensitive). Acceptable false values are: "0", false", "off", "disable", or "disabled" (case-insensitive). "lowered" Returns the argument lowered (NOTE: it is lowered according to IRC conventions, which does strange mapping with some punctuation characters). "anything" Returns anything as is. "something", errorMsg=None, p=None Takes anything but the empty string. errorMsg can be used to customize the error message. p is any predicate function that can be used to test the validity of the input. "filename" Used to get a filename argument. "commandName" Returns the canonical command name version of the given string (ie, the string is lowercased and dashes and underscores are removed). "text" Takes the rest of the arguments as one big string. Note that this differs from the "anything" context in that it clobbers the arg string when it's done. Using any converters after this is most likely incorrect. "glob" Gets a glob string. Basically, if there are no wildcards ('"*"', "?") in the argument, returns *string*, making a glob string that matches anything containing the given argument. "somethingWithoutSpaces" Same as something, only with the exception of disallowing spaces of course. "capability" Used to retrieve an argument that describes a capability. "channelDb" Sets the channel appropriately in order to get to the databases for that channel (handles whether or not a given channel uses channel-specific databases and whatnot). "hostmask" Returns the hostmask of any provided nick or hostmask argument. "banmask" Returns a generic banmask of the provided nick or hostmask argument. "user" Requires that the caller be a registered user. "matches", regexp, errmsg Searches the args with the given regexp and returns the matches. If no match is found, errmsg is given. "public" Requires that the command be sent in a channel instead of a private message. "private" Requires that the command be sent in a private message instead of a channel. "otherUser" Returns the user specified by the username or hostmask in the argument. "regexpMatcher" Gets a matching regexp argument (m// or //). "validChannel" Gets a channel argument once it makes sure it's a valid channel. "regexpReplacer" Gets a replacing regexp argument (s//). "owner" Requires that the command caller has the "owner" capability. "admin" Requires that the command caller has the "admin" capability. "checkCapability", capability Checks to make sure that the caller has the specified capability. "checkChannelCapability", capability Checks to make sure that the caller has the specified capability on the channel the command is called in. Contexts List ============= What contexts are available for me to use? The list of available contexts is below. Unless specified otherwise, it can be assumed that the type returned by the context itself matches the type of the converter it is applied to. any Looks for any number of arguments matching the supplied converter. Will return a sequence of converted arguments or None. many Looks for multiple arguments matching the supplied converter. Expects at least one to work, otherwise it will fail. Will return the sequence of converted arguments. optional Look for an argument that satisfies the supplied converter, but if it's not the type I'm expecting or there are no arguments for us to check, then use the default value. Will return the converted argument as is or None. additional Look for an argument that satisfies the supplied converter, making sure that it's the right type. If there aren't any arguments to check, then use the default value. Will return the converted argument as is or None. rest Treat the rest of the arguments as one big string, and then convert. If the conversion is unsuccessful, restores the arguments. getopts Handles --option style arguments. Each option should be a key in a dictionary that maps to the name of the converter that is to be used on that argument. To make the option take no argument, use "" as the converter name in the dictionary. For no conversion, use None as the converter name in the dictionary. first Tries each of the supplied converters in order and returns the result of the first successfully applied converter. reverse Reverse the argument list, apply the converters, and then reverse the argument list back. commalist Looks for a comma separated list of arguments that match the supplied converter. Returns a list of the successfully converted arguments. If any of the arguments fail, this whole context fails. Final Word ========== Now that you know how to use wrap, and you have a list of converters and contexts you can use, your task of writing clean, simple, and safe plugin code should become much easier. Enjoy! supybot-0.83.4.1.ds.orig/docs/FAQ0000644000000000000000000001731111206611405013165 0ustar How do I make my Supybot connect to multiple servers? Just use the `connect` command in the `Network` plugin. Why does my bot not recognize me or tell me that I don't have the 'owner' capability? Because you've not given it anything to recognize you from! You'll need to identify with the bot (``help identify`` to see how that works) or add your hostmask to your user record (``help hostmask add`` to see how that works) for it to know that you're you. You may wish to note that addhostmask can accept a password; rather than identify, you can send the command:: hostmask add myOwnerUser [hostmask] myOwnerUserPassword and the bot will add your current hostmask to your owner user (of course, you should change myOwnerUser and myOwnerUserPassword appropriately for your bot). What is a hostmask? Each user on IRC is uniquely identified by a string which we call a `hostmask`. The IRC RFC refers to it as a prefix. Either way, it consists of a nick, a user, and a host, in the form ``nick!user@host``. If your Supybot complains that something you've given to it isn't a hostmask, make sure that you have those three components and that they're joined in the appropriate manner. My bot can't handle nicks with brackets in them! It always complains about something not being a valid command, or about spurious or missing right brackets, etc. You should quote arguments (using double quotes, like this: ``"foo[bar]"``) that have brackets in them that you don't wish to be evaluated as nested commands. Otherwise, you can turn off nested commands by setting `supybot.commands.nested` to False, or change the brackets that nest commands, by setting `supybot.commands.nested.brackets` to some other value (like ``<>``, which can't occur in IRC nicks). I added an alias, but it doesn't work! Take a look at ``help ``. If the alias the bot has listed doesn't match what you're giving it, chances are you need to quote your alias in order for the brackets not to be evaluated. For instance, if you're adding an alias to give you a link to your homepage, you need to say:: alias add mylink "format concat http://my.host.com/ [urlquote $1]" and not:: alias add mylink format concat http://my.host.com/ [urlquote $1] The first version works; the second version will always return the same url. What does 'lobotomized' mean? I see this word in commands and in my `channels.conf`, but I don't know what it means. What does Supybot mean when it says "lobotomized"? A lobotomy is an operation that removes the frontal lobe of the brain, the part that does most of a person's thinking. To "lobotomize" a bot is to tell it to stop thinking--thus, a lobotomized bot will not respond to anything said by anyone other than its owner in whichever channels it is lobotomized. The term is certainly suboptimal, but remains in use because it was historically used by certain other IRC bots, and we wanted to ease the transition to Supybot from those bots by reusing as much terminology as possible. Is there a way to load all the plugins Supybot has? No, there isn't. Even if there were, some plugins conflict with other plugins, so it wouldn't make much sense to load them. For instance, what would a bot do with `Factoids`, `MoobotFactoids`, and `Infobot` all loaded? Probably just annoy people :) If you want to know more about the plugins that are available, check out our `plugin index`_ at our `website`_. Is there a command that can tell me what capability another command requires? No, there isn't, and there probably never will be. Commands have the flexibility to check any capabilities they wish to check; while this flexibility is useful, it also makes it hard to guess what capability a certain command requires. We could make a solution that would work in a large majority of cases, but it wouldn't (and couldn't!) be absolutely correct in all circumstances, and since we're anal and we hate doing things halfway, we probably won't ever add this partial solution. Why doesn't `Karma` seem to work for me? `Karma`, by default, doesn't acknowledge karma updates. If you check the karma of whatever you increased/decreased, you'll note that your increment or decrement still took place. If you'd rather `Karma` acknowledge karma updates, change the `supybot.plugins.Karma.response` configuration variable to "On". Why won't Supybot respond to private messages? The most likely cause is that you are running your bot on the Freenode network. Around Sept. 2005, Freenode enabled a `default policy`_ that disallows users from messaging other users unless they are registered with NickServ. So, the reason you aren't seeing a response from your Supybot is: * Your Supybot is not registered with NickServ and o you haven't registered with NickServ * or you have registered with NickServ o but you haven't allowed `unregistered users`_ to message you Can users with the "admin" capability change configuration? Currently, no. Feel free to make your case to us as to why a certain configuration variable should only require the `admin` capability instead of the `owner` capability, and if we agree with you, we'll change it for the next release. How can I make my Supybot log my IRC channel? To log all the channels your Supybot is in, simply load the `ChannelLogger` plugin, which is included in the main distribution. How do I find out channel modes? I want to know who's an op in a certain channel, or who's voiced, or what the modes on the channel are. How do I do that? Everything you need is kept in a `ChannelState` object in an `IrcState` object in the `Irc` object your plugin is given. To see the ops in a given channel, for instance, you would do this:: irc.state.channels['#channel'].ops To see a dictionary mapping mode chars to values (if any), you would do this:: irc.state.channels['#channel'].modes From there, things should be self-evident. Can Supybot connect through a proxy server? Supybot is not designed to be allowed to connect to an IRC server via a proxy server, however there are transparent proxy server helpers like tsocks_ that are designed to proxy-enable all network applications, and Supybot does work with these. Why can't Supybot find the plugin I want to load? Why does my bot say that 'No plugin "foo" exists.' when I try to load the foo plugin? First, make sure you are typing the plugin name correctly. ``@load foo`` is not the same as ``@load Foo`` [#plugindir]_. If that is not the problem, .. [#plugindir] Yes, it used to be the same, but then we moved to using directories for plugins instead of a single file. Apparently, that makes a difference to Python. I've found a bug, what do I do? Submit your bug on `Sourceforge`_ through our `project page`_. Is Python installed? I run Windows, and I'm not sure if Python is installed on my computer. How can I find out for sure? Python isn't commonly installed by default on Windows computers. If you don't see it in your start menu somewhere, it's probably not installed. The easiest way to find out if Python is installed is simply to `download it`_ and try to install it. If the installer complains, you probably already have it installed. If it doesn't, well, now you have Python installed. .. _plugin index: http://supybot.com/plugins.html .. _website: http://supybot.com/ .. _default policy: http://freenode.net/faq.shtml#privmsg .. _unregistered users: http://freenode.net/faq.shtml#fromunreg .. _tsocks: http://tsocks.sourceforge.net .. _Sourceforge: http://sourceforge.net/ .. _project page: http://sourceforge.net/projects/supybot .. _download it: http://python.org/download/ supybot-0.83.4.1.ds.orig/docs/STYLE0000644000000000000000000002073011206611405013455 0ustar ==================================================================== Code not following these style guidelines fastidiously is likely (*very* likely) not to be accepted into the Supybot core. ==================================================================== Read PEP 8 (Guido's Style Guide) and know that we use almost all the same style guidelines. Maximum line length is 79 characters. 78 is a safer bet, though. This is **NON-NEGOTIABLE**. Your code will not be accepted while you are violating this guidline. Identation is 4 spaces per level. No tabs. This also is **NON-NEGOTIABLE**. Your code, again, will *never* be accepted while you have literal tabs in it. Single quotes are used for all string literals that aren't docstrings. They're just easier to type. Triple double quotes (""") are always used for docstrings. Raw strings (r'' or r"") should be used for regular expressions. Spaces go around all operators (except around '=' in default arguments to functions) and after all commas (unless doing so keeps a line within the 79 character limit). Functions calls should look like this: "foo(bar(baz(x), y))". They should not look like "foo (bar (baz (x), y))", or like "foo(bar(baz(x), y) )" or like anything else. I hate extraneous spaces. Class names are StudlyCaps. Method and function names are camelCaps (StudlyCaps with an initial lowercase letter). If variable and attribute names can maintain readability without being camelCaps, then they should be entirely in lowercase, otherwise they should also use camelCaps. Plugin names are StudlyCaps. Imports should always happen at the top of the module, one import per line (so if imports need to be added or removed later, it can be done easily). Unless absolutely required by some external force, imports should be ordered by the string length of the module imported. I just think it looks prettier. A blank line should be between all consecutive method declarations in a class definition. Two blank lines should be between all consecutive class definitions in a file. Comments are even better than blank lines for separating classes. Database filenames should generally begin with the name of the plugin and the extension should be 'db'. plugins.DBHandler does this already. Whenever creating a file descriptor or socket, keep a reference around and be sure to close it. There should be no code like this: s = urllib2.urlopen('url').read() Instead, do this: fd = urllib2.urlopen('url') try: s = fd.read() finally: fd.close() This is to be sure the bot doesn't leak file descriptors. All plugin files should include a docstring decsribing what the plugin does. This docstring will be returned when the user is configuring the plugin. All plugin classes should also include a docstring describing how to do things with the plugin; this docstring will be returned when the user requests help on a plugin name. Method docstrings in classes deriving from callbacks.Privmsg should include an argument list as their first line, and after that a blank line followed by a longer description of what the command does. The argument list is used by the 'syntax' command, and the longer description is used by the 'help' command. Whenever joining more than two strings, use string interpolation, not addition: s = x + y + z # Bad. s = '%s%s%s' % (x, y, z) # Good. s = ''.join([x, y, z]) # Best, but not as general. This has to do with efficiency; the intermediate string x+y is made (and thus copied) before x+y+z is made, so it's less efficient. People who use string concatenation in a for loop will be swiftly kicked in the head. When writing strings that have formatting characters in them, don't use anything but %s unless you absolutely must. In particular, %d should never be used, it's less general than %s and serves no useful purpose. If you got the %d wrong, you'll get an exception that says, "foo instance can't be converted to an integer." But if you use %s, you'll get to see your nice little foo instance, if it doesn't convert to a string cleanly, and if it does convert cleanly, you'll get to see what you expect to see. Basically, %d just sucks. As a corrolary to the above, note that sometimes %f is used, but on when floats need to be formatted, e.g., %.2f. Use the log module to its fullest; when you need to print some values to debug, use self.log.debug to do so, and leave those statements in the code (commented out) so they can later be re-enabled. Remember that once code is buggy, it tends to have more bugs, and you'll probably need those print statements again. While on the topic of logs, note that we do not use % (i.e., str.__mod__) with logged strings; we simple pass the format parameters as additional arguments. The reason is simple: the logging module supports it, and it's cleaner (fewer tokens/glyphs) to read. While still on the topic of logs, it's also important to pick the appropriate log level for given information. DEBUG: Appropriate to tell a programmer *how* we're doing something (i.e., debugging printfs, basically). If you're trying to figure out why your code doesn't work, DEBUG is the new printf -- use that, and leave the statements in your code. INFO: Appropriate to tell a user *what* we're doing, when what we're doing isn't important for the user to pay attention to. A user who likes to keep up with things should enjoy watching our logging at the INFO level; it shouldn't be too low-level, but it should give enough information that it keeps him relatively interested at peak times. WARNING: Appropriate to tell a user when we're doing something that he really ought to pay attention to. Users should see WARNING and think, "Hmm, should I tell the Supybot developers about this?" Later, he should decide not to, but it should give the user a moment to pause and think about what's actually happening with his bot. ERROR: Appropriate to tell a user when something has gone wrong. Uncaught exceptions are ERRORs. Conditions that we absolutely want to hear about should be errors. Things that should *scare* the user should be errors. CRITICAL: Not really appropriate. I can think of no absolutely critical issue yet encountered in Supybot; the only possible thing I can imagine is to notify the user that the partition on which Supybot is running has filled up. That would be a CRITICAL condition, but it would also be hard to log :) All plugins should have test cases written for them. Even if it doesn't actually test anything but just exists, it's good to have the test there so there's a place to add more tests later (and so we can be sure that all plugins are adequately documented; PluginTestCase checks that every command has documentation) All uses of eval() that expect to get integrated in Supybot must be approved by jemfinch, no exceptions. Chances are, it won't be accepted. Have you looked at utils.safeEval? SQL table names should be all-lowercase and include underscores to separate words. This is because SQL itself is case-insensitive. This doesn't change, however the fact that variable/member names should be camel case. SQL statements in code should put SQL words in ALL CAPS: """SELECT quote FROM quotes ORDER BY random() LIMIT 1""". This makes SQL significantly easier to read. Common variable names: L => an arbitrary list. t => an arbitrary tuple. x => an arbitrary float. s => an arbitrary string. f => an arbitrary function. p => an arbitrary predicate. i,n => an arbitrary integer. cb => an arbitrary callback. db => a database handle. fd => a file-like object. msg => an ircmsgs.IrcMsg object. irc => an irclib.Irc object (or proxy) nick => a string that is an IRC nick. channel => a string that is an IRC channel. hostmask => a string that is a user's IRC prefix. When the semantic functionality (that is, the "meaning" of a variable is obvious from context, one of these names should be used. This just makes it easier for people reading our code to know what a variable represents without scouring the surrounding code. Multiple variable assignments should always be surrounded with parentheses -- i.e., if you're using the partition function, then your assignment statement should look like (good, bad) = partition(p, L). The parentheses make it obvious that you're doing a multiple assignment, and that's important because I hate reading code and wondering where a variable came from. supybot-0.83.4.1.ds.orig/docs/ADVANCED_PLUGIN_TESTING0000644000000000000000000003272511206611405016204 0ustar Advanced Plugin Testing ----------------------- The complete guide to writing tests for your plugins. Why Write Tests? ================ Why should I write tests for my plugin? Here's why. For those of you asking "Why should I write tests for my plugin? I tried it out, and it works!", read on. For those of you who already realize that Testing is Good (TM), skip to the next section. Here are a few quick reasons why to test your Supybot plugins. * When/if we rewrite or change certain features in Supybot, tests make sure your plugin will work with these changes. It's much easier to run supybot-test MyPlugin after upgrading the code and before even reloading the bot with the new code than it is to load the bot with new code and then load the plugin only to realize certain things don't work. You may even ultimately decide you want to stick with an older version for a while as you patch your custom plugin. This way you don't have to rush a patch while restless users complain since you're now using a newer version that doesn't have the plugin they really like. * Running the automated tests takes a few seconds, testing plugins in IRC on a live bot generally takes quite a bit longer. We make it so that writing tests generally doesn't take much time, so a small initial investment adds up to lots of long-term gains. * If you want your plugin to be included in any of our releases (the core Supybot if you think it's worthy, or our supybot-plugins package), it has to have tests. Period. For a bigger list of why to write unit tests, check out this article: http://www.onjava.com/pub/a/onjava/2003/04/02/javaxpckbk.html and also check out what the Extreme Programming folks have to say about unit tests: http://www.extremeprogramming.org/rules/unittests.html Plugin Tests ============ How to write tests for commands in your plugins. Introduction This tutorial assumes you've read through the plugin author tutorial, and that you used supybot-plugin-create to create your plugin (as everyone should). So, you should already have all the necessary imports and all that boilerplate stuff in test.py already, and you have already seen what a basic plugin test looks like from the plugin author tutorial. Now we'll go into more depth about what plugin tests are available to Supybot plugin authors. Plugin Test Case Classes Supybot comes with two plugin test case classes, PluginTestCase and ChannelPluginTestCase. The former is used when it doesn't matter whether or not the commands are issued in a channel, and the latter is used for when it does. For the most part their API is the same, so unless there's a distinction between the two we'll treat them as one and the same when discussing their functionality. The Most Basic Plugin Test Case At the most basic level, a plugin test case requires three things: * the class declaration (subclassing PluginTestCase or ChannelPluginTestCase) * a list of plugins that need to be loaded for these tests (does not include Owner, Misc, or Config, those are always automatically loaded) - often this is just the name of the plugin that you are writing tests for * some test methods Here's what the most basic plugin test case class looks like (for a plugin named MyPlugin): class MyPluginTestCase(PluginTestCase): plugins = ('MyPlugin',) def testSomething(self): # assertions and such go here Your plugin test case should be named TestCase as you see above, though it doesn't necessarily have to be named that way (supybot-plugin-create puts that in place for you anyway). As you can see we elected to subclass PluginTestCase because this hypothetical plugin apparently doesn't do anything channel-specific. As you probably noticed, the plugins attribute of the class is where the list of necessary plugins goes, and in this case just contains the plugin that we are testing. This will be the case for probably the majority of plugins. A lot of the time test writers will use a bot function that performs some function that they don't want to write code for and they will just use command nesting to feed the bot what they need by using that plugin's functionality. If you choose to do this, only do so with core bot plugins as this makes distribution of your plugin simpler. After all, we want people to be able to run your plugin tests without having to have all of your plugins! One last thing to note before moving along is that each of the test methods should describe what they are testing. If you want to test that your plugin only responds to registered users, don't be afraid to name your test method testOnlyRespondingToRegisteredUsers or testNotRespondingToUnregisteredUsers. You may have noticed some rather long and seemingly unwieldy test method names in our code, but that's okay because they help us know exactly what's failing when we run our tests. With an ambiguously named test method we may have to crack open test.py after running the tests just to see what it is that failed. For this reason you should also test only one thing per test method. Don't write a test method named testFoobarAndBaz. Just write two test methods, testFoobar and testBaz. Also, it is important to note that test methods must begin with test and that any method within the class that does begin with test will be run as a test by the supybot-test program. If you want to write utility functions in your test class that's fine, but don't name them something that begins with test or they will be executed as tests. Including Extra Setup Some tests you write may require a little bit of setup. For the most part it's okay just to include that in the individual test method itself, but if you're duplicating a lot of setup code across all or most of your test methods it's best to use the setUp method to perform whatever needs to be done prior to each test method. The setUp method is inherited from the whichever plugin test case class you chose for your tests, and you can add whatever functionality you want to it. Note the important distinction, however: you should be adding to it and not overriding it. Just define setUp in your own plugin test case class and it will be run before all the test methods are invoked. Let's do a quick example of one. Let's write a setUp method which registers a test user for our test bot: def setUp(self): ChannelPluginTestCase.setUp(self) # important!! # Create a valid user to use self.prefix = 'foo!bar@baz' self.feedMsg('register tester moo', to=self.nick, frm=self.prefix)) m = self.getMsg() # Response to registration. Now notice how the first line calls the parent class's setUp method first? This must be done first. Otherwise several problems are likely to arise. For one, you wouldn't have an irc object at self.irc that we use later on nor would self.nick be set. As for the rest of the method, you'll notice a few things that are available to the plugin test author. self.prefix refers to the hostmask of the hypothetical test user which will be "talking" to the bot, issuing commands. We set it to some generically fake hostmask, and then we use feedMsg to feed it a private message (using the bot's nick, accessible via self.nick) to register the username "tester" with the password "moo". We have to do it this way (rather than what you'll find out is the standard way of issuing commands to the bot in test cases a little later) because registration must be done in private. And lastly, since feedMsg doesn't dequeue any messages from the bot after getting fed a message, we perform a getMsg to get the response. You're not expected to know all this yet, but do take note of it since using these methods in test-writing is not uncommon. These utility methods as well as all of the available assertions are covered in the next section. So, now in any of the test methods we write, we'll be able to count on the fact that there will be a registered user "tester" with a password of "moo", and since we changed our prefix by altering self.prefix and registered after doing so, we are now identified as this user for all messages we send unless we specify that they are coming from some other prefix. The Opposite of Setting-up: Tearing Down If you did some things in your setUp that you want to clean up after, then this code belongs in the tearDown method of your test case class. It's essentially the same as setUp except that you probably want to wait to invoke the parent class's tearDown until after you've done all of your tearing down. But do note that you do still have to invoke the parent class's tearDown method if you decide to add in your own tear-down stuff. Setting Config Variables for Testing Before we delve into all of the fun assertions we can use in our test methods it's worth noting that each plugin test case can set custom values for any Supybot config variable they want rather easily. Much like how we can simply list the plugins we want loaded for our tests in the plugins attribute of our test case class, we can set config variables by creating a mapping of variables to values with the config attribute. So if, for example, we wanted to disable nested commands within our plugin testing for some reason, we could just do this: class MyPluginTestCase(PluginTestCase): config = {'supybot.commands.nested': False} def testThisThing(self): # stuff And now you can be assured that supybot.commands.nested is going to be off for all of your test methods in this test case class. Plugin Test Methods =================== The full list of test methods and how to use them. Introduction You know how to make plugin test case classes and you know how to do just about everything with them except to actually test stuff. Well, listed below are all of the assertions used in tests. If you're unfamiliar with what an assertion is in code testing, it is basically a requirement of something that must be true in order for that test to pass. It's a necessary condition. If any assertion within a test method fails the entire test method fails and it goes on to the next one. Assertions All of these are methods of the plugin test classes themselves and hence are accessed by using self.assertWhatever in your test methods. These are sorted in order of relative usefulness. * assertResponse(query, expectedResponse) - Feeds query to the bot as a message and checks to make sure the response is expectedResponse. The test fails if they do not match (note that prefixed nicks in the response do not need to be included in the expectedResponse). * assertError(query) - Feeds query to the bot and expects an error in return. Fails if the bot doesn't return an error. * assertNotError(query) - The opposite of assertError. It doesn't matter what the response to query is, as long as it isn't an error. If it is not an error, this test passes, otherwise it fails. * assertRegexp(query, regexp, flags=re.I) - Feeds query to the bot and expects something matching the regexp (no m// required) in regexp with the supplied flags. Fails if the regexp does not match the bot's response. * assertNotRegexp(query, regexp, flags=re.I) - The opposite of assertRegexp. Fails if the bot's output matches regexp with the supplied flags. * assertHelp(query) - Expects query to return the help for that command. Fails if the command help is not triggered. * assertAction(query, expectedResponse=None) - Feeds query to the bot and expects an action in response, specifically expectedResponse if it is supplied. Otherwise, the test passes for any action response. * assertActionRegexp(query, regexp, flags=re.I) - Basically like assertRegexp but carries the extra requirement that the response must be an action or the test will fail. Utilities * feedMsg(query, to=None, frm=None) - Simply feeds query to whoever is specified in to or to the bot itself if no one is specified. Can also optionally specify the hostmask of the sender with the frm keyword. Does not actually perform any assertions. * getMsg(query) - Feeds query to the bot and gets the response. Other Tests =========== If you had to write helper code for a plugin and want to test it, here's how. Previously we've only discussed how to test stuff in the plugin that is intended for IRC. Well, we realize that some Supybot plugins will require utility code that doesn't necessarily require all of the overhead of setting up IRC stuff, and so we provide a more lightweight test case class, SupyTestCase, which is a very very light wrapper around unittest.TestCase (from the standard unittest module) that basically just provides a little extra logging. This test case class is what you should use for writing those test cases which test things that are independent of IRC. For example, in the MoobotFactoids plugin there is a large chunk of utility code dedicating to parsing out random choices within a factoid using a class called OptionList. So, we wrote the OptionListTestCase as a SupyTestCase for the MoobotFactoids plugin. The setup for test methods is basically the same as before, only you don't have to define plugins since this is independent of IRC. You still have the choice of using setUp and tearDown if you wish, since those are inherited from unittest.TestCase. But, the same rules about calling the setUp or tearDown method from the parent class still apply. With all this in hand, now you can write great tests for your Supybot plugins! supybot-0.83.4.1.ds.orig/docs/GETTING_STARTED0000644000000000000000000002047711206611405015054 0ustar Introduction Ok, so you've decided to try out Supybot. That's great! The more people who use Supybot, the more people can submit bugs and help us to make it the best IRC bot in the world :) You should have already read through our install document (if you had to manually install) before reading any further. Now we'll give you a whirlwind tour as to how you can get Supybot setup and use Supybot effectively. Initial Setup Now that you have Supybot installed, you'll want to get it running. The first thing you'll want to do is run supybot-wizard. Before running supybot-wizard, you should be in the directory in which you want your bot-related files to reside. The wizard will walk you through setting up a base config file for your Supybot. Once you've completed the wizard, you will have a config file called botname.conf. In order to get the bot running, run 'supybot botname.conf'. Listing Commands Ok, so let's assume your bot connected to the server and joined the channels you told it to join. For now we'll assume you named your bot 'supybot' (you probably didn't, but it'll make it much clearer in the examples that follow to assume that you did). We'll also assume that you told it to join #channel (a nice generic name for a channel, isn't it? :)) So what do you do with this bot that you just made to join your channel? Try this in the channel:: supybot: list Replacing 'supybot' with the actual name you picked for your bot, of course. Your bot should reply with a list of the plugins he currently has loaded. At least Admin, Channel, Config, Misc, Owner, and User should be there; if you used supybot-wizard to create your configuration file you may have many more plugins loaded. The list command can also be used to list the commands in a given plugin:: supybot: list Misc will list all the commands in the Misc plugin. If you want to see the help for any command, just use the help command:: supybot: help help supybot: help list supybot: help load Sometimes more than one plugin will have a given command; for instance, the "list" command exists in both the Misc and Config plugins (both loaded by default). List, in this case, defaults to the Misc plugin, but you may want to get the help for the list command in the Config plugin. In that case, you'll want to give your command like this:: supybot: help config list Anytime your bot tells you that a given command is defined in several plugins, you'll want to use this syntax ("plugin command") to disambiguate which plugin's command you wish to call. For instance, if you wanted to call the Config plugin's list command, then you'd need to say:: supybot: config list Rather than just 'list'. Making Supybot Recognize You If you ran the wizard, then it is almost certainly the case that you already added an owner user for yourself. If not, however, you can add one via the handy-dandy 'supybot-adduser' script. You'll want to run it while the bot is not running (otherwise it could overwrite supybot-adduser's changes to your user database before you get a chance to reload them). Just follow the prompts, and when it asks if you want to give the user any capabilities, say yes and then give yourself the 'owner' capability, restart the bot and you'll be ready to load some plugins! Now, in order for the bot to recognize you as your owner user, you'll have to identify with the bot. Open up a query window in your irc client ('/query' should do it; if not, just know that you can't identify in a channel because it requires sending your password to the bot). Then type this:: help identify And follow the instructions; the command you send will probably look like this, with 'myowneruser' and 'myuserpassword' replaced:: identify myowneruser myuserpassword The bot will tell you that 'The operation succeeded' if you got the right name and password. Now that you're identified, you can do anything that requires any privilege: that includes all the commands in the Owner and Admin plugins, which you may want to take a look at (using the list and help commands, of course). One command in particular that you might want to use (it's from the User plugin) is the 'hostmask add' command: it lets you add a hostmask to your user record so the bot recognizes you by your hostmask instead of requiring you always to identify with it before it recognizes you. Use the 'help' command to see how this command works. Here's how I often use it:: hostmask add myuser [hostmask] mypassword You may not have seen that '[hostmask]' syntax before. Supybot allows nested commands, which means that any command's output can be nested as an argument to another command. The hostmask command from the Misc plugin returns the hostmask of a given nick, but if given no arguments, it returns the hostmask of the person giving the command. So the command above adds the hostmask I'm currently using to my user's list of recognized hostmasks. I'm only required to give mypassword if I'm not already identified with the bot. Loading Plugins Let's take a look at loading other plugins. If you didn't use supybot-wizard, though, you might do well to try it before playing around with loading plugins yourself: each plugin has its own configure function that the wizard uses to setup the appropriate registry entries if the plugin requires any. If you do want to play around with loading plugins, you're going to need to have the owner capability. Remember earlier when I told you to try 'help load'? That's the very command you'll be using. Basically, if you want to load, say, the Games plugin, then 'load Games'. Simple, right? If you need a list of the plugins you can load, you'll have to list the directory the plugins are in (using whatever command is appropriate for your operating system, either 'ls' or 'dir'). Getting More From Your Supybot Another command you might find yourself needing somewhat often is the 'more' command. The IRC protocol limits messages to 512 bytes, 60 or so of which must be devoted to some bookkeeping. Sometimes, however, Supybot wants to send a message that's longer than that. What it does, then, is break it into "chunks" and send the first one, following it with '(X more messages)' where X is how many more chunks there are. To get to these chunks, use the more command. One way to try is to look at the default value of supybot.replies.genericNoCapability -- it's so long that it'll stretch across two messages:: $config default supybot.replies.genericNoCapability jemfinch|lambda: You're missing some capability you need. This could be because you actually possess the anti-capability for the capability that's required of you, or because the channel provides that anti-capability by default, or because the global capabilities include that anti-capability. Or, it could be because the channel or the global defaultAllow is set to False, meaning (1 more message) $more jemfinch|lambda: that no commands are allowed unless explicitly in your capabilities. Either way, you can't do what you want to do. So basically, the bot keeps, for each person it sees, a list of "chunks" which are "released" one at a time by the 'more' command. In fact, you can even get the more chunks for another user: if you want to see another chunk in the last command jemfinch gave, for instance, you would just say 'more jemfinch' after which, his "chunks" now belong to you. So, you would just need to say 'more' to continue seeing chunks from jemfinch's initial command. Final Word You should now have a solid foundation for using Supybot. You can use the 'list' command to see what plugins your bot has loaded and what commands are in those plugins; you can use the 'help' command to see how to use a specific command, and you can use the 'more' command to continue a long response from the bot. With these three commands, you should have a strong basis with which to discover the rest of the features of Supybot! Do be sure to read our other documentation and make use of the resources we provide for assistance; this website, the forums on it, and, of course, #supybot on irc.freenode.net if you run into any trouble! supybot-0.83.4.1.ds.orig/docs/USING_UTILS0000644000000000000000000003724111206611405014427 0ustar Using Supybot's utils module ---------------------------- Supybot provides a wealth of utilities for plugin writers in the supybot.utils module, this tutorial describes these utilities and shows you how to use them. str.py ====== The Format Function The supybot.utils.str module provides a bunch of utility functions for handling string values. This section contains a quick rundown of all of the functions available, along with descriptions of the arguments they take. First and foremost is the format function, which provides a lot of capability in just one function that uses string-formatting style to accomplish a lot. So much so that it gets its own section in this tutorial. All other functions will be in other sections. format takes several arguments - first, the format string (using the format characters described below), and then after that, each individual item to be formatted. Do not attempt to use the % operator to do the formatting because that will fall back on the normal string formatting operator. The format function uses the following string formatting characters. * % - literal "%" * i - integer * s - string * f - float * r - repr * b - form of the verb "to be" (takes an int) * h - form of the verb "to have" (takes an int) * L - commaAndify (takes a list of strings or a tuple of ([strings], and)) * p - pluralize (takes a string) * q - quoted (takes a string) * n - n items (takes a 2-tuple of (n, item) or a 3-tuple of (n, between, item)) * t - time, formatted (takes an int) * u - url, wrapped in braces Here are a few examples to help elaborate on the above descriptions: >>> format("Error %q has been reported %n. For more information, see %u.", "AttributeError", (5, "time"), "http://supybot.com") 'Error "AttributeError" has been reported 5 times. For more information, see .' >>> i = 4 >>> format("There %b %n at this time. You are only allowed %n at any given time", i, (i, "active", "thread"), (5, "active", "thread")) 'There are 4 active threads at this time. You are only allowed 5 active threads at any given time' >>> i = 1 >>> format("There %b %n at this time. You are only allowed %n at any given time", i, (i, "active", "thread"), (5, "active", "thread")) 'There is 1 active thread at this time. You are only allowed 5 active threads at any given time' >>> ops = ["foo", "bar", "baz"] >>> format("The following %n %h the %s capability: %L", (len(ops), "user"), len(ops), "op", ops) 'The following 3 users have the op capability: foo, bar, and baz' As you can see, you can combine all sorts of combinations of formatting strings into one. In fact, that was the major motivation behind format. We have specific functions that you can use individually for each of those formatting types, but it became much easier just to use special formatting chars and the format function than concatenating a bunch of strings that were the result of other utils.str functions. The Other Functions These are the functions that can't be handled by format. They are sorted in what I perceive to be the general order of usefulness (and I'm leaving the ones covered by format for the next section). * ellipsisify(s, n) - Returns a shortened version of a string. Produces up to the first n chars at the nearest word boundary. - s: the string to be shortened - n: the number of characters to shorten it to * perlReToPythonRe(s) - Converts a Perl-style regexp (e.g., "/abcd/i" or "m/abcd/i") to an actual Python regexp (an re object) - s: the regexp string * perlReToReplacer(s) - converts a perl-style replacement regexp (eg, "s/foo/bar/g") to a Python function that performs such a replacement - s: the regexp string * dqrepr(s) - Returns a repr() of s guaranteed to be in double quotes. (Double Quote Repr) - s: the string to be double-quote repr()'ed * toBool(s) - Determines whether or not a string means True or False and returns the appropriate boolean value. True is any of "true", "on", "enable", "enabled", or "1". False is any of "false", "off", "disable", "disabled", or "0". - s: the string to determine the boolean value for * rsplit(s, sep=None, maxsplit=-1) - functionally the same as str.split in the Python standard library except splitting from the right instead of the left. Python 2.4 has str.rsplit (which this function defers to for those versions >= 2.4), but Python 2.3 did not. - s: the string to be split - sep: the separator to split on, defaults to whitespace - maxsplit: the maximum number of splits to perform, -1 splits all possible splits. * normalizeWhitespace(s) - reduces all multi-spaces in a string to a single space - s: the string to normalize * depluralize(s) - the opposite of pluralize - s: the string to depluralize * unCommaThe(s) - Takes a string of the form "foo, the" and turns it into "the foo" - s: string, the * distance(s, t) - computes the levenshtein distance (or "edit distance") between two strings - s: the first string - t: the second string * soundex(s, length=4) - computes the soundex for a given string - s: the string to compute the soundex for - length: the length of the soundex to generate * matchCase(s1, s2) - Matches the case of the first string in the second string. - s1: the first string - s2: the string which will be made to match the case of the first The Commands Format Already Covers These commands aren't necessary because you can achieve them more easily by using the format command, but they exist if you decide you want to use them anyway though it is greatly discouraged for general use. * commaAndify(seq, comma=",", And="and") - transforms a list of items into a comma separated list with an "and" preceding the last element. For example, ["foo", "bar", "baz"] becomes "foo, bar, and baz". Is smart enough to convert two-element lists to just "item1 and item2" as well. - seq: the sequence of items (don't have to be strings, but need to be 'str()'-able) - comma: the character to use to separate the list - And: the word to use before the last element * pluralize(s) - Returns the plural of a string. Put any exceptions to the general English rules of pluralization in the plurals dictionary in supybot.utils.str. - s: the string to pluralize * nItems(n, item, between=None) - returns a string that describes a given number of an item (with any string between the actual number and the item itself), handles pluralization with the pluralize function above. Note that the arguments here are in a different order since between is optional. - n: the number of items - item: the type of item - between: the optional string that goes between the number and the type of item * quoted(s) - Returns the string surrounded by double-quotes. - s: the string to quote * be(i) - Returns the proper form of the verb "to be" based on the number provided (be(1) is "is", be(anything else) is "are") - i: the number of things that "be" * has(i) - Returns the proper form of the verb "to have" based on the number provided (has(1) is "has", has(anything else) is "have") - i: the number of things that "has" structures.py ============= Intro This module provides a number of useful data structures that aren't found in the standard Python library. For the most part they were created as needed for the bot and plugins themselves, but they were created in such a way as to be of general use for anyone who needs a data structure that performs a like duty. As usual in this document, I'll try and order these in order of usefulness, starting with the most useful. The queue classes The structures module provides two general-purpose queue classes for you to use. The "queue" class is a robust full-featured queue that scales up to larger sized queues. The "smallqueue" class is for queues that will contain fewer (less than 1000 or so) items. Both offer the same common interface, which consists of: * a constructor which will optionally accept a sequence to start the queue off with * enqueue(item) - adds an item to the back of the queue * dequeue() - removes (and returns) the item from the front of the queue * peek() - returns the item from the front of the queue without removing it * reset() - empties the queue entirely In addition to these general-use queue classes, there are two other more specialized queue classes as well. The first is the "TimeoutQueue" which holds a queue of items until they reach a certain age and then they are removed from the queue. It features the following: * TimeoutQueue(timeout, queue=None) - you must specify the timeout (in seconds) in the constructor. Note that you can also optionally pass it a queue which uses any implementation you wish to use whether it be one of the above (queue or smallqueue) or if it's some custom queue you create that implements the same interface. If you don't pass it a queue instance to use, it will build its own using smallqueue. * reset(), enqueue(item), dequeue() - all same as above queue classes * setTimeout(secs) - allows you to change the timeout value And for the final queue class, there's the "MaxLengthQueue" class. As you may have guessed, it's a queue that is capped at a certain specified length. It features the following: * MaxLengthQueue(length, seq=()) - the constructor naturally requires that you set the max length and it allows you to optionally pass in a sequence to be used as the starting queue. The underlying implementation is actually the queue from before. * enqueue(item) - adds an item onto the back of the queue and if it would push it over the max length, it dequeues the item on the front (it does not return this item to you) * all the standard methods from the queue class are inherited for this class The Other Structures The most useful of the other structures is actually very similar to the "MaxLengthQueue". It's the "RingBuffer", which is essentially a MaxLengthQueue which fills up to its maximum size and then circularly replaces the old contents as new entries are added instead of dequeuing. It features the following: * RingBuffer(size, seq=()) - as with the MaxLengthQueue you specify the size of the RingBuffer and optionally give it a sequence. * append(item) - adds item to the end of the buffer, pushing out an item from the front if necessary * reset() - empties out the buffer entirely * resize(i) - shrinks/expands the RingBuffer to the size provided * extend(seq) - append the items from the provided sequence onto the end of the RingBuffer The next data structure is the TwoWayDictionary, which as the name implies is a dictionary in which key-value pairs have mappings going both directions. It features the following: * TwoWayDictionary(seq=(), **kwargs) - Takes an optional sequence of (key, value) pairs as well as any key=value pairs specified in the constructor as initial values for the two-way dict. * other than that, no extra features that a normal Python dict doesn't already offer with the exception that any (key, val) pair added to the dict is also added as (val, key) as well, so the mapping goes both ways. Elements are still accessed the same way you always do with Python 'dict's. There is also a MultiSet class available, but it's very unlikely that it will serve your purpose, so I won't go into it here. The curious coder can go check the source and see what it's all about if they wish (it's only used once in our code, in the Relay plugin). web.py ====== The web portion of Supybot's utils module is mainly used for retrieving data from websites but it also has some utility functions pertaining to HTML and email text as well. The functions in web are listed below, once again in order of usefulness. * getUrl(url, size=None, headers=None) - gets the data at the URL provided and returns it as one large string - url: the location of the data to be retrieved or a urllib2.Request object to be used in the retrieval - size: the maximum number of bytes to retrieve, defaults to None, meaning that it is to try to retrieve all data - headers: a dictionary mapping header types to header data * getUrlFd(url, headers=None) - returns a file-like object for a url - url: the location of the data to be retrieved or a urllib2.Request object to be used in the retrieval - headers: a dictionary mapping header types to header data * htmlToText(s, tagReplace=" ") - strips out all tags in a string of HTML, replacing them with the specified character - s: the HTML text to strip the tags out of - tagReplace: the string to replace tags with * strError(e) - pretty-printer for web exceptions, returns a descriptive string given a web-related exception - e: the exception to pretty-print * mungeEmail(s) - a naive e-mail obfuscation function, replaces "@" with "AT" and "." with "DOT" - s: the e-mail address to obfuscate * getDomain(url) - returns the domain of a URL - url: the URL in question The Best of the Rest ==================== Highlights the most useful of the remaining functionality in supybot.utils Intro Rather than document each of the remaining portions of the supybot.utils module, I've elected to just pick out the choice bits from specific parts and document those instead. Here they are, broken out by module name. supybot.utils.file - file utilities * touch(filename) - updates the access time of a file by opening it for writing and immediately closing it * mktemp(suffix="") - creates a decent random string, suitable for a temporary filename with the given suffix, if provided * the AtomicFile class - used for files that need to be atomically written, i.e., if there's a failure the original file remains unmodified. For more info consult file.py in src/utils supybot.utils.gen - general utilities * timeElapsed(elapsed, [lots of optional args]) - given the number of seconds elapsed, returns a string with the English description of the amount of time passed, consult gen.py in src/utils for the exact argument list and documentation if you feel you could use this function. * exnToString(e) - improved exception-to-string function. Provides nicer output than a simple str(e). * InsensitivePreservingDict class - a dict class that is case-insensitive when accessing keys supybot.utils.iter - iterable utilities * len(iterable) - returns the length of a given iterable * groupby(key, iterable) - equivalent to the itertools.groupby function available as of Python 2.4. Provided for backwards compatibility. * any(p, iterable) - Returns true if any element in the iterable satisfies the predicate p * all(p, iterable) - Returns true if all elements in the iterable satisfy the predicate p * choice(iterable) - Returns a random element from the iterable supybot-0.83.4.1.ds.orig/docs/CONFIGURATION0000644000000000000000000002146711206611405014574 0ustar Introduction ------------ So you've got your Supybot up and running and there are some things you don't like about it. Fortunately for you, chances are that these things are configurable, and this document is here to tell you how to configure them. Configuration of Supybot is handled via the `Config` plugin, which controls runtime access to Supybot's registry (the configuration file generated by the 'supybot-wizard' program you ran). The `Config` plugin provides a way to get or set variables, to list the available variables, and even to get help for certain variables. Take a moment now to read the help for each of those commands: ``config``, ``list``, and ``help``. If you don't know how to get help on those commands, take a look at the GETTING_STARTED document. Configuration Registry ---------------------- Now, if you're used to the Windows registry, don't worry, Supybot's registry is completely different. For one, it's completely plain text. There's no binary database sensitive to corruption, it's not necessary to use another program to edit it--all you need is a simple text editor. But there is at least one good idea in Windows' registry: hierarchical configuration. Supybot's configuration variables are organized in a hierarchy: variables having to do with the way Supybot makes replies all start with `supybot.reply`; variables having to do with the way a plugin works all start with `supybot.plugins.Plugin` (where 'Plugin' is the name of the plugin in question). This hierarchy is nice because it means the user isn't inundated with hundreds of unrelated and unsorted configuration variables. Some of the more important configuration values are located directly under the base group, `supybot`. Things like the bot's nick, its ident, etc. Along with these config values are a few subgroups that contain other values. Some of the more prominent subgroups are: `plugins` (where all the plugin-specific configuration is held), `reply` (where variables affecting the way a Supybot makes its replies resides), `replies` (where all the specific standard replies are kept), and `directories` (where all the directories a Supybot uses are defined). There are other subgroups as well, but these are the ones we'll use in our example. Configuration Groups -------------------- Using the `Config` plugin, you can list values in a subgroup and get or set any of the values anywhere in the configuration hierarchy. For example, let's say you wanted to see what configuration values were under the `supybot` (the base group) hierarchy. You would simply issue this command:: @config list supybot jemfinch|lambda: @abuse, @capabilities, @commands, @databases, @debug, @directories, @drivers, @log, @networks, @nick, @plugins, @protocols, @replies, @reply, alwaysJoinOnInvite, channels, defaultIgnore, defaultSocketTimeout, externalIP, flush, followIdentificationThroughNickChanges, ident, pidFile, snarfThrottle, upkeepInterval, and user These are all the configuration groups and values which are under the base `supybot` group. Actually, their full names would each have a 'supybot.' prepended to them, but it is omitted in the listing in order to shorten the output. The first entries in the output are the groups (distinguished by the '@' symbol in front of them), and the rest are the configuration values. The '@' symbol (like the '#' symbol we'll discuss later) is simply a visual cue and is not actually part of the name. Configuration Values -------------------- Okay, now that you've used the Config plugin to list configuration variables, it's time that we start looking at individual variables and their values. The first (and perhaps most important) thing you should know about each configuration variable is that they all have an associated help string to tell you what they represent. So the first command we'll cover is ``config help``. To see the help string for any value or group, simply use the ``config help`` command. For example, to see what this `supybot.snarfThrottle` configuration variable is all about, we'd do this:: @config help supybot.snarfThrottle jemfinch|lambda: A floating point number of seconds to throttle snarfed URLs, in order to prevent loops between two bots snarfing the same URLs and having the snarfed URL in the output of the snarf message. (Current value: 10.0) Pretty simply, eh? Now if you're curious what the current value of a configuration variable is, you'll use the ``config`` command with one argument, the name of the variable you want to see the value of:: @config supybot.reply.whenAddressedBy.chars jemfinch|lambda: '@' To set this value, just stick an extra argument after the name:: @config supybot.reply.whenAddressedBy.chars @$ jemfinch|lambda: The operation succeeded. Now check this out:: $config supybot.reply.whenAddressedBy.chars jemfinch|lambda: '@$' Note that we used '$' as our prefix character, and that the value of the configuration variable changed. If I were to use the ``flush`` command now, this change would be flushed to the registry file on disk (this would also happen if I made the bot quit, or pressed Ctrl-C in the terminal which the bot was running). Instead, I'll revert the change:: $config supybot.reply.whenAddressedBy.chars @ jemfinch|lambda: The operation succeeded. $note that this makes no response. Default Values -------------- If you're ever curious what the default for a given configuration variable is, use the ``config default`` command:: @config default supybot.reply.whenAddressedBy.chars jemfinch|lambda: '' Thus, to reset a configuration variable to its default value, you can simply say:: @config supybot.reply.whenAddressedBy.chars [config default supybot.reply.whenAddressedBy.chars] jemfinch|lambda: The operation succeeded. @note that this does nothing Simple, eh? Searching the Registry ---------------------- Now, let's say you want to find all configuration variables that might be even remotely related to opping. For that, you'll want the ``config search`` command. Check this out:: @config search op jemfinch|lambda: supybot.plugins.Enforcer.autoOp, supybot.plugins.Enforcer.autoHalfop, supybot.plugins.Enforcer.takeRevenge.onOps, supybot.plugins.Enforcer.cycleToGetOps, supybot.plugins.Topic, supybot.plugins.Topic.public, supybot.plugins.Topic.separator, supybot.plugins.Topic.format, supybot.plugins.Topic.recognizeTopiclen, supybot.plugins.Topic.default, supybot.plugins.Topic.undo.max, supybot.plugins.Relay.topicSync Sure, it showed all the topic-related stuff in there, but it also showed you all the op-related stuff, too. Do note, however, that you can only see configuration variables for plugins that are currently loaded or that you loaded in the past; if you've never loaded a plugin there's no way for the bot to know what configuration variables it registers. Channel-Specific Configuration ------------------------------ Many configuration variables can be specific to individual channels. The `Config` plugin provides an easy way to configure something for a specific channel; for instance, in order to set the prefix chars for a specific channel, do this in that channel:: @config channel supybot.reply.whenAddressedBy.chars ! jemfinch|lambda: The operation succeeded. That'll set the prefix chars in the channel from which the message was sent to '!'. Voila, channel-specific values! Also, note that when using the `Config` plugin's ``list`` command, channel-specific values are preceeded by a '#' character to indicate such (similar to how '@' is used to indicate a group of values). Editing the Configuration Values by Hand ---------------------------------------- Some people might like editing their registry file directly rather than manipulating all these things through the bot. For those people, we offer the ``config reload`` command, which reloads both registry configuration and user/channel/ignore database configuration. Just edit the interesting files and then give the bot the ``config reload`` command and it'll work as expected. Do note, however, that Supybot flushes his configuration files and database to disk every hour or so, and if this happens after you've edited your configuration files but before you reload your changes, you could lose the changes you made. To prevent this, set the `supybot.flush` value to 'Off' while editing the files, and no automatic flushing will occur. supybot-0.83.4.1.ds.orig/docs/ADVANCED_PLUGIN_CONFIG0000644000000000000000000003747511206611405016043 0ustar Advanced Plugin Config ---------------------- This tutorial covers some of the more advanced plugin config features available to Supybot plugin authors. What's This Tutorial For? ========================= Brief overview of what this tutorial covers and the target audience. Want to know the crazy advanced features available to you, the Supybot plugin author? Well, this is the tutorial for you. This article assumes you've read the Supybot plugin author tutorial since all the basics of plugin config are handled there first. In this tutorial we'll cover: * Using the configure function more effectively by using the functions provided in supybot.questions * Creating config variable groups and config variables underneath those groups. * The built-in config variable types ("registry types") for use with config variables * Creating custom registry types to handle config variable values more effectively Using 'configure' effectively ============================= How to use 'configure' effectively using the functions from 'supybot.questions' In the original Supybot plugin author tutorial you'll note that we gloss over the configure portion of the config.py file for the sake of keeping the tutorial to a reasonable length. Well, now we're going to cover it in more detail. The supybot.questions module is a nice little module coded specifically to help clean up the configure section of every plugin's config.py. The boilerplate config.py code imports the four most useful functions from that module: * "expect" is a very general prompting mechanism which can specify certain inputs that it will accept and also specify a default response. It takes the following arguments: - prompt: The text to be displayed - possibilities: The list of possible responses (can be the empty list, []) - default (optional): Defaults to None. Specifies the default value to use if the user enters in no input. - acceptEmpty (optional): Defaults to False. Specifies whether or not to accept no input as an answer. * "anything" is basically a special case of except which takes anything (including no input) and has no default value specified. It takes only one argument: - prompt: The text to be displayed * "something" is also a special case of except, requiring some input and allowing an optional default. It takes the following arguments: - prompt: The text to be displayed - default (optional): Defaults to None. The default value to use if the user doesn't input anything. * "yn" is for "yes or no" questions and basically forces the user to input a "y" for yes, or "n" for no. It takes the following arguments: - prompt: The text to be displayed - default (optional): Defaults to None. Default value to use if the user doesn't put anything. All of these functions, with the exception of "yn", return whatever string results as the answer whether it be input from the user or specified as the default when the user inputs nothing. The "yn" function returns True for "yes" answers and False for "no" answers. For the most part, the latter three should be sufficient, but we expose expect to anyone who needs a more specialized configuration. Let's go through a quick example configure that covers all four of these functions. First I'll give you the code, and then we'll go through it, discussing each usage of a supybot.questions function just to make sure you realize what the code is actually doing. Here it is: 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 WorldDom = conf.registerPlugin('WorldDom', True) if yn("""The WorldDom plugin allows for total world domination with simple commands. Would you like these commands to be enabled for everyone?""", default=False): WorldDom.globalWorldDominationRequires.setValue("") else: cap = something("""What capability would you like to require for this command to be used?""", default="Admin") WorldDom.globalWorldDominationRequires.setValue(cap) dir = expect("""What direction would you like to attack from in your quest for world domination?""", ["north", "south", "east", "west", "ABOVE"], default="ABOVE") WorldDom.attackDirection.setValue(dir) As you can see, this is the WorldDom plugin, which I am currently working on. The first thing our configure function checks is to see whether or not the bot owner would like the world domination commands in this plugin to be available to everyone. If they say yes, we set the globalWorldDominationRequires configuration variable to the empty string, signifying that no specific capabilities are necessary. If they say no, we prompt them for a specific capability to check for, defaulting to the "Admin" capability. Here they can create their own custom capability to grant to folks which this plugin will check for if they want, but luckily for the bot owner they don't really have to do this since Supybot's capabilities system can be flexed to take care of this. Lastly, we check to find out what direction they want to attack from as they venture towards world domination. I prefer "death from above!", so I made that the default response, but the more boring cardinal directions are available as choices as well. Using Config Groups =================== A brief overview of how to use config groups to organize config variables Supybot's Hierarchical Configuration Supybot's configuration is inherently hierarchical, as you've probably already figured out in your use of the bot. Naturally, it makes sense to allow plugin authors to create their own hierarchies to organize their configuration variables for plugins that have a lot of plugin options. If you've taken a look at the plugins that Supybot comes with, you've probably noticed that several of them take advantage of this. In this section of this tutorial we'll go over how to make your own config hierarchy for your plugin. Here's the brilliant part about Supybot config values which makes hierarchical structuring all that much easier - values are groups. That is, any config value you may already defined in your plugins can already be treated as a group, you simply need to know how to add items to that group. Now, if you want to just create a group that doesn't have an inherent value you can do that as well, but you'd be surprised at how rarely you have to do that. In fact if you look at most of the plugins that Supybot comes with, you'll only find that we do this in a handful of spots yet we use the "values as groups" feature quite a bit. Creating a Config Group ======================= As stated before, config variables themselves are groups, so you can create a group simply by creating a configuration variable: conf.registerGlobalValue(WorldDom, 'globalWorldDominationRequires', registry.String('', """Determines the capability required to access the world domination commands in this plugin.""")) As you probably know by now this creates the config variable supybot.plugins.WorldDom.globalWorldDominationRequires which you can access/set using the Config plugin directly on the running bot. What you may not have known prior to this tutorial is that that variable is also a group. Specifically, it is now the WorldDom.globalWorldDominationRequires group, and we can add config variables to it! Unfortunately, this particular bit of configuration doesn't really require anything underneath it, so let's create a new group which does using the "create only a group, not a value" command. Let's create a configurable list of targets for different types of attacks (land, sea, air, etc.). We'll call the group attackTargets. Here's how you create just a config group alone with no value assigned: conf.registerGroup(WorldDom, 'attackTargets') The first argument is just the group under which you want to create your new group (and we got WorldDom from conf.registerPlugin which was in our boilerplate code from the plugin creation wizard). The second argument is, of course, the group name. So now we have WorldDom.attackTargets (or, fully, supybot.plugins.WorldDom.attackTargets). Adding Values to a Group ======================== Actually, you've already done this several times, just never to a custom group of your own. You've always added config values to your plugin's config group. With that in mind, the only slight modification needed is to simply point to the new group: conf.registerGlobalValue(WorldDom.attackTargets, 'air', registry.SpaceSeparatedListOfStrings('', """Contains the list of air targets.""")) And now we have a nice list of air targets! You'll notice that the first argument is WorldDom.attackTargets, our new group. Make sure that the conf.registerGroup call is made before this one or else you'll get a nasty AttributeError. The Built-in Registry Types =========================== A rundown of all of the built-in registry types available for use with config variables. The "registry" module defines the following config variable types for your use (I'll include the 'registry.' on each one since that's how you'll refer to it in code most often). Most of them are fairly self-explanatory, so excuse the boring descriptions: * registry.Boolean - A simple true or false value. Also accepts the following for true: "true", "on" "enable", "enabled", "1", and the following for false: "false", "off", "disable", "disabled", "0", * registry.Integer - Accepts any integer value, positive or negative. * registry.NonNegativeInteger - Will hold any non-negative integer value. * registry.PositiveInteger - Same as above, except that it doesn't accept 0 as a value. * registry.Float - Accepts any floating point number. * registry.PositiveFloat - Accepts any positive floating point number. * registry.Probability - Accepts any floating point number between 0 and 1 (inclusive, meaning 0 and 1 are also valid). * registry.String - Accepts any string that is not a valid Python command * registry.NormalizedString - Accepts any string (with the same exception above) but will normalize sequential whitespace to a single space.. * registry.StringSurroundedBySpaces - Accepts any string but assures that it has a space preceding and following it. Useful for configuring a string that goes in the middle of a response. * registry.StringWithSpaceOnRight - Also accepts any string but assures that it has a space after it. Useful for configuring a string that begins a response. * registry.Regexp - Accepts only valid (Perl or Python) regular expressions * registry.SpaceSeparatedListOfStrings - Accepts a space-separated list of strings. There are a few other built-in registry types that are available but are not usable in their current state, only by creating custom registry types, which we'll go over in the next section. Custom Registry Types ===================== How to create and use your own custom registry types for use in customizing plugin config variables. Why Create Custom Registry Types? For most configuration, the provided types in the registry module are sufficient. However, for some configuration variables it's not only convenient to use custom registry types, it's actually recommended. Customizing registry types allows for tighter restrictions on the values that get set and for greater error-checking than is possible with the provided types. What Defines a Registry Type? First and foremost, it needs to subclass one of the existing registry types from the registry module, whether it be one of the ones in the previous section or one of the other classes in registry specifically designed to be subclassed. Also it defines a number of other nice things: a custom error message for your type, customized value-setting (transforming the data you get into something else if wanted), etc. Creating Your First Custom Registry Type As stated above, priority number one is that you subclass one of the types in the registry module. Basically, you just subclass one of those and then customize whatever you want. Then you can use it all you want in your own plugins. We'll do a quick example to demonstrate. We already have registry.Integer and registry.PositiveInteger, but let's say we want to accept only negative integers. We can create our own NegativeInteger registry type like so: class NegativeInteger(registry.Integer): """Value must be a negative integer.""" def setValue(self, v): if v >= 0: self.error() registry.Integer.setValue(self, v) All we need to do is define a new error message for our custom registry type (specified by the docstring for the class), and customize the setValue function. Note that all you have to do when you want to signify that you've gotten an invalid value is to call self.error(). Finally, we call the parent class's setValue to actually set the value. What Else Can I Customize? Well, the error string and the setValue function are the most useful things that are available for customization, but there are other things. For examples, look at the actual built-in registry types defined in registry.py (in the src directory distributed with the bot). What Subclasses Can I Use? Chances are one of the built-in types in the previous section will be sufficient, but there are a few others of note which deserve mention: * registry.Value - Provides all the core functionality of registry types (including acting as a group for other config variables to reside underneath), but nothing more. * registry.OnlySomeStrings - Allows you to specify only a certain set of strings as valid values. Simply override validStrings in the inheriting class and you're ready to go. * registry.SeparatedListOf - The generic class which is the parent class to registry.SpaceSeparatedListOfStrings. Allows you to customize four things: the type of sequence it is (list, set, tuple, etc.), what each item must be (String, Boolean, etc.), what separates each item in the sequence (using custom splitter/joiner functions), and whether or not the sequence is to be sorted. Look at the definitions of registry.SpaceSeparatedListOfStrings and registry.CommaSeparatedListOfStrings at the bottom of registry.py for more information. Also, there will be an example using this in the section below. Using My Custom Registry Type Using your new registry type is relatively straightforward. Instead of using whatever registry built-in you might have used before, now use your own custom class. Let's say we define a registry type to handle a comma-separated list of probabilities: class CommaSeparatedListOfProbabilities(registry.SeparatedListOf): Value = registry.Probability def splitter(self, s): return re.split(r'\s*,\s*', s) joiner = ', '.join Now, to use that type we simply have to specify it whenever we create a config variable using it: conf.registerGlobalValue(SomePlugin, 'someConfVar', CommaSeparatedListOfProbabilities('0.0, 1.0', """Holds the list of probabilities for whatever.""")) Note that we initialize it just the same as we do any other registry type, with two arguments: the default value, and then the description of the config variable. supybot-0.83.4.1.ds.orig/docs/man/0000755000000000000000000000000011206611405013403 5ustar supybot-0.83.4.1.ds.orig/docs/man/supybot-adduser.10000644000000000000000000000223211206611405016616 0ustar .\" Process this file with .\" groff -man -Tascii supybot-adduser.1 .\" .TH SUPYBOT-ADDUSER 1 "APRIL 2005" .SH NAME supybot-adduser \- Adds a user to a Supybot users.conf file .SH SYNOPSIS .B supybot-adduser .RI [ options ] " users.conf .SH DESCRIPTION .B supybot-adduser adds a user to the specified users.conf file. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-u " NAME" "\fR,\fP \-\^\-username=" NAME Specifies the username to use for the new user. .TP .BR \-p " PASSWORD" "\fR,\fP \-\^\-password=" PASSWORD Specifies the password to use for the new user. .TP .BR \-c " CAPABILITY" "\fR,\fP \-\^\-capability=" CAPABILITY Capability the user should have; this option may be given multiple times. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/docs/man/supybot-test.10000644000000000000000000000271011206611405016147 0ustar .\" Process this file with .\" groff -man -Tascii supybot-test.1 .\" .TH SUPYBOT-TEST 1 "OCTOBER 2005" .SH NAME supybot-test \- Runs the test suite for a Supybot plugin .SH SYNOPSIS .B supybot-test .RI [ options ] " plugins .SH DESCRIPTION .B supybot-test Runs the test suite for a Supybot plugin .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-c ", " \-\^\-clean Cleans the various data/conf/logs directories before running tests. .TP .BR \-t " TIMEOUT" "\fR,\fP \-\^\-timeout=" TIMEOUT Specifies the timeout for tests to return responses. .TP .BR \-v ", " \-\^\-verbose Sets the verbose flag, logging extra information about each test that runs. .TP .BR \-\^\-no\-network Prevents the network-based tests from being run. .TP .BR \-\^\-trace Traces all calls made. Unless you're really in a pinch, you probably shouldn't do this; it results in copious amounts of output. .TP .BR "\fR,\fP \-\^\-plugins\-dir=" PLUGINSDIR Looks in the given directory for plugins and loads the tests for all of them. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/docs/man/supybot-plugin-doc.10000644000000000000000000000251711206611405017236 0ustar .\" Process this file with .\" groff -man -Tascii supybot-plugin-doc.1 .\" .TH SUPYBOT-PLUGIN-DOC 1 "May 2009" .SH NAME supybot-plugin-doc \- Generates the documentation for a Supybot plugin .SH SYNOPSIS .B supybot-plugin-doc .RI [ options ] .SH DESCRIPTION .B supybot-plugin-doc is used to generate documentation (StructuredText or reStructuredText format) for a .IR supybot (1) plugin. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-c ", " \-\^\-clean Clean the various data/conf/log directories after generating the docs. .TP .BR \-o ", " \-\^\-output\-dir= \fIOUTPUTDIR Specifies the directory in which to write the documentation for the plugin. .TP .BR \-f ", " \-\^\-format= \fIFORMAT Specifies which output format to use. Choices are 'rst' or 'stx'. .TP .BI \-\^\-plugins\-dir= PLUGINSDIRS Looks in the given directory for plugins and generates documentation for all of them. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/docs/man/supybot.10000644000000000000000000000364211206611405015177 0ustar .\" Process this file with .\" groff -man -Tascii supybot.1 .\" .TH SUPYBOT 1 "APRIL 2005" .SH NAME supybot \- A robust and user friendly Python IRC bot .SH SYNOPSIS .B supybot .RI [ options ] " configFile .SH DESCRIPTION .B Supybot is a robust, user-friendly, and programmer-friendly Python IRC bot. It aims to be an adequate replacement for most existing IRC bots. It includes a very flexible and powerful ACL system for controlling access to commands, as well as more than 50 builtin plugins providing around 400 actual commands. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-P ", " \-\^\-profile Enable profiling. .TP .BI \-n " NICK" "\fR,\fP \-\^\-nick=" NICK Nick the bot should use. .TP .BI \-u " USER" "\fR,\fP \-\^\-user=" USER Full username the bot should use. .TP .BI \-i " IDENT" "\fR,\fP \-\^\-ident=" IDENT Ident the bot should use. .TP .BR \-d ", " \-\^\-daemon Determines whether the bot will daemonize. This is a no-op on non-POSIX systems. .TP .B \-\^\-allow\-default\-owner Determines whether the bot will allow its defaultCapabilities not to include "-owner", thus giving all users the owner capability by default. This is dumb, hence we require a command-line option to enable it. .TP .B \-\^\-allow\-root Determines whether the bot will be allowed to run as root. You do not want this. Do not do it. Even if you think you want it, you do not. .TP .B \-\^\-debug Determines whether some extra debugging stuff will be logged by this script. .SH "SEE ALSO" .IR python (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/docs/man/supybot-wizard.10000644000000000000000000000222411206611405016470 0ustar .\" Process this file with .\" groff -man -Tascii supybot-wizard.1 .\" .TH SUPYBOT-WIZARD 1 "SEPTEMBER 2004" .SH NAME supybot-wizard \- A wizard for creating Supybot configuration files .SH SYNOPSIS .B supybot-wizard .RI [ options ] .SH DESCRIPTION .B supybot-wizard is an in-depth wizard that provides a nice user interface for creating configuration files for .IR supybot (1). .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .B \-\^\-allow\-root Determines whether the wizard will be allowed to run as root. You do not want this. Do not do it. Even if you think you want it, you do not. .TP .B \-\^\-no\-network Determines whether the wizard will be allowed to run without a network connection. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/docs/man/supybot-botchk.10000644000000000000000000000266711206611405016455 0ustar .\" Process this file with .\" groff -man -Tascii supybot-botchk.1 .\" .TH SUPYBOT-BOTCHK 1 "APRIL 2005" .SH NAME supybot-botchk \- A script to start Supybot if it's not already running. .SH SYNOPSIS .B supybot-botchk .RI [ options ] .SH DESCRIPTION .B supybot-botchk is a script that will start Supybot if it detects that one is not currently running. This can be useful for scheduling .IR supybot (1) to run via .IR cron (8). .SH OPTIONS .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BR \-v ", " \-\^\-verbose Use verbose output when running the script. .TP .BI \-\^\-botdir= BOTDIR Determines which directory the bot be started in. .TP .BI \-\^\-pidfile= PIDFILE Specifies the name of the pidfile to look for. This should be relative to the given botdir. .TP .BI \-\^\-supybot= SUPYBOT Specifies the location of .IR supybot (1). If this is not given, it is assumed that .IR supybot (1) is in the user's $PATH. .TP .BI \-\^\-conffile= CONFFILE Specifies the path to the bot's configuration file. This will be used when (re)starting the bot. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1), .IR supybot-plugin-create (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/docs/man/supybot-plugin-create.10000644000000000000000000000212211206611405017724 0ustar .\" Process this file with .\" groff -man -Tascii supybot-plugin-create.1 .\" .TH SUPYBOT-PLUGIN-CREATE 1 "APRIL 2005" .SH NAME supybot-plugin-create \- A wizard for creating Supybot plugins .SH SYNOPSIS .B supybot-plugin-create .RI [ options ] .SH DESCRIPTION .B supybot-plugin-create is a wizard that creates a template python source file for a new .IR supybot (1) plugin. .SH OPTIONS .TP .B \-\^\-version Show version of program. .TP .BR \-h ", " \-\^\-help Show summary of options. .TP .BI \-n " NAME" "\fR,\fP \-\^\-name=" NAME Sets the name for the plugin. .TP .BR \-t ", " \-\^\-thread Makes the plugin threaded. .TP .BI \-\^\-real\-name= REALNAME Specify what real name the copyright is assigned to. .SH "SEE ALSO" .IR python (1), .IR supybot (1), .IR supybot-test (1), .IR supybot-botchk (1), .IR supybot-wizard (1), .IR supybot-adduser (1), .IR supybot-plugin-doc (1) .SH AUTHOR This manual page was originally written by James Vega . Permission is granted to copy, distribute and/or modify this document under the terms of the Supybot license, a BSD-style license. supybot-0.83.4.1.ds.orig/RELNOTES0000644000000000000000000003706511206611405013071 0ustar Version 0.83.4.1 Simple bug-fix release for a couple changes that were introduced last minute before the previous release. No incompatibilities. Version 0.83.4 This release contains fixes for Python2.6 compability as well as a re-written Google plugin which uses the AJAX API. The re-written plugin gained a translate command and no longer needs an API key. ChannelLogger has a new config variable, supybot.plugins.ChannelLogger.enable, which can be used on a per-channel basis to determine whether that channel is logged. The RSS announce command has been restructured into the commands "announce list", "announce remove", and "announce add" in order to simplify the command's use. The supybot.directories.plugins config variable now determines its global directory dynamically instead of adding the directory to the value. This means values like '/usr/lib/python2.5/site-packages/supybot/plugins' can be removed from the variable. This should help ease transitioning a Supybot config from one Python release to another. Incompatibilities: supybot.plugins.Google.safeSearch has been renamed to supybot.plugins.Google.searchFilter supybot.plugins.Channel.banmask has been removed in favor of a new supybot.protocols.irc.banmask config variable. This general config variable is used by the various commands which would need to know what style of banmask to use. Version 0.83.3 Overdue bug fix and Python2.5-compatible release. No significant changes to worry about from the user perspective. Version 0.83.2 Mainly bug fix release. The most noticeable change being a value of 'default' for supybot.drivers.module will use Socket instead of Twisted. Version 0.83.1 No incompatibilities, just fixing an important bug with plugin loading. Version 0.83.0 This far overdue release contains mostly bug-fixes. Incompatibilities: Changed the prefixName keyword argument (which appears in various places in callbacks.py and the reply-related irc methods) to prefixNick. Version 0.83.0rc3 This release candidate contains mostly bug-fixes. Incompatibilities: utils.changeFunctionName was moved to utils.python.changeFunctionName Version 0.83.0rc2 This release candidate contains a few bug-fixes and some plugins we forgot in the last RC. Otherwise, everything is compatible. Version 0.83.0rc1 There have been some fairly significant changes since our last release. This means that you should uninstall your previous version before installing this version. First, plugins are now a directory of files rather than a single file. This makes it much easier for an individual plugin to supply any 3rd-party modules it may depend on and resolves some bugs we had with reloading plugins. supybot-plugin-create (nee supybot-newplugin) has been updated to reflect this. A side effect of using a directory-based plugin is that @load/@reload are now case-sensitive. "@load foo" is not the same as "@load Foo". As part of the conversion to the new plugin format, some plugins were broken up into more focused plugins. Also, not all of the plugins that we used to ship are part of this release. Some we moved to the supybot-plugins package and some others (which would be candidates for supybot-plugins) have yet to be converted to the new format. Second, we've updated the scripts that ship with Supybot. As noted in the previous section, supybot-newplugin is now named supybot-plugin-create. We've also added the following scripts: supybot-botchk - Handy script for starting the bot and keeping it running. Ideal for having cron automatically start the bot. supybot-plugin-doc - Generates documentation for the specified plugin(s). Currently, the documentation is generated using Structured TeXt so that it can easily be uploaded to our website. supybot-plugin-package - The beginning of a script to make a plugin package which can be uploaded to our website for general consumption. supybot-test - Runs a plugin's test suite. Third, we've broken supybot.utils into focused sub-modules. There's no longer a supybot.fix module and we now have the following modules: supybot.utils.file - utilities for dealing with files (e.g. the old supybot.utils.transactionalFile is now supybot.utils.file.AtomicFile) supybot.utils.iter - utilities for dealing with iterables (all, any, partition, groupBy, choice, etc) supybot.utils.gen - general purpose utilities which are imported into the supybot.utils namespace supybot.utils.net - utilities for dealing with the network supybot.utils.python - utilities for dealing with Python supybot.utils.seq - utilities for dealing with sequences supybot.utils.str - utilities for dealing with strings (including our new format() function) supybot.utils.structures - general purpose structures used in Supybot supybot.utils.web - utilities for dealing with the web (used to be supybot.webutils) Fourth, we've added source-nested plugins (using the class callbacks.Commands). This allows you to group similar commands together. Some examples are: Channel.{ban add,ban list,ban remove} User.{hostmask add,hostmask list,hostmask remove} Fifth, we've removed the privmsgs module. All of the functionality that was offered in that module is now available by using commands.wrap. Use of this is documented at: http://supybot.com/documentation/help/tutorial/wrap Sixth, we've renamed some plugin-related API changes. Some classes had their names changed. The old names are still available for backwards-compatibility. callbacks.Privmsg -> callbacks.Plugin callbacks.PrivmsgCommandAndRegexp -> callbacks.PluginRegexp callbacks.IrcObjectProxy -> callbacks.NestedCommandsIrcProxy callbacks.PrivmsgRegexp was removed since its functionality is covered by setting using PluginRegexp. Also, the prototype for a plugin's __init__ method changed: def __init__(self): -> def __init__(self, irc): Remember to pass the irc object on when you call the parent class's __init__ method. Version 0.80.0 We *finally* hit 0.80.0! This release is completely compatible with the last release candidate. An update to Babelfish may cause an error message to be displayed in the console when the bot is first run. The error message should be gone when the bot is restarted. We also have a new community website at http://www.supybot.com/ where our users can submit their own plugins, view/download other people's plugins and discuss all things Supybot-related. Version 0.80.0rc3 Another bugfix release. This one was pretty important as it actually makes supybot.database.plugins.channelSpecific work properly. Version 0.80.0rc2 supybot.databases.plugins.channelSpecific.channel was renamed to supybot.databases.plugins.channelSpecific.link. supybot.databases.plugins.channelSpecific.link.allow was added, which determines whether a channel will allow other channels to link to its database. Infobot is no longer deprecated and the following changes were made to its config variables: supybot.plugins.Infobot.answerUnaddressedQuestions was renamed to supybot.plugins.Infobot.unaddressed.answerQuestions. supybot.plugins.Infobot.snarfUnaddressedDefinitions was renamed to supybot.plugins.Infobot.unaddressed.snarfDefinitions. supybot.plugins.Infobot.unaddressed.replyExistingFactoid was added to determine whether the bot will reply when someone attempts to create a duplicate factoid. Version 0.80.0pre6 Another bugfix release. No incompatibilities known. The only registry change is that supybot.databases.users.hash has been removed. Version 0.80.0pre5 Completely bugfix release. No incompatibilies known. Version 0.80.0pre4 Mainly a bug fix release. This will likely be the last release before 0.80.0final, but we're gonna let it stew for a couple weeks to attempt to catch any lingering bugs. ansycoreDrivers is now deprecated in favor of socketDrivers or twistedDrivers. supybot.databases.plugins.channelSpecific.channel is now a channelValue so that you can link specific channels together (instead of all channels being linked together). For those of you that use eval and/or exec, they have been removed from the Owner plugin and are now in sandbox/Debug.py (which you'll have to grab from CVS). Version 0.80.0pre3 The database format for the Note plugin has changed to a flatfile format; use tools/noteConvert.py to convert it to the new format. Ditto that for the URL database. FunDB is deprecated and will be removed at the next major release; use tools/fundbConvert.py to convert your old FunDB databases to Lart and Praise databases. If you had turned off supybot.databases.plugins.channelSpecific, your non-channel-specific database files had gone directly into your data/ directory. We had some problems with poor interactions between that configuration variable and channel capabilities, though, so we changed the implementation so that non-channel-specific databases are considered databases of a single (configurable) channel (defaulting to "#"). This will also help others who are converting from channel-specific to non-channel-specific databases, but for you who've already made the switch, you'll need to move your database files again, from data/ to data/# (or whatever channel you might change that variable to). supybot.channels doesn't exist anymore; now the only list of channels to join is per-network, in supybot.networks..channels. We weren't serializing supybot.replies.* properly in older versions. Now we are, but the old, improperly serialized versions won't work properly. Remove from your configuration file all variables beginning with "supybot.replies" before you start the bot. The URL database has been changed again, but it will use a different filename so you shouldn't run into conflicts, just a newly-empty database. We upgraded the SOAP stuff in others; you may do well to do a setup.py install --clean this time around. Version 0.80.0pre2 Many more bugs have been fixed. A few more plugins have been updated to use our new-style database abstraction. If it seems like your databases are suddenly empty, look for a new database file named Plugin.dbtype.db. We've also added a few more configuration variables. Version 0.80.0pre1 Tons of bugs fixed, many features and plugins added. Everything should be entirely compatible; many more configuration variables have been added. Version 0.79.9999 Some more bugs fixed, added a few features and a couple configuration variabless. This should hopefully be the last release before 0.80.0, which will finally bring us to pure Beta status. Version 0.79.999 Some bugs fixed, but the ones that were fixed were pretty big. This is, of course, completely compatible with the last release. Version 0.79.99 Many bugs fixed, thanks to the users who reported them. We're getting asymptotically closer to 0.80.0 -- maybe this'll be the last one, maybe we'll have to release an 0.79.999 -- either way, we're getting close :) Check out the ChangeLog for the fixes and a few new features. Version 0.79.9 We've changed so much stuff in this release that we've given up on users upgrading their configuration files for the new release. So do a clean install (python2.3 setup.py install --clean), run the wizard again, and kick some butt. (It's rumored that you can save most of your old configuration by appending your new configuration at the end of your old configuration and running supybot with that new configuration file. This, of course, comes with no warranty or guarantee of utility -- try it if you want, but backup your original configuration file!) Version 0.77.2 This is a drop-in replacement for 0.77.1, with two exceptions. The configuration variable formerly known as "supybot.plugins.Services.password" is now known as "supybot.plugins.Services.NickServ.password", due to the fact that there might be different passwords for NickServ and ChanServ (and ChanServ passwords are per-channel, whereas NickServ passwords are global). If you're using the Services plugin, you'll need to make this change in order to continue identifying with services. The configuration variable formerly known as "supybot.plugins.Babelfish.disabledLanguages" is now known as "supybot.plugins.Babelfish.languages". The configuration variable now accepts the languages that *will* be translated as opposed to ones that are *not* translated. Tests and the developer sandbox are not longer delivered with our release tarballs. If you're a developer and you want these, you should either check out CVS or download one of our weekly CVS snapshots, available at http://supybot.sourceforge.net/snapshots/ . Version 0.77.1 This is a drop-in replacement for 0.77.0 -- no incompatibilities, to out knowledge. Simply install over your old installation and restart your bot :) Version 0.77.0 Setup.py will automatically remove your old installations for you, no need to worry about that yourself. Configuration has been *entirely* redone. Read the new GETTING_STARTED document to see how to work with configuration variables now. Your old botscripts from earlier versions *will not* work with the new configuration method. We'd appreciate it if you'd rerun the wizard in order for us to find any bugs that remain in it before we officially declare ourselves Beta. Note also that because of the new configuration method, the interface for plugins' configure function has changed: there are no longer any onStart or afterConnect arguments, so all configuration should be performed via the registry. Channel capabilities have been changed; rather than being #channel.capability, they're now #channel,capability. It's a bit uglier, we know, but dots can be valid in channel names, and we needed the dot for handling plugin.command capabilities. tools/ircdbConvert.py should update this for you. The on-disk format of the user/channel databases has changed to be far more readable. A conversion utility is included, as mentioned before: tools/ircdbConvert.py. Run this with no arguments to see the directions for using it. Uh, we were just kidding about the upgrade script in 0.76.0 :) It'll be a little while longer. We do have several little upgrade scripts, though. Version 0.76.1 Almost entirely bugfixes, just some minor (and some less minor) bugs that need to get in before we really start hacking on the next version. Should be *entirely* compatible with 0.76.0. Version 0.76.0 Major bugfix release. A great number of bugs fixed. This is the last release without an upgrade script. The only hiccup in the upgrade from 0.75.0 should be that you'll need to update your botscript to reflect the removal of the debug module. We'd rather you use supybot-wizard to generate a new botscript, of course, but if you insist on modifying your existing botscript, take a look at to see what you need to do. Version 0.75.0 Don't forget to reinstall (i.e., run "python setup.py install" as root). Sometimes it even does good to remove the old installation; $PYTHON/site-packages/supybot can be removed with no problems whatsoever. You will need to re-run supybot-wizard and generate a new botscript. The Infobot plugin has been removed from this release; it's not ready for prime time. If you're interested in getting it running (i.e., you want full Infobot compatibility and aren't satisfied with either MoobotFactoids or Factoids) then swing over to #supybot and we can discuss the tests. We simply don't know enough about Infobot to make sure our Infobot plugin is an exact replica, and need someone's help with making the changes necessary for that. supybot-0.83.4.1.ds.orig/ACKS0000644000000000000000000000064011206611405012344 0ustar * johhnyace, who gave me the modem that helped me tremendously early on in development. * sweede, for hosting the "main" supybot for awhile. * bwp, who rewrote the Http.weather command (ham site), and also hosted the canonical supybot in #supybot on OFTC and Freenode for quite some time. * HostPC.com, for hosting the current canonical "supybot" and for graciously providing DNS services and email. supybot-0.83.4.1.ds.orig/INSTALL0000644000000000000000000000742511206611405012741 0ustar Common First things first: Supybot *requires* at least Python 2.3. There ain't no getting around it. You can get it from http://www.python.org/. Recommended Software PySQLite -- Version 1.x Twisted -- Version 1.2.0 or greater For more information and help on how to use Supybot, checkout the documents under docs/ (especially GETTING_STARTED and CONFIGURATION). So what do you do? That depends on which operating system you're running. We've split this document up to address the different methods, so find the section for your operating system and continue from there. UNIX/Linux/BSD If you're installing Python using your distributor's packages, you may need a python-dev package installed, too. If you don't have a '/usr/lib/python2.x/distutils' directory or '/usr/lib/python2.x/config/Makefile' (assuming '/usr/lib/python2.x' is where your Python libs are installed), then you will need a python-dev package. After you extract Supybot and cd into the supybot directory just created, you'll want to run (as root) 'python setup.py install'. This will install Supybot globally. If you need to install locally for whatever reason, see the notes at the end of this section. You'll then have several new programs installed where Python scripts are normally installed on your system ('/usr/bin' or '/usr/local/bin' are common on UNIX systems). The two that might be of particular interest to you, the new user, are 'supybot' and 'supybot-wizard'. The former, 'supybot', is the script to run an actual bot; the latter, 'supybot-wizard', is an in-depth wizard that provides a nice user interface for creating a registry file for your bot. Local Install You can install Supybot in a local directory by using the '--prefix' option when running 'setup.py'. E.g., 'python setup.py install --prefix=$HOME' to install into your home directory. You'll now have a $HOME/bin directory containing Supybot programs ('supybot', 'supybot-wizard', etc.) and a $HOME/lib directory containing the Supybot libraries. It is also recommended that you setup a proper PYTHONPATH environment variable in your shell's init file. bash -- 'export PYTHONPATH=$HOME/lib/python2.x/site-packages' (t)csh -- 'setenv PYTHONPATH $HOME/lib/python2.x/site-packages' Windows **Note**: If you are using an IPV6 connection, you will not be able to run Supybot under Windows (unless Python has fixed things). Current versions of Python for Windows are *not* built with IPV6 support. This isn't expected to be fixed until Python 2.4, at the earliest. Now that you have Python installed, open up a command prompt. The easiest way to do this is to open the run dialog (Programs -> run) and type "cmd" (for Windows 2000/XP/2003) or "command" (for Windows 9x). In order to reduce the amount of typing you need to do, I suggest adding Python's directory to your path. If you installed Python using the default settings, you would then do the following in the command prompt (otherwise change the path to match your settings):: set PATH=C:\Python2x\;%PATH% You should now be able to type 'python' to start the Python interpreter. Exit by pressing CTRL-Z and then Return. Now that that's setup, you'll want to cd into the directory that was created when you unzipped Supybot; I'll assume you unzipped it to 'C:\Supybot' for these instructions. From 'C:\Supybot', run 'python setup.py install'. This will install Supybot under 'C:\Python2x\'. You will now have several new programs installed in 'C:\Python2x\Scripts\'. The two that might be of particular interest to you, the new user, are 'supybot' and 'supybot-wizard'. The former, 'supybot', is the script to run an actual bot; the latter, 'supybot-wizard', is an in-depth wizard that provides a nice user interface for creating a registry file for your bot. supybot-0.83.4.1.ds.orig/plugins/0000755000000000000000000000000011206611405013361 5ustar supybot-0.83.4.1.ds.orig/plugins/NickCapture/0000755000000000000000000000000011206611405015571 5ustar supybot-0.83.4.1.ds.orig/plugins/NickCapture/test.py0000644000000000000000000000333311206611405017124 0ustar ### # 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 NickCaptureTestCase(PluginTestCase): plugins = ('NickCapture',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/NickCapture/__init__.py0000644000000000000000000000461111206611405017704 0ustar ### # 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. ### """ This module attempts to capture the bot's nick, watching for an opportunity to switch to that nick. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/NickCapture/README.txt0000644000000000000000000000011711206611405017266 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/NickCapture/plugin.py0000644000000000000000000000737211206611405017452 0ustar ### # 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 time import supybot.conf as conf import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks class NickCapture(callbacks.Plugin): """This module constantly tries to take whatever nick is configured as supybot.nick. Just make sure that's set appropriately, and thus plugin will do the rest.""" public = False def __init__(self, irc): self.__parent = super(NickCapture, self) self.__parent.__init__(irc) self.lastIson = 0 def _getNick(self): return conf.supybot.nick() def __call__(self, irc, msg): if irc.afterConnect: nick = self._getNick() if nick and not ircutils.strEqual(nick, irc.nick): # We used to check this, but nicksToHostmasks is never cleared # except on reconnects, which can cause trouble. # if nick not in irc.state.nicksToHostmasks: self._ison(irc, nick) self.__parent.__call__(irc, msg) def _ison(self, irc, nick): if self.registryValue('ison'): now = time.time() if now - self.lastIson > self.registryValue('ison.period'): self.lastIson = now self._sendIson(irc, nick) def _sendIson(self, irc, nick): self.log.info('Checking if %s ISON %s.', nick, irc.network) irc.queueMsg(ircmsgs.ison(nick)) def _sendNick(self, irc, nick): self.log.info('Attempting to switch to nick %s on %s.', nick, irc.network) irc.sendMsg(ircmsgs.nick(nick)) def doQuit(self, irc, msg): nick = self._getNick() if ircutils.strEqual(msg.nick, nick): self._sendNick(irc, nick) def doNick(self, irc, msg): nick = self._getNick() if ircutils.strEqual(msg.nick, nick): self._sendNick(irc, nick) def do303(self, irc, msg): """This is returned by the ISON command.""" if not msg.args[1]: nick = self._getNick() if nick: self._sendNick(irc, nick) Class = NickCapture # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/NickCapture/config.py0000644000000000000000000000500211206611405017405 0ustar ### # 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('NickCapture', True) NickCapture = conf.registerPlugin('NickCapture') conf.registerPlugin('NickCapture') conf.registerGlobalValue(NickCapture, 'ison', registry.Boolean(True, """Determines whether the bot will check occasionally if its preferred nick is in use via the ISON command.""")) conf.registerGlobalValue(NickCapture.ison, 'period', registry.PositiveInteger(600, """Determines how often (in seconds) the bot will check whether its nick ISON.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Nickometer/0000755000000000000000000000000011206611405015461 5ustar supybot-0.83.4.1.ds.orig/plugins/Nickometer/test.py0000644000000000000000000000351611206611405017017 0ustar ### # Copyright (c) 2005, aafshar@gmail.com # 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 NickometerTestCase(PluginTestCase): plugins = ('Nickometer',) def testNickometer(self): self.assertNotError('nickometer') self.assertNotError('nickometer jemfinch') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Nickometer/__init__.py0000644000000000000000000000615611206611405017602 0ustar ### # Copyright (c) 2004, William Robinson. # Derived from work (c) 1998, Adam Spiers # 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. ### ### # This algorithm is almost a direct from a the perl nickometer from # blootbot. Hardly any of the original code has been used, though most of # the comments, I copy-pasted. As a matter of courtesy, the original copyright # message follows: # # # # # Lame-o-Nickometer backend # # # # (c) 1998 Adam Spiers # # # # You may do whatever you want with this code, but give me credit. # # # # $Id: Nickometer.py,v 1.13 2004/10/22 22:19:30 jamessan Exp $ # # ### """ A port of Infobot's nickometer command from Perl. This plugin provides one command (called nickometer) which will tell you how 'lame' an IRC nick is. It's an elitist hacker thing, but quite fun. """ 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.baggins # 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: supybot-0.83.4.1.ds.orig/plugins/Nickometer/README.txt0000644000000000000000000000011711206611405017156 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Nickometer/plugin.py0000644000000000000000000002207311206611405017335 0ustar ### # Copyright (c) 2004, William Robinson. # Derived from work (c) 1998, Adam Spiers # 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. ### ### # This algorithm is almost a direct from a the perl nickometer from # blootbot. Hardly any of the original code has been used, though most of # the comments, I copy-pasted. As a matter of courtesy, the original copyright # message follows: # # # # # Lame-o-Nickometer backend # # # # (c) 1998 Adam Spiers # # # # You may do whatever you want with this code, but give me credit. # # # # $Id: Nickometer.py,v 1.13 2004/10/22 22:19:30 jamessan Exp $ # # ### import supybot import re import math import string import supybot.callbacks as callbacks from supybot.commands import wrap, additional def slowExponent(x): return 1.3 * x * (1 - math.atan(x / 6.0) * 2 / math.pi) def slowPow(x, y): return math.pow(x, slowExponent(y)) def caseShifts(s): s=re.sub('[^a-zA-Z]', '', s) s=re.sub('[A-Z]+', 'U', s) s=re.sub('[a-z]+', 'l', s) return len(s)-1 def numberShifts(s): s=re.sub('[^a-zA-Z0-9]', '', s) s=re.sub('[a-zA-Z]+', 'l', s) s=re.sub('[0-9]+', 'n', s) return len(s)-1 class Nickometer(callbacks.Plugin): def punish(self, damage, reason): self.log.debug('%s lameness points awarded: %s', damage, reason) return damage def nickometer(self, irc, msg, args, nick): """[] Tells you how lame said nick is. If is not given, uses the nick of the person giving the command. """ score = 0L if not nick: nick = msg.nick originalNick = nick if not nick: irc.error('Give me a nick to judge as the argument, please.') return specialCost = [('69', 500), ('dea?th', 500), ('dark', 400), ('n[i1]ght', 300), ('n[i1]te', 500), ('fuck', 500), ('sh[i1]t', 500), ('coo[l1]', 500), ('kew[l1]', 500), ('lame', 500), ('dood', 500), ('dude', 500), ('[l1](oo?|u)[sz]er', 500), ('[l1]eet', 500), ('e[l1]ite', 500), ('[l1]ord', 500), ('pron', 1000), ('warez', 1000), ('xx', 100), ('\\[rkx]0', 1000), ('\\0[rkx]', 1000)] letterNumberTranslator = string.maketrans('023457+8', 'ozeasttb') for special in specialCost: tempNick = nick if special[0][0] != '\\': tempNick = tempNick.translate(letterNumberTranslator) if tempNick and re.search(special[0], tempNick, re.IGNORECASE): score += self.punish(special[1], 'matched special case /%s/' % special[0]) # I don't really know about either of these next two statements, # but they don't seem to do much harm. # Allow Perl referencing nick=re.sub('^\\\\([A-Za-z])', '\1', nick); # C-- ain't so bad either nick=re.sub('^C--$', 'C', nick); # Punish consecutive non-alphas matches=re.findall('[^\w\d]{2,}',nick) for match in matches: score += self.punish(slowPow(10, len(match)), '%s consecutive non-alphas ' % len(match)) # Remove balanced brackets ... while 1: nickInitial = nick nick=re.sub('^([^()]*)(\()(.*)(\))([^()]*)$', '\1\3\5', nick, 1) nick=re.sub('^([^{}]*)(\{)(.*)(\})([^{}]*)$', '\1\3\5', nick, 1) nick=re.sub('^([^[\]]*)(\[)(.*)(\])([^[\]]*)$', '\1\3\5', nick, 1) if nick == nickInitial: break self.log.debug('Removed some matching brackets %r => %r', nickInitial, nick) # ... and punish for unmatched brackets unmatched = re.findall('[][(){}]', nick) if len(unmatched) > 0: score += self.punish(slowPow(10, len(unmatched)), '%s unmatched parentheses' % len(unmatched)) # Punish k3wlt0k k3wlt0k_weights = (5, 5, 2, 5, 2, 3, 1, 2, 2, 2) for i in range(len(k3wlt0k_weights)): hits=re.findall(`i`, nick) if (hits and len(hits)>0): score += self.punish(k3wlt0k_weights[i] * len(hits) * 30, '%s occurrences of %s ' % (len(hits), i)) # An alpha caps is not lame in middle or at end, provided the first # alpha is caps. nickOriginalCase = nick match = re.search('^([^A-Za-z]*[A-Z].*[a-z].*?)[-_]?([A-Z])', nick) if match: nick = ''.join([nick[:match.start(2)], nick[match.start(2)].lower(), nick[match.start(2)+1:]]) match = re.search('^([^A-Za-z]*)([A-Z])([a-z])', nick) if match: nick = ''.join([nick[:match.start(2)], nick[match.start(2):match.end(2)].lower(), nick[match.end(2):]]) # Punish uppercase to lowercase shifts and vice-versa, modulo # exceptions above # the commented line is the equivalent of the original, but i think # they intended my version, otherwise, the first caps alpha will # still be punished #cshifts = caseShifts(nickOriginalCase); cshifts = caseShifts(nick); if cshifts > 1 and re.match('.*[A-Z].*', nick): score += self.punish(slowPow(9, cshifts), '%s case shifts' % cshifts) # Punish lame endings if re.match('.*[XZ][^a-zA-Z]*$', nickOriginalCase): score += self.punish(50, 'the last alphanumeric character was lame') # Punish letter to numeric shifts and vice-versa nshifts = numberShifts(nick); if nshifts > 1: score += self.punish(slowPow(9, nshifts), '%s letter/number shifts' % nshifts) # Punish extraneous caps caps = re.findall('[A-Z]', nick) if caps and len(caps) > 0: score += self.punish(slowPow(7, len(caps)), '%s extraneous caps' % len(caps)) # one trailing underscore is ok. i also added a - for parasite- nick = re.sub('[-_]$','',nick) # Punish anything that's left remains = re.findall('[^a-zA-Z0-9]', nick) if remains and len(remains) > 0: score += self.punish(50*len(remains) + slowPow(9, len(remains)), '%s extraneous symbols' % len(remains)) # Use an appropriate function to map [0, +inf) to [0, 100) percentage = 100 * (1 + math.tanh((score - 400.0) / 400.0)) * \ (1 - 1 / (1 + score / 5.0)) / 2 # if it's above 99.9%, show as many digits as is insteresting score_string=re.sub('(99\\.9*\\d|\\.\\d).*','\\1',`percentage`) irc.reply('The "lame nick-o-meter" reading for "%s" is %s%%.' % (originalNick, score_string)) self.log.debug('Calculated lameness score for %s as %s ' '(raw score was %s)', originalNick, score_string, score) nickometer = wrap(nickometer, [additional('text')]) Class = Nickometer # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Nickometer/config.py0000644000000000000000000000461411206611405017305 0ustar ### # Copyright (c) 2004, William Robinson. # Derived from work (c) 1998, Adam Spiers # 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 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('Nickometer', True) Nickometer = conf.registerPlugin('Nickometer') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Nickometer, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/String/0000755000000000000000000000000011206611405014627 5ustar supybot-0.83.4.1.ds.orig/plugins/String/test.py0000644000000000000000000001436411206611405016170 0ustar ### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 from supybot.test import * import supybot.utils as utils nicks = ['fatjim','scn','moshez','LordVan','MetaCosm','pythong','fishfart', 'alb','d0rt','jemfinch','StyxAlso','fors','deltab','gd', 'hellz_hunter','are_j|pub_comp','jason_','dreid','sayke_','winjer', 'TenOfTen','GoNoVas','queuetue','the|zzz','Hellfried','Therion', 'shro','DaCa','rexec','polin8','r0ky','aaron_','ironfroggy','eugene', 'faassen','tirloni','mackstann','Yhg1s','ElBarono','vegai','shang', 'typo_','kikoforgetme','asqui','TazyTiggy','fab','nixman','liiwi', 'AdamV','paolo','red_one','_AleX_','lament','jamessan','supybot', 'macr0_zzz','plaisthos','redghost','disco','mphardy','gt3','mathie', 'jonez','r0ky-office','tic','d33p','ES3merge','talin','af','flippo', 'sholden','ameoba','shepherg','j2','Acapnotic','dash','merlin262', 'Taaus','_moshez','rik','jafo__','blk-majik','JT__','itamar', 'kermit-','davidmccabe','glyph','jojo','dave_p','goo','hyjinx', 'SamB','exarkun','drewp','Ragica','skylan','redgore','k3','Ra1stlin', 'StevenK','carball','h3x','carljm','_jacob','teratorn','frangen', 'phed','datazone','Yaggo','acct_','nowhere','pyn','ThomasWaldmann', 'dunker','pilotLight','brainless','LoganH_','jmpnz','steinn', 'EliasREC','lowks__','OldSmrf','Mad77','snibril','delta','psy', 'skimpIzu','Kengur','MoonFallen','kotkis','Hyperi'] def group(seq, groupSize, noneFill=True): """Groups a given sequence into sublists of length groupSize.""" ret = [] L = [] i = groupSize for elt in seq: if i > 0: L.append(elt) else: ret.append(L) i = groupSize L = [] L.append(elt) i -= 1 if L: if noneFill: while len(L) < groupSize: L.append(None) ret.append(L) return ret class StringTestCase(PluginTestCase): plugins = ('String', 'Format', 'Status') def testLen(self): self.assertResponse('len foo', '3') self.assertHelp('len') def testNoErrors(self): self.assertNotError('levenshtein Python Perl') def testSoundex(self): self.assertNotError('soundex jemfinch') self.assertNotRegexp('soundex foobar 3:30', 'ValueError') def testChr(self): for i in range(256): c = chr(i) regexp = r'%s|%s' % (re.escape(c), re.escape(repr(c))) self.assertRegexp('chr %s' % i, regexp) def testOrd(self): for c in map(chr, range(256)): i = ord(c) self.assertResponse('ord %s' % utils.str.dqrepr(c), str(i)) def testEncodeDecode(self): # This no longer works correctly. It almost seems like were throwing # in a repr() somewhere. s = 'the recalcitrant jamessan tests his scramble function' self.assertNotRegexp('encode aldkfja foobar', 'LookupError') self.assertNotRegexp('decode asdflkj foobar', 'LookupError') self.assertResponse('decode zlib [encode zlib %s]' % s, s) self.assertRegexp('decode base64 $BCfBg7;9D;R(B', 'padded with') def testRe(self): self.assertResponse('re "m/system time/" [status cpu]', 'system time') self.assertResponse('re s/user/luser/g user user', 'luser luser') self.assertResponse('re s/user/luser/ user user', 'luser user') self.assertNotRegexp('re m/foo/ bar', 'has no attribute') self.assertResponse('re m/a\S+y/ "the bot angryman is hairy"','angry') def testReNotEmptyString(self): self.assertError('re s//foo/g blah') def testReWorksWithJustCaret(self): self.assertResponse('re s/^/foo/ bar', 'foobar') def testReNoEscapingUnpackListOfWrongSize(self): self.assertNotRegexp('re foo bar baz', 'unpack list of wrong size') def testReBug850931(self): self.assertResponse('re s/\b(\w+)\b/\1./g foo bar baz', 'foo. bar. baz.') def testNotOverlongRe(self): self.assertError('re [strjoin "" s/./ [eval \'xxx\'*400]] blah blah') def testXor(self): # This no longer works correctly. It almost seems like were throwing # in a repr() somewhere. L = [nick for nick in nicks if '|' not in nick and '[' not in nick and ']' not in nick] for s0, s1, s2, s3, s4, s5, s6, s7, s8, s9 in group(L, 10): data = '%s%s%s%s%s%s%s%s%s' % (s0, s1, s2, s3, s4, s5, s6, s7, s8) self.assertResponse('xor %s [xor %s %s]' % (s9, s9, data), data) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/String/__init__.py0000644000000000000000000000452411206611405016745 0ustar ### # Copyright (c) 2003-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. ### """ Provides various string-related commands. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/String/README.txt0000644000000000000000000000011711206611405016324 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/String/plugin.py0000644000000000000000000001512711206611405016505 0ustar ### # Copyright (c) 2003-2005, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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 types import binascii import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class String(callbacks.Plugin): def ord(self, irc, msg, args, letter): """ Returns the 8-bit value of . """ irc.reply(str(ord(letter))) ord = wrap(ord, ['letter']) def chr(self, irc, msg, args, i): """ Returns the character associated with the 8-bit value """ try: irc.reply(chr(i)) except ValueError: irc.error('That number doesn\'t map to an 8-bit character.') chr = wrap(chr, ['int']) def encode(self, irc, msg, args, encoding, text): """ Returns an encoded form of the given text; the valid encodings are available in the documentation of the Python codecs module: . """ try: irc.reply(text.encode(encoding).rstrip('\n')) except LookupError: irc.errorInvalid('encoding', encoding) encode = wrap(encode, ['something', 'text']) def decode(self, irc, msg, args, encoding, text): """ Returns an un-encoded form of the given text; the valid encodings are available in the documentation of the Python codecs module: . """ try: irc.reply(text.decode(encoding).encode('utf-8')) except LookupError: irc.errorInvalid('encoding', encoding) except binascii.Error: irc.errorInvalid('base64 string', s='Base64 strings must be a multiple of 4 in ' 'length, padded with \'=\' if necessary.') decode = wrap(decode, ['something', 'text']) def levenshtein(self, irc, msg, args, s1, s2): """ Returns the levenshtein distance (also known as the "edit distance" between and ) """ max = self.registryValue('levenshtein.max') if len(s1) > max or len(s2) > max: irc.error('Levenshtein distance is a complicated algorithm, try ' 'it with some smaller inputs.') else: irc.reply(str(utils.str.distance(s1, s2))) levenshtein = wrap(levenshtein, ['something', 'text']) def soundex(self, irc, msg, args, text, length): """ [] Returns the Soundex hash to a given length. The length defaults to 4, since that's the standard length for a soundex hash. For unlimited length, use 0. """ irc.reply(utils.str.soundex(text, length)) soundex = wrap(soundex, ['somethingWithoutSpaces', additional('int', 4)]) def len(self, irc, msg, args, text): """ Returns the length of . """ irc.reply(str(len(text))) len = wrap(len, ['text']) def re(self, irc, msg, args, ff, text): """ If is of the form m/regexp/flags, returns the portion of that matches the regexp. If is of the form s/regexp/replacement/flags, returns the result of applying such a regexp to """ if isinstance(ff, (types.FunctionType, types.MethodType)): f = ff else: f = lambda s: ff.search(s) and ff.search(s).group(0) or '' if f('') and len(f(' ')) > len(f(''))+1: # Matches the empty string. s = 'You probably don\'t want to match the empty string.' irc.error(s) else: irc.reply(f(text)) re = wrap(re, [('checkCapability', 'trusted'), first('regexpMatcher', 'regexpReplacer'), 'text']) def xor(self, irc, msg, args, password, text): """ Returns XOR-encrypted with . See http://www.yoe.org/developer/xor.html for information about XOR encryption. """ chars = utils.iter.cycle(password) ret = [chr(ord(c) ^ ord(chars.next())) for c in text] irc.reply(''.join(ret)) xor = wrap(xor, ['something', 'text']) def md5(self, irc, msg, args, text): """ Returns the md5 hash of a given string. Read http://www.rsasecurity.com/rsalabs/faq/3-6-6.html for more information about md5. """ irc.reply(utils.crypt.md5(text).hexdigest()) md5 = wrap(md5, ['text']) def sha(self, irc, msg, args, text): """ Returns the SHA hash of a given string. Read http://www.secure-hash-algorithm-md5-sha-1.co.uk/ for more information about SHA. """ irc.reply(utils.crypt.sha(text).hexdigest()) sha = wrap(sha, ['text']) Class = String # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/String/config.py0000644000000000000000000000535711206611405016460 0ustar ### # Copyright (c) 2003-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('String', True) String = conf.registerPlugin('String') conf.registerGroup(String, 'levenshtein') conf.registerGlobalValue(String.levenshtein, 'max', registry.PositiveInteger(256, """Determines the maximum size of a string given to the levenshtein command. The levenshtein command uses an O(m*n) algorithm, which means that with strings of length 256, it can take 1.5 seconds to finish; with strings of length 384, though, it can take 4 seconds to finish, and with strings of much larger lengths, it takes more and more time. Using nested commands, strings can get quite large, hence this variable, to limit the size of arguments passed to the levenshtein command.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Anonymous/0000755000000000000000000000000011206611405015351 5ustar supybot-0.83.4.1.ds.orig/plugins/Anonymous/test.py0000644000000000000000000000537011206611405016707 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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 AnonymousTestCase(ChannelPluginTestCase): plugins = ('Anonymous',) def testSay(self): m = self.assertError('anonymous say %s I love you!' % self.channel) try: orig = conf.supybot.plugins.Anonymous.requireRegistration() conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) m = self.assertNotError('anonymous say %s foo!'%self.channel) self.failUnless(m.args[1] == 'foo!') finally: conf.supybot.plugins.Anonymous.requireRegistration.setValue(orig) def testAction(self): m = self.assertError('anonymous do %s loves you!' % self.channel) try: orig = conf.supybot.plugins.Anonymous.requireRegistration() conf.supybot.plugins.Anonymous.requireRegistration.setValue(False) m = self.assertNotError('anonymous do %s loves you!'%self.channel) self.assertEqual(m.args, ircmsgs.action(self.channel, 'loves you!').args) finally: conf.supybot.plugins.Anonymous.requireRegistration.setValue(orig) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Anonymous/__init__.py0000644000000000000000000000463511206611405017472 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ Allows folks to talk through the bot anonymously. """ 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__ = "%%VERSION%%" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.strike # 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: supybot-0.83.4.1.ds.orig/plugins/Anonymous/README.txt0000644000000000000000000000011711206611405017046 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Anonymous/plugin.py0000644000000000000000000001045411206611405017225 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.callbacks as callbacks class Anonymous(callbacks.Plugin): """This plugin allows users to act through the bot anonymously. The 'do' command has the bot perform an anonymous action in a given channel, and the 'say' command allows other people to speak through the bot. Since this can be fairly well abused, you might want to set supybot.plugins.Anonymous.requireCapability so only users with that capability can use this plugin. For extra security, you can require that the user be *in* the channel they are trying to address anonymously with supybot.plugins.Anonymous.requirePresenceInChannel, or you can require that the user be registered by setting supybot.plugins.Anonymous.requireRegistration. """ def _preCheck(self, irc, msg, channel): if self.registryValue('requireRegistration'): try: _ = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) capability = self.registryValue('requireCapability') if capability: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) if self.registryValue('requirePresenceInChannel', channel) and \ msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to %q in there.', channel, 'say'), Raise=True) c = ircdb.channels.getChannel(channel) if c.lobotomized: irc.error(format('I\'m lobotomized in %s.', channel), Raise=True) if not c._checkCapability(self.name()): irc.error('That channel has set its capabilities so as to ' 'disallow the use of this plugin.', Raise=True) def say(self, irc, msg, args, channel, text): """ Sends to . """ self._preCheck(irc, msg, channel) self.log.info('Saying %q in %s due to %s.', text, channel, msg.prefix) irc.queueMsg(ircmsgs.privmsg(channel, text)) irc.noReply() say = wrap(say, ['inChannel', 'text']) def do(self, irc, msg, args, channel, text): """ Performs in . """ self._preCheck(irc, msg, channel) self.log.info('Performing %q in %s due to %s.', text, channel, msg.prefix) irc.queueMsg(ircmsgs.action(channel, text)) irc.noReply() do = wrap(do, ['inChannel', 'text']) Class = Anonymous # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Anonymous/config.py0000644000000000000000000000647711206611405017206 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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('Anonymous', True) Anonymous = conf.registerPlugin('Anonymous') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Anonymous, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(conf.supybot.plugins.Anonymous, 'requirePresenceInChannel', registry.Boolean(True, """Determines whether the bot should require people trying to use this plugin to be in the channel they wish to anonymously send to.""")) conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireRegistration', registry.Boolean(True, """Determines whether the bot should require people trying to use this plugin to be registered.""")) conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'requireCapability', registry.String('', """Determines what capability (if any) the bot should require people trying to use this plugin to have.""")) conf.registerGlobalValue(conf.supybot.plugins.Anonymous, 'allowPrivateTarget', registry.Boolean(False, """Determines whether the bot will require targets of the "say" command to be public (i.e., channels). If this is True, the bot will allow people to use the "say" command to send private messages to other users.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Admin/0000755000000000000000000000000011206611405014411 5ustar supybot-0.83.4.1.ds.orig/plugins/Admin/test.py0000644000000000000000000001237511206611405015752 0ustar ### # 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 * class AdminTestCase(PluginTestCase): plugins = ('Admin',) def testChannels(self): def getAfterJoinMessages(): m = self.irc.takeMsg() self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertEqual(m.command, 'WHO') self.assertRegexp('channels', 'not.*in any') self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) getAfterJoinMessages() self.assertRegexp('channels', '#foo') self.irc.feedMsg(ircmsgs.join('#bar', prefix=self.prefix)) getAfterJoinMessages() self.assertRegexp('channels', '#bar and #foo') self.irc.feedMsg(ircmsgs.join('#Baz', prefix=self.prefix)) getAfterJoinMessages() self.assertRegexp('channels', '#bar, #Baz, and #foo') def testIgnoreAddRemove(self): self.assertNotError('admin ignore add foo!bar@baz') self.assertError('admin ignore add alsdkfjlasd') self.assertNotError('admin ignore remove foo!bar@baz') self.assertError('admin ignore remove foo!bar@baz') def testIgnoreList(self): self.assertNotError('admin ignore list') self.assertNotError('admin ignore add foo!bar@baz') self.assertNotError('admin ignore list') self.assertNotError('admin ignore add foo!bar@baz') self.assertRegexp('admin ignore list', 'foo') def testCapabilityAdd(self): self.assertError('capability add foo bar') u = ircdb.users.newUser() u.name = 'foo' ircdb.users.setUser(u) self.assertNotError('capability add foo bar') self.assertError('addcapability foo baz') self.assert_('bar' in u.capabilities) ircdb.users.delUser(u.id) def testCapabilityRemove(self): self.assertError('capability remove foo bar') u = ircdb.users.newUser() u.name = 'foo' ircdb.users.setUser(u) self.assertNotError('capability add foo bar') self.assert_('bar' in u.capabilities) self.assertError('removecapability foo bar') self.assertNotError('capability remove foo bar') self.assert_(not 'bar' in u.capabilities) ircdb.users.delUser(u.id) def testJoin(self): m = self.getMsg('join #foo') self.assertEqual(m.command, 'JOIN') self.assertEqual(m.args[0], '#foo') m = self.getMsg('join #foo key') self.assertEqual(m.command, 'JOIN') self.assertEqual(m.args[0], '#foo') self.assertEqual(m.args[1], 'key') def testPart(self): def getAfterJoinMessages(): m = self.irc.takeMsg() self.assertEqual(m.command, 'MODE') m = self.irc.takeMsg() self.assertEqual(m.command, 'WHO') self.assertError('part #foo') self.assertRegexp('part #foo', 'not in') self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) getAfterJoinMessages() m = self.getMsg('part #foo') self.assertEqual(m.command, 'PART') self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) getAfterJoinMessages() m = self.getMsg('part #foo reason') self.assertEqual(m.command, 'PART') self.assertEqual(m.args[0], '#foo') self.assertEqual(m.args[1], 'reason') def testNick(self): original = conf.supybot.nick() try: m = self.getMsg('nick foobar') self.assertEqual(m.command, 'NICK') self.assertEqual(m.args[0], 'foobar') finally: conf.supybot.nick.setValue(original) def testAddCapabilityOwner(self): self.assertError('admin capability add %s owner' % self.nick) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Admin/__init__.py0000644000000000000000000000435111206611405016525 0ustar ### # Copyright (c) 2004-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. ### """ These are commands useful for administrating the bot; they all require their caller to have the 'admin' capability. This plugin is loaded by default. """ import supybot import supybot.world as world __author__ = supybot.authors.jemfinch # 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__ = "%%VERSION%%" # 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 supybot-0.83.4.1.ds.orig/plugins/Admin/plugin.py0000644000000000000000000003337511206611405016274 0ustar ### # 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 sys import time import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.schedule as schedule import supybot.callbacks as callbacks class Admin(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Admin, self) self.__parent.__init__(irc) self.joins = {} self.pendingNickChanges = {} def do437(self, irc, msg): """Nick/channel temporarily unavailable.""" target = msg.args[0] if irc.isChannel(target): # We don't care about nicks. t = time.time() + 30 # Let's schedule a rejoin. networkGroup = conf.supybot.networks.get(irc.network) def rejoin(): irc.queueMsg(networkGroup.channels.join(target)) # We don't need to schedule something because we'll get another # 437 when we try to join later. schedule.addEvent(rejoin, t) self.log.info('Scheduling a rejoin to %s at %s; ' 'Channel temporarily unavailable.', target, t) def do471(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error('Cannot join %s, it\'s full.' % channel) except KeyError: self.log.debug('Got 471 without Admin.join being called.') def do473(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error('Cannot join %s, I was not invited.' % channel) except KeyError: self.log.debug('Got 473 without Admin.join being called.') def do474(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error('Cannot join %s, it\'s banned me.' % channel) except KeyError: self.log.debug('Got 474 without Admin.join being called.') def do475(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error('Cannot join %s, my keyword was wrong.' % channel) except KeyError: self.log.debug('Got 475 without Admin.join being called.') def do515(self, irc, msg): try: channel = msg.args[1] (irc, msg) = self.joins.pop(channel) irc.error('Cannot join %s, I\'m not identified with the NickServ.' % channel) except KeyError: self.log.debug('Got 515 without Admin.join being called.') def doJoin(self, irc, msg): if msg.prefix == irc.prefix: try: del self.joins[msg.args[0]] except KeyError: s = 'Joined a channel without Admin.join being called.' self.log.debug(s) def doInvite(self, irc, msg): channel = msg.args[1] if channel not in irc.state.channels: if conf.supybot.alwaysJoinOnInvite() or \ ircdb.checkCapability(msg.prefix, 'admin'): self.log.info('Invited to %s by %s.', channel, msg.prefix) networkGroup = conf.supybot.networks.get(irc.network) irc.queueMsg(networkGroup.channels.join(channel)) conf.supybot.networks.get(irc.network).channels().add(channel) else: self.log.warning('Invited to %s by %s, but ' 'supybot.alwaysJoinOnInvite was False and ' 'the user lacked the "admin" capability.', channel, msg.prefix) def join(self, irc, msg, args, channel, key): """ [] Tell the bot to join the given channel. If is given, it is used when attempting to join the channel. """ if not irc.isChannel(channel): irc.errorInvalid('channel', channel, Raise=True) networkGroup = conf.supybot.networks.get(irc.network) networkGroup.channels().add(channel) if key: networkGroup.channels.key.get(channel).setValue(key) maxchannels = irc.state.supported.get('maxchannels', sys.maxint) if len(irc.state.channels) + 1 > maxchannels: irc.error('I\'m already too close to maximum number of ' 'channels for this network.', Raise=True) irc.queueMsg(networkGroup.channels.join(channel)) irc.noReply() self.joins[channel] = (irc, msg) join = wrap(join, ['validChannel', additional('something')]) def channels(self, irc, msg, args): """takes no arguments Returns the channels the bot is on. Must be given in private, in order to protect the secrecy of secret channels. """ L = irc.state.channels.keys() if L: utils.sortBy(ircutils.toLower, L) irc.reply(format('%L', L)) else: irc.reply('I\'m not currently in any channels.') channels = wrap(channels, ['private']) def do484(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error('My connection is restricted, I can\'t change nicks.') else: self.log.debug('Got 484 without Admin.nick being called.') def do433(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error('Someone else is already using that nick.') else: self.log.debug('Got 433 without Admin.nick being called.') def do435(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error('That nick is currently banned.') else: self.log.debug('Got 435 without Admin.nick being called.') def do438(self, irc, msg): irc = self.pendingNickChanges.get(irc, None) if irc is not None: irc.error(format('I can\'t change nicks, the server said %q.', msg.args[2]), private=True) else: self.log.debug('Got 438 without Admin.nick being called.') def doNick(self, irc, msg): if msg.nick == irc.nick or msg.args[0] == irc.nick: try: del self.pendingNickChanges[irc] except KeyError: self.log.debug('Got NICK without Admin.nick being called.') def nick(self, irc, msg, args, nick): """[] Changes the bot's nick to . If no nick is given, returns the bot's current nick. """ if nick: conf.supybot.nick.setValue(nick) irc.queueMsg(ircmsgs.nick(nick)) self.pendingNickChanges[irc.getRealIrc()] = irc else: irc.reply(irc.nick) nick = wrap(nick, [additional('nick')]) def part(self, irc, msg, args, channel, reason): """[] [] Tells the bot to part the list of channels you give it. is only necessary if you want the bot to part a channel other than the current channel. If is specified, use it as the part message. """ if channel is None: if irc.isChannel(msg.args[0]): channel = msg.args[0] else: irc.error(Raise=True) try: network = conf.supybot.networks.get(irc.network) network.channels().remove(channel) except KeyError: pass if channel not in irc.state.channels: irc.error('I\'m not in %s.' % channel, Raise=True) irc.queueMsg(ircmsgs.part(channel, reason or msg.nick)) if msg.nick in irc.state.channels[channel].users: irc.noReply() else: irc.replySuccess() part = wrap(part, [optional('validChannel'), additional('text')]) class capability(callbacks.Commands): def add(self, irc, msg, args, user, capability): """ Gives the user specified by (or the user to whom currently maps) the specified capability """ # Ok, the concepts that are important with capabilities: # ### 1) No user should be able to elevate his privilege to owner. ### 2) Admin users are *not* superior to #channel.ops, and don't ### have God-like powers over channels. ### 3) We assume that Admin users are two things: non-malicious and ### and greedy for power. So they'll try to elevate their ### privilege to owner, but they won't try to crash the bot for ### no reason. # Thus, the owner capability can't be given in the bot. Admin # users can only give out capabilities they have themselves (which # will depend on supybot.capabilities and its child default) but # generally means they can't mess with channel capabilities. if ircutils.strEqual(capability, 'owner'): irc.error('The "owner" capability can\'t be added in the bot.' ' Use the supybot-adduser program (or edit the ' 'users.conf file yourself) to add an owner ' 'capability.') return if ircdb.isAntiCapability(capability) or \ ircdb.checkCapability(msg.prefix, capability): user.addCapability(capability) ircdb.users.setUser(user) irc.replySuccess() else: irc.error('You can\'t add capabilities you don\'t have.') add = wrap(add, ['otherUser', 'lowered']) def remove(self, irc, msg, args, user, capability): """ Takes from the user specified by (or the user to whom currently maps) the specified capability """ if ircdb.checkCapability(msg.prefix, capability) or \ ircdb.isAntiCapability(capability): try: user.removeCapability(capability) ircdb.users.setUser(user) irc.replySuccess() except KeyError: irc.error('That user doesn\'t have that capability.') else: s = 'You can\'t remove capabilities you don\'t have.' irc.error(s) remove = wrap(remove, ['otherUser','lowered']) class ignore(callbacks.Commands): def add(self, irc, msg, args, hostmask, expires): """ [] Ignores or, if a nick is given, ignores whatever hostmask that nick is currently using. is a "seconds from now" value that determines when the ignore will expire; if, for instance, you wish for the ignore to expire in an hour, you could give an of 3600. If no is given, the ignore will never automatically expire. """ ircdb.ignores.add(hostmask, expires) irc.replySuccess() add = wrap(add, ['hostmask', additional('expiry', 0)]) def remove(self, irc, msg, args, hostmask): """ Ignores or, if a nick is given, ignores whatever hostmask that nick is currently using. """ try: ircdb.ignores.remove(hostmask) irc.replySuccess() except KeyError: irc.error('%s wasn\'t in the ignores database.' % hostmask) remove = wrap(remove, ['hostmask']) def list(self, irc, msg, args): """takes no arguments Returns the hostmasks currently being globally ignored. """ # XXX Add the expirations. if ircdb.ignores.hostmasks: irc.reply(format('%L', (map(repr,ircdb.ignores.hostmasks)))) else: irc.reply('I\'m not currently globally ignoring anyone.') list = wrap(list) Class = Admin # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Admin/config.py0000644000000000000000000000446711206611405016243 0ustar ### # Copyright (c) 2004-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('Admin', True) Admin = conf.registerPlugin('Admin') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Admin, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Utilities/0000755000000000000000000000000011206611405015334 5ustar supybot-0.83.4.1.ds.orig/plugins/Utilities/test.py0000644000000000000000000000510211206611405016663 0ustar ### # 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 UtilitiesTestCase(PluginTestCase): plugins = ('Utilities',) def testIgnore(self): self.assertNoResponse('utilities ignore foo bar baz', 1) self.assertError('utilities ignore [re m/foo bar]') def testSuccess(self): self.assertNotError('success 1') self.assertError('success [re m/foo bar]') def testLast(self): self.assertResponse('utilities last foo bar baz', 'baz') def testEcho(self): self.assertHelp('echo') self.assertResponse('echo foo', 'foo') def testEchoDollarOneRepliesDollarOne(self): self.assertResponse('echo $1', '$1') def testEchoStandardSubstitute(self): self.assertNotRegexp('echo $nick', r'\$') def testApply(self): self.assertResponse('apply "utilities last" a', 'a') self.assertResponse('apply "utilities last" a b', 'b') def testShuffle(self): self.assertResponse('shuffle a', 'a') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Utilities/__init__.py0000644000000000000000000000433211206611405017447 0ustar ### # 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. ### """ Various utility commands, mostly useful for manipulating nested commands. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Utilities/README.txt0000644000000000000000000000011711206611405017031 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Utilities/plugin.py0000644000000000000000000001051611206611405017207 0ustar ### # 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 types import random from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Utilities(callbacks.Plugin): # Yes, I really do mean "requires no arguments" below. "takes no # arguments" would probably lead people to think it was a useless command. def ignore(self, irc, msg, args): """requires no arguments Does nothing. Useful sometimes for sequencing commands when you don't care about their non-error return values. """ pass # Do be careful not to wrap this unless you do any('something'). def success(self, irc, msg, args, text): """[] Does nothing except to reply with a success message. This is useful when you want to run multiple commands as nested commands, and don't care about their output as long as they're successful. An error, of course, will break out of this command. , if given, will be appended to the end of the success message. """ irc.replySuccess(text) success = wrap(success, [additional('text')]) def last(self, irc, msg, args): """ [ ...] Returns the last argument given. Useful when you'd like multiple nested commands to run, but only the output of the last one to be returned. """ args = filter(None, args) if args: irc.reply(args[-1]) else: raise callbacks.ArgumentError def echo(self, irc, msg, args, text): """ Returns the arguments given it. Uses our standard substitute on the string(s) given to it; $nick (or $who), $randomNick, $randomInt, $botnick, $channel, $user, $host, $today, $now, and $randomDate are all handled appropriately. """ text = ircutils.standardSubstitute(irc, msg, text) irc.reply(text, prefixNick=False) echo = wrap(echo, ['text']) def shuffle(self, irc, msg, args, things): """ [ ...] Shuffles the arguments given it. """ random.shuffle(things) irc.reply(' '.join(things)) shuffle = wrap(shuffle, [many('anything')]) def apply(self, irc, msg, args, command, rest): """ Tokenizes and calls with the resulting arguments. """ args = [token and token or '""' for token in rest] text = ' '.join(args) commands = command.split() commands = map(callbacks.canonicalName, commands) tokens = callbacks.tokenize(text) allTokens = commands + tokens self.Proxy(irc, msg, allTokens) apply = wrap(apply, ['something', many('anything')]) Class = Utilities # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Utilities/config.py0000644000000000000000000000450211206611405017154 0ustar ### # 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 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('Utilities', True) Utilities = conf.registerPlugin('Utilities') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Utilities, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Ctcp/0000755000000000000000000000000011206611405014252 5ustar supybot-0.83.4.1.ds.orig/plugins/Ctcp/test.py0000644000000000000000000000331511206611405015605 0ustar ### # 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 CtcpTestCase(PluginTestCase): plugins = ('Ctcp',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Ctcp/__init__.py0000644000000000000000000000434711206611405016373 0ustar ### # 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. ### """ Handles standard CTCP responses to PING, TIME, SOURCE, VERSION, USERINFO, and FINGER. """ import supybot import supybot.world as world __version__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Ctcp/README.txt0000644000000000000000000000011711206611405015747 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Ctcp/plugin.py0000644000000000000000000001427611206611405016134 0ustar ### # 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 time 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.schedule as schedule import supybot.callbacks as callbacks class Ctcp(callbacks.PluginRegexp): public = False regexps = ('ctcpPing', 'ctcpVersion', 'ctcpUserinfo', 'ctcpTime', 'ctcpFinger', 'ctcpSource') def __init__(self, irc): self.__parent = super(Ctcp, self) self.__parent.__init__(irc) self.ignores = ircutils.IrcDict() self.floods = ircutils.FloodQueue(60) def callCommand(self, command, irc, msg, *args, **kwargs): if conf.supybot.abuse.flood.ctcp(): now = time.time() for (ignore, expiration) in self.ignores.items(): if expiration < now: del self.ignores[ignore] elif ircutils.hostmaskPatternEqual(ignore, msg.prefix): return self.floods.enqueue(msg) max = conf.supybot.abuse.flood.ctcp.maximum() if self.floods.len(msg) > max: expires = conf.supybot.abuse.flood.ctcp.punishment() self.log.warning('Apparent CTCP flood from %s, ' 'ignoring CTCP messages for %s seconds.', msg.prefix, expires) ignoreMask = '*!%s@%s' % (msg.user, msg.host) self.ignores[ignoreMask] = now + expires return self.__parent.callCommand(command, irc, msg, *args, **kwargs) def _reply(self, irc, msg, s): s = '\x01%s\x01' % s irc.reply(s, notice=True, private=True, to=msg.nick) def ctcpPing(self, irc, msg, match): "\x01PING ?(.*)\x01" self.log.info('Received CTCP PING from %s', msg.prefix) payload = match.group(1) if payload: self._reply(irc, msg, 'PING %s' % match.group(1)) else: self._reply(irc, msg, 'PING') def ctcpVersion(self, irc, msg, match): "\x01VERSION\x01" self.log.info('Received CTCP VERSION from %s', msg.prefix) self._reply(irc, msg, 'VERSION Supybot %s' % conf.version) def ctcpUserinfo(self, irc, msg, match): "\x01USERINFO\x01" self.log.info('Received CTCP USERINFO from %s', msg.prefix) self._reply(irc, msg, 'USERINFO') def ctcpTime(self, irc, msg, match): "\x01TIME\x01" self.log.info('Received CTCP TIME from %s', msg.prefix) self._reply(irc, msg, 'TIME %s' % time.ctime()) def ctcpFinger(self, irc, msg, match): "\x01FINGER\x01" self.log.info('Received CTCP FINGER from %s', msg.prefix) self._reply(irc, msg, 'FINGER Supybot, the best Python IRC bot in existence!') def ctcpSource(self, irc, msg, match): "\x01SOURCE\x01" self.log.info('Received CTCP SOURCE from %s', msg.prefix) self._reply(irc, msg, 'SOURCE http://www.sourceforge.net/projects/supybot/') def doNotice(self, irc, msg): if ircmsgs.isCtcp(msg): try: (version, payload) = msg.args[1][1:-1].split(None, 1) except ValueError: return if version == 'VERSION': self.versions.setdefault(payload, []).append(msg.nick) def version(self, irc, msg, args, channel, optlist): """[] [--nicks] Sends a CTCP VERSION to , returning the various version strings returned. It waits for 10 seconds before returning the versions received at that point. If --nicks is given, nicks are associated with the version strings; otherwise, only the version strings are given. """ self.versions = ircutils.IrcDict() nicks = False for (option, arg) in optlist: if option == 'nicks': nicks = True irc.queueMsg(ircmsgs.privmsg(channel, '\x01VERSION\x01')) def doReply(): if self.versions: L = [] for (reply, nicks) in self.versions.iteritems(): if nicks: L.append(format('%L responded with %q', nicks, reply)) else: L.append(reply) irc.reply(format('%L', L)) else: irc.reply('I received no version responses.') wait = self.registryValue('versionWait') schedule.addEvent(doReply, time.time()+wait) version = wrap(version, ['channel', getopts({'nicks':''})]) Class = Ctcp # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Ctcp/config.py0000644000000000000000000000642211206611405016075 0ustar ### # 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('Ctcp', True) ### # Ctcp plugin configuration variables. ### Ctcp = conf.registerPlugin('Ctcp') conf.registerGlobalValue(Ctcp, 'versionWait', registry.PositiveInteger(10, """Determines how many seconds the bot will wait after getting a version command (not a CTCP VERSION, but an actual call of the command in this plugin named "version") before replying with the results it has collected.""")) ### # supybot.abuse configuration variables. ### conf.registerGlobalValue(conf.supybot.abuse.flood, 'ctcp', registry.Boolean(True, """Determines whether the bot will defend itself against CTCP flooding.""")) conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'maximum', registry.PositiveInteger(5, """Determines how many CTCP messages (not including actions) the bot will reply to from a given user in a minute. If a user sends more than this many CTCP messages in a 60 second period, the bot will ignore CTCP messages from this user for supybot.abuse.flood.ctcp.punishment seconds.""")) conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'punishment', registry.PositiveInteger(300, """Determines how many seconds the bot will ignore CTCP messages from users who flood it with CTCP messages.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Plugin/0000755000000000000000000000000011206611405014617 5ustar supybot-0.83.4.1.ds.orig/plugins/Plugin/test.py0000644000000000000000000000702711206611405016156 0ustar ### # 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 * class PluginTestCase(PluginTestCase): plugins = ('Plugin', 'Utilities') def testPlugin(self): self.assertRegexp('plugin plugin', 'available.*Plugin plugin') self.assertResponse('echo [plugin plugin]', 'Plugin') def testList(self): self.assertRegexp('plugin list', 'Plugin.*Utilities') def testHelp(self): self.assertRegexp('plugin help plugin', 'manage their plugins') def testAuthor(self): self.assertRegexp('plugin author plugin', 'jemfinch') def testContributors(self): # Test ability to list contributors self.assertNotError('contributors Plugin') # Test ability to list contributions # Verify that when a single command contribution has been made, # the word "command" is properly not pluralized. # Note: This will break if the listed person ever makes more than # one contribution to the Plugin plugin self.assertRegexp('contributors Plugin skorobeus', 'command') # Test handling of pluralization of "command" when person has # contributed more than one command to the plugin. # -- Need to create this case, check it with the regexp 'commands' # Test handling of invalid plugin self.assertRegexp('contributors InvalidPlugin', 'not a valid plugin') # Test handling of invalid person self.assertRegexp('contributors Plugin noname', 'not a registered contributor') # Test handling of valid person with no contributions # Note: This will break if the listed person ever makes a contribution # to the Plugin plugin self.assertRegexp('contributors Plugin bwp', 'listed as a contributor') def testContributorsIsCaseInsensitive(self): self.assertNotError('contributors Plugin Skorobeus') self.assertNotError('contributors Plugin sKoRoBeUs') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Plugin/__init__.py0000644000000000000000000000523711206611405016737 0ustar ### # 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. ### """ This plugin handles various plugin-related things, such as getting help for a plugin, getting a list of the loaded plugins, and searching and downloading plugins from supybot.com. """ 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__ = "%%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__ = { supybot.authors.skorobeus: ['contributors'], } import config # This had to be renamed because of stupid case-insensitivity issues. 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: supybot-0.83.4.1.ds.orig/plugins/Plugin/README.txt0000644000000000000000000000011711206611405016314 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Plugin/plugin.py0000644000000000000000000002275511206611405016502 0ustar ### # 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 import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Plugin(callbacks.Plugin): """This plugin exists to help users manage their plugins. Use 'plugin list' to list the loaded plugins; use 'plugin help' to get this sort of help from other plugins; use the 'plugin' command itself to determine what plugin a command exists in.""" def help(self, irc, msg, args, cb): """ Returns a useful description of how to use , if the plugin has one. """ doc = cb.getPluginHelp() if doc: irc.reply(utils.str.normalizeWhitespace(doc)) else: irc.reply('That plugin is loaded, but has no plugin help.') help = wrap(help, ['plugin']) def list(self, irc, msg, args): """takes no arguments Returns a list of the currently loaded plugins. """ L = [cb.name() for cb in irc.callbacks] L.sort() irc.reply(format('%L', L)) list = wrap(list) def plugin(self, irc, msg, args, command): """ Returns the plugin(s) that is in. """ (maxL, cbs) = irc.findCallbacksForArgs(command) L = [] if maxL == command: for cb in cbs: L.append(cb.name()) command = callbacks.formatCommand(command) if L: if irc.nested: irc.reply(format('%L', L)) else: if len(L) > 1: plugin = 'plugins' else: plugin = 'plugin' irc.reply(format('The %q command is available in the %L %s.', command, L, plugin)) else: irc.error(format('There is no command %q.', command)) plugin = wrap(plugin, [many('something')]) def author(self, irc, msg, args, cb): """ Returns the author of . This is the person you should talk to if you have ideas, suggestions, or other comments about a given plugin. """ if cb is None: irc.error('That plugin does not seem to be loaded.') return module = cb.classModule if hasattr(module, '__author__') and module.__author__: irc.reply(str(module.__author__)) else: irc.reply('That plugin doesn\'t have an author that claims it.') author = wrap(author, [('plugin')]) def contributors(self, irc, msg, args, cb, nick): """ [] Replies with a list of people who made contributions to a given plugin. If is specified, that person's specific contributions will be listed. Note: The is the part inside of the parentheses in the people listing. """ def getShortName(authorInfo): """ Take an Authors object, and return only the name and nick values in the format 'First Last (nick)'. """ return '%(name)s (%(nick)s)' % authorInfo.__dict__ def buildContributorsString(longList): """ Take a list of long names and turn it into : shortname[, shortname and shortname]. """ L = [getShortName(n) for n in longList] return format('%L', L) def sortAuthors(): """ Sort the list of 'long names' based on the number of contributions associated with each. """ L = module.__contributors__.items() def negativeSecondElement(x): return -len(x[1]) utils.sortBy(negativeSecondElement, L) return [t[0] for t in L] def buildPeopleString(module): """ Build the list of author + contributors (if any) for the requested plugin. """ head = 'The %s plugin' % cb.name() author = 'has not been claimed by an author' conjunction = 'and' contrib = 'has no contributors listed.' hasAuthor = False hasContribs = False if hasattr(module, '__author__'): if module.__author__ != supybot.authors.unknown: author = 'was written by %s' % \ utils.web.mungeEmail(str(module.__author__)) hasAuthor = True if hasattr(module, '__contributors__'): contribs = sortAuthors() if hasAuthor: try: contribs.remove(module.__author__) except ValueError: pass if contribs: contrib = format('%s %h contributed to it.', buildContributorsString(contribs), len(contribs)) hasContribs = True elif hasAuthor: contrib = 'has no additional contributors listed.' if hasContribs and not hasAuthor: conjunction = 'but' return ' '.join([head, author, conjunction, contrib]) def buildPersonString(module): """ Build the list of contributions (if any) for the requested person for the requested plugin """ isAuthor = False authorInfo = None moduleContribs = module.__contributors__.keys() lnick = nick.lower() for contrib in moduleContribs: if contrib.nick.lower() == lnick: authorInfo = contrib break authorInfo = authorInfo or getattr(supybot.authors, nick, None) if not authorInfo: return 'The nick specified (%s) is not a registered ' \ 'contributor.' % nick fullName = utils.web.mungeEmail(str(authorInfo)) contributions = [] if hasattr(module, '__contributors__'): if authorInfo not in module.__contributors__: return 'The %s plugin does not have \'%s\' listed as a ' \ 'contributor.' % (cb.name(), nick) contributions = module.__contributors__[authorInfo] isAuthor = getattr(module, '__author__', False) == authorInfo (nonCommands, commands) = utils.iter.partition(lambda s: ' ' in s, contributions) results = [] if commands: s = 'command' if len(commands) > 1: s = utils.str.pluralize(s) results.append(format('the %L %s', commands, s)) if nonCommands: results.append(format('the %L', nonCommands)) if results and isAuthor: return format( '%s wrote the %s plugin and also contributed %L.', (fullName, cb.name(), results)) elif results and not isAuthor: return format('%s contributed %L to the %s plugin.', fullName, results, cb.name()) elif isAuthor and not results: return '%s wrote the %s plugin' % (fullName, cb.name()) # XXX Does this ever actually get reached? else: return '%s has no listed contributions for the %s plugin.' % \ (fullName, cb.name()) # First we need to check and see if the requested plugin is loaded module = cb.classModule if not nick: irc.reply(buildPeopleString(module)) else: nick = ircutils.toLower(nick) irc.reply(buildPersonString(module)) contributors = wrap(contributors, ['plugin', additional('nick')]) Class = Plugin # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Plugin/config.py0000644000000000000000000000446611206611405016450 0ustar ### # 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('Plugin', True) Plugin = conf.registerPlugin('Plugin') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Plugin, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/RSS/0000755000000000000000000000000011206611405014030 5ustar supybot-0.83.4.1.ds.orig/plugins/RSS/test.py0000644000000000000000000000730511206611405015366 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 * url = 'http://www.advogato.org/rss/articles.xml' class RSSTestCase(ChannelPluginTestCase): plugins = ('RSS','Plugin') def testRssAddBadName(self): self.assertError('rss add "foo bar" %s' % url) def testCantAddFeedNamedRss(self): self.assertError('rss add rss %s' % url) def testCantRemoveMethodThatIsntFeed(self): self.assertError('rss remove rss') if network: def testRssinfo(self): self.assertNotError('rss info %s' % url) self.assertNotError('rss add advogato %s' % url) self.assertNotError('rss info advogato') self.assertNotError('rss info AdVogATo') self.assertNotError('rss remove advogato') def testRssinfoDoesTimeProperly(self): self.assertNotRegexp('rss info http://slashdot.org/slashdot.rss', '-1 years') def testAnnounce(self): self.assertNotError('rss add advogato %s' % url) self.assertNotError('rss announce add advogato') self.assertNotRegexp('rss announce', r'ValueError') self.assertNotError('rss announce remove advogato') self.assertNotError('rss remove advogato') def testRss(self): self.assertNotError('rss %s' % url) m = self.assertNotError('rss %s 2' % url) self.failUnless(m.args[1].count('||') == 1) def testRssAdd(self): self.assertNotError('rss add advogato %s' % url) self.assertNotError('advogato') self.assertNotError('rss advogato') self.assertNotError('rss remove advogato') self.assertNotRegexp('list RSS', 'advogato') self.assertError('advogato') self.assertError('rss advogato') def testNonAsciiFeeds(self): self.assertNotError('rss http://www.heise.de/newsticker/heise.rdf') self.assertNotError('rss http://www.golem.de/rss.php?feed=ATOM0.3') self.assertNotError('rss info http://br-linux.org/main/index.xml') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/RSS/__init__.py0000644000000000000000000000453611206611405016151 0ustar ### # 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. ### """ Provides basic functionality for handling RSS/RDF feeds. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/RSS/README.txt0000644000000000000000000000066111206611405015531 0ustar This plugin allows you to poll and periodically announce new items from RSS feeds. In order to use this plugin you must have the following modules installed: - feedparser: http://feedparser.org/ Basic usage ----------- Adding a feed @rss add supybot http://sourceforge.net/export/rss2_projfiles.php?group_id=58965 Add announcements for a feed @rss announce add supybot Stop announcements for a feed @rss announce remove supybot supybot-0.83.4.1.ds.orig/plugins/RSS/plugin.py0000644000000000000000000004407311206611405015710 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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 new import time import socket import sgmllib import threading import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks try: feedparser = utils.python.universalImport('feedparser', 'local.feedparser') except ImportError: raise callbacks.Error, \ 'You the feedparser module installed to use this plugin. ' \ 'Download the module at .' def getFeedName(irc, msg, args, state): if not registry.isValidRegistryName(args[0]): state.errorInvalid('feed name', args[0], 'Feed names must not include spaces.') state.args.append(callbacks.canonicalName(args.pop(0))) addConverter('feedName', getFeedName) class RSS(callbacks.Plugin): """This plugin is useful both for announcing updates to RSS feeds in a channel, and for retrieving the headlines of RSS feeds via command. Use the "add" command to add feeds to this plugin, and use the "announce" command to determine what feeds should be announced in a given channel.""" threaded = True def __init__(self, irc): self.__parent = super(RSS, self) self.__parent.__init__(irc) # Schema is feed : [url, command] self.feedNames = callbacks.CanonicalNameDict() self.locks = {} self.lastRequest = {} self.cachedFeeds = {} self.gettingLockLock = threading.Lock() for name in self.registryValue('feeds'): self._registerFeed(name) try: url = self.registryValue(registry.join(['feeds', name])) except registry.NonExistentRegistryEntry: self.log.warning('%s is not a registered feed, removing.',name) continue self.makeFeedCommand(name, url) self.getFeed(url) # So announced feeds don't announce on startup. def isCommandMethod(self, name): if not self.__parent.isCommandMethod(name): if name in self.feedNames: return True else: return False else: return True def listCommands(self): return self.__parent.listCommands(self.feedNames.keys()) def getCommandMethod(self, command): try: return self.__parent.getCommandMethod(command) except AttributeError: return self.feedNames[command[0]][1] def _registerFeed(self, name, url=''): self.registryValue('feeds').add(name) group = self.registryValue('feeds', value=False) group.register(name, registry.String(url, '')) def __call__(self, irc, msg): self.__parent.__call__(irc, msg) irc = callbacks.SimpleProxy(irc, msg) newFeeds = {} for channel in irc.state.channels: feeds = self.registryValue('announce', channel) for name in feeds: commandName = callbacks.canonicalName(name) if self.isCommandMethod(commandName): url = self.feedNames[commandName][0] else: url = name if self.willGetNewFeed(url): newFeeds.setdefault((url, name), []).append(channel) for ((url, name), channels) in newFeeds.iteritems(): # We check if we can acquire the lock right here because if we # don't, we'll possibly end up spawning a lot of threads to get # the feed, because this thread may run for a number of bytecodes # before it switches to a thread that'll get the lock in # _newHeadlines. if self.acquireLock(url, blocking=False): try: t = threading.Thread(target=self._newHeadlines, name=format('Fetching %u', url), args=(irc, channels, name, url)) self.log.info('Checking for announcements at %u', url) world.threadsSpawned += 1 t.setDaemon(True) t.start() finally: self.releaseLock(url) time.sleep(0.1) # So other threads can run. def buildHeadlines(self, headlines, channel, config='announce.showLinks'): newheadlines = [] if self.registryValue(config, channel): for headline in headlines: if headline[1]: newheadlines.append(format('%s %u', headline[0], headline[1].encode('utf-8'))) else: newheadlines.append(format('%s', headline[0])) else: for headline in headlines: newheadlines = [format('%s', h[0]) for h in headlines] return newheadlines def _newHeadlines(self, irc, channels, name, url): try: # We acquire the lock here so there's only one announcement thread # in this code at any given time. Otherwise, several announcement # threads will getFeed (all blocking, in turn); then they'll all # want to send their news messages to the appropriate channels. # Note that we're allowed to acquire this lock twice within the # same thread because it's an RLock and not just a normal Lock. self.acquireLock(url) try: oldresults = self.cachedFeeds[url] oldheadlines = self.getHeadlines(oldresults) except KeyError: oldheadlines = [] newresults = self.getFeed(url) newheadlines = self.getHeadlines(newresults) if len(newheadlines) == 1: s = newheadlines[0][0] if s in ('Timeout downloading feed.', 'Unable to download feed.'): self.log.debug('%s %u', s, url) return def canonize(headline): return (tuple(headline[0].lower().split()), headline[1]) oldheadlines = set(map(canonize, oldheadlines)) for (i, headline) in enumerate(newheadlines): if canonize(headline) in oldheadlines: newheadlines[i] = None newheadlines = filter(None, newheadlines) # Removes Nones. if newheadlines: for channel in channels: bold = self.registryValue('bold', channel) sep = self.registryValue('headlineSeparator', channel) prefix = self.registryValue('announcementPrefix', channel) pre = format('%s%s: ', prefix, name) if bold: pre = ircutils.bold(pre) sep = ircutils.bold(sep) headlines = self.buildHeadlines(newheadlines, channel) irc.replies(headlines, prefixer=pre, joiner=sep, to=channel, prefixNick=False, private=True) finally: self.releaseLock(url) def willGetNewFeed(self, url): now = time.time() wait = self.registryValue('waitPeriod') if url not in self.lastRequest or now - self.lastRequest[url] > wait: return True else: return False def acquireLock(self, url, blocking=True): try: self.gettingLockLock.acquire() try: lock = self.locks[url] except KeyError: lock = threading.RLock() self.locks[url] = lock return lock.acquire(blocking=blocking) finally: self.gettingLockLock.release() def releaseLock(self, url): self.locks[url].release() def getFeed(self, url): def error(s): return {'items': [{'title': s}]} try: # This is the most obvious place to acquire the lock, because a # malicious user could conceivably flood the bot with rss commands # and DoS the website in question. self.acquireLock(url) if self.willGetNewFeed(url): try: self.log.debug('Downloading new feed from %u', url) results = feedparser.parse(url) if 'bozo_exception' in results: raise results['bozo_exception'] except sgmllib.SGMLParseError: self.log.exception('Uncaught exception from feedparser:') raise callbacks.Error, 'Invalid (unparsable) RSS feed.' except socket.timeout: return error('Timeout downloading feed.') except Exception, e: # These seem mostly harmless. We'll need reports of a # kind that isn't. self.log.debug('Allowing bozo_exception %r through.', e) if results.get('feed', {}): self.cachedFeeds[url] = results self.lastRequest[url] = time.time() else: self.log.debug('Not caching results; feed is empty.') try: return self.cachedFeeds[url] except KeyError: wait = self.registryValue('waitPeriod') # If there's a problem retrieving the feed, we should back off # for a little bit before retrying so that there is time for # the error to be resolved. self.lastRequest[url] = time.time() - .5 * wait return error('Unable to download feed.') finally: self.releaseLock(url) def _getConverter(self, feed): toText = utils.web.htmlToText if 'encoding' in feed: return lambda s: toText(s).strip().encode(feed['encoding'], 'replace') else: return lambda s: toText(s).strip() def getHeadlines(self, feed): headlines = [] conv = self._getConverter(feed) for d in feed['items']: if 'title' in d: title = conv(d['title']) link = d.get('link') if link: headlines.append((title, link)) else: headlines.append((title, None)) return headlines def makeFeedCommand(self, name, url): docstring = format("""[] Reports the titles for %s at the RSS feed %u. If is given, returns only that many headlines. RSS feeds are only looked up every supybot.plugins.RSS.waitPeriod seconds, which defaults to 1800 (30 minutes) since that's what most websites prefer. """, name, url) if url not in self.locks: self.locks[url] = threading.RLock() if self.isCommandMethod(name): s = format('I already have a command in this plugin named %s.',name) raise callbacks.Error, s def f(self, irc, msg, args): args.insert(0, url) self.rss(irc, msg, args) f = utils.python.changeFunctionName(f, name, docstring) f = new.instancemethod(f, self, RSS) self.feedNames[name] = (url, f) self._registerFeed(name, url) def add(self, irc, msg, args, name, url): """ Adds a command to this plugin that will look up the RSS feed at the given URL. """ self.makeFeedCommand(name, url) irc.replySuccess() add = wrap(add, ['feedName', 'url']) def remove(self, irc, msg, args, name): """ Removes the command for looking up RSS feeds at from this plugin. """ if name not in self.feedNames: irc.error('That\'s not a valid RSS feed command name.') return del self.feedNames[name] conf.supybot.plugins.RSS.feeds().remove(name) conf.supybot.plugins.RSS.feeds.unregister(name) irc.replySuccess() remove = wrap(remove, ['feedName']) class announce(callbacks.Commands): def list(self, irc, msg, args, channel): """[] Returns the list of feeds announced in . is only necessary if the message isn't sent in the channel itself. """ announce = conf.supybot.plugins.RSS.announce feeds = format('%L', list(announce.get(channel)())) irc.reply(feeds or 'I am currently not announcing any feeds.') list = wrap(list, [optional('channel')]) def add(self, irc, msg, args, channel, feeds): """[] [ ...] Adds the list of feeds to the current list of announced feeds in . Valid feeds include the names of registered feeds as well as URLs for RSS feeds. is only necessary if the message isn't sent in the channel itself. """ announce = conf.supybot.plugins.RSS.announce S = announce.get(channel)() for feed in feeds: S.add(feed) announce.get(channel).setValue(S) irc.replySuccess() add = wrap(add, [('checkChannelCapability', 'op'), many(first('url', 'feedName'))]) def remove(self, irc, msg, args, channel, feeds): """[] [ ...] Removes the list of feeds from the current list of announced feeds in . Valid feeds include the names of registered feeds as well as URLs for RSS feeds. is only necessary if the message isn't sent in the channel itself. """ announce = conf.supybot.plugins.RSS.announce S = announce.get(channel)() for feed in feeds: S.discard(feed) announce.get(channel).setValue(S) irc.replySuccess() remove = wrap(remove, [('checkChannelCapability', 'op'), many(first('url', 'feedName'))]) def rss(self, irc, msg, args, url, n): """ [] Gets the title components of the given RSS feed. If is given, return only that many headlines. """ self.log.debug('Fetching %u', url) feed = self.getFeed(url) if irc.isChannel(msg.args[0]): channel = msg.args[0] else: channel = None headlines = self.getHeadlines(feed) if not headlines: irc.error('Couldn\'t get RSS feed.') return headlines = self.buildHeadlines(headlines, channel, 'showLinks') if n: headlines = headlines[:n] sep = self.registryValue('headlineSeparator', channel) if self.registryValue('bold', channel): sep = ircutils.bold(sep) irc.replies(headlines, joiner=sep) rss = wrap(rss, ['url', additional('int')]) def info(self, irc, msg, args, url): """ Returns information from the given RSS feed, namely the title, URL, description, and last update date, if available. """ try: url = self.registryValue('feeds.%s' % url) except registry.NonExistentRegistryEntry: pass feed = self.getFeed(url) conv = self._getConverter(feed) info = feed.get('feed') if not info: irc.error('I couldn\'t retrieve that RSS feed.') return # check the 'modified_parsed' key, if it's there, convert it here first if 'modified' in info: seconds = time.mktime(info['modified_parsed']) now = time.mktime(time.gmtime()) when = utils.timeElapsed(now - seconds) + ' ago' else: when = 'time unavailable' title = conv(info.get('title', 'unavailable')) desc = conv(info.get('description', 'unavailable')) link = conv(info.get('link', 'unavailable')) # The rest of the entries are all available in the channel key response = format('Title: %s; URL: %u; ' 'Description: %s; Last updated: %s.', title, link, desc, when) irc.reply(utils.str.normalizeWhitespace(response)) info = wrap(info, [first('url', 'feedName')]) Class = RSS # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/RSS/config.py0000644000000000000000000000770111206611405015654 0ustar ### # 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 import supybot.callbacks as callbacks 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('RSS', True) class FeedNames(registry.SpaceSeparatedListOfStrings): List = callbacks.CanonicalNameSet RSS = conf.registerPlugin('RSS') conf.registerChannelValue(RSS, 'bold', registry.Boolean( True, """Determines whether the bot will bold the title of the feed when it announces new news.""")) conf.registerChannelValue(RSS, 'headlineSeparator', registry.StringSurroundedBySpaces(' || ', """Determines what string is used to separate headlines in new feeds.""")) conf.registerChannelValue(RSS, 'announcementPrefix', registry.StringWithSpaceOnRight('New news from ', """Determines what prefix is prepended (if any) to the new news item announcements made in the channel.""")) conf.registerChannelValue(RSS, 'announce', registry.SpaceSeparatedSetOfStrings([], """Determines which RSS feeds should be announced in the channel; valid input is a list of strings (either registered RSS feeds or RSS feed URLs) separated by spaces.""")) conf.registerGlobalValue(RSS, 'waitPeriod', registry.PositiveInteger(1800, """Indicates how many seconds the bot will wait between retrieving RSS feeds; requests made within this period will return cached results.""")) conf.registerGlobalValue(RSS, 'feeds', FeedNames([], """Determines what feeds should be accessible as commands.""")) conf.registerChannelValue(RSS, 'showLinks', registry.Boolean(False, """Determines whether the bot will list the link along with the title of the feed when the rss command is called. supybot.plugins.RSS.announce.showLinks affects whether links will be listed when a feed is automatically announced.""")) conf.registerGroup(RSS, 'announce') conf.registerChannelValue(RSS.announce, 'showLinks', registry.Boolean(False, """Determines whether the bot will list the link along with the title of the feed when a feed is automatically announced.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Web/0000755000000000000000000000000011206611405014076 5ustar supybot-0.83.4.1.ds.orig/plugins/Web/test.py0000644000000000000000000001441011206611405015427 0ustar ### # 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 * class WebTestCase(ChannelPluginTestCase): plugins = ('Web',) timeout = 10 if network: def testHeaders(self): self.assertError('headers ftp://ftp.cdrom.com/pub/linux') self.assertNotError('headers http://www.slashdot.org/') def testDoctype(self): self.assertError('doctype ftp://ftp.cdrom.com/pub/linux') self.assertNotError('doctype http://www.slashdot.org/') m = self.getMsg('doctype http://moobot.sf.net/') self.failUnless(m.args[1].endswith('>')) def testSize(self): self.assertError('size ftp://ftp.cdrom.com/pub/linux') self.assertNotError('size http://supybot.sf.net/') self.assertNotError('size http://www.slashdot.org/') def testTitle(self): self.assertResponse('title http://www.slashdot.org/', 'Slashdot - News for nerds, stuff that matters') # Amazon add a bunch of scripting stuff to the top of their page, # so we need to allow for a larger peekSize # Actually, screw Amazon. Even bumping this up to 10k doesn't give us enough # info. # try: # orig = conf.supybot.protocols.http.peekSize() # conf.supybot.protocols.http.peekSize.setValue(8192) # self.assertNotRegexp('title ' # 'http://www.amazon.com/exec/obidos/tg/detail/-/' # '1884822312/qid=1063140754/sr=8-1/ref=sr_8_1/' # '002-9802970-2308826?v=glance&s=books&n=507846', # 'no HTML title') # finally: # conf.supybot.protocols.http.peekSize.setValue(orig) # Checks the non-greediness of the regexp self.assertResponse('title ' 'http://www.space.com/scienceastronomy/' 'jupiter_dark_spot_031023.html', 'SPACE.com -- Mystery Spot on Jupiter Baffles ' 'Astronomers') # Checks for @title not-working correctly self.assertResponse('title ' 'http://www.catb.org/~esr/jargon/html/F/foo.html', 'foo') # Checks for only grabbing the real title tags instead of title # tags inside, for example, script tags. Bug #1190350 self.assertNotRegexp('title ' 'http://www.irinnews.org/report.asp?ReportID=45910&' 'SelectRegion=West_Africa&SelectCountry=CHAD', r'document\.write\(') # Checks that title parser grabs the full title instead of just # part of it. self.assertRegexp('title http://www.n-e-r-d.com/', 'N.*E.*R.*D') # Checks that the parser doesn't hang on invalid tags print print "If we have not fixed a bug with the parser, the following", print "test will hang the test-suite." self.assertNotError( 'title http://www.youtube.com/watch?v=x4BtiqPN4u8') def testNetcraft(self): self.assertNotError('netcraft slashdot.org') def testTitleSnarfer(self): try: conf.supybot.plugins.Web.titleSnarfer.setValue(True) self.assertSnarfResponse('http://microsoft.com/', 'Title: Microsoft Corporation' ' (at microsoft.com)') finally: conf.supybot.plugins.Web.titleSnarfer.setValue(False) def testNonSnarfing(self): snarf = conf.supybot.plugins.Web.nonSnarfingRegexp() title = conf.supybot.plugins.Web.titleSnarfer() try: conf.supybot.plugins.Web.nonSnarfingRegexp.set('m/sf/') try: conf.supybot.plugins.Web.titleSnarfer.setValue(True) self.assertSnarfNoResponse('http://sf.net/', 2) self.assertSnarfRegexp('http://www.sourceforge.net/', r'Sourceforge\.net') finally: conf.supybot.plugins.Web.titleSnarfer.setValue(title) finally: conf.supybot.plugins.Web.nonSnarfingRegexp.setValue(snarf) def testNonSnarfingRegexpConfigurable(self): self.assertSnarfNoResponse('http://foo.bar.baz/', 2) try: conf.supybot.plugins.Web.nonSnarfingRegexp.set('m/biff/') self.assertSnarfNoResponse('http://biff.bar.baz/', 2) finally: conf.supybot.plugins.Web.nonSnarfingRegexp.set('') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Web/__init__.py0000644000000000000000000000427011206611405016212 0ustar ### # 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. ### """ Includes various web-related commands. """ import supybot import supybot.world as world __version__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Web/README.txt0000644000000000000000000000011711206611405015573 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Web/plugin.py0000644000000000000000000002144511206611405015754 0ustar ### # 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 re import HTMLParser import htmlentitydefs import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Title(HTMLParser.HTMLParser): entitydefs = htmlentitydefs.entitydefs.copy() entitydefs['nbsp'] = ' ' entitydefs['apos'] = '\'' def __init__(self): self.inTitle = False self.title = '' HTMLParser.HTMLParser.__init__(self) def handle_starttag(self, tag, attrs): if tag == 'title': self.inTitle = True def handle_endtag(self, tag): if tag == 'title': self.inTitle = False def handle_data(self, data): if self.inTitle: self.title += data def handle_entityref(self, name): if self.inTitle: if name in self.entitydefs: self.title += self.entitydefs[name] class Web(callbacks.PluginRegexp): """Add the help for "@help Web" here.""" threaded = True regexps = ['titleSnarfer'] def callCommand(self, command, irc, msg, *args, **kwargs): try: super(Web, self).callCommand(command, irc, msg, *args, **kwargs) except utils.web.Error, e: irc.reply(str(e)) def titleSnarfer(self, irc, msg, match): r"https?://[^\])>\s]+" channel = msg.args[0] if not irc.isChannel(channel): return if callbacks.addressed(irc.nick, msg): return if self.registryValue('titleSnarfer', channel): url = match.group(0) r = self.registryValue('nonSnarfingRegexp', channel) if r and r.search(url): self.log.debug('Not titleSnarfing %q.', url) return try: size = conf.supybot.protocols.http.peekSize() text = utils.web.getUrl(url, size=size) except utils.web.Error, e: self.log.info('Couldn\'t snarf title of %u: %s.', url, e) return parser = Title() try: parser.feed(text) except HTMLParser.HTMLParseError: self.log.debug('Encountered a problem parsing %u. Title may ' 'already be set, though', url) if parser.title: domain = utils.web.getDomain(url) title = utils.web.htmlToText(parser.title.strip()) s = format('Title: %s (at %s)', title, domain) irc.reply(s, prefixNick=False) titleSnarfer = urlSnarfer(titleSnarfer) def headers(self, irc, msg, args, url): """ Returns the HTTP headers of . Only HTTP urls are valid, of course. """ fd = utils.web.getUrlFd(url) try: s = ', '.join([format('%s: %s', k, v) for (k, v) in fd.headers.items()]) irc.reply(s) finally: fd.close() headers = wrap(headers, ['httpUrl']) _doctypeRe = re.compile(r'(]+>)', re.M) def doctype(self, irc, msg, args, url): """ Returns the DOCTYPE string of . Only HTTP urls are valid, of course. """ size = conf.supybot.protocols.http.peekSize() s = utils.web.getUrl(url, size=size) m = self._doctypeRe.search(s) if m: s = utils.str.normalizeWhitespace(m.group(0)) irc.reply(s) else: irc.reply('That URL has no specified doctype.') doctype = wrap(doctype, ['httpUrl']) def size(self, irc, msg, args, url): """ Returns the Content-Length header of . Only HTTP urls are valid, of course. """ fd = utils.web.getUrlFd(url) try: try: size = fd.headers['Content-Length'] irc.reply(format('%u is %i bytes long.', url, size)) except KeyError: size = conf.supybot.protocols.http.peekSize() s = fd.read(size) if len(s) != size: irc.reply(format('%u is %i bytes long.', url, len(s))) else: irc.reply(format('The server didn\'t tell me how long %u ' 'is but it\'s longer than %i bytes.', url, size)) finally: fd.close() size = wrap(size, ['httpUrl']) def title(self, irc, msg, args, url): """ Returns the HTML ... of a URL. """ size = conf.supybot.protocols.http.peekSize() text = utils.web.getUrl(url, size=size) parser = Title() try: parser.feed(text) except HTMLParser.HTMLParseError: self.log.debug('Encountered a problem parsing %u. Title may ' 'already be set, though', url) if parser.title: irc.reply(utils.web.htmlToText(parser.title.strip())) elif len(text) < size: irc.reply('That URL appears to have no HTML title.') else: irc.reply(format('That URL appears to have no HTML title ' 'within the first %i bytes.', size)) title = wrap(title, ['httpUrl']) _netcraftre = re.compile(r'td align="left">\s+]+>(.*?) Returns Netcraft.com's determination of what operating system and webserver is running on the host given. """ url = 'http://uptime.netcraft.com/up/graph/?host=' + hostname html = utils.web.getUrl(url) m = self._netcraftre.search(html) if m: html = m.group(1) s = utils.web.htmlToText(html, tagReplace='').strip() s = s.rstrip('-').strip() irc.reply(s) # Snip off "the site" elif 'We could not get any results' in html: irc.reply('No results found for %s.' % hostname) else: irc.error('The format of page the was odd.') netcraft = wrap(netcraft, ['text']) def urlquote(self, irc, msg, args, text): """ Returns the URL quoted form of the text. """ irc.reply(utils.web.urlquote(text)) urlquote = wrap(urlquote, ['text']) def urlunquote(self, irc, msg, args, text): """ Returns the text un-URL quoted. """ s = utils.web.urlunquote(text) irc.reply(s) urlunquote = wrap(urlunquote, ['text']) def fetch(self, irc, msg, args, url): """ Returns the contents of , or as much as is configured in supybot.plugins.Web.fetch.maximum. If that configuration variable is set to 0, this command will be effectively disabled. """ max = self.registryValue('fetch.maximum') if not max: irc.error('This command is disabled ' '(supybot.plugins.Web.fetch.maximum is set to 0).', Raise=True) fd = utils.web.getUrlFd(url) irc.reply(fd.read(max)) fetch = wrap(fetch, ['url']) Class = Web # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Web/config.py0000644000000000000000000000605211206611405015720 0ustar ### # 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 Web = conf.registerPlugin('Web', True) if yn("""This plugin also offers a snarfer that will try to fetch the title of URLs that it sees in the channel. Would like you this snarfer to be enabled?""", default=False): Web.titleSnarfer.setValue(True) Web = conf.registerPlugin('Web') conf.registerChannelValue(Web, 'titleSnarfer', registry.Boolean(False, """Determines whether the bot will output the HTML title of URLs it sees in the channel.""")) conf.registerChannelValue(Web, 'nonSnarfingRegexp', registry.Regexp(None, """Determines what URLs are to be snarfed and stored in the database in the channel; URLs matching the regexp given will not be snarfed. Give the empty string if you have no URLs that you'd like to exclude from being snarfed.""")) conf.registerGroup(Web, 'fetch') conf.registerGlobalValue(Web.fetch, 'maximum', registry.NonNegativeInteger(0, """Determines the maximum number of bytes the bot will download via the 'fetch' command in this plugin.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/URL/0000755000000000000000000000000011206611405014023 5ustar supybot-0.83.4.1.ds.orig/plugins/URL/test.py0000644000000000000000000000774211206611405015366 0ustar ### # 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 * urls = """ http://www.ureg.ohio-state.edu/courses/book3.asp http://wwwsearch.sourceforge.net/ClientForm/ http://slashdot.org/comments.pl?sid=75443&cid=6747654 http://baseball-almanac.com/rb_menu.shtml http://www.linuxquestions.org/questions/showthread.php?postid=442905#post442905 http://games.slashdot.org/comments.pl?sid=76027&cid=6785588' http://games.slashdot.org/comments.pl?sid=76027&cid=6785588 http://www.census.gov/ftp/pub/tiger/tms/gazetteer/zcta5.zip http://slashdot.org/~Strike http://lambda.weblogs.com/xml/rss.xml' http://lambda.weblogs.com/xml/rss.xml http://www.sourcereview.net/forum/index.php?showforum=8 http://www.sourcereview.net/forum/index.php?showtopic=291 http://www.sourcereview.net/forum/index.php?showtopic=291&st=0&#entry1778 http://dhcp065-024-059-168.columbus.rr.com:81/~jfincher/old-supybot.tar.gz http://www.sourcereview.net/forum/index.php? http://www.joelonsoftware.com/articles/BuildingCommunitieswithSo.html http://gameknot.com/stats.pl?ddipaolo http://slashdot.org/slashdot.rss http://gameknot.com/chess.pl?bd=1038943 http://codecentral.sleepwalkers.org/ http://gameknot.com/chess.pl?bd=1037471&r=327 http://dhcp065-024-059-168.columbus.rr.com:81/~jfincher/angryman.py https://sourceforge.net/projects/pyrelaychecker/ http://gameknot.com/tsignup.pl """.strip().splitlines() class URLTestCase(ChannelPluginTestCase): plugins = ('URL',) def test(self): counter = 0 #self.assertNotError('url random') for url in urls: self.assertRegexp('url stats', str(counter)) self.feedMsg(url) counter += 1 self.assertRegexp('url stats', str(counter)) self.assertRegexp('url last', re.escape(urls[-1])) self.assertRegexp('url last --proto https', re.escape(urls[-2])) self.assertRegexp('url last --with gameknot.com', re.escape(urls[-1])) self.assertRegexp('url last --with dhcp', re.escape(urls[-3])) self.assertRegexp('url last --from alsdkjf', '^No') self.assertRegexp('url last --without game', 'sourceforge') #self.assertNotError('url random') def testDefaultNotFancy(self): self.feedMsg(urls[0]) self.assertResponse('url last', urls[0]) def testAction(self): self.irc.feedMsg(ircmsgs.action(self.channel, urls[1])) self.assertNotRegexp('url last', '\\x01') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/URL/__init__.py0000644000000000000000000000473011206611405016140 0ustar ### # 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. ### """ Keeps track of URLs posted to a channel, along with relevant context. Allows searching for URLs and returning random URLs. Also provides statistics on the URLs in the database. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/URL/README.txt0000644000000000000000000000011711206611405015520 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/URL/plugin.py0000644000000000000000000001401711206611405015676 0ustar ### # 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 supybot.dbi as dbi 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.callbacks as callbacks class UrlRecord(dbi.Record): __fields__ = [ ('url', eval), ('by', eval), ('near', eval), ('at', eval), ] class DbiUrlDB(plugins.DbiChannelDB): class DB(dbi.DB): Record = UrlRecord def add(self, url, msg): record = self.Record(url=url, by=msg.nick, near=msg.args[1], at=msg.receivedAt) super(self.__class__, self).add(record) def urls(self, p): L = list(self.select(p)) L.reverse() return L URLDB = plugins.DB('URL', {'flat': DbiUrlDB}) class URL(callbacks.Plugin): def __init__(self, irc): self.__parent = super(URL, self) self.__parent.__init__(irc) self.db = URLDB() def doPrivmsg(self, irc, msg): channel = msg.args[0] if irc.isChannel(channel): if ircmsgs.isAction(msg): text = ircmsgs.unAction(msg) else: text = msg.args[1] for url in utils.web.urlRe.findall(text): r = self.registryValue('nonSnarfingRegexp', channel) if r and r.search(url): self.log.debug('Skipping adding %u to db.', url) continue self.log.debug('Adding %u to db.', url) self.db.add(channel, url, msg) def stats(self, irc, msg, args, channel): """[] Returns the number of URLs in the URL database. is only required if the message isn't sent in the channel itself. """ self.db.vacuum(channel) count = self.db.size(channel) irc.reply(format('I have %n in my database.', (count, 'URL'))) stats = wrap(stats, ['channeldb']) def last(self, irc, msg, args, channel, optlist): """[] [--{from,with,without,near,proto} ] [--nolimit] Gives the last URL matching the given criteria. --from is from whom the URL came; --proto is the protocol the URL used; --with is something inside the URL; --without is something that should not be in the URL; --near is something in the same message as the URL; If --nolimit is given, returns all the URLs that are found. to just the URL. is only necessary if the message isn't sent in the channel itself. """ predicates = [] f = None nolimit = False for (option, arg) in optlist: if isinstance(arg, basestring): arg = arg.lower() if option == 'nolimit': nolimit = True elif option == 'from': def f(record, arg=arg): return ircutils.strEqual(record.by, arg) elif option == 'with': def f(record, arg=arg): return arg in record.url.lower() elif option == 'without': def f(record, arg=arg): return arg not in record.url.lower() elif option == 'proto': def f(record, arg=arg): return record.url.lower().startswith(arg) elif option == 'near': def f(record, arg=arg): return arg in record.near.lower() if f is not None: predicates.append(f) def predicate(record): for predicate in predicates: if not predicate(record): return False return True urls = [record.url for record in self.db.urls(channel, predicate)] if not urls: irc.reply('No URLs matched that criteria.') else: if nolimit: urls = [format('%u', url) for url in urls] s = ', '.join(urls) else: # We should optimize this with another URLDB method eventually. s = urls[0] irc.reply(s) last = wrap(last, ['channeldb', getopts({'from': 'something', 'with': 'something', 'near': 'something', 'proto': 'something', 'nolimit': '', 'without': 'something',})]) Class = URL # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/URL/config.py0000644000000000000000000000463311206611405015650 0ustar ### # 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('URL', True) URL = conf.registerPlugin('URL') conf.registerChannelValue(URL, 'nonSnarfingRegexp', registry.Regexp(None, """Determines what URLs are to be snarfed and stored in the database in the channel; URLs matching the regexp given will not be snarfed. Give the empty string if you have no URLs that you'd like to exclude from being snarfed.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Factoids/0000755000000000000000000000000011206611405015115 5ustar supybot-0.83.4.1.ds.orig/plugins/Factoids/test.py0000644000000000000000000001757411206611405016464 0ustar ### # 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 * try: import sqlite except ImportError: sqlite = None if sqlite: class FactoidsTestCase(ChannelPluginTestCase): plugins = ('Factoids',) def testRandomfactoid(self): self.assertError('random') self.assertNotError('learn jemfinch as my primary author') self.assertRegexp('random', 'primary author') def testLearn(self): self.assertNotError('learn jemfinch as my primary author') self.assertNotError('info jemfinch') self.assertRegexp('whatis jemfinch', 'my primary author') self.assertRegexp('whatis JEMFINCH', 'my primary author') self.assertRegexp('whatis JEMFINCH 1', 'my primary author') self.assertNotError('learn jemfinch as a bad assembly programmer') self.assertRegexp('whatis jemfinch 2', 'bad assembly') self.assertNotRegexp('whatis jemfinch 2', 'primary author') self.assertRegexp('whatis jemfinch', r'.*primary author.*assembly') self.assertError('forget jemfinch') self.assertError('forget jemfinch 3') self.assertError('forget jemfinch 0') self.assertNotError('forget jemfinch 2') self.assertNotError('forget jemfinch 1') self.assertError('whatis jemfinch') self.assertError('info jemfinch') self.assertNotError('learn foo bar as baz') self.assertNotError('info foo bar') self.assertRegexp('whatis foo bar', 'baz') self.assertNotError('learn foo bar as quux') self.assertRegexp('whatis foo bar', '.*baz.*quux') self.assertError('forget foo bar') self.assertNotError('forget foo bar 2') self.assertNotError('forget foo bar 1') self.assertError('whatis foo bar') self.assertError('info foo bar') self.assertError('learn foo bar baz') # No 'as' self.assertError('learn foo bar') # No 'as' def testChangeFactoid(self): self.assertNotError('learn foo as bar') self.assertNotError('change foo 1 s/bar/baz/') self.assertRegexp('whatis foo', 'baz') self.assertError('change foo 2 s/bar/baz/') self.assertError('change foo 0 s/bar/baz/') def testSearchFactoids(self): self.assertNotError('learn jemfinch as my primary author') self.assertNotError('learn strike as a cool guy working on me') self.assertNotError('learn inkedmn as another of my developers') self.assertNotError('learn jamessan as a developer of much python') self.assertNotError('learn bwp as author of my weather command') self.assertRegexp('factoids search --regexp /.w./', 'bwp') self.assertRegexp('factoids search --regexp /^.+i/', 'jemfinch.*strike') self.assertNotRegexp('factoids search --regexp /^.+i/', 'inkedmn') self.assertRegexp('factoids search --regexp m/j/ --regexp m/ss/', 'jamessan') self.assertRegexp('factoids search --regexp m/^j/ *ss*', 'jamessan') self.assertRegexp('factoids search --regexp /^j/', 'jemfinch.*jamessan') self.assertRegexp('factoids search j*', 'jemfinch.*jamessan') self.assertRegexp('factoids search *ke*', 'inkedmn.*strike|strike.*inkedmn') self.assertRegexp('factoids search ke', 'inkedmn.*strike|strike.*inkedmn') self.assertRegexp('factoids search jemfinch', 'my primary author') self.assertRegexp('factoids search --values primary author', 'my primary author') def testWhatisOnNumbers(self): self.assertNotError('learn 911 as emergency number') self.assertRegexp('whatis 911', 'emergency number') def testNotZeroIndexed(self): self.assertNotError('learn foo as bar') self.assertNotRegexp('info foo', '#0') self.assertNotRegexp('whatis foo', '#0') self.assertNotError('learn foo as baz') self.assertNotRegexp('info foo', '#0') self.assertNotRegexp('whatis foo', '#0') def testInfoReturnsRightNumber(self): self.assertNotError('learn foo as bar') self.assertNotRegexp('info foo', '2 factoids') def testLearnSeparator(self): self.assertError('learn foo is bar') self.assertNotError('learn foo as bar') self.assertRegexp('whatis foo', 'bar') orig = conf.supybot.plugins.Factoids.learnSeparator() try: conf.supybot.plugins.Factoids.learnSeparator.setValue('is') self.assertError('learn bar as baz') self.assertNotError('learn bar is baz') self.assertRegexp('whatis bar', 'baz') finally: conf.supybot.plugins.Factoids.learnSeparator.setValue(orig) def testShowFactoidIfOnlyOneMatch(self): m1 = self.assertNotError('factoids search m/foo|bar/') orig = conf.supybot.plugins.Factoids.showFactoidIfOnlyOneMatch() try: conf.supybot.plugins.Factoids. \ showFactoidIfOnlyOneMatch.setValue(False) m2 = self.assertNotError('factoids search m/foo/') self.failUnless(m1.args[1].startswith(m2.args[1])) finally: conf.supybot.plugins.Factoids. \ showFactoidIfOnlyOneMatch.setValue(orig) def testInvalidCommand(self): orig = conf.supybot.plugins.Factoids.replyWhenInvalidCommand() try: conf.supybot.plugins.Factoids.\ replyWhenInvalidCommand.setValue(True) self.assertNotError('learn foo as bar') self.assertRegexp('foo', 'bar') finally: conf.supybot.plugins.Factoids.\ replyWhenInvalidCommand.setValue(orig) def testQuoteHandling(self): self.assertNotError('learn foo as "\\"bar\\""') self.assertRegexp('whatis foo', r'"bar"') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Factoids/__init__.py0000644000000000000000000000504511206611405017232 0ustar ### # 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. ### """ Handles 'factoids,' little tidbits of information held in a database and available on demand via several commands. """ 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__ = "0.1" __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} # This is a url where the most recent plugin package can be downloaded. __url__ = '' # 'http://supybot.com/Members/yourname/Factoids/download' 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: supybot-0.83.4.1.ds.orig/plugins/Factoids/README.txt0000644000000000000000000000011711206611405016612 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Factoids/plugin.py0000644000000000000000000004250311206611405016771 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 time import string import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks try: import sqlite except ImportError: raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ 'plugin. Download it at ' def getFactoid(irc, msg, args, state): assert not state.channel callConverter('channel', irc, msg, args, state) separator = state.cb.registryValue('learnSeparator', state.channel) try: i = args.index(separator) except ValueError: raise callbacks.ArgumentError args.pop(i) key = [] value = [] for (j, s) in enumerate(args[:]): if j < i: key.append(args.pop(0)) else: value.append(args.pop(0)) state.args.append(' '.join(key)) state.args.append(' '.join(value)) def getFactoidId(irc, msg, args, state): Type = 'key id' p = lambda i: i > 0 callConverter('int', irc, msg, args, state, Type, p) addConverter('factoid', getFactoid) addConverter('factoidId', getFactoidId) class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): def __init__(self, irc): callbacks.Plugin.__init__(self, irc) plugins.ChannelDBHandler.__init__(self) def makeDb(self, filename): if os.path.exists(filename): return sqlite.connect(filename) db = sqlite.connect(filename) cursor = db.cursor() cursor.execute("""CREATE TABLE keys ( id INTEGER PRIMARY KEY, key TEXT UNIQUE ON CONFLICT IGNORE, locked BOOLEAN )""") cursor.execute("""CREATE TABLE factoids ( id INTEGER PRIMARY KEY, key_id INTEGER, added_by TEXT, added_at TIMESTAMP, fact TEXT )""") cursor.execute("""CREATE TRIGGER remove_factoids BEFORE DELETE ON keys BEGIN DELETE FROM factoids WHERE key_id = old.id; END """) db.commit() return db def getCommandHelp(self, command, simpleSyntax=None): method = self.getCommandMethod(command) if method.im_func.func_name == 'learn': chan = None if dynamic.msg is not None: chan = dynamic.msg.args[0] s = self.registryValue('learnSeparator', chan) help = callbacks.getHelp if simpleSyntax is None: simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan) if simpleSyntax: help = callbacks.getSyntax return help(method, doc=method._fake__doc__ % (s, s), name=callbacks.formatCommand(command)) return super(Factoids, self).getCommandHelp(command, simpleSyntax) def learn(self, irc, msg, args, channel, key, factoid): db = self.getDb(channel) cursor = db.cursor() cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) if cursor.rowcount == 0: cursor.execute("""INSERT INTO keys VALUES (NULL, %s, 0)""", key) db.commit() cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s",key) (id, locked) = map(int, cursor.fetchone()) capability = ircdb.makeChannelCapability(channel, 'factoids') if not locked: if ircdb.users.hasUser(msg.prefix): name = ircdb.users.getUser(msg.prefix).name else: name = msg.nick cursor.execute("""INSERT INTO factoids VALUES (NULL, %s, %s, %s, %s)""", id, name, int(time.time()), factoid) db.commit() irc.replySuccess() else: irc.error('That factoid is locked.') learn = wrap(learn, ['factoid']) learn._fake__doc__ = """[] %s Associates with . is only necessary if the message isn't sent on the channel itself. The word '%s' is necessary to separate the key from the value. It can be changed to another word via the learnSeparator registry value. """ def _lookupFactoid(self, channel, key): db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.fact FROM factoids, keys WHERE keys.key LIKE %s AND factoids.key_id=keys.id ORDER BY factoids.id LIMIT 20""", key) return [t[0] for t in cursor.fetchall()] def _replyFactoids(self, irc, channel, key, factoids, number=0, error=True): if factoids: if number: try: irc.reply(factoids[number-1]) except IndexError: irc.error('That\'s not a valid number for that key.') return else: intro = self.registryValue('factoidPrefix', channel) prefix = format('%q %s', key, intro) if len(factoids) == 1: irc.reply(prefix + factoids[0]) else: factoidsS = [] counter = 1 for factoid in factoids: factoidsS.append(format('(#%i) %s', counter, factoid)) counter += 1 irc.replies(factoidsS, prefixer=prefix, joiner=', or ', onlyPrefixFirst=True) elif error: irc.error('No factoid matches that key.') def invalidCommand(self, irc, msg, tokens): if irc.isChannel(msg.args[0]): channel = msg.args[0] if self.registryValue('replyWhenInvalidCommand', channel): key = ' '.join(tokens) factoids = self._lookupFactoid(channel, key) self._replyFactoids(irc, channel, key, factoids, error=False) def whatis(self, irc, msg, args, channel, words): """[] [] Looks up the value of in the factoid database. If given a number, will return only that exact factoid. is only necessary if the message isn't sent in the channel itself. """ number = None if len(words) > 1: if words[-1].isdigit(): number = int(words.pop()) if number <= 0: irc.errorInvalid('key id') key = ' '.join(words) factoids = self._lookupFactoid(channel, key) self._replyFactoids(irc, channel, key, factoids, number) whatis = wrap(whatis, ['channel', many('something')]) def lock(self, irc, msg, args, channel, key): """[] Locks the factoid(s) associated with so that they cannot be removed or added to. is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("UPDATE keys SET locked=1 WHERE key LIKE %s", key) db.commit() irc.replySuccess() lock = wrap(lock, ['channel', 'text']) def unlock(self, irc, msg, args, channel, key): """[] Unlocks the factoid(s) associated with so that they can be removed or added to. is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("UPDATE keys SET locked=0 WHERE key LIKE %s", key) db.commit() irc.replySuccess() unlock = wrap(unlock, ['channel', 'text']) def forget(self, irc, msg, args, channel, words): """[] [|*] Removes the factoid from the factoids database. If there are more than one factoid with such a key, a number is necessary to determine which one should be removed. A * can be used to remove all factoids associated with a key. is only necessary if the message isn't sent in the channel itself. """ number = None if len(words) > 1: if words[-1].isdigit(): number = int(words.pop()) if number <= 0: irc.errorInvalid('key id') elif words[-1] == '*': words.pop() number = True key = ' '.join(words) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT keys.id, factoids.id FROM keys, factoids WHERE key LIKE %s AND factoids.key_id=keys.id""", key) if cursor.rowcount == 0: irc.error('There is no such factoid.') elif cursor.rowcount == 1 or number is True: (id, _) = cursor.fetchone() cursor.execute("""DELETE FROM factoids WHERE key_id=%s""", id) cursor.execute("""DELETE FROM keys WHERE key LIKE %s""", key) db.commit() irc.replySuccess() else: if number is not None: results = cursor.fetchall() try: (_, id) = results[number-1] except IndexError: irc.error('Invalid factoid number.') return cursor.execute("DELETE FROM factoids WHERE id=%s", id) db.commit() irc.replySuccess() else: irc.error('%s factoids have that key. ' 'Please specify which one to remove, ' 'or use * to designate all of them.' % cursor.rowcount) forget = wrap(forget, ['channel', many('something')]) def random(self, irc, msg, args, channel): """[] Returns a random factoid from the database for . is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact, key_id FROM factoids ORDER BY random() LIMIT 3""") if cursor.rowcount != 0: L = [] for (factoid, id) in cursor.fetchall(): cursor.execute("""SELECT key FROM keys WHERE id=%s""", id) (key,) = cursor.fetchone() L.append('"%s": %s' % (ircutils.bold(key), factoid)) irc.reply('; '.join(L)) else: irc.error('I couldn\'t find a factoid.') random = wrap(random, ['channel']) def info(self, irc, msg, args, channel, key): """[] Gives information about the factoid(s) associated with . is only necessary if the message isn't sent in the channel itself. """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("SELECT id, locked FROM keys WHERE key LIKE %s", key) if cursor.rowcount == 0: irc.error('No factoid matches that key.') return (id, locked) = map(int, cursor.fetchone()) cursor.execute("""SELECT added_by, added_at FROM factoids WHERE key_id=%s ORDER BY id""", id) factoids = cursor.fetchall() L = [] counter = 0 for (added_by, added_at) in factoids: counter += 1 added_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(added_at))) L.append(format('#%i was added by %s at %s', counter, added_by, added_at)) factoids = '; '.join(L) s = format('Key %q is %s and has %n associated with it: %s', key, locked and 'locked' or 'not locked', (counter, 'factoid'), factoids) irc.reply(s) info = wrap(info, ['channel', 'text']) def change(self, irc, msg, args, channel, key, number, replacer): """[] Changes the factoid # associated with according to . """ db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.id, factoids.fact FROM keys, factoids WHERE keys.key LIKE %s AND keys.id=factoids.key_id""", key) if cursor.rowcount == 0: irc.error(format('I couldn\'t find any key %q', key)) return elif cursor.rowcount < number: irc.errorInvalid('key id') (id, fact) = cursor.fetchall()[number-1] newfact = replacer(fact) cursor.execute("UPDATE factoids SET fact=%s WHERE id=%s", newfact, id) db.commit() irc.replySuccess() change = wrap(change, ['channel', 'something', 'factoidId', 'regexpReplacer']) _sqlTrans = string.maketrans('*?', '%_') def search(self, irc, msg, args, channel, optlist, globs): """[] [--values] [--{regexp} ] [ ...] Searches the keyspace for keys matching . If --regexp is given, it associated value is taken as a regexp and matched against the keys. If --values is given, search the value space instead of the keyspace. """ if not optlist and not globs: raise callbacks.ArgumentError tables = ['keys'] formats = [] criteria = [] target = 'keys.key' predicateName = 'p' db = self.getDb(channel) for (option, arg) in optlist: if option == 'values': target = 'factoids.fact' if 'factoids' not in tables: tables.append('factoids') criteria.append('factoids.key_id=keys.id') elif option == 'regexp': criteria.append('%s(TARGET)' % predicateName) def p(s, r=arg): return int(bool(r.search(s))) db.create_function(predicateName, 1, p) predicateName += 'p' for glob in globs: criteria.append('TARGET LIKE %s') formats.append(glob.translate(self._sqlTrans)) cursor = db.cursor() sql = """SELECT keys.key FROM %s WHERE %s""" % \ (', '.join(tables), ' AND '.join(criteria)) sql = sql.replace('TARGET', target) cursor.execute(sql, formats) if cursor.rowcount == 0: irc.reply('No keys matched that query.') elif cursor.rowcount == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel): self.whatis(irc, msg, [cursor.fetchone()[0]]) elif cursor.rowcount > 100: irc.reply('More than 100 keys matched that query; ' 'please narrow your query.') else: keys = [repr(t[0]) for t in cursor.fetchall()] s = format('%L', keys) irc.reply(s) search = wrap(search, ['channel', getopts({'values': '', 'regexp': 'regexpMatcher'}), any('glob')]) Class = Factoids # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Factoids/config.py0000644000000000000000000000610211206611405016733 0ustar ### # 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.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('Factoids', True) Factoids = conf.registerPlugin('Factoids') conf.registerChannelValue(Factoids, 'learnSeparator', registry.String('as', """Determines what separator must be used in the learn command. Defaults to 'as' -- learn as . Users might feel more comfortable with 'is' or something else, so it's configurable.""")) conf.registerChannelValue(Factoids, 'showFactoidIfOnlyOneMatch', registry.Boolean(True, """Determines whether the bot will reply with the single matching factoid if only one factoid matches when using the search command.""")) conf.registerChannelValue(Factoids, 'replyWhenInvalidCommand', registry.Boolean(True, """Determines whether the bot will reply to invalid commands by searching for a factoid; basically making the whatis unnecessary when you want all factoids for a given key.""")) conf.registerChannelValue(Factoids, 'factoidPrefix', registry.StringWithSpaceOnRight('could be ', """Determines the string that factoids will be introduced by.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/ChannelStats/0000755000000000000000000000000011206611405015750 5ustar supybot-0.83.4.1.ds.orig/plugins/ChannelStats/test.py0000644000000000000000000001040711206611405017303 0ustar ### # 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 * import supybot.ircdb as ircdb class ChannelStatsTestCase(ChannelPluginTestCase): plugins = ('ChannelStats', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' self.nick = 'foo' self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'register foo bar', prefix=self.prefix)) _ = self.irc.takeMsg() chanop = ircdb.makeChannelCapability(self.channel, 'op') ircdb.users.getUser(self.nick).addCapability(chanop) def test(self): self.assertNotError('channelstats') self.assertNotError('channelstats') self.assertNotError('channelstats') def testStats(self): self.assertError('channelstats stats %s' % self.nick) self.assertNotError('channelstats stats %s' % self.nick) self.assertNotError('channelstats stats %s' % self.nick.upper()) self.assertNotError('channelstats stats') self.assertRegexp('channelstats stats', self.nick) def testSelfStats(self): self.assertError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick.upper()) u = ircdb.users.getUser(self.prefix) u.addCapability(ircdb.makeChannelCapability(self.channel, 'op')) ircdb.users.setUser(u) try: conf.supybot.plugins.ChannelStats.selfStats.setValue(False) m1 = self.getMsg('channelstats stats %s' % self.irc.nick) m2 = self.getMsg('channelstats stats %s' % self.irc.nick) self.assertEqual(m1.args[1], m2.args[1]) finally: conf.supybot.plugins.ChannelStats.selfStats.setValue(True) def testNoKeyErrorStats(self): self.assertNotRegexp('stats sweede', 'KeyError') def testRank(self): self.assertError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick) self.assertNotError('channelstats stats %s' % self.irc.nick.upper()) self.assertNotError('channelstats stats %s' % self.nick) self.assertNotError('channelstats stats %s' % self.nick.upper()) self.assertNotError('channelstats stats') self.assertNotError('channelstats rank chars / msgs') self.assertNotError('channelstats rank kicks/kicked') # Tests inf self.assertNotError('channelstats rank log(msgs)') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/ChannelStats/__init__.py0000644000000000000000000000475511206611405020074 0ustar ### # 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. ### """ Silently listens to every message received on a channel and keeps statistics concerning joins, parts, and various other commands in addition to tracking statistics about smileys, actions, characters, and 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: supybot-0.83.4.1.ds.orig/plugins/ChannelStats/README.txt0000644000000000000000000000011711206611405017445 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/ChannelStats/plugin.py0000644000000000000000000003245411206611405017630 0ustar ### # 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 types import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class ChannelStat(irclib.IrcCommandDispatcher): _values = ['actions', 'chars', 'frowns', 'joins', 'kicks','modes', 'msgs', 'parts', 'quits', 'smileys', 'topics', 'words'] def __init__(self, actions=0, chars=0, frowns=0, joins=0, kicks=0, modes=0, msgs=0, parts=0, quits=0, smileys=0, topics=0, words=0): self.actions = actions self.chars = chars self.frowns = frowns self.joins = joins self.kicks = kicks self.modes = modes self.msgs = msgs self.parts = parts self.quits = quits self.smileys = smileys self.topics = topics self.words = words def values(self): return [getattr(self, s) for s in self._values] def addMsg(self, msg): self.msgs += 1 method = self.dispatchCommand(msg.command) if method is not None: method(msg) def doPayload(self, channel, payload): channel = plugins.getChannel(channel) self.chars += len(payload) self.words += len(payload.split()) fRe = conf.supybot.plugins.ChannelStats.get('frowns').get(channel)() sRe =conf.supybot.plugins.ChannelStats.get('smileys').get(channel)() self.frowns += len(fRe.findall(payload)) self.smileys += len(sRe.findall(payload)) def doPrivmsg(self, msg): self.doPayload(*msg.args) if ircmsgs.isAction(msg): self.actions += 1 def doTopic(self, msg): self.doPayload(*msg.args) self.topics += 1 def doKick(self, msg): self.kicks += 1 def doPart(self, msg): if len(msg.args) == 2: self.doPayload(*msg.args) self.parts += 1 def doJoin(self, msg): if len(msg.args) == 2: self.doPayload(*msg.args) self.joins += 1 def doMode(self, msg): self.modes += 1 # doQuit is handled by the plugin. class UserStat(ChannelStat): _values = ['kicked'] + ChannelStat._values def __init__(self, kicked=0, *args): ChannelStat.__init__(self, *args) self.kicked = kicked def doKick(self, msg): self.doPayload(msg.args[0], msg.args[2]) self.kicks += 1 class StatsDB(plugins.ChannelUserDB): def __init__(self, *args, **kwargs): plugins.ChannelUserDB.__init__(self, *args, **kwargs) def serialize(self, v): return v.values() def deserialize(self, channel, id, L): L = map(int, L) if id == 'channelStats': return ChannelStat(*L) else: return UserStat(*L) def addMsg(self, msg, id=None): if ircutils.isChannel(msg.args[0]): channel = plugins.getChannel(msg.args[0]) if (channel, 'channelStats') not in self: self[channel, 'channelStats'] = ChannelStat() self[channel, 'channelStats'].addMsg(msg) try: if id is None: id = ircdb.users.getUserId(msg.prefix) except KeyError: return if (channel, id) not in self: self[channel, id] = UserStat() self[channel, id].addMsg(msg) def getChannelStats(self, channel): return self[channel, 'channelStats'] def getUserStats(self, channel, id): return self[channel, id] filename = conf.supybot.directories.data.dirize('ChannelStats.db') class ChannelStats(callbacks.Plugin): noIgnore = True def __init__(self, irc): self.__parent = super(ChannelStats, self) self.__parent.__init__(irc) self.lastmsg = None self.laststate = None self.outFiltering = False self.db = StatsDB(filename) self._flush = self.db.flush world.flushers.append(self._flush) def die(self): world.flushers.remove(self._flush) self.db.close() self.__parent.die() def __call__(self, irc, msg): try: if self.lastmsg: self.laststate.addMsg(irc, self.lastmsg) else: self.laststate = irc.state.copy() finally: self.lastmsg = msg self.db.addMsg(msg) super(ChannelStats, self).__call__(irc, msg) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG': if ircutils.isChannel(msg.args[0]): if self.registryValue('selfStats', msg.args[0]): try: self.outFiltering = True self.db.addMsg(msg, 0) finally: self.outFiltering = False return msg def doQuit(self, irc, msg): try: id = ircdb.users.getUserId(msg.prefix) except KeyError: id = None for (channel, c) in self.laststate.channels.iteritems(): if msg.nick in c.users: if (channel, 'channelStats') not in self.db: self.db[channel, 'channelStats'] = ChannelStat() self.db[channel, 'channelStats'].quits += 1 if id is not None: if (channel, id) not in self.db: self.db[channel, id] = UserStat() self.db[channel, id].quits += 1 def doKick(self, irc, msg): (channel, nick, _) = msg.args hostmask = irc.state.nickToHostmask(nick) try: id = ircdb.users.getUserId(hostmask) except KeyError: return if channel not in self.db.channels: self.db.channels[channel] = {} if id not in self.db.channels[channel]: self.db.channels[channel][id] = UserStat() self.db.channels[channel][id].kicked += 1 def stats(self, irc, msg, args, channel, name): """[] [] Returns the statistics for on . is only necessary if the message isn't sent on the channel itself. If isn't given, it defaults to the user sending the command. """ if name and ircutils.strEqual(name, irc.nick): id = 0 elif not name: try: id = ircdb.users.getUserId(msg.prefix) name = ircdb.users.getUser(id).name except KeyError: irc.error('I couldn\'t find you in my user database.') return elif not ircdb.users.hasUser(name): try: hostmask = irc.state.nickToHostmask(name) id = ircdb.users.getUserId(hostmask) except KeyError: irc.errorNoUser() return else: id = ircdb.users.getUserId(name) try: stats = self.db.getUserStats(channel, id) s = format('%s has sent %n; a total of %n, %n, ' '%n, and %n; %s of those messages %s' '%s has joined %n, parted %n, quit %n, ' 'kicked someone %n, been kicked %n, ' 'changed the topic %n, and changed the ' 'mode %n.', name, (stats.msgs, 'message'), (stats.chars, 'character'), (stats.words, 'word'), (stats.smileys, 'smiley'), (stats.frowns, 'frown'), stats.actions, stats.actions == 1 and 'was an ACTION. ' or 'were ACTIONs. ', name, (stats.joins, 'time'), (stats.parts, 'time'), (stats.quits, 'time'), (stats.kicks, 'time'), (stats.kicked, 'time'), (stats.topics, 'time'), (stats.modes, 'time')) irc.reply(s) except KeyError: irc.error(format('I have no stats for that %s in %s.', name, channel)) stats = wrap(stats, ['channeldb', additional('something')]) _env = {'__builtins__': types.ModuleType('__builtins__')} _env.update(math.__dict__) def rank(self, irc, msg, args, channel, expr): """[] Returns the ranking of users according to the given stat expression. Valid variables in the stat expression include 'msgs', 'chars', 'words', 'smileys', 'frowns', 'actions', 'joins', 'parts', 'quits', 'kicks', 'kicked', 'topics', and 'modes'. Any simple mathematical expression involving those variables is permitted. """ # XXX I could do this the right way, and abstract out a safe eval, # or I could just copy/paste from the Math plugin. if expr != expr.translate(utils.str.chars, '_[]'): irc.error('There\'s really no reason why you should have ' 'underscores or brackets in your mathematical ' 'expression. Please remove them.', Raise=True) if 'lambda' in expr: irc.error('You can\'t use lambda in this command.', Raise=True) expr = expr.lower() users = [] for ((c, id), stats) in self.db.items(): if ircutils.strEqual(c, channel) and ircdb.users.hasUser(id): e = self._env.copy() for attr in stats._values: e[attr] = float(getattr(stats, attr)) try: v = eval(expr, e, e) except ZeroDivisionError: v = float('inf') except NameError, e: irc.errorInvalid('stat variable', str(e).split()[1]) except Exception, e: irc.error(utils.exnToString(e), Raise=True) users.append((v, ircdb.users.getUser(id).name)) users.sort() users.reverse() s = utils.str.commaAndify(['#%s %s (%.3g)' % (i, u, v) for (i, (v, u)) in enumerate(users)]) irc.reply(s) rank = wrap(rank, ['channeldb', 'text']) def channelstats(self, irc, msg, args, channel): """[] Returns the statistics for . is only necessary if the message isn't sent on the channel itself. """ try: stats = self.db.getChannelStats(channel) s = format('On %s there have been %i messages, containing %i ' 'characters, %n, %n, and %n; ' '%i of those messages %s. There have been ' '%n, %n, %n, %n, %n, and %n.', channel, stats.msgs, stats.chars, (stats.words, 'word'), (stats.smileys, 'smiley'), (stats.frowns, 'frown'), stats.actions, stats.actions == 1 and 'was an ACTION' or 'were ACTIONs', (stats.joins, 'join'), (stats.parts, 'part'), (stats.quits, 'quit'), (stats.kicks, 'kick'), (stats.modes, 'mode', 'change'), (stats.topics, 'topic', 'change')) irc.reply(s) except KeyError: irc.error(format('I\'ve never been on %s.', channel)) channelstats = wrap(channelstats, ['channeldb']) Class = ChannelStats # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/ChannelStats/config.py0000644000000000000000000000621611206611405017574 0ustar ### # 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 re 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('ChannelStats', True) class Smileys(registry.Value): def set(self, s): L = s.split() self.setValue(L) def setValue(self, v): self.s = ' '.join(v) self.value = re.compile('|'.join(map(re.escape, v))) def __str__(self): return self.s ChannelStats = conf.registerPlugin('ChannelStats') conf.registerChannelValue(ChannelStats, 'selfStats', registry.Boolean(True, """Determines whether the bot will keep channel statistics on itself, possibly skewing the channel stats (especially in cases where the bot is relaying between channels on a network).""")) conf.registerChannelValue(ChannelStats, 'smileys', Smileys(':) ;) ;] :-) :-D :D :P :p (= =)'.split(), """Determines what words (i.e., pieces of text with no spaces in them) are considered 'smileys' for the purposes of stats-keeping.""")) conf.registerChannelValue(ChannelStats, 'frowns', Smileys(':| :-/ :-\\ :\\ :/ :( :-( :\'('.split(), """Determines what words (i.e., pieces of text with no spaces in them ) are considered 'frowns' for the purposes of stats-keeping.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Status/0000755000000000000000000000000011206611405014644 5ustar supybot-0.83.4.1.ds.orig/plugins/Status/test.py0000644000000000000000000000565311206611405016206 0ustar ### # 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 sys import supybot.world as world class StatusTestCase(PluginTestCase): plugins = ('Status',) def testNet(self): self.assertNotError('net') def testCpu(self): m = self.assertNotError('status cpu') self.failIf('kB kB' in m.args[1]) self.failIf('None' in m.args[1], 'None in cpu output: %r.' % m) for s in ['linux', 'freebsd', 'openbsd', 'netbsd', 'darwin']: if sys.platform.startswith(s): self.failUnless('kB' in m.args[1], 'No memory string on supported platform.') try: original = conf.supybot.plugins.Status.cpu.get('children')() conf.supybot.plugins.Status.cpu.get('children').setValue(False) self.assertNotRegexp('cpu', 'children') finally: conf.supybot.plugins.Status.cpu.get('children').setValue(original) def testUptime(self): self.assertNotError('uptime') def testCmd(self): self.assertNotError('cmd') def testCommands(self): self.assertNotError('commands') def testLogfilesize(self): self.feedMsg('list') self.feedMsg('list Status') self.assertNotError('upkeep') def testThreads(self): self.assertNotError('threads') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Status/__init__.py0000644000000000000000000000437311206611405016764 0ustar ### # 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 simple module to handle various informational commands querying the bot's current status and statistics. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Status/README.txt0000644000000000000000000000011711206611405016341 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Status/plugin.py0000644000000000000000000001775411206611405016532 0ustar ### # 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 os import sys import time import threading import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.callbacks as callbacks class Status(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Status, self) self.__parent.__init__(irc) # XXX It'd be nice if these could be kept in the registry. self.sentMsgs = 0 self.recvdMsgs = 0 self.sentBytes = 0 self.recvdBytes = 0 self.connected = {} def __call__(self, irc, msg): self.recvdMsgs += 1 self.recvdBytes += len(msg) self.__parent.__call__(irc, msg) def outFilter(self, irc, msg): self.sentMsgs += 1 self.sentBytes += len(msg) return msg def do001(self, irc, msg): self.connected[irc] = time.time() def status(self, irc, msg, args): """takes no arguments Returns the status of the bot. """ networks = {} for Irc in world.ircs: networks.setdefault(Irc.network, []).append(Irc.nick) networks = networks.items() networks.sort() networks = [format('%s as %L', net, nicks) for (net,nicks) in networks] L = [format('I am connected to %L.', networks)] if world.profiling: L.append('I am currently in code profiling mode.') irc.reply(' '.join(L)) status = wrap(status) def threads(self, irc, msg, args): """takes no arguments Returns the current threads that are active. """ threads = [t.getName() for t in threading.enumerate()] threads.sort() s = format('I have spawned %n; %n %b still currently active: %L.', (world.threadsSpawned, 'thread'), (len(threads), 'thread'), len(threads), threads) irc.reply(s) threads = wrap(threads) def net(self, irc, msg, args): """takes no arguments Returns some interesting network-related statistics. """ try: elapsed = time.time() - self.connected[irc.getRealIrc()] timeElapsed = utils.timeElapsed(elapsed) except KeyError: timeElapsed = 'an indeterminate amount of time' irc.reply('I have received %s messages for a total of %s bytes. ' 'I have sent %s messages for a total of %s bytes. ' 'I have been connected to %s for %s.' % (self.recvdMsgs, self.recvdBytes, self.sentMsgs, self.sentBytes, irc.server, timeElapsed)) net = wrap(net) def cpu(self, irc, msg, args): """takes no arguments Returns some interesting CPU-related statistics on the bot. """ (user, system, childUser, childSystem, elapsed) = os.times() now = time.time() target = msg.args[0] timeRunning = now - world.startedAt if self.registryValue('cpu.children', target) and \ user+system < timeRunning+1: # Fudge for FPU inaccuracies. children = 'My children have taken %.2f seconds of user time ' \ 'and %.2f seconds of system time ' \ 'for a total of %.2f seconds of CPU time. ' % \ (childUser, childSystem, childUser+childSystem) else: children = '' activeThreads = threading.activeCount() response = 'I have taken %.2f seconds of user time and %.2f seconds ' \ 'of system time, for a total of %.2f seconds of CPU ' \ 'time. %s' % (user, system, user + system, children) if self.registryValue('cpu.threads', target): response += format('I have spawned %n; I currently have %i still ' 'running.', (world.threadsSpawned, 'thread'), activeThreads) if self.registryValue('cpu.memory', target): mem = 'an unknown amount' pid = os.getpid() plat = sys.platform try: if plat.startswith('linux') or plat.startswith('sunos') or \ plat.startswith('freebsd') or plat.startswith('openbsd') or \ plat.startswith('darwin'): try: r = os.popen('ps -o rss -p %s' % pid) r.readline() # VSZ Header. mem = r.readline().strip() finally: r.close() elif sys.platform.startswith('netbsd'): mem = '%s kB' % os.stat('/proc/%s/mem' % pid)[7] response += ' I\'m taking up %s kB of memory.' % mem except Exception: self.log.exception('Uncaught exception in cpu.memory:') irc.reply(utils.str.normalizeWhitespace(response)) cpu = wrap(cpu) def cmd(self, irc, msg, args): """takes no arguments Returns some interesting command-related statistics. """ commands = 0 callbacksPlugin = 0 for cb in irc.callbacks: if isinstance(cb, callbacks.Plugin): callbacksPlugin += 1 commands += len(cb.listCommands()) s = format('I offer a total of %n in %n. I have processed %n.', (commands, 'command'), (callbacksPlugin, 'command-based', 'plugin'), (world.commandsProcessed, 'command')) irc.reply(s) cmd = wrap(cmd) def commands(self, irc, msg, args): """takes no arguments Returns a list of the commands offered by the bot. """ commands = set() for cb in irc.callbacks: if isinstance(cb, callbacks.Plugin): for command in cb.listCommands(): commands.add(command) irc.reply(format('%L', sorted(commands))) commands = wrap(commands) def uptime(self, irc, msg, args): """takes no arguments Returns the amount of time the bot has been running. """ response = 'I have been running for %s.' % \ utils.timeElapsed(time.time() - world.startedAt) irc.reply(response) uptime = wrap(uptime) def server(self, irc, msg, args): """takes no arguments Returns the server the bot is on. """ irc.reply(irc.server) server = wrap(server) Class = Status # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Status/config.py0000644000000000000000000000524711206611405016473 0ustar ### # 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 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('Status', True) Status = conf.registerPlugin('Status') conf.registerGroup(Status, 'cpu') conf.registerChannelValue(Status.cpu, 'children', registry.Boolean(True, """Determines whether the cpu command will list the time taken by children as well as the bot's process.""")) conf.registerChannelValue(Status.cpu, 'threads', registry.Boolean(False, """Determines whether the cpu command will provide the number of threads spawned and active.""")) conf.registerChannelValue(Status.cpu, 'memory', registry.Boolean(True, """Determines whether the cpu command will report the amount of memory being used by the bot.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Dunno/0000755000000000000000000000000011206611405014444 5ustar supybot-0.83.4.1.ds.orig/plugins/Dunno/test.py0000644000000000000000000000651211206611405016001 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 DunnoTestCase(ChannelPluginTestCase): plugins = ('Dunno', 'User') def setUp(self): PluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' self.assertNotError('register tester moo', private=True) def testDunnoAdd(self): self.assertNotError('dunno add moo') self.assertResponse('asdfagagfosdfk', 'moo') def testDunnoRemove(self): self.assertNotError('dunno add moo') self.assertNotError('dunno remove 1') def testDunnoSearch(self): self.assertNotError('dunno add foo') self.assertRegexp('dunno search moo', 'No.*dunnos.*found') # Test searching using just the getopts self.assertRegexp('dunno search --regexp m/foo/', r'1 found') self.assertNotError('dunno add moo') self.assertRegexp('dunno search moo', r'1 found') self.assertRegexp('dunno search m', r'1 found') # Test multiple adds for i in range(5): self.assertNotError('dunno add moo%s' % i) self.assertRegexp('dunno search moo', r'6 found') def testDunnoGet(self): self.assertNotError('dunno add moo') self.assertRegexp('dunno get 1', r'#1.*moo') self.assertNotError('dunno add $who') self.assertRegexp('dunno get 2', r'#2.*\$who') self.assertError('dunno get 3') self.assertError('dunno get a') def testDunnoChange(self): self.assertNotError('dunno add moo') self.assertNotError('dunno change 1 s/moo/bar/') self.assertRegexp('dunno get 1', '.*?: [\'"]bar[\'"]') def testDollarCommand(self): self.assertNotError("dunno add I can't $command.") self.assertResponse('asdf', "I can't asdf.") # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Dunno/__init__.py0000644000000000000000000000506711206611405016565 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ The Dunno module is used to spice up the reply when given an invalid command with random 'I dunno'-like responses. If you want something spicier than ' is not a valid command'-like responses, use this plugin. """ 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__ = "0.1" __author__ = supybot.authors.strike # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = { supybot.authors.jemfinch: ['Flatfile DB implementation.'], } 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: supybot-0.83.4.1.ds.orig/plugins/Dunno/README.txt0000644000000000000000000000011711206611405016141 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Dunno/plugin.py0000644000000000000000000000516611206611405016324 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils class Dunno(plugins.ChannelIdDatabasePlugin): """This plugin was written initially to work with MoobotFactoids, the two of them to provide a similar-to-moobot-and-blootbot interface for factoids. Basically, it replaces the standard 'Error: is not a valid command.' messages with messages kept in a database, able to give more personable responses.""" callAfter = ['MoobotFactoids', 'Factoids', 'Infobot'] def invalidCommand(self, irc, msg, tokens): channel = msg.args[0] if irc.isChannel(channel): dunno = self.db.random(channel) if dunno is not None: dunno = dunno.text prefixNick = self.registryValue('prefixNick', channel) env = {'command': tokens[0]} dunno = ircutils.standardSubstitute(irc, msg, dunno, env=env) irc.reply(dunno, prefixNick=prefixNick) Class = Dunno # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Dunno/config.py0000644000000000000000000000445011206611405016266 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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('Dunno', True) Dunno = conf.registerPlugin('Dunno') conf.registerChannelValue(Dunno, 'prefixNick', registry.Boolean(True, """Determines whether the bot will prefix the nick of the user giving an invalid command to the "dunno" response.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/User/0000755000000000000000000000000011206611405014277 5ustar supybot-0.83.4.1.ds.orig/plugins/User/test.py0000644000000000000000000001353311206611405015635 0ustar ### # 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 supybot.world as world import supybot.ircdb as ircdb class UserTestCase(PluginTestCase): plugins = ('User',) prefix1 = 'somethingElse!user@host.tld' prefix2 = 'EvensomethingElse!user@host.tld' def testHostmaskList(self): self.assertError('hostmask list') original = self.prefix self.prefix = self.prefix1 self.assertNotError('register foo bar') self.prefix = original self.assertError('hostmask list foo') self.assertNotError('hostmask add foo [hostmask] bar') self.assertNotError('hostmask add foo') self.assertNotRegexp('hostmask add foo', 'IrcSet') def testHostmaskListHandlesEmptyListGracefully(self): self.assertError('hostmask list') self.prefix = self.prefix1 self.assertNotError('register foo bar') self.assertNotError('hostmask remove foo %s' % self.prefix1) self.assertNotError('identify foo bar') self.assertRegexp('hostmask list', 'no registered hostmasks') def testHostmask(self): self.assertResponse('hostmask', self.prefix) self.assertError('@hostmask asdf') m = self.irc.takeMsg() self.failIf(m is not None, m) def testRegisterUnregister(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') self.assertError('register foo baz') self.failUnless(ircdb.users.getUserId('foo')) self.assertError('unregister foo') self.assertNotError('unregister foo bar') self.assertRaises(KeyError, ircdb.users.getUserId, 'foo') def testDisallowedUnregistration(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') orig = conf.supybot.databases.users.allowUnregistration() conf.supybot.databases.users.allowUnregistration.setValue(False) try: self.assertError('unregister foo') m = self.irc.takeMsg() self.failIf(m is not None, m) self.failUnless(ircdb.users.getUserId('foo')) finally: conf.supybot.databases.users.allowUnregistration.setValue(orig) def testList(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') self.assertResponse('user list', 'foo') self.prefix = self.prefix2 self.assertNotError('register biff quux') self.assertResponse('user list', 'biff and foo') self.assertResponse('user list f', 'biff and foo') self.assertResponse('user list f*', 'foo') self.assertResponse('user list *f', 'biff') self.assertNotError('unregister biff quux') self.assertResponse('user list', 'foo') self.assertNotError('unregister foo bar') self.assertRegexp('user list', 'no registered users') self.assertRegexp('user list asdlfkjasldkj', 'no matching registered') def testListHandlesCaps(self): self.prefix = self.prefix1 self.assertNotError('register Foo bar') self.assertResponse('user list', 'Foo') self.assertResponse('user list f*', 'Foo') def testChangeUsername(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') self.prefix = self.prefix2 self.assertNotError('register bar baz') self.prefix = self.prefix1 self.assertError('changename foo bar') self.assertNotError('changename foo baz') def testSetpassword(self): self.prefix = self.prefix1 self.assertNotError('register foo bar') password = ircdb.users.getUser(self.prefix).password self.assertNotEqual(password, 'bar') self.assertNotError('set password foo bar baz') self.assertNotEqual(ircdb.users.getUser(self.prefix).password,password) self.assertNotEqual(ircdb.users.getUser(self.prefix).password, 'baz') def testStats(self): self.assertNotError('user stats') self.assertNotError('load Lart') self.assertNotError('user stats') def testUserPluginAndUserList(self): self.prefix = self.prefix1 self.assertNotError('register Foo bar') self.assertResponse('user list', 'Foo') self.assertNotError('load Seen') self.assertResponse('user list', 'Foo') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/User/__init__.py0000644000000000000000000000424611206611405016416 0ustar ### # Copyright (c) 2004-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. ### """ Provides commands useful to users in general. This plugin is loaded by default. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure supybot-0.83.4.1.ds.orig/plugins/User/plugin.py0000644000000000000000000004575011206611405016162 0ustar ### # 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 re import fnmatch import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks class User(callbacks.Plugin): def _checkNotChannel(self, irc, msg, password=' '): if password and irc.isChannel(msg.args[0]): raise callbacks.Error, conf.supybot.replies.requiresPrivacy() def list(self, irc, msg, args, optlist, glob): """[--capability=] [] Returns the valid registered usernames matching . If is not given, returns all registered usernames. """ predicates = [] for (option, arg) in optlist: if option == 'capability': def p(u, cap=arg): try: return u._checkCapability(cap) except KeyError: return False predicates.append(p) if glob: r = re.compile(fnmatch.translate(glob), re.I) def p(u): return r.match(u.name) is not None predicates.append(p) users = [] for u in ircdb.users.itervalues(): for predicate in predicates: if not predicate(u): break else: users.append(u.name) if users: utils.sortBy(str.lower, users) irc.reply(format('%L', users)) else: if predicates: irc.reply('There are no matching registered users.') else: irc.reply('There are no registered users.') list = wrap(list, [getopts({'capability':'capability'}), additional('glob')]) def register(self, irc, msg, args, name, password): """ Registers with the given password and the current hostmask of the person registering. You shouldn't register twice; if you're not recognized as a user but you've already registered, use the hostmask add command to add another hostmask to your already-registered user, or use the identify command to identify just for a session. This command (and all other commands that include a password) must be sent to the bot privately, not in a channel. """ addHostmask = True try: ircdb.users.getUserId(name) irc.error('That name is already assigned to someone.', Raise=True) except KeyError: pass if ircutils.isUserHostmask(name): irc.errorInvalid('username', name, 'Hostmasks are not valid usernames.', Raise=True) try: u = ircdb.users.getUser(msg.prefix) if u._checkCapability('owner'): addHostmask = False else: irc.error('Your hostmask is already registered to %s' % u.name) return except KeyError: pass user = ircdb.users.newUser() user.name = name user.setPassword(password) if addHostmask: user.addHostmask(msg.prefix) ircdb.users.setUser(user) irc.replySuccess() register = wrap(register, ['private', 'something', 'something']) def unregister(self, irc, msg, args, user, password): """ [] Unregisters from the user database. If the user giving this command is an owner user, the password is not necessary. """ try: caller = ircdb.users.getUser(msg.prefix) isOwner = caller._checkCapability('owner') except KeyError: caller = None isOwner = False if not conf.supybot.databases.users.allowUnregistration(): if not caller or not isOwner: self.log.warning('%s tried to unregister user %s.', msg.prefix, user.name) irc.error('This command has been disabled. You\'ll have to ' 'ask the owner of this bot to unregister your user.', Raise=True) if isOwner or user.checkPassword(password): ircdb.users.delUser(user.id) irc.replySuccess() else: irc.error(conf.supybot.replies.incorrectAuthentication()) unregister = wrap(unregister, ['private', 'otherUser', additional('anything')]) def changename(self, irc, msg, args, user, newname, password): """ [] Changes your current user database name to the new name given. is only necessary if the user isn't recognized by hostmask. If you include the parameter, this message must be sent to the bot privately (not on a channel). """ try: id = ircdb.users.getUserId(newname) irc.error(format('%q is already registered.', newname)) return except KeyError: pass if user.checkHostmask(msg.prefix) or user.checkPassword(password): user.name = newname ircdb.users.setUser(user) irc.replySuccess() changename = wrap(changename, ['private', 'otherUser', 'something', additional('something', '')]) class set(callbacks.Commands): def password(self, irc, msg, args, user, password, newpassword): """ Sets the new password for the user specified by to . Obviously this message must be sent to the bot privately (not in a channel). If the requesting user is an owner user (and the user whose password is being changed isn't that same owner user), then needn't be correct. """ try: u = ircdb.users.getUser(msg.prefix) except KeyError: u = None if user.checkPassword(password) or \ (u and u._checkCapability('owner') and not u == user): user.setPassword(newpassword) ircdb.users.setUser(user) irc.replySuccess() else: irc.error(conf.supybot.replies.incorrectAuthentication()) password = wrap(password, ['otherUser', 'something', 'something']) def secure(self, irc, msg, args, user, password, value): """ [] Sets the secure flag on the user of the person sending the message. Requires that the person's hostmask be in the list of hostmasks for that user in addition to the password being correct. When the secure flag is set, the user *must* identify before he can be recognized. If a specific True/False value is not given, it inverts the current value. """ if value is None: value = not user.secure if user.checkPassword(password) and \ user.checkHostmask(msg.prefix, useAuth=False): user.secure = value ircdb.users.setUser(user) irc.reply('Secure flag set to %s' % value) else: irc.error(conf.supybot.replies.incorrectAuthentication()) secure = wrap(secure, ['private', 'user', 'something', additional('boolean')]) def username(self, irc, msg, args, hostmask): """ Returns the username of the user specified by or if the user is registered. """ if ircutils.isNick(hostmask): try: hostmask = irc.state.nickToHostmask(hostmask) except KeyError: irc.error('I haven\'t seen %s.' % hostmask, Raise=True) try: user = ircdb.users.getUser(hostmask) irc.reply(user.name) except KeyError: irc.error('I don\'t know who that is.') username = wrap(username, [first('nick', 'hostmask')]) class hostmask(callbacks.Commands): def hostmask(self, irc, msg, args, nick): """[] Returns the hostmask of . If isn't given, return the hostmask of the person giving the command. """ if not nick: nick = msg.nick irc.reply(irc.state.nickToHostmask(nick)) hostmask = wrap(hostmask, [additional('seenNick')]) def list(self, irc, msg, args, name): """[] Returns the hostmasks of the user specified by ; if isn't specified, returns the hostmasks of the user calling the command. """ def getHostmasks(user): hostmasks = map(repr, user.hostmasks) if hostmasks: hostmasks.sort() return format('%L', hostmasks) else: irc.reply(format('%s has no registered hostmasks.', user)) try: user = ircdb.users.getUser(msg.prefix) if name: if name != user.name and \ not ircdb.checkCapability(msg.prefix, 'owner'): irc.error('You may only retrieve your own hostmasks.', Raise=True) else: try: user = ircdb.users.getUser(name) irc.reply(getHostmasks(user)) except KeyError: irc.errorNoUser() else: irc.reply(getHostmasks(user)) except KeyError: irc.errorNotRegistered() list = wrap(list, ['private', additional('something')]) def add(self, irc, msg, args, user, hostmask, password): """[] [] [] Adds the hostmask to the user specified by . The may only be required if the user is not recognized by hostmask. is also not required if an owner user is giving the command on behalf of some other user. If is not given, it defaults to your current hostmask. If is not given, it defaults to your currently identified name. This message must be sent to the bot privately (not on a channel) since it may contain a password. """ if not hostmask: hostmask = msg.prefix if not ircutils.isUserHostmask(hostmask): irc.errorInvalid('hostmask', hostmask, 'Make sure your hostmask includes a nick, ' 'then an exclamation point (!), then a user, ' 'then an at symbol (@), then a host. Feel ' 'free to use wildcards (* and ?, which work ' 'just like they do on the command line) in ' 'any of these parts.', Raise=True) try: otherId = ircdb.users.getUserId(hostmask) if otherId != user.id: irc.error('That hostmask is already registered.', Raise=True) except KeyError: pass if not user.checkPassword(password) and \ not user.checkHostmask(msg.prefix): try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.error(conf.supybot.replies.incorrectAuthentication(), Raise=True) if not u._checkCapability('owner'): irc.error(conf.supybot.replies.incorrectAuthentication(), Raise=True) try: user.addHostmask(hostmask) except ValueError, e: irc.error(str(e), Raise=True) try: ircdb.users.setUser(user) except ValueError, e: irc.error(str(e), Raise=True) irc.replySuccess() add = wrap(add, ['private', first('otherUser', 'user'), optional('something'), additional('something', '')]) def remove(self, irc, msg, args, user, hostmask, password): """ [] Removes the hostmask from the record of the user specified by . If the hostmask given is 'all' then all hostmasks will be removed. The may only be required if the user is not recognized by his hostmask. This message must be sent to the bot privately (not on a channel) since it may contain a password. """ if not user.checkPassword(password) and \ not user.checkHostmask(msg.prefix): u = ircdb.users.getUser(msg.prefix) if not u._checkCapability('owner'): irc.error(conf.supybot.replies.incorrectAuthentication()) return try: s = '' if hostmask == 'all': user.hostmasks.clear() s = 'All hostmasks removed.' else: user.removeHostmask(hostmask) except KeyError: irc.error('There was no such hostmask.') return ircdb.users.setUser(user) irc.replySuccess(s) remove = wrap(remove, ['private', 'otherUser', 'something', additional('something', '')]) def capabilities(self, irc, msg, args, user): """[] Returns the capabilities of the user specified by ; if isn't specified, returns the capabilities of the user calling the command. """ try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered() else: if u == user or u._checkCapability('owner'): irc.reply('[%s]' % '; '.join(user.capabilities), private=True) else: irc.error(conf.supybot.replies.incorrectAuthentication(), Raise=True) capabilities = wrap(capabilities, [first('otherUser', 'user')]) def identify(self, irc, msg, args, user, password): """ Identifies the user as . This command (and all other commands that include a password) must be sent to the bot privately, not in a channel. """ if user.checkPassword(password): try: user.addAuth(msg.prefix) ircdb.users.setUser(user, flush=False) irc.replySuccess() except ValueError: irc.error('Your secure flag is true and your hostmask ' 'doesn\'t match any of your known hostmasks.') else: self.log.warning('Failed identification attempt by %s (password ' 'did not match for %s).', msg.prefix, user.name) irc.error(conf.supybot.replies.incorrectAuthentication()) identify = wrap(identify, ['private', 'otherUser', 'something']) def unidentify(self, irc, msg, args, user): """takes no arguments Un-identifies you. Note that this may not result in the desired effect of causing the bot not to recognize you anymore, since you may have added hostmasks to your user that can cause the bot to continue to recognize you. """ user.clearAuth() ircdb.users.setUser(user) irc.replySuccess('If you remain recognized after giving this command, ' 'you\'re being recognized by hostmask, rather than ' 'by password. You must remove whatever hostmask is ' 'causing you to be recognized in order not to be ' 'recognized.') unidentify = wrap(unidentify, ['user']) def whoami(self, irc, msg, args): """takes no arguments Returns the name of the user calling the command. """ try: user = ircdb.users.getUser(msg.prefix) irc.reply(user.name) except KeyError: irc.reply('I don\'t recognize you.') whoami = wrap(whoami) def stats(self, irc, msg, args): """takes no arguments Returns some statistics on the user database. """ users = 0 owners = 0 admins = 0 hostmasks = 0 for user in ircdb.users.itervalues(): users += 1 hostmasks += len(user.hostmasks) try: if user._checkCapability('owner'): owners += 1 elif user._checkCapability('admin'): admins += 1 except KeyError: pass irc.reply(format('I have %s registered users ' 'with %s registered hostmasks; ' '%n and %n.', users, hostmasks, (owners, 'owner'), (admins, 'admin'))) stats = wrap(stats) Class = User # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/User/config.py0000644000000000000000000000446311206611405016125 0ustar ### # Copyright (c) 2004-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('User', True) User = conf.registerPlugin('User') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(User, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Karma/0000755000000000000000000000000011206611405014414 5ustar supybot-0.83.4.1.ds.orig/plugins/Karma/test.py0000644000000000000000000002327111206611405015752 0ustar ### # 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 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Karma/__init__.py0000644000000000000000000000462311206611405016532 0ustar ### # 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 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Karma/README.txt0000644000000000000000000000011711206611405016111 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Karma/plugin.py0000644000000000000000000003612411206611405016272 0ustar ### # 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 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.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 Karma. 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(lambda s: s.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+1 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.Plugin): 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() 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 invalidCommand(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): # We don't handle this if we've been addressed because invalidCommand # will handle it for us. This prevents us from accessing the db twice # and therefore crashing. if not (msg.addressed or 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(format('%s has neutral karma.', name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel): s = format('%s: %i', name, total) else: s = format('Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.', name, (added, 'time'), (subtracted, 'time'), total) irc.reply(s) elif len(things) > 1: (L, neutrals) = self.db.gets(channel, things) if L: s = format('%L', [format('%s: %i', *t) for t in L]) if neutrals: neutral = format('. %L %h neutral karma', neutrals, 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 = [format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit)] lowest = [format('%q (%s)', 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 = format(' You (%s) are ranked %i out of %i.', msg.nick, rank, total) else: rankS = '' s = format('Highest karma: %L. Lowest karma: %L.%s', highest, 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 = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', 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 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 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Karma/config.py0000644000000000000000000000641511206611405016241 0ustar ### # 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 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Reply/0000755000000000000000000000000011206611405014454 5ustar supybot-0.83.4.1.ds.orig/plugins/Reply/test.py0000644000000000000000000000511111206611405016003 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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 supybot.ircutils as ircutils class ReplyTestCase(ChannelPluginTestCase): plugins = ('Reply',) def testPrivate(self): m = self.getMsg('private [list]') self.failIf(ircutils.isChannel(m.args[0])) def testNotice(self): m = self.getMsg('notice [list]') self.assertEqual(m.command, 'NOTICE') def testNoticePrivate(self): m = self.assertNotError('notice [private [list]]') self.assertEqual(m.command, 'NOTICE') self.assertEqual(m.args[0], self.nick) m = self.assertNotError('private [notice [list]]') self.assertEqual(m.command, 'NOTICE') self.assertEqual(m.args[0], self.nick) class ReplyNonChannelTestCase(PluginTestCase): plugins = ('Reply',) def testAction(self): self.prefix = 'something!else@somewhere.else' self.nick = 'something' m = self.assertAction('action foo', 'foo') self.failIf(m.args[0] == self.irc.nick) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Reply/__init__.py0000644000000000000000000000471111206611405016570 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ This plugin contains various commands which elicit certain types of responses from the bot. """ 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__ = "%%VERSION%%" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.strike # 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: supybot-0.83.4.1.ds.orig/plugins/Reply/README.txt0000644000000000000000000000011711206611405016151 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Reply/plugin.py0000644000000000000000000000573211206611405016333 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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.commands import * import supybot.callbacks as callbacks class Reply(callbacks.Plugin): """This plugins contains a few commands that construct various types of replies. Some bot owners would be wise to not load this plugin because it can be easily abused. """ def private(self, irc, msg, args, text): """ Replies with in private. Use nested commands to your benefit here. """ irc.reply(text, private=True) private = wrap(private, ['text']) def action(self, irc, msg, args, text): """ Replies with as an action. use nested commands to your benefit here. """ if text: irc.reply(text, action=True) else: raise callbacks.ArgumentError action = wrap(action, ['text']) def notice(self, irc, msg, args, text): """ Replies with in a notice. Use nested commands to your benefit here. If you want a private notice, nest the private command. """ irc.reply(text, notice=True) notice = wrap(notice, ['text']) def reply(self, irc, msg, args, text): """ Replies with . Equivalent to the alias, 'echo $nick: $1'. """ irc.reply(text, prefixNick=True) reply = wrap(reply, ['text']) Class = Reply # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Reply/config.py0000644000000000000000000000446011206611405016277 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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('Reply', True) Reply = conf.registerPlugin('Reply') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Reply, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Seen/0000755000000000000000000000000011206611405014253 5ustar supybot-0.83.4.1.ds.orig/plugins/Seen/test.py0000644000000000000000000000672211206611405015613 0ustar ### # 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 * import supybot.ircdb as ircdb class ChannelDBTestCase(ChannelPluginTestCase): plugins = ('Seen', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) self.prefix = 'foo!bar@baz' self.nick = 'foo' self.wildcardTest = ['f*', '*oo', '*foo*', 'f*o*o'] self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'register foo bar', prefix=self.prefix)) _ = self.irc.takeMsg() chancap = ircdb.makeChannelCapability(self.channel, 'op') ircdb.users.getUser(self.nick).addCapability(chancap) def testNoKeyErrorEscapeFromSeen(self): self.assertRegexp('seen asldfkjasdlfkj', '^I have not seen') self.assertNotRegexp('seen asldfkjasdlfkj', 'KeyError') def testAny(self): self.assertRegexp('seen any', 'test has joined') self.irc.feedMsg(ircmsgs.mode(self.channel, args=('+o', self.nick), prefix=self.prefix)) self.assertRegexp('seen any %s' % self.nick, '^%s was last seen' % self.nick) def testSeen(self): self.assertNotError('seen last') self.assertNotError('list') self.assertNotError('seen %s' % self.nick) m = self.assertNotError('seen %s' % self.nick.upper()) self.failUnless(self.nick.upper() in m.args[1]) self.assertRegexp('seen user %s' % self.nick, '^%s was last seen' % self.nick) for wildcard in self.wildcardTest: self.assertRegexp('seen %s' % wildcard, '^%s was last seen' % self.nick) self.assertRegexp('seen bar*', '^I haven\'t seen anyone matching') def testSeenNoUser(self): self.assertNotRegexp('seen user alsdkfjalsdfkj', 'KeyError') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Seen/__init__.py0000644000000000000000000000454011206611405016367 0ustar ### # 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. ### """ Keeps track of the last time a user was seen on 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Seen/README.txt0000644000000000000000000000011711206611405015750 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Seen/plugin.py0000644000000000000000000002574111206611405016134 0ustar ### # 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 time import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class IrcStringAndIntDict(utils.InsensitivePreservingDict): def key(self, x): if isinstance(x, int): return x else: return ircutils.toLower(x) class SeenDB(plugins.ChannelUserDB): IdDict = IrcStringAndIntDict def serialize(self, v): return list(v) def deserialize(self, channel, id, L): (seen, saying) = L return (float(seen), saying) def update(self, channel, nickOrId, saying): seen = time.time() self[channel, nickOrId] = (seen, saying) self[channel, ''] = (seen, saying) def seenWildcard(self, channel, nick): nicks = ircutils.IrcSet() nickRe = re.compile('.*'.join(nick.split('*')), re.I) for (searchChan, searchNick) in self.keys(): #print 'chan: %s ... nick: %s' % (searchChan, searchNick) if isinstance(searchNick, int): # We need to skip the reponses that are keyed by id as they # apparently duplicate the responses for the same person that # are keyed by nick-string continue if ircutils.strEqual(searchChan, channel): try: s = nickRe.match(searchNick).group() except AttributeError: continue nicks.add(s) L = [[nick, self.seen(channel, nick)] for nick in nicks] def negativeTime(x): return -x[1][0] utils.sortBy(negativeTime, L) return L def seen(self, channel, nickOrId): return self[channel, nickOrId] filename = conf.supybot.directories.data.dirize('Seen.db') anyfilename = conf.supybot.directories.data.dirize('Seen.any.db') class Seen(callbacks.Plugin): noIgnore = True def __init__(self, irc): self.__parent = super(Seen, self) self.__parent.__init__(irc) self.db = SeenDB(filename) self.anydb = SeenDB(anyfilename) self.lastmsg = {} self.ircstates = {} world.flushers.append(self.db.flush) def die(self): if self.db.flush in world.flushers: world.flushers.remove(self.db.flush) else: self.log.debug('Odd, no flush in flushers: %r', world.flushers) self.db.close() if self.anydb.flush in world.flushers: world.flushers.remove(self.anydb.flush) else: self.log.debug('Odd, no flush in flushers: %r', world.flushers) self.anydb.close() self.__parent.die() def __call__(self, irc, msg): try: if irc not in self.ircstates: self._addIrc(irc) self.ircstates[irc].addMsg(irc, self.lastmsg[irc]) finally: self.lastmsg[irc] = msg self.__parent.__call__(irc, msg) def _addIrc(self, irc): # Let's just be extra-special-careful here. if irc not in self.ircstates: self.ircstates[irc] = irclib.IrcState() if irc not in self.lastmsg: self.lastmsg[irc] = ircmsgs.ping('this is just a fake message') if not world.testing: for channel in irc.state.channels: irc.queueMsg(ircmsgs.who(channel)) irc.queueMsg(ircmsgs.names(channel)) def doPrivmsg(self, irc, msg): if irc.isChannel(msg.args[0]): channel = msg.args[0] said = ircmsgs.prettyPrint(msg) self.db.update(channel, msg.nick, said) self.anydb.update(channel, msg.nick, said) try: id = ircdb.users.getUserId(msg.prefix) self.db.update(channel, id, said) self.anydb.update(channel, id, said) except KeyError: pass # Not in the database. def doPart(self, irc, msg): channel = msg.args[0] said = ircmsgs.prettyPrint(msg) self.anydb.update(channel, msg.nick, said) try: id = ircdb.users.getUserId(msg.prefix) self.anydb.update(channel, id, said) except KeyError: pass # Not in the database. doJoin = doPart doKick = doPart def doQuit(self, irc, msg): said = ircmsgs.prettyPrint(msg) if irc not in self.ircstates: return try: id = ircdb.users.getUserId(msg.prefix) except KeyError: id = None # Not in the database. for channel in self.ircstates[irc].channels: if msg.nick in self.ircstates[irc].channels[channel].users: self.anydb.update(channel, msg.nick, said) if id is not None: self.anydb.update(channel, id, said) doNick = doQuit def doMode(self, irc, msg): # Filter out messages from network Services if msg.nick: self.doQuit(irc, msg) doTopic = doMode def _seen(self, irc, channel, name, any=False): if any: db = self.anydb else: db = self.db try: results = [] if '*' in name: results = db.seenWildcard(channel, name) else: results = [[name, db.seen(channel, name)]] if len(results) == 1: (nick, info) = results[0] (when, said) = info irc.reply(format('%s was last seen in %s %s ago: %s', nick, channel, utils.timeElapsed(time.time()-when), said)) elif len(results) > 1: L = [] for (nick, info) in results: (when, said) = info L.append(format('%s (%s ago)', nick, utils.timeElapsed(time.time()-when))) irc.reply(format('%s could be %L', name, (L, 'or'))) else: irc.reply(format('I haven\'t seen anyone matching %s.', name)) except KeyError: irc.reply(format('I have not seen %s.', name)) def seen(self, irc, msg, args, channel, name): """[] Returns the last time was seen and what was last seen saying. is only necessary if the message isn't sent on the channel itself. """ self._seen(irc, channel, name) seen = wrap(seen, ['channel', 'nick']) def any(self, irc, msg, args, channel, optlist, name): """[] [--user ] [] Returns the last time was seen and what was last seen doing. This includes any form of activity, instead of just PRIVMSGs. If isn't specified, returns the last activity seen in . If --user is specified, looks up name in the user database and returns the last time user was active in . is only necessary if the message isn't sent on the channel itself. """ if name and optlist: raise callbacks.ArgumentError elif name: self._seen(irc, channel, name, any=True) elif optlist: for (option, arg) in optlist: if option == 'user': user = arg self._user(irc, channel, user, any=True) else: self._last(irc, channel, any=True) any = wrap(any, ['channel', getopts({'user': 'otherUser'}), additional('nick')]) def _last(self, irc, channel, any=False): if any: db = self.anydb else: db = self.db try: (when, said) = db.seen(channel, '') irc.reply(format('Someone was last seen in %s %s ago: %s', channel, utils.timeElapsed(time.time()-when), said)) except KeyError: irc.reply('I have never seen anyone.') def last(self, irc, msg, args, channel): """[] Returns the last thing said in . is only necessary if the message isn't sent in the channel itself. """ self._last(irc, channel) last = wrap(last, ['channel']) def _user(self, irc, channel, user, any=False): if any: db = self.anydb else: db = self.db try: (when, said) = db.seen(channel, user.id) irc.reply(format('%s was last seen in %s %s ago: %s', user.name, channel, utils.timeElapsed(time.time()-when), said)) except KeyError: irc.reply(format('I have not seen %s.', user.name)) def user(self, irc, msg, args, channel, user): """[] Returns the last time was seen and what was last seen saying. This looks up in the user seen database, which means that it could be any nick recognized as user that was seen. is only necessary if the message isn't sent in the channel itself. """ self._user(irc, channel, user) user = wrap(user, ['channel', 'otherUser']) Class = Seen # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Seen/config.py0000644000000000000000000000445611206611405016103 0ustar ### # 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('Seen', True) Seen = conf.registerPlugin('Seen') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Seen, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Scheduler/0000755000000000000000000000000011206611405015277 5ustar supybot-0.83.4.1.ds.orig/plugins/Scheduler/test.py0000644000000000000000000001015311206611405016630 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 supybot.schedule as schedule class SchedulerTestCase(ChannelPluginTestCase): plugins = ('Scheduler', 'Utilities') def tearDown(self): schedule.schedule.reset() ChannelPluginTestCase.tearDown(self) def testAddRemove(self): self.assertRegexp('scheduler list', 'no.*commands') m = self.assertNotError('scheduler add 5 echo testAddRemove') self.assertNotRegexp('scheduler list', 'no.*commands') self.assertNoResponse(' ', 3) self.assertResponse(' ', 'testAddRemove') m = self.assertNotError('scheduler add 5 echo testAddRemove2') # Get id. id = None for s in m.args[1].split(): s = s.lstrip('#') if s.isdigit(): id = s break self.failUnless(id, 'Couldn\'t find id in reply.') self.assertNotError('scheduler remove %s' % id) self.assertNoResponse(' ', 5) # Need this test to run first so it has id 0 for its event def test00RemoveZero(self): id = None m = self.assertNotError('scheduler add 5 echo testRemoveZero') for s in m.args[1].split(): s = s.lstrip('#') if s.isdigit(): id = s break self.assertNotError('scheduler remove %s' % id) self.assertNoResponse(' ', 5) def testRepeat(self): self.assertNotError('scheduler repeat repeater 5 echo testRepeat') self.assertResponse(' ', 'testRepeat') self.assertResponse('scheduler list', 'repeater: "echo testRepeat"') self.assertNoResponse(' ', 3) self.assertResponse(' ', 'testRepeat') self.assertNotError('scheduler remove repeater') self.assertNotRegexp('scheduler list', 'repeater') self.assertNoResponse(' ', 5) def testRepeatWorksWithNestedCommands(self): self.assertNotError('scheduler repeat foo 5 "echo foo [echo nested]"') self.assertResponse(' ', 'foo nested') self.assertNoResponse(' ', 3) self.assertResponse(' ', 'foo nested') self.assertNotError('scheduler remove foo') self.assertNoResponse(' ', 5) def testRepeatDisallowsIntegerNames(self): self.assertError('scheduler repeat 1234 1234 "echo NoIntegerNames"') def testRepeatDisallowsDuplicateNames(self): self.assertNotError('scheduler repeat foo 5 "echo foo"') self.assertError('scheduler repeat foo 5 "echo another foo fails"') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Scheduler/__init__.py0000644000000000000000000000463711206611405017422 0ustar ### # 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. ### """ Gives the user the ability to schedule commands to run at a particular time, or repeatedly run at a particular interval. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Scheduler/README.txt0000644000000000000000000000011711206611405016774 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Scheduler/plugin.py0000644000000000000000000001172411206611405017154 0ustar ### # Copyright (c) 2003-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 time import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.schedule as schedule import supybot.callbacks as callbacks class Scheduler(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Scheduler, self) self.__parent.__init__(irc) self.events = {} def _makeCommandFunction(self, irc, msg, command, remove=True): """Makes a function suitable for scheduling from command.""" tokens = callbacks.tokenize(command) def f(): if remove: del self.events[str(f.eventId)] self.Proxy(irc.irc, msg, tokens) return f def add(self, irc, msg, args, seconds, command): """ Schedules the command string to run seconds in the future. For example, 'scheduler add [seconds 30m] "echo [cpu]"' will schedule the command "cpu" to be sent to the channel the schedule add command was given in (with no prefixed nick, a consequence of using echo). Do pay attention to the quotes in that example. """ f = self._makeCommandFunction(irc, msg, command) id = schedule.addEvent(f, time.time() + seconds) f.eventId = id self.events[str(id)] = command irc.replySuccess(format('Event #%i added.', id)) add = wrap(add, ['positiveInt', 'text']) def remove(self, irc, msg, args, id): """ Removes the event scheduled with id from the schedule. """ if id in self.events: del self.events[id] try: id = int(id) except ValueError: pass try: schedule.removeEvent(id) irc.replySuccess() except KeyError: irc.error('Invalid event id.') else: irc.error('Invalid event id.') remove = wrap(remove, ['lowered']) def repeat(self, irc, msg, args, name, seconds, command): """ Schedules the command to run every seconds, starting now (i.e., the command runs now, and every seconds thereafter). is a name by which the command can be unscheduled. """ name = name.lower() if name in self.events: irc.error('There is already an event with that name, please ' 'choose another name.', Raise=True) self.events[name] = command f = self._makeCommandFunction(irc, msg, command, remove=False) id = schedule.addPeriodicEvent(f, seconds, name) assert id == name # We don't reply because the command runs immediately. # But should we? What if the command doesn't have visible output? # irc.replySuccess() repeat = wrap(repeat, ['nonInt', 'positiveInt', 'text']) def list(self, irc, msg, args): """takes no arguments Lists the currently scheduled events. """ L = self.events.items() if L: L.sort() for (i, (name, command)) in enumerate(L): L[i] = format('%s: %q', name, command) irc.reply(format('%L', L)) else: irc.reply('There are currently no scheduled commands.') list = wrap(list) Class = Scheduler # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Scheduler/config.py0000644000000000000000000000450211206611405017117 0ustar ### # 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('Scheduler', True) Scheduler = conf.registerPlugin('Scheduler') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Scheduler, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Note/0000755000000000000000000000000011206611405014266 5ustar supybot-0.83.4.1.ds.orig/plugins/Note/test.py0000644000000000000000000000763411206611405015631 0ustar ### # Copyright (c) 2003, Brett Kelly # 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 NoteTestCase(PluginTestCase): plugins = ('Note', 'Misc', 'User') config = {'supybot.reply.whenNotCommand': False} def setUp(self): PluginTestCase.setUp(self) # setup a user self.prefix = 'foo!bar@baz' self.assertNotError('register inkedmn bar') self.assertNotError('hostmask add inkedmn test2!bar@baz') def testSendnote(self): self.assertRegexp('note send inkedmn test', '#1') # have to getMsg(' ') after each Note.send to absorb supybot's # automatic "You have an unread note" message _ = self.getMsg(' ') self.assertError('note send alsdkjfasldk foo') self.assertNotError('note send inkedmn test2') _ = self.getMsg(' ') # verify that sending a note to a user via their nick instead of their # ircdb user name works self.prefix = 'test2!bar@baz' self.assertNotError('note send test2 foo') _ = self.getMsg(' ') def testNote(self): self.assertNotError('note send inkedmn test') _ = self.getMsg(' ') self.assertRegexp('note 1', 'test') self.assertError('note blah') def testList(self): self.assertResponse('note list', 'You have no unread notes.') self.assertNotError('note send inkedmn testing') _ = self.getMsg(' ') self.assertNotError('note send inkedmn 1,2,3') _ = self.getMsg(' ') self.assertRegexp('note list --sent', r'#2.*#1') self.assertRegexp('note list --sent --to inkedmn', r'#2.*#1') self.assertRegexp('note list', r'#1.*#2') self.assertRegexp('note 1', 'testing') self.assertRegexp('note list --old', '#1 from inkedmn') self.assertRegexp('note list --old --from inkedmn','#1 from inkedmn') def testSearch(self): self.assertNotError('note send inkedmn testing') _ = self.getMsg(' ') self.assertNotError('note send inkedmn 1,2,3') _ = self.getMsg(' ') self.assertRegexp('note search test', r'#1') self.assertRegexp('note search --regexp m/1,2/', r'#2') self.assertRegexp('note search --sent test', r'#1') def testNext(self): self.assertNotError('note send inkedmn testing') _ = self.getMsg(' ') self.assertRegexp('note next', 'testing') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Note/__init__.py0000644000000000000000000000467011206611405016406 0ustar ### # 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. ### """ A complete messaging system that allows users to leave 'notes' for other users that can be retrieved later. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {'inkedmn': ['Original implementation.']} 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: supybot-0.83.4.1.ds.orig/plugins/Note/README.txt0000644000000000000000000000011711206611405015763 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Note/plugin.py0000644000000000000000000003766611206611405016160 0ustar ### # Copyright (c) 2004, Brett Kelly # 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 time import fnmatch import operator import supybot.dbi as dbi import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class NoteRecord(dbi.Record): __fields__ = [ 'frm', 'to', 'at', 'notified', 'read', 'public', 'text', ] class DbiNoteDB(dbi.DB): Mapping = 'flat' Record = NoteRecord def __init__(self, *args, **kwargs): dbi.DB.__init__(self, *args, **kwargs) self.unRead = {} self.unNotified = {} for record in self: self._addCache(record) def _addCache(self, record): if not record.read: self.unRead.setdefault(record.to, []).append(record.id) if not record.notified: self.unNotified.setdefault(record.to, []).append(record.id) def _removeCache(self, record): if record.notified: try: self.unNotified[record.to].remove(record.id) except (KeyError, ValueError): pass if record.read: try: self.unRead[record.to].remove(record.id) except (KeyError, ValueError): pass def setRead(self, id): n = self.get(id) n.read = True n.notified = True self._removeCache(n) self.set(id, n) def setNotified(self, id): n = self.get(id) n.notified = True self._removeCache(n) self.set(id, n) def getUnnotifiedIds(self, to): ## def p(note): ## return not note.notified and note.to == to ## return [note.id for note in self.select(p)] return self.unNotified.get(to, []) def getUnreadIds(self, to): ## def p(note): ## return not note.read and note.to == to ## return [note.id for note in self.select(p)] return self.unRead.get(to, []) def send(self, frm, to, public, text): n = self.Record(frm=frm, to=to, text=text, at=time.time(), public=public) id = self.add(n) self._addCache(n) return id def unsend(self, id): self.remove(id) for cache in self.unRead, self.unNotified: for (to, ids) in cache.items(): while id in ids: ids.remove(id) NoteDB = plugins.DB('Note', {'flat': DbiNoteDB}) class Note(callbacks.Plugin): def __init__(self, irc): self.__parent= super(Note, self) self.__parent.__init__(irc) self.db = NoteDB() def die(self): self.__parent.die() self.db.close() def doPrivmsg(self, irc, msg): self._notify(irc, msg) def doJoin(self, irc, msg): if self.registryValue('notify.onJoin'): repeatedly = self.registryValue('notify.onJoin.repeatedly') self._notify(irc, msg, repeatedly) def _notify(self, irc, msg, repeatedly=False): irc = callbacks.SimpleProxy(irc, msg) try: to = ircdb.users.getUserId(msg.prefix) except KeyError: return ids = self.db.getUnnotifiedIds(to) if len(ids) <= self.registryValue('notify.autoSend'): for id in ids: irc.reply(self._formatNote(self.db.get(id), to), private=True) self.db.setRead(id) return unnotifiedIds = ['#%s' % nid for nid in ids] unnotified = len(unnotifiedIds) if unnotified or repeatedly: unreadIds = ['#%s' % nid for nid in self.db.getUnreadIds(to)] unread = len(unreadIds) s = format('You have %n; %i that I haven\'t told you about ' 'before now. %L %b still unread.', (unread, 'unread', 'note'), unnotified, unreadIds, unread) # Later we'll have a user value for allowing this to be a NOTICE. irc.reply(s, private=True) for nid in unnotifiedIds: id = int(nid[1:]) self.db.setNotified(id) def _getUserId(self, irc, name): if ircdb.users.hasUser(name): return ircdb.users.getUserId(name) else: try: hostmask = irc.state.nickToHostmask(name) return ircdb.users.getUserId(hostmask) except KeyError: return None def send(self, irc, msg, args, user, targets, text): """,[,[...]] Sends a new note to the user specified. Multiple recipients may be specified by separating their names by commas. """ # Let's get the from user. public = irc.isChannel(msg.args[0]) sent = [] for target in targets: id = self.db.send(user.id, target.id, public, text) s = format('note #%i sent to %s', id, target.name) sent.append(s) irc.reply(format('%L.', sent).capitalize()) send = wrap(send, ['user', commalist('otherUser'), 'text']) def reply(self, irc, msg, args, user, id, text): """ Sends a note in reply to . """ try: note = self.db.get(id) except dbi.NoRecordError: irc.error('That\'s not a note in my database.', Raise=True) if note.to != user.id: irc.error('You may only reply to notes ' 'that have been sent to you.', Raise=True) self.db.setRead(id) text += ' (in reply to #%s)' % id public = irc.isChannel(msg.args[0]) try: target = ircdb.users.getUser(note.frm) except KeyError: irc.error('The user who sent you that note ' 'is no longer in my user database.', Raise=True) id = self.db.send(user.id, note.frm, public, text) irc.reply(format('Note #%i sent to %s.', id, target.name)) reply = wrap(reply, ['user', ('id', 'note'), 'text']) def unsend(self, irc, msg, args, user, id): """ Unsends the note with the id given. You must be the author of the note, and it must be unread. """ try: note = self.db.get(id) except dbi.NoRecordError: irc.errorInvalid('note id') if note.frm == user.id: if not note.read: self.db.unsend(id) irc.replySuccess() else: irc.error('That note has been read already.') else: irc.error('That note wasn\'t sent by you.') unsend = wrap(unsend, ['user', ('id', 'note')]) def _formatNote(self, note, to): elapsed = utils.timeElapsed(time.time() - note.at) if note.to == to: author = plugins.getUserName(note.frm) return format('#%i: %s (Sent by %s %s ago)', note.id, note.text, author, elapsed) else: assert note.frm == to, 'Odd, userid isn\'t frm either.' recipient = plugins.getUserName(note.to) return format('#%i: %s (Sent to %s %s ago)', note.id, note.text, recipient, elapsed) def note(self, irc, msg, args, user, id): """ Retrieves a single note by its unique note id. Use the 'note list' command to see what unread notes you have. """ try: note = self.db.get(id) except dbi.NoRecordError: irc.errorInvalid('note id') if user.id != note.frm and user.id != note.to: s = 'You may only retrieve notes you\'ve sent or received.' irc.error(s) return newnote = self._formatNote(note, user.id) irc.reply(newnote, private=(not note.public)) self.db.setRead(id) note = wrap(note, ['user', ('id', 'note')]) def _formatNoteId(self, msg, note, sent=False): if note.public or not ircutils.isChannel(msg.args[0]): if sent: sender = plugins.getUserName(note.to) return format('#%i to %s', note.id, sender) else: sender = plugins.getUserName(note.frm) return format('#%i from %s', note.id, sender) else: return format('#%i (private)', note.id) def search(self, irc, msg, args, user, optlist, glob): """[--{regexp} ] [--sent] [] Searches your received notes for ones matching . If --regexp is given, its associated value is taken as a regexp and matched against the notes. If --sent is specified, only search sent notes. """ criteria = [] def to(note): return note.to == user.id def frm(note): return note.frm == user.id own = to for (option, arg) in optlist: if option == 'regexp': criteria.append(arg.search) elif option == 'sent': own = frm if glob: glob = fnmatch.translate(glob) # ignore the trailing $ fnmatch.translate adds to the regexp criteria.append(re.compile(glob[:-1]).search) def match(note): for p in criteria: if not p(note.text): return False return True notes = list(self.db.select(lambda n: match(n) and own(n))) if not notes: irc.reply('No matching notes were found.') else: utils.sortBy(operator.attrgetter('id'), notes) ids = [self._formatNoteId(msg, note) for note in notes] ids = self._condense(ids) irc.reply(format('%L', ids)) search = wrap(search, ['user', getopts({'regexp': ('regexpMatcher', True), 'sent': ''}), additional('glob')]) def list(self, irc, msg, args, user, optlist): """[--{old,sent}] [--{from,to} ] Retrieves the ids of all your unread notes. If --old is given, list read notes. If --sent is given, list notes that you have sent. If --from is specified, only lists notes sent to you from . If --to is specified, only lists notes sent by you to . """ (sender, receiver, old, sent) = (None, None, False, False) for (option, arg) in optlist: if option == 'old': old = True if option == 'sent': sent = True if option == 'from': sender = arg if option == 'to': receiver = arg sent = True if old: return self._oldnotes(irc, msg, sender) if sent: return self._sentnotes(irc, msg, receiver) def p(note): return not note.read and note.to == user.id if sender: originalP = p def p(note): return originalP(note) and note.frm == sender.id notes = list(self.db.select(p)) if not notes: irc.reply('You have no unread notes.') else: utils.sortBy(operator.attrgetter('id'), notes) ids = [self._formatNoteId(msg, note) for note in notes] ids = self._condense(ids) irc.reply(format('%L.', ids)) list = wrap(list, ['user', getopts({'old': '', 'sent': '', 'from': 'otherUser', 'to': 'otherUser'})]) def next(self, irc, msg, args, user): """takes no arguments Retrieves your next unread note, if any. """ notes = self.db.getUnreadIds(user.id) if not notes: irc.reply('You have no unread notes.') else: found = False for id in notes: try: note = self.db.get(id) except KeyError: continue found = True break if not found: irc.reply('You have no unread notes.') else: irc.reply(self._formatNote(note, user.id), private=(not note.public)) self.db.setRead(note.id) next = wrap(next, ['user']) def _condense(self, notes): temp = {} for note in notes: note = note.split(' ', 1) if note[1] in temp: temp[note[1]].append(note[0]) else: temp[note[1]] = [note[0]] notes = [] for (k,v) in temp.iteritems(): if '(private)' in k: k = k.replace('(private)', format('%b private', len(v))) notes.append(format('%L %s', v, k)) return notes def _sentnotes(self, irc, msg, receiver): try: user = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered() return def p(note): return note.frm == user.id if receiver: originalP = p def p(note): return originalP(note) and note.to == receiver.id notes = list(self.db.select(p)) if not notes: irc.error('I couldn\'t find any sent notes for your user.') else: utils.sortBy(operator.attrgetter('id'), notes) notes.reverse() # Most recently sent first. ids = [self._formatNoteId(msg, note, sent=True) for note in notes] ids = self._condense(ids) irc.reply(format('%L.', ids)) def _oldnotes(self, irc, msg, sender): try: user = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered() return def p(note): return note.to == user.id and note.read if sender: originalP = p def p(note): return originalP(note) and note.frm == sender.id notes = list(self.db.select(p)) if not notes: irc.reply('I couldn\'t find any matching read notes ' 'for your user.') else: utils.sortBy(operator.attrgetter('id'), notes) notes.reverse() ids = [self._formatNoteId(msg, note) for note in notes] ids = self._condense(ids) irc.reply(format('%L.', ids)) Class = Note # vim: shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Note/config.py0000644000000000000000000000636211206611405016114 0ustar ### # 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('Note', True) Note = conf.registerPlugin('Note') conf.registerGroup(Note, 'notify') conf.registerGlobalValue(Note.notify, 'onJoin', registry.Boolean(False, """Determines whether the bot will notify people of their new messages when they join the channel. Normally it will notify them when they send a message to the channel, since oftentimes joins are the result of netsplits and not the actual presence of the user.""")) conf.registerGlobalValue(Note.notify.onJoin, 'repeatedly', registry.Boolean(False, """Determines whether the bot will repeatedly notify people of their new messages when they join the channel. That means when they join the channel, the bot will tell them they have unread messages, even if it's told them before.""")) conf.registerGlobalValue(Note.notify, 'autoSend', registry.NonNegativeInteger(0, """Determines the upper limit for automatically sending messages instead of notifications. I.e., if this value is 2 and there are 2 new messages to notify a user about, instead of sending a notification message, the bot will simply send those new messages. If there are 3 new messages, however, the bot will send a notification message.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Math/0000755000000000000000000000000011206611405014252 5ustar supybot-0.83.4.1.ds.orig/plugins/Math/test.py0000644000000000000000000002047411206611405015612 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 MathTestCase(PluginTestCase): plugins = ('Math',) def testBase(self): self.assertNotRegexp('base 56 asdflkj', 'ValueError') self.assertResponse('base 16 2 F', '1111') self.assertResponse('base 2 16 1111', 'F') self.assertResponse('base 20 BBBB', '92631') self.assertResponse('base 10 20 92631', 'BBBB') self.assertResponse('base 2 36 10', '2') self.assertResponse('base 36 2 10', '100100') self.assertResponse('base 2 1010101', '85') self.assertResponse('base 2 2 11', '11') self.assertResponse('base 12 0', '0') self.assertResponse('base 36 2 0', '0') self.assertNotError("base 36 " +\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ") self.assertResponse("base 10 36 [base 36 " +\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"\ "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz]", "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"\ "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ") self.assertResponse('base 2 10 [base 10 2 12]', '12') self.assertResponse('base 16 2 [base 2 16 110101]', '110101') self.assertResponse('base 10 8 [base 8 76532]', '76532') self.assertResponse('base 10 36 [base 36 csalnwea]', 'CSALNWEA') self.assertResponse('base 5 4 [base 4 5 212231]', '212231') self.assertError('base 37 1') self.assertError('base 1 1') self.assertError('base 12 1 1') self.assertError('base 1 12 1') self.assertError('base 1.0 12 1') self.assertError('base A 1') self.assertError('base 4 4') self.assertError('base 10 12 A') print print "If we have not fixed a bug with Math.base, the following ", print "tests will hang the test-suite." self.assertRegexp('base 2 10 [base 10 2 -12]', '-12') self.assertRegexp('base 16 2 [base 2 16 -110101]', '-110101') def testCalc(self): self.assertResponse('calc 5*0.06', str(5*0.06)) self.assertResponse('calc 2.0-7.0', str(2-7)) self.assertResponse('calc (-1)**.5', 'i') self.assertResponse('calc e**(i*pi)+1', '0') self.assertResponse('calc (-5)**.5', '2.2360679775i') self.assertResponse('calc -((-5)**.5)', '-2.2360679775i') self.assertNotRegexp('calc [9, 5] + [9, 10]', 'TypeError') self.assertError('calc [9, 5] + [9, 10]') self.assertNotError('calc degrees(2)') self.assertNotError('calc (2 * 3) - 2*(3*4)') self.assertNotError('calc (3) - 2*(3*4)') self.assertNotError('calc (1600 * 1200) - 2*(1024*1280)') self.assertNotError('calc 3-2*4') self.assertNotError('calc (1600 * 1200)-2*(1024*1280)') def testCalcNoNameError(self): self.assertNotRegexp('calc foobar(x)', 'NameError') def testCalcImaginary(self): self.assertResponse('calc 3 + sqrt(-1)', '3+i') def testCalcFloorWorksWithSqrt(self): self.assertNotError('calc floor(sqrt(5))') def testCaseInsensitive(self): self.assertNotError('calc PI**PI') def testCalcMaxMin(self): self.assertResponse('calc max(1,2)', '2') self.assertResponse('calc min(1,2)', '1') def testCalcStrFloat(self): self.assertResponse('calc 3+33333333333333', '33333333333336') def testICalc(self): self.assertResponse('icalc 1^1', '0') self.assertResponse('icalc 10**24', '1' + '0'*24) self.assertRegexp('icalc 49/6', '8.16') def testRpn(self): self.assertResponse('rpn 5 2 +', '7') self.assertResponse('rpn 1 2 3 +', 'Stack: [1, 5]') self.assertResponse('rpn 1 dup', 'Stack: [1, 1]') self.assertResponse('rpn 2 3 4 + -', str(2-7)) self.assertNotError('rpn 2 degrees') def testRpnSwap(self): self.assertResponse('rpn 1 2 swap', 'Stack: [2, 1]') def testRpmNoSyntaxError(self): self.assertNotRegexp('rpn 2 3 foobar', 'SyntaxError') def testConvert(self): self.assertResponse('convert 1 m to cm', '100') self.assertResponse('convert m to cm', '100') self.assertResponse('convert 3 metres to km', '0.003') self.assertResponse('convert 32 F to C', '0') self.assertResponse('convert 32 C to F', '89.6') self.assertResponse('convert [calc 2*pi] rad to degree', '360') self.assertResponse('convert amu to atomic mass unit', '1') self.assertResponse('convert [calc 2*pi] rad to circle', '1') self.assertError('convert 1 meatball to bananas') self.assertError('convert 1 gram to meatballs') self.assertError('convert 1 mol to grams') self.assertError('convert 1 m to kpa') def testConvertSingularPlural(self): self.assertResponse('convert [calc 2*pi] rads to degrees', '360') self.assertResponse('convert 1 carat to grams', '0.2') self.assertResponse('convert 10 lbs to oz', '160') self.assertResponse('convert mA to amps', '0.001') def testConvertCaseSensitivity(self): self.assertError('convert MA to amps') self.assertError('convert M to amps') self.assertError('convert Radians to rev') def testUnits(self): self.assertNotError('units') self.assertNotError('units mass') self.assertNotError('units flux density') def testAbs(self): self.assertResponse('calc abs(2)', '2') self.assertResponse('calc abs(-2)', '2') self.assertResponse('calc abs(2.0)', '2') self.assertResponse('calc abs(-2.0)', '2') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Math/__init__.py0000644000000000000000000000434011206611405016364 0ustar ### # 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. ### """ Various math-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {supybot.Author('Keith Jones', 'kmj', ''): ['convert']} 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 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Math/local/0000755000000000000000000000000011206611405015344 5ustar supybot-0.83.4.1.ds.orig/plugins/Math/local/__init__.py0000644000000000000000000000007211206611405017454 0ustar # Stub so local is a module, used for third-party modules supybot-0.83.4.1.ds.orig/plugins/Math/local/convertcore.py0000644000000000000000000012254311206611405020256 0ustar #**************************************************************************** # This file has been modified from its original version. It has been # formatted to fit your irc bot. # # The original version is a nifty PyQt application written by Douglas Bell, # available at http://convertall.bellz.org/ # # Below is the original copyright. Doug Bell rocks. # The hijacker is Keith Jones, and he has no bomb in his shoe. # #**************************************************************************** import re, copy, sys, os.path, StringIO import supybot.conf as conf import supybot.registry as registry unitData = \ """ #***************************************************************************** # units.dat, the units data file, version 0.3.1 # # ConvertAll, a units conversion program # Copyright (C) 2005, Douglas W. Bell # # This is free software; you can redistribute it and/or modify it under the # terms of the GNU General Public License, Version 2. This program is # distributed in the hope that it will be useful, but WITTHOUT ANY WARRANTY. #***************************************************************************** # # Units are defined by an optional quantity and an equivalent unit or unit # combination. A python expression may be used for the quantity, but is # resticted to using only the following operators: *, /, +, -, **, (, ). # Beware of integer division truncation: be sure to use a float for at # least one of the values. # # The unit type must be placed in square brackets before a set of units. # The first comment after the equivalent unit will be put in parenthesis after # the unit name (usually used to give the full name of an abbreviated unit). # The next comment will be used in the program list's comment column; # later comments and full line comments are ignored. # # Non-linear units are indicated with an equivalent unit in square brackets, # followed by either equations or equivalency lists for the definition. # For equations, two are given, separated by a ';'. Both are functions of # "x", the first going from the unit to the equivalent unit and the second # one in reverse. Any valid Python expression returning a float (including # the functions in the math module) should work. The equivalency list is a # python list of tuples giving points for linear interpolation. # # All units must reduce to primitive units, which are indicated by an '!' # as the equivalent unit. Circular refernces must also be avoided. # # Primitive units: kg, m, s, K, A, mol, cd, rad, sr, bit, unit # ############################################################################## # # mass units # [mass] kg = ! # kilogram kilogram = kg key = kg # # drug slang hectogram = 100 gram dekagram = 10 gram gram = 0.001 kg g = gram # gram decigram = 0.1 gram centigram = 0.01 gram milligram = 0.001 gram mg = milligram # milligram microgram = 0.001 mg tonne = 1000 kg # # metric metric ton = tonne megagram = tonne kilotonne = 1000 tonne # # metric gigagram = 1e9 gram teragram = 1e12 gram carat = 0.2 gram ct = carat # carat amu = 1.66053873e-27 kg # atomic mass atomic mass unit = amu pound = 0.45359237 kg lb = pound # pound lbm = pound # pound ounce = 1/16.0 pound oz = ounce # ounce lid = ounce # # drug slang pound troy = 5760 grain lb troy = pound troy # pound troy ounce troy = 1/12.0 lb troy oz troy = ounce troy # ounce troy ton = 2000 lb # # non-metric kiloton = 1000 ton # # non-metric slug = lbf*s^2/ft stone = 14 lb grain = 1/7000.0 lb # # length / distance units # [length] m = ! # meter meter = m metre = m decimeter = 0.1 m cm = 0.01 m # centimeter centimeter = cm mm = 0.001 m # millimeter millimeter = mm micrometer = 1e-6 m micron = micrometer nanometer = 1e-9 m nm = nanometer # nanometer dekameter = 10 m hectometer = 100 m km = 1000 m # kilometer kilometer = km megameter = 1000 km angstrom = 1e-10 m fermi = 1e-15 m # # nuclear sizes inch = 2.54 cm in = inch # inch inches = inch mil = 0.001 inch microinch = 1e-6 inch microinches = microinch foot = 12 inch ft = foot # foot feet = foot yard = 3 ft yd = yard # yard mile = 5280 ft mi = mile # mile nautical mile = 1852 m nmi = nautical mile # nautical mile league = 3 mile chain = 66 ft fathom = 6 ft rod = 5.5 yard furlong = 40 rod hand = 4 inch cubit = 21.8 inch # # biblical unit point = 1/72.27 inch pica = 12 point caliber = 0.01 inch # # bullet sizes football field = 100 yd marathon = 46145 yd mil Swedish = 10 km au = 1.49597870691e11 m # astronomical unit astronomical unit = au light year = 365.25 light speed * day light minute = light speed * min light second = light speed * s parsec = 3.0856775813e16 m kiloparsec = 1000 parsec megaparsec = 1000 kiloparsec screw size = [in] 0.013*x + 0.06 ; (x - 0.06) / 0.013 \ # # Unified diameters, non-linear AWG = [in] 92.0**((36-x)/39.0)/200.0 ; \ 36 - 39.0*log(200.0*x)/log(92.0) \ # American Wire Gauge \ # use -1, -2 for 00, 000; non-linear American Wire Gauge = [in] 92.0**((36-x)/39.0)/200.0 ; \ 36 - 39.0*log(200.0*x)/log(92.0) \ # # use -1, -2 for 00, 000; non-linear standard gauge = [in] [(-5, .448350), (1, .269010), (14, .0747250), \ (16, .0597800), (17, .0538020), (20, .0358680), \ (26, .0179340), (31, .0104615), (36, .00672525), \ (38, .00597800)] # steel \ # Manufacturers Std. Gauge, non-linear zinc gauge = [in] [(1, .002), (10, .02), (15, .04), (19, .06), \ (23, .1), (24, .125), (27, .5), (28, 1)] \ # # sheet metal thickness, non-linear ring size = [in] 0.1018*x + 1.4216 ; (x - 1.4216) / 0.1018 \ # # US size, circum., non-linear shoe size mens = [in] x/3.0 + 7 + 1/3.0 ; (x - 7 - 1/3.0) * 3 \ # # US sizes, non-linear shoe size womens = [in] x/3.0 + 6 + 5/6.0 ; (x - 6 - 5/6.0) * 3 \ # # US sizes, non-linear # # time units # [time] s = ! # second sec = s # second second = s ms = 0.001 s # millisecond millisecond = ms microsecond = 1e-6 s ns = 1e-9 s # nanosecond nanosecond = ns minute = 60 s min = minute # minute hour = 60 min hr = hour # hour bell = 30 min # # naval definition watch = 4 hour watches = watch day = 24 hr week = 7 day wk = week # week fortnight = 14 day month = 1/12.0 year year = 365.242198781 day yr = year # year calendar year = 365 day decade = 10 year century = 100 year centuries = century millennium = 1000 year millennia = millennium [scheduling] man hour = 168/40.0 hour man week = 40 man hour man month = 1/12.0 man year man year = 52 man week # # temperature # [temperature] K = ! # Kelvin Kelvin = K deg K = K # Kelvin degree Kelvin = K C = [K] x + 273.15 ; x - 273.15 # Celsius # non-linear Celsius = [K] x + 273.15 ; x - 273.15 # # non-linear deg C = [K] x + 273.15 ; x - 273.15 # Celsius # non-linear degree Celsius = [K] x + 273.15 ; x - 273.15 # # non-linear R = 5/9.0 K # Rankine Rankine = R deg R = R # Rankine F = [R] x + 459.67 ; x - 459.67 # Fahrenheit # non-linear Fahrenheit = [R] x + 459.67 ; x - 459.67 # # non-linear deg F = [R] x + 459.67 ; x - 459.67 # Fahrenheit # non-linear degree Fahrenheit = [R] x + 459.67 ; x - 459.67 # # non-linear [temp. diff.] C deg = K # Celsius degree Celsius degree = C deg F deg = R # Fahrenheit deg. Fahrenheit degree = F deg # # electrical units # [current] A = ! # ampere ampere = A amp = A milliampere = 0.001 A milliamp = milliampere mA = milliampere # milliampere microampere = 0.001 mA kiloampere = 1000 A kA = kiloampere # kiloampere [charge] coulomb = A*s amp hour = A*hr mAh = 0.001 amp hour # milliamp hour milliamp hour = mAh [potential] volt = W/A V = volt # volt millivolt = 0.001 volt mV = millivolt # millivolt kilovolt = 1000 volt kV = kilovolt # kilovolt [resistance] ohm = V/A milliohm = 0.001 ohm microhm = 0.001 milliohm kilohm = 1000 ohm [conductance] siemens = A/V [capacitance] farad = coulomb/V millifarad = 0.001 farad microfarad = 0.001 millifarad nanofarad = 1e-9 farad picofarad = 1e-12 farad [magn. flux] weber = V*s Wb = weber # weber [inductance] henry = Wb/A H = henry # henry millihenry = 0.001 henry mH = millihenry # millihenry microhenry = 0.001 mH [flux density] tesla = Wb/m^2 T = tesla # tesla # # molecular units # [molecular qty] mol = ! # mole # gram mole mole = mol # # gram mole gram mole = mol kilomole = 1000 mol kmol = kilomole # kilomole pound mole = mol*lbm/gram lbmol = pound mole # pound mole [size of a mol] avogadro = gram/amu*mol # # Illumination units # [lum. intens.] cd = ! # candela candela = cd [luminous flux] lumen = cd * sr lm = lumen # lumen [illuminance] lux = lumen/m^2 footcandle = lumen/ft^2 metercandle = lumen/m^2 [luminance] lambert = cd/pi*cm^2 millilambert = 0.001 lambert footlambert = cd/pi*ft^2 # # angular units # [angle] radian = ! rad = radian # radian circle = 2 pi*radian turn = circle revolution = circle rev = revolution # revolution degree = 1/360.0 circle deg = degree # degree arc min = 1/60.0 degree # minute arc minute = arc min min arc = arc min # minute minute arc = arc min arc sec = 1/60.0 arc min # second arc second = arc sec sec arc = arc sec # second second arc = arc sec quadrant = 1/4.0 circle right angle = quadrant gradian = 0.01 quadrant # # solid angle units # [solid angle] sr = ! # steradian steradian = sr sphere = 4 pi*sr hemisphere = 1/2.0 sphere # # information units # [data] bit = ! kilobit = 1000 bit # # based on power of 10 megabit = 1000 kilobit # # based on power of 10 byte = 8 bit B = byte # byte kilobyte = 1024 byte # # based on power of 2 kB = kilobyte # kilobyte # based on power of 2 megabyte = 1024 kB # # based on power of 2 MB = megabyte # megabyte # based on power of 2 gigabyte = 1024 MB # # based on power of 2 GB = gigabyte # gigabyte # based on power of 2 terabyte = 1024 GB # # based on power of 2 TB = terabyte # terabyte # based on power of 2 petabyte = 1024 TB # # based on power of 2 PB = petabyte # petabyte # based on power of 2 kilobyte IEC std = 1000 byte # # based on power of 10 kB IEC std = kilobyte IEC std # kilobyte # based on power of 10 megabyte IEC std = 1000 kB IEC std # # based on power of 10 MB IEC std = megabyte IEC std # megabyte # based on power of 10 gigabyte IEC std = 1000 MB IEC std # # based on power of 10 GB IEC std = gigabyte IEC std # gigabyte # based on power of 10 terabyte IEC std = 1000 GB IEC std # # based on power of 10 TB IEC std = terabyte IEC std # terabyte # based on power of 10 petabyte IEC std = 1000 TB IEC std # # based on power of 10 PB IEC std = petabyte IEC std # petabyte # based on power of 10 kibibyte = 1024 byte KiB = kibibyte # kibibyte mebibyte = 1024 KiB MiB = mebibyte # mebibyte gibibyte = 1024 MiB GiB = gibibyte # gibibyte tebibyte = 1024 GiB TiB = tebibyte # tebibyte pebibyte = 1024 TiB PiB = pebibyte # pebibyte [data transfer] bps = bit/sec # bits / second kbps = 1000 bps # kilobits / sec. # based on power of 10 # # Unitless numbers # [quantity] unit = ! 1 = unit # unit pi = 3.14159265358979323846 unit pair = 2 unit hat trick = 3 unit # # sports dozen = 12 unit doz = dozen # dozen bakers dozen = 13 unit score = 20 unit gross = 144 unit great gross = 12 gross ream = 500 unit percent = 0.01 unit % = percent mill = 0.001 unit [interest rate] APR = [unit] log(1 + x/100) ; (exp(x) - 1)*100 \ # annual % rate # based on continuous compounding [concentration] proof = 1/200.0 unit # # alcohol content ppm = 1e-6 unit # parts per million parts per million = ppm ppb = 1e-9 unit # parts per billion parts per billion = ppb ppt = 1e-12 unit # parts per trillion parts per trillion = ppt karat = 1/24.0 unit # # gold purity carat gold = karat # # gold purity # # force units # [force] newton = kg*m/s^2 N = newton # newton dekanewton = 10 newton kilonewton = 1000 N kN = kilonewton # kilonewton meganewton = 1000 kN millinewton = 0.001 N dyne = cm*g/s^2 kg force = kg * gravity # kilogram f kgf = kg force # kilogram force kilogram force = kg force gram force = g * gravity pound force = lbm * gravity lbf = pound force # pound force ton force = ton * gravity ounce force = ounce * gravity ozf = ounce force # ounce force # # area units # [area] barn = 1e-28 m^2 # # particle physics are = 100 m^2 decare = 10 are dekare = 10 are hectare = 100 are acre = 10 chain^2 section = mile^2 township = 36 section homestead = 160 acre rai = 1600 m^2 # # Thai ngaan = 400 m^2 # # Thai circular inch = 1/4.0 pi*in^2 # # area of 1 inch circle circular mil = 1/4.0 pi*mil^2 # # area of 1 mil circle # # volume units # [volume] cc = cm^3 # cubic centimeter cubic centimeter = cc liter = 1000 cc l = liter # liter litre = liter deciliter = 0.1 liter centiliter = 0.01 liter milliliter = cc ml = milliliter # milliliter dekaliter = 10 liter hectoliter = 100 liter kiloliter = 1000 liter kl = kiloliter # kiloliter megaliter = 1000 kiloliter gallon = 231 in^3 # # US liquid gal = gallon # gallon # US liquid quart = 1/4.0 gallon # # US liquid qt = quart # quart # US liquid pint = 1/2.0 quart # # US liquid pt = pint # pint # US liquid fluid ounce = 1/16.0 pint # # US fl oz = fluid ounce # fluid ounce # US ounce fluid = fluid ounce # # US imperial gallon = 4.54609 liter imp gal = imperial gallon # imperial gallon gallon imperial = imperial gallon imperial quart = 1/4.0 imp gal imp qt = imperial quart # imperial quart quart imperial = imperial quart imperial pint = 1/8.0 imp gal imp pt = imperial pint # imperial pint pint imperial = imperial pint imperial fluid ounce = 1/160.0 imp gal imp fl oz = imperial fluid ounce # imperial fluid ounce cup = 8 fl oz tablespoon = 1/16.0 cup tbsp = tablespoon # tablespoon teaspoon = 1/3.0 tbsp tsp = teaspoon # teaspoon barrel = 42 gallon bbl = barrel # barrel shot = 1.5 fl oz fifth = 1/5.0 gallon # # alcohol wine bottle = 750 ml magnum = 1.5 liter # # alcohol keg = 15.5 gallon # # beer hogshead wine = 63 gal hogshead beer = 54 gal bushel = 2150.42 in^3 peck = 1/4.0 bushel cord = 128 ft^3 board foot = ft^2*in board feet = board foot # # velocity units # [velocity] knot = nmi/hr kt = knot # knot light speed = 2.99792458e8 m/s mph = mi/hr # miles/hour kph = km/hr # kilometers/hour mach = 340.29 m/s # # speed sound at STP [rot. velocity] rpm = rev/min # rev/min rps = rev/sec # rev/sec # # flow rate units # [fluid flow] gph = gal/hr # gallons/hour gpm = gal/min # gallons/minute cfs = ft^3/sec # cu ft/second cfm = ft^3/min # cu ft/minute lpm = l/min # liter/min [gas flow] sccm = atm*cc/min # std cc/min # pressure * flow sccs = atm*cc/sec # std cc/sec # pressure * flow slpm = atm*l/min # std liter/min # pressure * flow slph = atm*l/hr # std liter/hour # pressure * flow scfh = atm*ft^3/hour # std cu ft/hour # pressure * flow scfm = atm*ft^3/min # std cu ft/min # pressure * flow # # pressure units # [pressure] Pa = N/m^2 # pascal pascal = Pa hPa = 100 Pa # hectopascal hectopascal = hPa kPa = 1000 Pa # kilopascal kilopascal = kPa MPa = 1000 kPa # megapascal megapascal = MPa GPa = 1000 MPa # gigapascal gigapascal = GPa atm = 101325 Pa # atmosphere atmosphere = atm bar = 1e5 Pa mbar = 0.001 bar # millibar millibar = mbar microbar = 0.001 mbar decibar = 0.1 bar kilobar = 1000 bar megabar = 1000 kilobar mm Hg = mm*density Hg*gravity millimeter of Hg = mm Hg torr = mm Hg in Hg = in*density Hg*gravity # inch of Hg inch of Hg = in Hg m water = m*density water*gravity # meter of H2O m H2O = m water # meter of H2O meter of water = m water in water = in*density water*gravity # inch of H2O in H2O = in water # inch of H2O inch of water = in water ft water = ft*density water*gravity # feet of H2O ft H2O = ft water # feet of H20 feet of water = ft water foot of head = ft water ft hd = ft water # foot of head psi = lbf/in^2 # pound / sq inch pound per sq inch = psi ksi = 1000 psi # 1000 lb / sq inch # # density units # [density] density water = gram/cm^3 density sea water = 1.025 gram/cm^3 density Hg = 13.5950981 gram/cm^3 density air = 1.293 kg/m^3 # # at STP density steel = 0.283 lb/in^3 # # carbon steel density aluminum = 0.098 lb/in^3 density zinc = 0.230 lb/in^3 density brass = 0.310 lb/in^3 # # 80Cu-20Zn density copper = 0.295 lb/in^3 density iron = 0.260 lb/in^3 # # cast iron density nickel = 0.308 lb/in^3 density tin = 0.275 lb/in^3 density titanium = 0.170 lb/in^3 density silver = 0.379 lb/in^3 density nylon = 0.045 lb/in^3 density polycarbonate = 0.045 lb/in^3 # # energy units # [energy] joule = N*m J = joule # joule kilojoule = 1000 joule kJ = kilojoule # kilojoule megajoule = 1000 kilojoule gigajoule = 1000 megajoule millijoule = 0.001 joule mJ = millijoule # millijoule calorie = 4.1868 J cal = calorie # calorie kilocalorie = 1000 cal kcal = kilocalorie # kilocalorie calorie food = kilocalorie Btu = cal*lb*R/g*K # British thermal unit British thermal unit = Btu erg = cm*dyne electronvolt = 1.602176462e-19 J eV = electronvolt # electronvolt kWh = kW*hour # kilowatt-hour kilowatt hour = kWh ton TNT = 4.184e9 J # # power units # [power] watt = J/s W = watt # watt kilowatt = 1000 W kW = kilowatt # kilowatt megawatt = 1000 kW MW = megawatt # megawatt gigawatt = 1000 MW GW = gigawatt # gigawatt milliwatt = 0.001 W horsepower = 550 ft*lbf/sec hp = horsepower # horsepower metric horsepower = 75 kgf*m/s # # frequency # [frequency] hertz = unit/sec Hz = hertz # hertz millihertz = 0.001 Hz kilohertz = 1000 Hz kHz = kilohertz # kilohertz megahertz = 1000 kHz MHz = megahertz # megahertz gigahertz = 1000 MHz GHz = gigahertz # gigahertz # # radioactivity # [radioactivity] becquerel = unit/sec Bq = becquerel # becquerel curie = 3.7e10 Bq millicurie = 0.001 curie roentgen = 2.58e-4 coulomb/kg [radiation dose] gray = J/kg Gy = gray # gray rad. abs. dose = 0.001 Gy # # commonly rad sievert = J/kg # # equiv. dose millisievert = 0.001 sievert # # equiv. dose Sv = sievert # sievert # equiv. dose rem = 0.01 Sv # # roentgen equiv mammal millirem = 0.001 rem # # roentgen equiv mammal # # viscosity # [dyn viscosity] poise = g/cm*s P = poise # poise centipoise = 0.01 poise cP = centipoise # centipoise [kin viscosity] stokes = cm^2/s St = stokes # stokes centistokes = 0.01 stokes cSt = centistokes # centistokes # # misc. units # [acceleration] gravity = 9.80665 m/s^2 [constant] gravity constant = 6.673e-11 N*m^2/kg^2 gas constant = 8.314472 J/mol*K # R [fuel consumpt.] mpg = mi/gal # miles/gallon liter per 100 km = [mpg] 3.785411784 / (x * 0.01609344) ; \ 3.785411784 / (x * 0.01609344) # # non-linear """ class UnitGroup: "Stores, updates and converts a group of units" maxDecPlcs = 8 def __init__(self, unitData, option): self.unitData = unitData self.option = option self.unitList = [] self.currentNum = 0 self.factor = 1.0 self.reducedList = [] self.linear = 1 def update(self, text, cursorPos=None): "Decode user entered text into units" self.unitList = self.parseGroup(text) if cursorPos != None: self.updateCurrentUnit(text, cursorPos) else: self.currentNum = len(self.unitList) - 1 def updateCurrentUnit(self, text, cursorPos): "Set current unit number" self.currentNum = len(re.findall('[\*/]', text[:cursorPos])) def currentUnit(self): "Return current unit if its a full match, o/w None" if self.unitList and self.unitList[self.currentNum].equiv: return self.unitList[self.currentNum] return None def currentPartialUnit(self): "Return unit with at least a partial match, o/w None" if not self.unitList: return None return self.unitData.findPartialMatch(self.unitList[self.currentNum]\ .name) def currentSortPos(self): "Return unit near current unit for sorting" if not self.unitList: return self.unitData[self.unitData.sortedKeys[0]] return self.unitData.findSortPos(self.unitList[self.currentNum]\ .name) def replaceCurrent(self, unit): "Replace the current unit with unit" if self.unitList: exp = self.unitList[self.currentNum].exp self.unitList[self.currentNum] = copy.copy(unit) self.unitList[self.currentNum].exp = exp else: self.unitList.append(copy.copy(unit)) def completePartial(self): "Replace a partial unit with a full one" if self.unitList and not self.unitList[self.currentNum].equiv: text = self.unitList[self.currentNum].name unit = self.unitData.findPartialMatch(text) if unit: exp = self.unitList[self.currentNum].exp self.unitList[self.currentNum] = copy.copy(unit) self.unitList[self.currentNum].exp = exp def moveToNext(self, upward): "Replace unit with adjacent one based on match or sort position" unit = self.currentSortPos() num = self.unitData.sortedKeys.index(unit.name.\ replace(' ', '')) \ + (upward and -1 or 1) if 0 <= num < len(self.unitData.sortedKeys): self.replaceCurrent(self.unitData[self.unitData.sortedKeys[num]]) def addOper(self, mult): "Add new operator & blank unit after current, * if mult is true" if self.unitList: self.completePartial() prevExp = self.unitList[self.currentNum].exp self.currentNum += 1 self.unitList.insert(self.currentNum, Unit('')) if (not mult and prevExp > 0) or (mult and prevExp < 0): self.unitList[self.currentNum].exp = -1 def changeExp(self, newExp): "Change the current unit's exponent" if self.unitList: self.completePartial() if self.unitList[self.currentNum].exp > 0: self.unitList[self.currentNum].exp = newExp else: self.unitList[self.currentNum].exp = -newExp def clearUnit(self): "Remove units" self.unitList = [] def parseGroup(self, text): "Return list of units from text string" unitList = [] parts = [part.strip() for part in re.split('([\*/])', text)] numerator = 1 while parts: unit = self.parseUnit(parts.pop(0)) if not numerator: unit.exp = -unit.exp if parts and parts.pop(0) == '/': numerator = not numerator unitList.append(unit) return unitList def parseUnit(self, text): "Return a valid or invalid unit with exponent from a text string" parts = text.split('^', 1) exp = 1 if len(parts) > 1: # has exponent try: exp = int(parts[1]) except ValueError: if parts[1].lstrip().startswith('-'): exp = -Unit.partialExp # tmp invalid exp else: exp = Unit.partialExp unitText = parts[0].strip().replace(' ', '') unit = copy.copy(self.unitData.get(unitText, None)) if not unit and unitText and unitText[-1] == 's' and not \ self.unitData.findPartialMatch(unitText): # check for plural unit = copy.copy(self.unitData.get(unitText[:-1], None)) if not unit: #unit = Unit(parts[0].strip()) # tmp invalid unit raise UnitDataError('%s is not a valid unit.' % (unitText)) unit.exp = exp return unit def unitString(self, unitList=None): "Return the full string for this group or a given group" if unitList == None: unitList = self.unitList[:] fullText = '' if unitList: fullText = unitList[0].unitText(0) numerator = 1 for unit in unitList[1:]: if (numerator and unit.exp > 0) \ or (not numerator and unit.exp < 0): fullText = '%s * %s' % (fullText, unit.unitText(1)) else: fullText = '%s / %s' % (fullText, unit.unitText(1)) numerator = not numerator return fullText def groupValid(self): "Return 1 if all unitself.reducedLists are valid" if not self.unitList: return 0 for unit in self.unitList: if not unit.unitValid(): return 0 return 1 def reduceGroup(self): "Update reduced list of units and factor" self.linear = 1 self.reducedList = [] self.factor = 1.0 if not self.groupValid(): return count = 0 tmpList = self.unitList[:] while tmpList: count += 1 if count > 5000: raise UnitDataError, 'Circular unit definition' unit = tmpList.pop(0) if unit.equiv == '!': self.reducedList.append(copy.copy(unit)) elif not unit.equiv: raise UnitDataError, 'Invalid conversion for "%s"' % unit.name else: if unit.fromEqn: self.linear = 0 newList = self.parseGroup(unit.equiv) for newUnit in newList: newUnit.exp *= unit.exp tmpList.extend(newList) self.factor *= unit.factor**unit.exp self.reducedList.sort() tmpList = self.reducedList[:] self.reducedList = [] for unit in tmpList: if self.reducedList and unit == self.reducedList[-1]: self.reducedList[-1].exp += unit.exp else: self.reducedList.append(unit) self.reducedList = [unit for unit in self.reducedList if \ unit.name != 'unit' and unit.exp != 0] def categoryMatch(self, otherGroup): "Return 1 if unit types are equivalent" if not self.checkLinear() or not otherGroup.checkLinear(): return 0 return self.reducedList == otherGroup.reducedList and \ [unit.exp for unit in self.reducedList] \ == [unit.exp for unit in otherGroup.reducedList] def checkLinear(self): "Return 1 if linear or acceptable non-linear" if not self.linear: if len(self.unitList) > 1 or self.unitList[0].exp != 1: return 0 return 1 def compatStr(self): "Return string with reduced unit or linear compatability problem" if self.checkLinear(): return self.unitString(self.reducedList) return 'Cannot combine non-linear units' def convert(self, num, toGroup): "Return num of this group converted to toGroup" if self.linear: num *= self.factor else: num = self.nonLinearCalc(num, 1) * self.factor n2 = -1 if toGroup.linear: n2 = num / toGroup.factor else: n2 = toGroup.nonLinearCalc(num / toGroup.factor, 0) return n2 def nonLinearCalc(self, num, isFrom): "Return result of non-linear calculation" x = num try: if self.unitList[0].toEqn: # regular equations if isFrom: temp = float(eval(self.unitList[0].fromEqn)) return temp temp = float(eval(self.unitList[0].toEqn)) return temp data = list(eval(self.unitList[0].fromEqn)) # extrapolation list if isFrom: data = [(float(group[0]), float(group[1])) for group in data] else: data = [(float(group[1]), float(group[0])) for group in data] data.sort() pos = len(data) - 1 for i in range(len(data)): if num <= data[i][0]: pos = i break if pos == 0: pos = 1 y = (num-data[pos-1][0]) / float(data[pos][0]-data[pos-1][0]) \ * (data[pos][1]-data[pos-1][1]) + data[pos-1][1] return y except OverflowError: return 1e9999 except: raise UnitDataError, 'Bad equation for %s' % self.unitList[0].name def convertStr(self, num, toGroup): "Return formatted string of converted number" return self.formatNumStr(self.convert(num, toGroup)) def formatNumStr(self, num): "Return num string formatted per options" decPlcs = self.option.intData('DecimalPlaces', 0, UnitGroup.maxDecPlcs) if self.option.boolData('SciNotation'): return ('%%0.%dE' % decPlcs) % num if self.option.boolData('FixedDecimals'): return ('%%0.%df' % decPlcs) % num return ('%%0.%dG' % decPlcs) % num class UnitDataError(Exception): pass class UnitData(dict): def __init__(self): dict.__init__(self) self.sortedKeys = [] def readData(self): "Read all unit data from file" types = [] typeUnits = {} try: f = StringIO.StringIO(unitData) lines = f.readlines() f.close() except IOError: raise UnitDataError, 'Can not read "units.dat" file' for i in range(len(lines)): # join continuation lines delta = 1 while lines[i].rstrip().endswith('\\'): lines[i] = ''.join([lines[i].rstrip()[:-1], lines[i+delta]]) lines[i+delta] = '' delta += 1 units = [Unit(line) for line in lines if \ line.split('#', 1)[0].strip()] # remove comment lines typeText = '' for unit in units: # find & set headings if unit.name.startswith('['): typeText = unit.name[1:-1].strip() types.append(typeText) typeUnits[typeText] = [] unit.typeName = typeText units = [unit for unit in units if unit.equiv] # keep valid units for unit in units: self[unit.name.replace(' ', '')] = unit typeUnits[unit.typeName].append(unit.name) self.sortedKeys = self.keys() self.sortedKeys.sort() if len(self.sortedKeys) < len(units): raise UnitDataError, 'Duplicate unit names found' return (types, typeUnits) def findPartialMatch(self, text): "Return first partially matching unit or None" text = text.replace(' ', '') if not text: return None for name in self.sortedKeys: if name.startswith(text): return self[name] return None def findSortPos(self, text): "Return unit whose abbrev comes immediately after text" text = text.replace(' ', '') for name in self.sortedKeys: if text <= name: return self[name] return self[self.sortedKeys[-1]] class Unit: "Reads and stores a single unit conversion" partialExp = 1000 def __init__(self, dataStr): dataList = dataStr.split('#') unitList = dataList.pop(0).split('=', 1) self.name = unitList.pop(0).strip() self.equiv = '' self.factor = 1.0 self.fromEqn = '' # used only for non-linear units self.toEqn = '' # used only for non-linear units if unitList: self.equiv = unitList[0].strip() if self.equiv[0] == '[': # used only for non-linear units try: self.equiv, self.fromEqn = re.match('\[(.*?)\](.*)', \ self.equiv).groups() if ';' in self.fromEqn: self.fromEqn, self.toEqn = self.fromEqn.split(';', 1) self.toEqn = self.toEqn.strip() self.fromEqn = self.fromEqn.strip() except AttributeError: raise UnitDataError, 'Bad equation for "%s"' % self.name else: # split factor and equiv unit for linear parts = self.equiv.split(None, 1) if len(parts) > 1 and re.search('[^\d\.eE\+\-\*/]', parts[0]) \ == None: # only allowed digits and operators try: self.factor = float(eval(parts[0])) self.equiv = parts[1] except: pass self.comments = [comm.strip() for comm in dataList] self.comments.extend([''] * (2 - len(self.comments))) self.exp = 1 self.viewLink = [None, None] self.typeName = '' def description(self): "Return name and 1st comment (usu. full name) if applicable" if self.comments[0]: return '%s (%s)' % (self.name, self.comments[0]) return self.name def unitValid(self): "Return 1 if unit and exponent are valid" if self.equiv and -Unit.partialExp < self.exp < Unit.partialExp: return 1 return 0 def unitText(self, absExp=0): "Return text for unit name with exponent or absolute value of exp" exp = self.exp if absExp: exp = abs(self.exp) if exp == 1: return self.name if -Unit.partialExp < exp < Unit.partialExp: return '%s^%d' % (self.name, exp) if exp > 1: return '%s^' % self.name else: return '%s^-' % self.name def __cmp__(self, other): return cmp(self.name, other.name) ############################################################################ # Wrapper functionality # ############################################################################ # Parse the data file, and set everything up for conversion data = UnitData() (types, unitsByType) = data.readData() # At the moment, we're not handling options option = None # set up the objects for unit conversion fromUnit = UnitGroup(data, option) toUnit = UnitGroup(data, option) def convert(num, unit1, unit2): """ Convert from one unit to another num is the factor for the first unit. Raises UnitDataError for various errors. """ fromUnit.update(unit1) toUnit.update(unit2) fromUnit.reduceGroup() toUnit.reduceGroup() # Match up unit categories if not fromUnit.categoryMatch(toUnit): raise UnitDataError('unit categories did not match') return fromUnit.convert(num, toUnit) def units(type): """ Return comma separated string list of units of given type, or a list of types if the argument is not valid. """ if type in types: return '%s units: %s' % (type, ', '.join(unitsByType[type])) else: return 'valid types: ' + ', '.join(types) supybot-0.83.4.1.ds.orig/plugins/Math/README.txt0000644000000000000000000000011711206611405015747 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Math/plugin.py0000644000000000000000000003035411206611405016127 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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 __future__ import division import re import math import cmath import types import string import supybot.utils as utils from supybot.commands import * import supybot.callbacks as callbacks convertcore = utils.python.universalImport('local.convertcore') baseArg = ('int', 'base', lambda i: i <= 36) class Math(callbacks.Plugin): def base(self, irc, msg, args, frm, to, number): """ [] Converts from base to base . If is left out, it converts to decimal. """ if not number: number = str(to) to = 10 try: irc.reply(self._convertBaseToBase(number, to, frm)) except ValueError: irc.error('Invalid for base %s: %s' % (frm, number)) base = wrap(base, [('int', 'base', lambda i: 2 <= i <= 36), optional(('int', 'base', lambda i: 2 <= i <= 36), 10), additional('something')]) def _convertDecimalToBase(self, number, base): """Convert a decimal number to another base; returns a string.""" if number == 0: return '0' elif number < 0: negative = True number = -number else: negative = False digits = [] while number != 0: digit = number % base if digit >= 10: digit = string.uppercase[digit - 10] else: digit = str(digit) digits.append(digit) number = number // base digits.reverse() return '-'*negative + ''.join(digits) def _convertBaseToBase(self, number, toBase, fromBase): """Convert a number from any base, 2 through 36, to any other base, 2 through 36. Returns a string.""" number = long(str(number), fromBase) if toBase == 10: return str(number) return self._convertDecimalToBase(number, toBase) _mathEnv = {'__builtins__': types.ModuleType('__builtins__'), 'i': 1j} _mathEnv.update(math.__dict__) _mathEnv.update(cmath.__dict__) def _sqrt(x): if isinstance(x, complex) or x < 0: return cmath.sqrt(x) else: return math.sqrt(x) _mathEnv['sqrt'] = _sqrt _mathEnv['abs'] = abs _mathEnv['max'] = max _mathEnv['min'] = min _mathRe = re.compile(r'((?:(? Returns the value of the evaluated . The syntax is Python syntax; the type of arithmetic is floating point. Floating point arithmetic is used in order to prevent a user from being able to crash to the bot with something like '10**10**10**10'. One consequence is that large values such as '10**24' might not be exact. """ if text != text.translate(utils.str.chars, '_[]'): irc.error('There\'s really no reason why you should have ' 'underscores or brackets in your mathematical ' 'expression. Please remove them.') return #text = text.translate(utils.str.chars, '_[] \t') if 'lambda' in text: irc.error('You can\'t use lambda in this command.') return text = text.lower() def handleMatch(m): s = m.group(1) if s.startswith('0x'): i = int(s, 16) elif s.startswith('0') and '.' not in s: try: i = int(s, 8) except ValueError: i = int(s) else: i = float(s) x = complex(i) if x == abs(x): x = abs(x) # Need to use string-formatting here instead of str() because # use of str() on large numbers loses information: # str(float(33333333333333)) => '3.33333333333e+13' # float('3.33333333333e+13') => 33333333333300.0 return '%f' % x return str(x) text = self._mathRe.sub(handleMatch, text) try: self.log.info('evaluating %q from %s', text, msg.prefix) x = complex(eval(text, self._mathEnv, self._mathEnv)) irc.reply(self._complexToString(x)) except OverflowError: maxFloat = math.ldexp(0.9999999999999999, 1024) irc.error('The answer exceeded %s or so.' % maxFloat) except TypeError: irc.error('Something in there wasn\'t a valid number.') except NameError, e: irc.error('%s is not a defined function.' % str(e).split()[1]) except Exception, e: irc.error(str(e)) calc = wrap(calc, ['text']) def icalc(self, irc, msg, args, text): """ This is the same as the calc command except that it allows integer math, and can thus cause the bot to suck up CPU. Hence it requires the 'trusted' capability to use. """ if text != text.translate(utils.str.chars, '_[]'): irc.error('There\'s really no reason why you should have ' 'underscores or brackets in your mathematical ' 'expression. Please remove them.') return # This removes spaces, too, but we'll leave the removal of _[] for # safety's sake. text = text.translate(utils.str.chars, '_[] \t') if 'lambda' in text: irc.error('You can\'t use lambda in this command.') return text = text.replace('lambda', '') try: self.log.info('evaluating %q from %s', text, msg.prefix) irc.reply(str(eval(text, self._mathEnv, self._mathEnv))) except OverflowError: maxFloat = math.ldexp(0.9999999999999999, 1024) irc.error('The answer exceeded %s or so.' % maxFloat) except TypeError: irc.error('Something in there wasn\'t a valid number.') except NameError, e: irc.error('%s is not a defined function.' % str(e).split()[1]) except Exception, e: irc.error(utils.exnToString(e)) icalc = wrap(icalc, [('checkCapability', 'trusted'), 'text']) _rpnEnv = { 'dup': lambda s: s.extend([s.pop()]*2), 'swap': lambda s: s.extend([s.pop(), s.pop()]) } def rpn(self, irc, msg, args): """ Returns the value of an RPN expression. """ stack = [] for arg in args: try: x = complex(arg) if x == abs(x): x = abs(x) stack.append(x) except ValueError: # Not a float. if arg in self._mathEnv: f = self._mathEnv[arg] if callable(f): called = False arguments = [] while not called and stack: arguments.append(stack.pop()) try: stack.append(f(*arguments)) called = True except TypeError: pass if not called: irc.error('Not enough arguments for %s' % arg) return else: stack.append(f) elif arg in self._rpnEnv: self._rpnEnv[arg](stack) else: arg2 = stack.pop() arg1 = stack.pop() s = '%s%s%s' % (arg1, arg, arg2) try: stack.append(eval(s, self._mathEnv, self._mathEnv)) except SyntaxError: irc.error(format('%q is not a defined function.', arg)) return if len(stack) == 1: irc.reply(str(self._complexToString(complex(stack[0])))) else: s = ', '.join(map(self._complexToString, map(complex, stack))) irc.reply('Stack: [%s]' % s) def convert(self, irc, msg, args, number, unit1, unit2): """[] to Converts from to . If number isn't given, it defaults to 1. For unit information, see 'units' command. """ try: newNum = convertcore.convert(number, unit1, unit2) newNum = self._floatToString(newNum) irc.reply(str(newNum)) except convertcore.UnitDataError, ude: irc.error(str(ude)) convert = wrap(convert, [optional('float', 1.0),'something','to','text']) def units(self, irc, msg, args, type): """ [] With no arguments, returns a list of measurement types, which can be passed as arguments. When called with a type as an argument, returns the units of that type. """ irc.reply(convertcore.units(type)) units = wrap(units, [additional('text')]) Class = Math # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Math/config.py�����������������������������������������������������0000644�0000000�0000000�00000004456�11206611405�016102� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 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('Math', True) Math = conf.registerPlugin('Math') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Math, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Owner/�������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014453� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Owner/test.py������������������������������������������������������0000644�0000000�0000000�00000007642�11206611405�016015� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 supybot.conf as conf import supybot.plugin as plugin class OwnerTestCase(PluginTestCase): # Defaults, but hey, I'm cool. plugins = ('Owner', 'Config', 'Misc', 'Admin') def testHelpLog(self): self.assertHelp('help log') def testSrcAmbiguity(self): self.assertError('capability add foo bar') def testIrcquote(self): self.assertResponse('ircquote PRIVMSG %s :foo' % self.irc.nick, 'foo') def testFlush(self): self.assertNotError('flush') def testUpkeep(self): self.assertNotError('upkeep') def testLoad(self): self.assertError('load Owner') self.assertError('load owner') self.assertNotError('load Channel') self.assertNotError('list Owner') def testReload(self): self.assertError('reload Channel') self.assertNotError('load Channel') self.assertNotError('reload Channel') self.assertNotError('reload Channel') def testUnload(self): self.assertError('unload Foobar') self.assertNotError('load Channel') self.assertNotError('unload Channel') self.assertError('unload Channel') self.assertNotError('load Channel') self.assertNotError('unload CHANNEL') def testDisable(self): self.assertError('disable enable') self.assertError('disable identify') def testEnable(self): self.assertError('enable enable') def testEnableIsCaseInsensitive(self): self.assertNotError('disable Foo') self.assertNotError('enable foo') def testRename(self): self.assertError('rename Admin ignore IGNORE') self.assertError('rename Admin ignore ig-nore') self.assertNotError('rename Admin "capability remove" rmcap') self.assertNotRegexp('list Admin', 'capability remove') self.assertRegexp('list Admin', 'rmcap') self.assertNotError('reload Admin') self.assertNotRegexp('list Admin', 'capability remove') self.assertRegexp('list Admin', 'rmcap') self.assertNotError('unrename Admin') self.assertRegexp('list Admin', 'capability remove') self.assertNotRegexp('list Admin', 'rmcap') def testDefaultPluginErrorsWhenCommandNotInPlugin(self): self.assertError('defaultplugin foobar owner') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Owner/__init__.py��������������������������������������������������0000644�0000000�0000000�00000004356�11206611405�016574� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-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. ### """ Provides commands useful to the owner of the bot; the commands here require their caller to have the 'owner' capability. This plugin is loaded by default. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Owner/plugin.py����������������������������������������������������0000644�0000000�0000000�00000062535�11206611405�016336� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 gc import os import sys import time import socket import linecache if sys.version_info >= (2, 5, 0): import re as sre else: import sre import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.irclib as irclib import supybot.plugin as plugin import supybot.plugins as plugins import supybot.drivers as drivers import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks ### # supybot.commands. ### def registerDefaultPlugin(command, plugin): command = callbacks.canonicalName(command) conf.registerGlobalValue(conf.supybot.commands.defaultPlugins, command, registry.String(plugin, '')) # This must be set, or the quotes won't be removed. conf.supybot.commands.defaultPlugins.get(command).set(plugin) def registerRename(plugin, command=None, newName=None): g = conf.registerGlobalValue(conf.supybot.commands.renames, plugin, registry.SpaceSeparatedSetOfStrings([], """Determines what commands in this plugin are to be renamed.""")) if command is not None: g().add(command) v = conf.registerGlobalValue(g, command, registry.String('', '')) if newName is not None: v.setValue(newName) # In case it was already registered. return v else: return g def renameCommand(cb, name, newName): assert not hasattr(cb, newName), 'Cannot rename over existing attributes.' assert newName == callbacks.canonicalName(newName), \ 'newName must already be canonized.' if name != newName: method = getattr(cb.__class__, name) setattr(cb.__class__, newName, method) delattr(cb.__class__, name) registerDefaultPlugin('list', 'Misc') registerDefaultPlugin('help', 'Misc') registerDefaultPlugin('ignore', 'Admin') registerDefaultPlugin('reload', 'Owner') registerDefaultPlugin('enable', 'Owner') registerDefaultPlugin('disable', 'Owner') registerDefaultPlugin('unignore', 'Admin') registerDefaultPlugin('capabilities', 'User') registerDefaultPlugin('addcapability', 'Admin') registerDefaultPlugin('removecapability', 'Admin') class holder(object): pass # This is used so we can support a "log" command as well as a "self.log" # Logger. class LogProxy(object): """ Logs to the global Supybot log at critical priority. Useful for marking logfiles for later searching. """ __name__ = 'log' # Necessary for help. def __init__(self, log): self.log = log self.im_func = holder() self.im_func.func_name = 'log' def __call__(self, irc, msg, args, text): log.critical(text) irc.replySuccess() __call__ = wrap(__call__, ['text']) def __getattr__(self, attr): return getattr(self.log, attr) class Owner(callbacks.Plugin): # This plugin must be first; its priority must be lowest; otherwise odd # things will happen when adding callbacks. def __init__(self, irc=None): if irc is not None: assert not irc.getCallback(self.name()) self.__parent = super(Owner, self) self.__parent.__init__(irc) # Setup log object/command. self.log = LogProxy(self.log) # Setup command flood detection. self.commands = ircutils.FloodQueue(60) # Setup plugins and default plugins for commands. # # This needs to be done before we connect to any networks so that the # children of supybot.plugins (the actual plugins) exist and can be # loaded. for (name, s) in registry._cache.iteritems(): if 'alwaysLoadDefault' in name or 'alwaysLoadImportant' in name: continue if name.startswith('supybot.plugins'): try: (_, _, name) = registry.split(name) except ValueError: # unpack list of wrong size. continue # This is just for the prettiness of the configuration file. # There are no plugins that are all-lowercase, so we'll at # least attempt to capitalize them. if name == name.lower(): name = name.capitalize() conf.registerPlugin(name) if name.startswith('supybot.commands.defaultPlugins'): try: (_, _, _, name) = registry.split(name) except ValueError: # unpack list of wrong size. continue registerDefaultPlugin(name, s) # Setup Irc objects, connected to networks. If world.ircs is already # populated, chances are that we're being reloaded, so don't do this. if not world.ircs: for network in conf.supybot.networks(): try: self._connect(network) except socket.error, e: self.log.error('Could not connect to %s: %s.', network, e) except Exception, e: self.log.exception('Exception connecting to %s:', network) self.log.error('Could not connect to %s: %s.', network, e) def callPrecedence(self, irc): return ([], [cb for cb in irc.callbacks if cb is not self]) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG' and not world.testing: if ircutils.strEqual(msg.args[0], irc.nick): self.log.warning('Tried to send a message to myself: %r.', msg) return None return msg def isCommandMethod(self, name): return name == 'log' or \ self.__parent.isCommandMethod(name) def reset(self): # This has to be done somewhere, I figure here is as good place as any. callbacks.IrcObjectProxy._mores.clear() self.__parent.reset() def _connect(self, network, serverPort=None, password='', ssl=False): try: group = conf.supybot.networks.get(network) (server, port) = group.servers()[0] except (registry.NonExistentRegistryEntry, IndexError): if serverPort is None: raise ValueError, 'connect requires a (server, port) ' \ 'if the network is not registered.' conf.registerNetwork(network, password, ssl) serverS = '%s:%s' % serverPort conf.supybot.networks.get(network).servers.append(serverS) assert conf.supybot.networks.get(network).servers(), \ 'No servers are set for the %s network.' % network self.log.info('Creating new Irc for %s.', network) newIrc = irclib.Irc(network) for irc in world.ircs: if irc != newIrc: newIrc.state.history = irc.state.history driver = drivers.newDriver(newIrc) self._loadPlugins(newIrc) return newIrc def _loadPlugins(self, irc): self.log.info('Loading plugins (connecting to %s).', irc.network) alwaysLoadImportant = conf.supybot.plugins.alwaysLoadImportant() important = conf.supybot.commands.defaultPlugins.importantPlugins() for (name, value) in conf.supybot.plugins.getValues(fullNames=False): if irc.getCallback(name) is None: load = value() if not load and name in important: if alwaysLoadImportant: s = '%s is configured not to be loaded, but is being '\ 'loaded anyway because ' \ 'supybot.plugins.alwaysLoadImportant is True.' self.log.warning(s, name) load = True if load: # We don't load plugins that don't start with a capital # letter. if name[0].isupper() and not irc.getCallback(name): # This is debug because each log logs its beginning. self.log.debug('Loading %s.', name) try: m = plugin.loadPluginModule(name, ignoreDeprecation=True) plugin.loadPluginClass(irc, m) except callbacks.Error, e: # This is just an error message. log.warning(str(e)) except (plugins.NoSuitableDatabase, ImportError), e: s = 'Failed to load %s: %s' % (name, e) if not s.endswith('.'): s += '.' log.warning(s) except Exception, e: log.exception('Failed to load %s:', name) else: # Let's import the module so configuration is preserved. try: _ = plugin.loadPluginModule(name) except Exception, e: log.debug('Attempted to load %s to preserve its ' 'configuration, but load failed: %s', name, e) world.starting = False def do376(self, irc, msg): networkGroup = conf.supybot.networks.get(irc.network) for channel in networkGroup.channels(): irc.queueMsg(networkGroup.channels.join(channel)) do422 = do377 = do376 def doPrivmsg(self, irc, msg): assert self is irc.callbacks[0], \ 'Owner isn\'t first callback: %r' % irc.callbacks if ircmsgs.isCtcp(msg): return s = callbacks.addressed(irc.nick, msg) if s: ignored = ircdb.checkIgnored(msg.prefix) if ignored: self.log.info('Ignoring command from %s.', msg.prefix) return maximum = conf.supybot.abuse.flood.command.maximum() self.commands.enqueue(msg) if conf.supybot.abuse.flood.command() \ and self.commands.len(msg) > maximum \ and not ircdb.checkCapability(msg.prefix, 'owner'): punishment = conf.supybot.abuse.flood.command.punishment() banmask = ircutils.banmask(msg.prefix) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'command flood.', banmask, punishment) ircdb.ignores.add(banmask, time.time() + punishment) irc.reply('You\'ve given me %s commands within the last ' 'minute; I\'m now ignoring you for %s.' % (maximum, utils.timeElapsed(punishment, seconds=False))) return try: tokens = callbacks.tokenize(s, channel=msg.args[0]) self.Proxy(irc, msg, tokens) except SyntaxError, e: irc.queueMsg(callbacks.error(msg, str(e))) def announce(self, irc, msg, args, text): """ Sends to all channels the bot is currently on and not lobotomized in. """ u = ircdb.users.getUser(msg.prefix) text = 'Announcement from my owner (%s): %s' % (u.name, text) for channel in irc.state.channels: c = ircdb.channels.getChannel(channel) if not c.lobotomized: irc.queueMsg(ircmsgs.privmsg(channel, text)) irc.noReply() announce = wrap(announce, ['text']) def defaultplugin(self, irc, msg, args, optlist, command, plugin): """[--remove] [] Sets the default plugin for to . If --remove is given, removes the current default plugin for . If no plugin is given, returns the current default plugin set for . See also, supybot.commands.defaultPlugins.importantPlugins. """ remove = False for (option, arg) in optlist: if option == 'remove': remove = True (_, cbs) = irc.findCallbacksForArgs([command]) if remove: try: conf.supybot.commands.defaultPlugins.unregister(command) irc.replySuccess() except registry.NonExistentRegistryEntry: s = 'I don\'t have a default plugin set for that command.' irc.error(s) elif not cbs: irc.errorInvalid('command', command) elif plugin: if not plugin.isCommand(command): irc.errorInvalid('command in the %s plugin' % plugin, command) registerDefaultPlugin(command, plugin.name()) irc.replySuccess() else: try: irc.reply(conf.supybot.commands.defaultPlugins.get(command)()) except registry.NonExistentRegistryEntry: s = 'I don\'t have a default plugin set for that command.' irc.error(s) defaultplugin = wrap(defaultplugin, [getopts({'remove': ''}), 'commandName', additional('plugin')]) def ircquote(self, irc, msg, args, s): """ Sends the raw string given to the server. """ try: m = ircmsgs.IrcMsg(s) except Exception, e: irc.error(utils.exnToString(e)) else: irc.queueMsg(m) irc.noReply() ircquote = wrap(ircquote, ['text']) def quit(self, irc, msg, args, text): """[] Exits the bot with the QUIT message . If is not given, the default quit message (supybot.plugins.Owner.quitMsg) will be used. If there is no default quitMsg set, your nick will be used. """ text = text or self.registryValue('quitMsg') or msg.nick irc.noReply() m = ircmsgs.quit(text) world.upkeep() for irc in world.ircs[:]: irc.queueMsg(m) irc.die() quit = wrap(quit, [additional('text')]) def flush(self, irc, msg, args): """takes no arguments Runs all the periodic flushers in world.flushers. This includes flushing all logs and all configuration changes to disk. """ world.flush() irc.replySuccess() flush = wrap(flush) def upkeep(self, irc, msg, args, level): """[] Runs the standard upkeep stuff (flushes and gc.collects()). If given a level, runs that level of upkeep (currently, the only supported level is "high", which causes the bot to flush a lot of caches as well as do normal upkeep stuff. """ L = [] if level == 'high': L.append(format('Regexp cache flushed: %n cleared.', (len(sre._cache), 'regexp'))) sre.purge() L.append(format('Pattern cache flushed: %n cleared.', (len(ircutils._patternCache), 'compiled pattern'))) ircutils._patternCache.clear() L.append(format('hostmaskPatternEqual cache flushed: %n cleared.', (len(ircutils._hostmaskPatternEqualCache), 'result'))) ircutils._hostmaskPatternEqualCache.clear() L.append(format('ircdb username cache flushed: %n cleared.', (len(ircdb.users._nameCache), 'username to id mapping'))) ircdb.users._nameCache.clear() L.append(format('ircdb hostmask cache flushed: %n cleared.', (len(ircdb.users._hostmaskCache), 'hostmask to id mapping'))) ircdb.users._hostmaskCache.clear() L.append(format('linecache line cache flushed: %n cleared.', (len(linecache.cache), 'line'))) linecache.clearcache() sys.exc_clear() collected = world.upkeep() if gc.garbage: L.append('Garbage! %r.' % gc.garbage) L.append(format('%n collected.', (collected, 'object'))) irc.reply(' '.join(L)) upkeep = wrap(upkeep, [additional(('literal', ['high']))]) def load(self, irc, msg, args, optlist, name): """[--deprecated] Loads the plugin from any of the directories in conf.supybot.directories.plugins; usually this includes the main installed directory and 'plugins' in the current directory. --deprecated is necessary if you wish to load deprecated plugins. """ ignoreDeprecation = False for (option, argument) in optlist: if option == 'deprecated': ignoreDeprecation = True if name.endswith('.py'): name = name[:-3] if irc.getCallback(name): irc.error('%s is already loaded.' % name.capitalize()) return try: module = plugin.loadPluginModule(name, ignoreDeprecation) except plugin.Deprecated: irc.error('%s is deprecated. Use --deprecated ' 'to force it to load.' % name.capitalize()) return except ImportError, e: if name in str(e): irc.error('No plugin named %s exists.' % utils.str.dqrepr(name)) else: irc.error(str(e)) return cb = plugin.loadPluginClass(irc, module) name = cb.name() # Let's normalize this. conf.registerPlugin(name, True) irc.replySuccess() load = wrap(load, [getopts({'deprecated': ''}), 'something']) def reload(self, irc, msg, args, name): """ Unloads and subsequently reloads the plugin by name; use the 'list' command to see a list of the currently loaded plugins. """ callbacks = irc.removeCallback(name) if callbacks: module = sys.modules[callbacks[0].__module__] if hasattr(module, 'reload'): x = module.reload() try: module = plugin.loadPluginModule(name) if hasattr(module, 'reload'): module.reload(x) for callback in callbacks: callback.die() del callback gc.collect() # This makes sure the callback is collected. callback = plugin.loadPluginClass(irc, module) irc.replySuccess() except ImportError: for callback in callbacks: irc.addCallback(callback) irc.error('No plugin %s exists.' % name) else: irc.error('There was no plugin %s.' % name) reload = wrap(reload, ['something']) def unload(self, irc, msg, args, name): """ Unloads the callback by name; use the 'list' command to see a list of the currently loaded callbacks. Obviously, the Owner plugin can't be unloaded. """ if ircutils.strEqual(name, self.name()): irc.error('You can\'t unload the %s plugin.' % name) return # Let's do this so even if the plugin isn't currently loaded, it doesn't # stay attempting to load. conf.registerPlugin(name, False) callbacks = irc.removeCallback(name) if callbacks: for callback in callbacks: callback.die() del callback gc.collect() irc.replySuccess() else: irc.error('There was no plugin %s.' % name) unload = wrap(unload, ['something']) def defaultcapability(self, irc, msg, args, action, capability): """{add|remove} Adds or removes (according to the first argument) from the default capabilities given to users (the configuration variable supybot.capabilities stores these). """ if action == 'add': conf.supybot.capabilities().add(capability) irc.replySuccess() elif action == 'remove': try: conf.supybot.capabilities().remove(capability) irc.replySuccess() except KeyError: if ircdb.isAntiCapability(capability): irc.error('That capability wasn\'t in ' 'supybot.capabilities.') else: anticap = ircdb.makeAntiCapability(capability) conf.supybot.capabilities().add(anticap) irc.replySuccess() defaultcapability = wrap(defaultcapability, [('literal', ['add','remove']), 'capability']) def disable(self, irc, msg, args, plugin, command): """[] Disables the command for all users (including the owners). If is given, only disables the from . If you want to disable a command for most users but not for yourself, set a default capability of -plugin.command or -command (if you want to disable the command in all plugins). """ if command in ('enable', 'identify'): irc.error('You can\'t disable %s.' % command) return if plugin: if plugin.isCommand(command): pluginCommand = '%s.%s' % (plugin.name(), command) conf.supybot.commands.disabled().add(pluginCommand) else: irc.error('%s is not a command in the %s plugin.' % (command, plugin.name())) return self._disabled.add(pluginCommand, plugin.name()) else: conf.supybot.commands.disabled().add(command) self._disabled.add(command) irc.replySuccess() disable = wrap(disable, [optional('plugin'), 'commandName']) def enable(self, irc, msg, args, plugin, command): """[] Enables the command for all users. If if given, only enables the from . This command is the inverse of disable. """ try: if plugin: command = '%s.%s' % (plugin.name(), command) self._disabled.remove(command, plugin.name()) else: self._disabled.remove(command) conf.supybot.commands.disabled().remove(command) irc.replySuccess() except KeyError: irc.error('That command wasn\'t disabled.') enable = wrap(enable, [optional('plugin'), 'commandName']) def rename(self, irc, msg, args, plugin, command, newName): """ Renames in to the . """ if not plugin.isCommand(command): what = 'command in the %s plugin' % plugin.name() irc.errorInvalid(what, command) if hasattr(plugin, newName): irc.error('The %s plugin already has an attribute named %s.' % (plugin, newName)) return registerRename(plugin.name(), command, newName) renameCommand(plugin, command, newName) irc.replySuccess() rename = wrap(rename, ['plugin', 'commandName', 'commandName']) def unrename(self, irc, msg, args, plugin): """ Removes all renames in . The plugin will be reloaded after this command is run. """ try: conf.supybot.commands.renames.unregister(plugin.name()) except registry.NonExistentRegistryEntry: irc.errorInvalid('plugin', plugin.name()) self.reload(irc, msg, [plugin.name()]) # This makes the replySuccess. unrename = wrap(unrename, ['plugin']) Class = Owner # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Owner/config.py����������������������������������������������������0000644�0000000�0000000�00000005154�11206611405�016277� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-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('Owner', True) Owner = conf.registerPlugin('Owner', True) conf.registerGlobalValue(Owner, 'public', registry.Boolean(True, """Determines whether this plugin is publicly visible.""")) conf.registerGlobalValue(Owner, 'quitMsg', registry.String('', """Determines what quit message will be used by default. If the quit command is called without a quit message, this will be used. If this value is empty, the nick of the person giving the quit command will be used.""")) conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Success/�����������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014771� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Success/test.py����������������������������������������������������0000644�0000000�0000000�00000003313�11206611405�016322� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 SuccessTestCase(PluginTestCase): plugins = ('Success',) # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Success/__init__.py������������������������������������������������0000644�0000000�0000000�00000004702�11206611405�017105� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ The Success plugin spices up success replies just like Dunno spices up "no such command" replies. """ 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.strike # 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=8 expandtab textwidth=78: ��������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Success/README.txt�������������������������������������������������0000644�0000000�0000000�00000000117�11206611405�016466� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Success/plugin.py��������������������������������������������������0000644�0000000�0000000�00000006530�11206611405�016645� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils class Success(plugins.ChannelIdDatabasePlugin): """This plugin was written initially to work with MoobotFactoids, the two of them to provide a similar-to-moobot-and-blootbot interface for factoids. Basically, it replaces the standard 'The operation succeeded.' messages with messages kept in a database, able to give more personable responses.""" def __init__(self, irc): self.__parent = super(Success, self) self.__parent.__init__(irc) self.target = None pluginSelf = self self.originalClass = conf.supybot.replies.success.__class__ class MySuccessClass(self.originalClass): def __call__(self): ret = pluginSelf.db.random(pluginSelf.target) if ret is None: try: self.__class__ = pluginSelf.originalClass ret = self() finally: self.__class__ = MySuccessClass else: ret = ret.text return ret def get(self, attr): if ircutils.isChannel(attr): pluginSelf.target = attr return self conf.supybot.replies.success.__class__ = MySuccessClass def die(self): self.__parent.die() conf.supybot.replies.success.__class__ = self.originalClass def inFilter(self, irc, msg): # We need the target, but we need it before Owner.doPrivmsg is called, # so this seems like the only way to do it. self.target = msg.args[0] return msg Class = Success # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Success/config.py��������������������������������������������������0000644�0000000�0000000�00000005023�11206611405�016610� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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('Success', True) Success = conf.registerPlugin('Success') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Success, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(conf.supybot.plugins.Success, 'prefixNick', registry.Boolean(True, """Determines whether the bot will prefix the nick of the user giving an invalid command to the success response.""")) # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78 �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/QuoteGrabs/��������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�015435� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/QuoteGrabs/test.py�������������������������������������������������0000644�0000000�0000000�00000012542�11206611405�016772� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # Copyright (c) 2008, James Vega # 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 QuoteGrabsTestCase(ChannelPluginTestCase): plugins = ('QuoteGrabs',) def testQuoteGrab(self): testPrefix = 'foo!bar@baz' self.assertError('grab foo') # Test join/part/notice (shouldn't grab) self.irc.feedMsg(ircmsgs.join(self.channel, prefix=testPrefix)) self.assertError('grab foo') self.irc.feedMsg(ircmsgs.part(self.channel, prefix=testPrefix)) self.assertError('grab foo') # Test privmsgs self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quote foo', ' something') # Test actions self.irc.feedMsg(ircmsgs.action(self.channel, 'moos', prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quote foo', '* foo moos') def testList(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testList', prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quotegrabs list foo', '#1: testList') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'a' * 80, prefix=testPrefix)) self.assertNotError('grab foo') self.assertResponse('quotegrabs list foo', '#2: %s... and #1: testList' %\ ('a'*43)) # 50 - length of "#2: ..." def testDuplicateGrabs(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testDupe', prefix=testPrefix)) self.assertNotError('grab foo') self.assertNotError('grab foo') # note:NOTanerror,stillwon'tdupe self.assertResponse('quotegrabs list foo', '#1: testDupe') def testCaseInsensitivity(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testCI', prefix=testPrefix)) self.assertNotError('grab FOO') self.assertNotError('quote foo') self.assertNotError('quote FoO') self.assertNotError('quote Foo') self.assertNotError('quotegrabs list FOO') self.assertNotError('quotegrabs list fOo') def testRandom(self): testPrefix = 'foo!bar@baz' self.assertError('random') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testRandom', prefix=testPrefix)) self.assertError('random') # still none in the db self.assertNotError('grab foo') self.assertResponse('random', ' testRandom') self.assertResponse('random foo', ' testRandom') self.assertResponse('random FOO', ' testRandom') def testGet(self): testPrefix= 'foo!bar@baz' self.assertError('quotegrabs get asdf') self.assertError('quotegrabs get 1') self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testGet', prefix=testPrefix)) self.assertNotError('grab foo') self.assertNotError('quotegrabs get 1') def testSearch(self): testPrefix= 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testSearch', prefix=testPrefix)) self.assertError('quotegrabs search test') # still none in db self.assertNotError('grab foo') self.assertNotError('quotegrabs search test') class QuoteGrabsNonChannelTestCase(QuoteGrabsTestCase): config = { 'databases.plugins.channelSpecific' : False } # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/QuoteGrabs/__init__.py���������������������������������������������0000644�0000000�0000000�00000005060�11206611405�017547� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # 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. ### """ Quotegrabs are like IRC sound bites. When someone says something funny, incriminating, stupid, outrageous, ... anything that might be worth remembering, you can grab that quote for that person. With this plugin, you can store many quotes per person and display their most recent quote, as well as see who "grabbed" the quote in the first place. """ 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__ = "%%VERSION%%" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.strike # 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 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/QuoteGrabs/README.txt����������������������������������������������0000644�0000000�0000000�00000000117�11206611405�017132� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/QuoteGrabs/plugin.py�����������������������������������������������0000644�0000000�0000000�00000032145�11206611405�017312� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # Copyright (c) 2008, James Vega # 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 time import random import supybot.dbi as dbi import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class QuoteGrabsRecord(dbi.Record): __fields__ = [ 'by', 'text', 'grabber', 'at', 'hostmask', ] def __str__(self): grabber = plugins.getUserName(self.grabber) return format('%s (Said by: %s; grabbed by %s at %t)', self.text, self.hostmask, grabber, self.at) class SqliteQuoteGrabsDB(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 QuoteGrabs. Download it at ' \ '' filename = plugins.makeChannelFilename(self.filename, channel) def p(s1, s2): return int(ircutils.nickEqual(s1, s2)) if filename in self.dbs: return self.dbs[filename] if os.path.exists(filename): self.dbs[filename] = sqlite.connect(filename, converters={'bool': bool}) self.dbs[filename].create_function('nickeq', 2, p) return self.dbs[filename] db = sqlite.connect(filename, converters={'bool': bool}) self.dbs[filename] = db self.dbs[filename].create_function('nickeq', 2, p) cursor = db.cursor() cursor.execute("""CREATE TABLE quotegrabs ( id INTEGER PRIMARY KEY, nick TEXT, hostmask TEXT, added_by TEXT, added_at TIMESTAMP, quote TEXT );""") db.commit() return db def get(self, channel, id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, nick, quote, hostmask, added_at, added_by FROM quotegrabs WHERE id = %s""", id) if cursor.rowcount == 0: raise dbi.NoRecordError (id, by, quote, hostmask, at, grabber) = cursor.fetchone() return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask, at=at, grabber=grabber) def random(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() if nick: cursor.execute("""SELECT quote FROM quotegrabs WHERE nickeq(nick, %s) ORDER BY random() LIMIT 1""", nick) else: cursor.execute("""SELECT quote FROM quotegrabs ORDER BY random() LIMIT 1""") if cursor.rowcount == 0: raise dbi.NoRecordError return cursor.fetchone()[0] def list(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, quote FROM quotegrabs WHERE nickeq(nick, %s) ORDER BY id DESC""", nick) return [QuoteGrabsRecord(id, text=quote) for (id, quote) in cursor.fetchall()] def getQuote(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT quote FROM quotegrabs WHERE nickeq(nick, %s) ORDER BY id DESC LIMIT 1""", nick) if cursor.rowcount == 0: raise dbi.NoRecordError return cursor.fetchone()[0] def select(self, channel, nick): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT added_at FROM quotegrabs WHERE nickeq(nick, %s) ORDER BY id DESC LIMIT 1""", nick) if cursor.rowcount == 0: raise dbi.NoRecordError return cursor.fetchone()[0] def add(self, channel, msg, by): db = self._getDb(channel) cursor = db.cursor() text = ircmsgs.prettyPrint(msg) # Check to see if the latest quotegrab is identical cursor.execute("""SELECT quote FROM quotegrabs WHERE nick=%s ORDER BY id DESC LIMIT 1""", msg.nick) if cursor.rowcount != 0: if text == cursor.fetchone()[0]: return cursor.execute("""INSERT INTO quotegrabs VALUES (NULL, %s, %s, %s, %s, %s)""", msg.nick, msg.prefix, by, int(time.time()), text) db.commit() def search(self, channel, text): db = self._getDb(channel) cursor = db.cursor() text = '%' + text + '%' cursor.execute("""SELECT id, nick, quote FROM quotegrabs WHERE quote LIKE %s ORDER BY id DESC""", text) if cursor.rowcount == 0: raise dbi.NoRecordError return [QuoteGrabsRecord(id, text=quote, by=nick) for (id, nick, quote) in cursor.fetchall()] QuoteGrabsDB = plugins.DB('QuoteGrabs', {'sqlite': SqliteQuoteGrabsDB}) class QuoteGrabs(callbacks.Plugin): """Add the help for "@help QuoteGrabs" here.""" def __init__(self, irc): self.__parent = super(QuoteGrabs, self) self.__parent.__init__(irc) self.db = QuoteGrabsDB() def doPrivmsg(self, irc, msg): irc = callbacks.SimpleProxy(irc, msg) if irc.isChannel(msg.args[0]): (chan, payload) = msg.args words = self.registryValue('randomGrabber.minimumWords', chan) length = self.registryValue('randomGrabber.minimumCharacters',chan) grabTime = \ self.registryValue('randomGrabber.averageTimeBetweenGrabs', chan) channel = plugins.getChannel(chan) if self.registryValue('randomGrabber', chan): if len(payload) > length and len(payload.split()) > words: try: last = int(self.db.select(channel, msg.nick)) except dbi.NoRecordError: self._grab(irc, channel, msg, irc.prefix) self._sendGrabMsg(irc, msg) else: elapsed = int(time.time()) - last if (random.random() * elapsed) > (grabTime / 2): self._grab(channel, irc, msg, irc.prefix) self._sendGrabMsg(irc, msg) def _grab(self, channel, irc, msg, addedBy): self.db.add(channel, msg, addedBy) def _sendGrabMsg(self, irc, msg): s = 'jots down a new quote for %s' % msg.nick irc.reply(s, action=True, prefixNick=False) def grab(self, irc, msg, args, channel, nick): """[] Grabs a quote from by for the quotegrabs table. is only necessary if the message isn't sent in the channel itself. """ # chan is used to make sure we know where to grab the quote from, as # opposed to channel which is used to determine which db to store the # quote in chan = msg.args[0] if chan is None: raise callbacks.ArgumentError if ircutils.nickEqual(nick, msg.nick): irc.error('You can\'t quote grab yourself.', Raise=True) for m in reversed(irc.state.history): if m.command == 'PRIVMSG' and ircutils.nickEqual(m.nick, nick) \ and ircutils.strEqual(m.args[0], chan): self._grab(channel, irc, m, msg.prefix) irc.replySuccess() return irc.error('I couldn\'t find a proper message to grab.') grab = wrap(grab, ['channeldb', 'nick']) def quote(self, irc, msg, args, channel, nick): """[] Returns 's latest quote grab in . is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.getQuote(channel, nick)) except dbi.NoRecordError: irc.error('I couldn\'t find a matching quotegrab for %s.' % nick, Raise=True) quote = wrap(quote, ['channeldb', 'nick']) def list(self, irc, msg, args, channel, nick): """[] Returns a list of shortened quotes that have been grabbed for as well as the id of each quote. These ids can be used to get the full quote. is only necessary if the message isn't sent in the channel itself. """ try: records = self.db.list(channel, nick) L = [] for record in records: # strip the nick from the quote quote = record.text.replace('<%s> ' % nick, '', 1) item = utils.str.ellipsisify('#%s: %s' % (record.id, quote),50) L.append(item) irc.reply(utils.str.commaAndify(L)) except dbi.NoRecordError: irc.error('I couldn\'t find any quotegrabs for %s.' % nick, Raise=True) list = wrap(list, ['channeldb', 'nick']) def random(self, irc, msg, args, channel, nick): """[] [] Returns a randomly grabbed quote, optionally choosing only from those quotes grabbed for . is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.random(channel, nick)) except dbi.NoRecordError: if nick: irc.error('Couldn\'t get a random quote for that nick.') else: irc.error('Couldn\'t get a random quote. Are there any ' 'grabbed quotes in the database?') random = wrap(random, ['channeldb', additional('nick')]) def get(self, irc, msg, args, channel, id): """[] Return the quotegrab with the given . is only necessary if the message isn't sent in the channel itself. """ try: irc.reply(self.db.get(channel, id)) except dbi.NoRecordError: irc.error('No quotegrab for id %s' % utils.str.quoted(id), Raise=True) get = wrap(get, ['channeldb', 'id']) def search(self, irc, msg, args, channel, text): """[] Searches for in a quote. is only necessary if the message isn't sent in the channel itself. """ try: records = self.db.search(channel, text) L = [] for record in records: # strip the nick from the quote quote = record.text.replace('<%s> ' % record.by, '', 1) item = utils.str.ellipsisify('#%s: %s' % (record.id, quote),50) L.append(item) irc.reply(utils.str.commaAndify(L)) except dbi.NoRecordError: irc.error('No quotegrabs matching %s' % utils.str.quoted(text), Raise=True) search = wrap(search, ['channeldb', 'text']) Class = QuoteGrabs # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/QuoteGrabs/config.py�����������������������������������������������0000644�0000000�0000000�00000006636�11206611405�017267� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004, Daniel DiPaolo # 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('QuoteGrabs', True) QuoteGrabs = conf.registerPlugin('QuoteGrabs') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(QuoteGrabs, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs, 'randomGrabber', registry.Boolean(False, """Determines whether the bot will randomly grab possibly-suitable quotes on occasion. The suitability of a given message is determined by ...""")) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber, 'averageTimeBetweenGrabs', registry.PositiveInteger(864000, """Determines about how many seconds, on average, should elapse between random grabs. This is only an average value; grabs can happen from any time after half this time until never, although that's unlikely to occur.""")) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber, 'minimumWords', registry.PositiveInteger(3, """Determines the minimum number of words in a message for it to be considered for random grabbing.""")) conf.registerChannelValue(conf.supybot.plugins.QuoteGrabs.randomGrabber, 'minimumCharacters', registry.PositiveInteger(8, """Determines the minimum number of characters in a message for it to be considered for random grabbing.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Praise/������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014604� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Praise/test.py�����������������������������������������������������0000644�0000000�0000000�00000003556�11206611405�016146� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 PraiseTestCase(PluginTestCase): plugins = ('Praise',) def testAdd(self): self.assertError('praise add foo') # needs $who def testPraise(self): self.assertError('praise foo') # no praises! # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Praise/__init__.py�������������������������������������������������0000644�0000000�0000000�00000004635�11206611405�016725� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ Hand out praise to IRC denizens with this plugin. """ 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__ = "%%VERSION%%" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.strike # 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: ���������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Praise/README.txt��������������������������������������������������0000644�0000000�0000000�00000000117�11206611405�016301� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Praise/plugin.py���������������������������������������������������0000644�0000000�0000000�00000007565�11206611405�016471� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils class Praise(plugins.ChannelIdDatabasePlugin): """Praise is a plugin for ... well, praising things. Feel free to add your own flavor to it by customizing what praises it gives. Use "praise add " to add new ones, making sure to include "$who" in where you want to insert the thing being praised. """ _meRe = re.compile(r'\bme\b', re.I) _myRe = re.compile(r'\bmy\b', re.I) def _replaceFirstPerson(self, s, nick): s = self._meRe.sub(nick, s) s = self._myRe.sub('%s\'s' % nick, s) return s def addValidator(self, irc, text): if '$who' not in text: irc.error('Praises must contain $who.', Raise=True) def praise(self, irc, msg, args, channel, id, text): """[] [] [for ] Praises (for , if given). If is given, uses that specific praise. is only necessary if the message isn't sent in the channel itself. """ if ' for ' in text: (target, reason) = map(str.strip, text.split(' for ', 1)) else: (target, reason) = (text, '') if ircutils.strEqual(target, irc.nick): target = 'itself' if id is not None: try: praise = self.db.get(channel, id) except KeyError: irc.error(format('There is no praise with id #%i.', id)) return else: praise = self.db.random(channel) if not praise: irc.error(format('There are no praises in my database ' \ 'for %s.', channel)) return text = self._replaceFirstPerson(praise.text, msg.nick) reason = self._replaceFirstPerson(reason, msg.nick) target = self._replaceFirstPerson(target, msg.nick) text = text.replace('$who', target) if reason: text += ' for ' + reason if self.registryValue('showIds', channel): text += format(' (#%i)', praise.id) irc.reply(text, action=True) praise = wrap(praise, ['channeldb', optional('id'), 'text']) Class = Praise # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Praise/config.py���������������������������������������������������0000644�0000000�0000000�00000004734�11206611405�016433� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Daniel DiPaolo # 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('Praise', True) Praise = conf.registerPlugin('Praise') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Praise, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(Praise, 'showIds', registry.Boolean(False, """Determines whether the bot will show the ids of a praise when the praise is given.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������supybot-0.83.4.1.ds.orig/plugins/__init__.py��������������������������������������������������������0000644�0000000�0000000�00000054323�11206611405�015501� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 gc import os import re import csv import sys import math import time import random import fnmatch import os.path import UserDict import threading import supybot.log as log import supybot.dbi as dbi import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks try: # We need to sweep away all that mx.* crap because our code doesn't account # for PySQLite's arbitrary use of it. Whoever decided to change sqlite's # behavior based on whether or not that module is installed was a *CRACK* # **FIEND**, plain and simple. mxCrap = {} for (name, module) in sys.modules.items(): if name.startswith('mx'): mxCrap[name] = module sys.modules.pop(name) # Now that the mx crap is gone, we can import sqlite. import sqlite # And now we'll put it back, even though it sucks. sys.modules.update(mxCrap) # Just in case, we'll do this as well. It doesn't seem to work fine by # itself, though, or else we'd just do this in the first place. sqlite.have_datetime = False Connection = sqlite.Connection class MyConnection(sqlite.Connection): def commit(self, *args, **kwargs): if self.autocommit: return else: Connection.commit(self, *args, **kwargs) def __del__(self): try: Connection.__del__(self) except AttributeError: pass except Exception, e: try: log.exception('Uncaught exception in __del__:') except: pass sqlite.Connection = MyConnection #del Connection.__del__ except ImportError: pass class NoSuitableDatabase(Exception): def __init__(self, suitable): self.suitable = suitable self.suitable.sort() def __str__(self): return format('No suitable databases were found. Suitable databases ' 'include %L. If you have one of these databases ' 'installed, make sure it is listed in the ' 'supybot.databases configuration variable.', self.suitable) def DB(filename, types): # We don't care if any of the DBs are actually available when # documenting, so just fake that we found something suitable if world.documenting: def junk(*args, **kwargs): pass return junk filename = conf.supybot.directories.data.dirize(filename) def MakeDB(*args, **kwargs): for type in conf.supybot.databases(): # Can't do this because Python sucks. Go ahead, try it! # filename = '.'.join([filename, type, 'db']) fn = '.'.join([filename, type, 'db']) try: return types[type](fn, *args, **kwargs) except KeyError: continue raise NoSuitableDatabase, types.keys() return MakeDB def makeChannelFilename(filename, channel=None, dirname=None): assert channel is not None, 'Death to those who use None for their channel' filename = os.path.basename(filename) channelSpecific = conf.supybot.databases.plugins.channelSpecific channel = channelSpecific.getChannelLink(channel) channel = ircutils.toLower(channel) if dirname is None: dirname = conf.supybot.directories.data.dirize(channel) if not os.path.exists(dirname): os.makedirs(dirname) return os.path.join(dirname, filename) def getChannel(channel): assert channel is not None, 'Death to those who use None for their channel' channelSpecific = conf.supybot.databases.plugins.channelSpecific return channelSpecific.getChannelLink(channel) # XXX This shouldn't be a mixin. This should be contained by classes that # want such behavior. But at this point, it wouldn't gain much for us # to refactor it. # XXX We need to get rid of this, it's ugly and opposed to # database-independence. class ChannelDBHandler(object): """A class to handle database stuff for individual channels transparently. """ suffix = '.db' def __init__(self, suffix='.db'): self.dbCache = ircutils.IrcDict() suffix = self.suffix if self.suffix and self.suffix[0] != '.': suffix = '.' + suffix self.suffix = suffix def makeFilename(self, channel): """Override this to specialize the filenames of your databases.""" channel = ircutils.toLower(channel) className = self.__class__.__name__ return makeChannelFilename(className + self.suffix, channel) def makeDb(self, filename): """Override this to create your databases.""" raise NotImplementedError def getDb(self, channel): """Use this to get a database for a specific channel.""" currentThread = threading.currentThread() if channel not in self.dbCache and currentThread == world.mainThread: self.dbCache[channel] = self.makeDb(self.makeFilename(channel)) if currentThread != world.mainThread: db = self.makeDb(self.makeFilename(channel)) else: db = self.dbCache[channel] db.autocommit = 1 return db def die(self): for db in self.dbCache.itervalues(): try: db.commit() except AttributeError: # In case it's not an SQLite database. pass try: db.close() except AttributeError: # In case it doesn't have a close method. pass del db gc.collect() class DbiChannelDB(object): """This just handles some of the general stuff for Channel DBI databases. Check out ChannelIdDatabasePlugin for an example of how to use this.""" def __init__(self, filename): self.filename = filename self.dbs = ircutils.IrcDict() def _getDb(self, channel): filename = makeChannelFilename(self.filename, channel) try: db = self.dbs[channel] except KeyError: db = self.DB(filename) self.dbs[channel] = db return db def close(self): for db in self.dbs.itervalues(): db.close() def flush(self): for db in self.dbs.itervalues(): db.flush() def __getattr__(self, attr): def _getDbAndDispatcher(channel, *args, **kwargs): db = self._getDb(channel) return getattr(db, attr)(*args, **kwargs) return _getDbAndDispatcher class ChannelUserDictionary(UserDict.DictMixin): IdDict = dict def __init__(self): self.channels = ircutils.IrcDict() def __getitem__(self, (channel, id)): return self.channels[channel][id] def __setitem__(self, (channel, id), v): if channel not in self.channels: self.channels[channel] = self.IdDict() self.channels[channel][id] = v def __delitem__(self, (channel, id)): del self.channels[channel][id] def iteritems(self): for (channel, ids) in self.channels.iteritems(): for (id, v) in ids.iteritems(): yield ((channel, id), v) def keys(self): L = [] for (k, _) in self.iteritems(): L.append(k) return L # XXX The interface to this needs to be made *much* more like the dbi.DB # interface. This is just too odd and not extensible; any extension # would very much feel like an extension, rather than part of the db # itself. class ChannelUserDB(ChannelUserDictionary): def __init__(self, filename): ChannelUserDictionary.__init__(self) self.filename = filename try: fd = file(self.filename) except EnvironmentError, e: log.warning('Couldn\'t open %s: %s.', self.filename, e) return reader = csv.reader(fd) try: lineno = 0 for t in reader: lineno += 1 try: channel = t.pop(0) id = t.pop(0) try: id = int(id) except ValueError: # We'll skip over this so, say, nicks can be kept here. pass v = self.deserialize(channel, id, t) self[channel, id] = v except Exception, e: log.warning('Invalid line #%s in %s.', lineno, self.__class__.__name__) log.debug('Exception: %s', utils.exnToString(e)) except Exception, e: # This catches exceptions from csv.reader. log.warning('Invalid line #%s in %s.', lineno, self.__class__.__name__) log.debug('Exception: %s', utils.exnToString(e)) def flush(self): fd = utils.file.AtomicFile(self.filename, makeBackupIfSmaller=False) writer = csv.writer(fd) items = self.items() if not items: log.debug('%s: Refusing to write blank file.', self.__class__.__name__) fd.rollback() return items.sort() for ((channel, id), v) in items: L = self.serialize(v) L.insert(0, id) L.insert(0, channel) writer.writerow(L) fd.close() def close(self): self.flush() self.clear() def deserialize(self, channel, id, L): """Should take a list of strings and return an object to be accessed via self.get(channel, id).""" raise NotImplementedError def serialize(self, x): """Should take an object (as returned by self.get(channel, id)) and return a list (of any type serializable to csv).""" raise NotImplementedError def getUserName(id): if isinstance(id, int): try: return ircdb.users.getUser(id).name except KeyError: return 'a user that is no longer registered' else: return id class ChannelIdDatabasePlugin(callbacks.Plugin): class DB(DbiChannelDB): class DB(dbi.DB): class Record(dbi.Record): __fields__ = [ 'at', 'by', 'text' ] def add(self, at, by, text, **kwargs): record = self.Record(at=at, by=by, text=text, **kwargs) return super(self.__class__, self).add(record) def __init__(self, irc): self.__parent = super(ChannelIdDatabasePlugin, self) self.__parent.__init__(irc) self.db = DB(self.name(), {'flat': self.DB})() def die(self): self.db.close() self.__parent.die() def getCommandHelp(self, name): help = self.__parent.getCommandHelp(name) help = help.replace('$Types', format('%p', self.name())) help = help.replace('$Type', self.name()) help = help.replace('$types', format('%p', self.name().lower())) help = help.replace('$type', self.name().lower()) return help def noSuchRecord(self, irc, channel, id): irc.error('There is no %s with id #%s in my database for %s.' % (self.name(), id, channel)) def checkChangeAllowed(self, irc, msg, channel, user, record): if user.id == record.by: return True cap = ircdb.makeChannelCapability(channel, 'op') if ircdb.checkCapability(msg.prefix, cap): return True irc.errorNoCapability(cap) def addValidator(self, irc, text): """This should irc.error or raise an exception if text is invalid.""" pass def add(self, irc, msg, args, user, channel, text): """[] Adds to the $type database for . is only necessary if the message isn't sent in the channel itself. """ at = time.time() self.addValidator(irc, text) if text is not None: id = self.db.add(channel, at, user.id, text) irc.replySuccess('%s #%s added.' % (self.name(), id)) add = wrap(add, ['user', 'channeldb', 'text']) def remove(self, irc, msg, args, user, channel, id): """[] Removes the $type with id from the $type database for . is only necessary if the message isn't sent in the channel itself. """ try: record = self.db.get(channel, id) self.checkChangeAllowed(irc, msg, channel, user, record) self.db.remove(channel, id) irc.replySuccess() except KeyError: self.noSuchRecord(irc, channel, id) remove = wrap(remove, ['user', 'channeldb', 'id']) def searchSerializeRecord(self, record): text = utils.str.ellipsisify(record.text, 50) return format('#%s: %q', record.id, text) def search(self, irc, msg, args, channel, optlist, glob): """[] [--{regexp,by} ] [] Searches for $types matching the criteria given. """ predicates = [] def p(record): for predicate in predicates: if not predicate(record): return False return True for (opt, arg) in optlist: if opt == 'by': predicates.append(lambda r, arg=arg: r.by == arg.id) elif opt == 'regexp': predicates.append(lambda r, arg=arg: arg.search(r.text)) if glob: def globP(r, glob=glob.lower()): return fnmatch.fnmatch(r.text.lower(), glob) predicates.append(globP) L = [] for record in self.db.select(channel, p): L.append(self.searchSerializeRecord(record)) if L: L.sort() irc.reply(format('%s found: %L', len(L), L)) else: what = self.name().lower() irc.reply(format('No matching %p were found.', what)) search = wrap(search, ['channeldb', getopts({'by': 'otherUser', 'regexp': 'regexpMatcher'}), additional(rest('glob'))]) def showRecord(self, record): name = getUserName(record.by) return format('%s #%s: %q (added by %s at %t)', self.name(), record.id, record.text, name, record.at) def get(self, irc, msg, args, channel, id): """[] Gets the $type with id from the $type database for . is only necessary if the message isn't sent in the channel itself. """ try: record = self.db.get(channel, id) irc.reply(self.showRecord(record)) except KeyError: self.noSuchRecord(irc, channel, id) get = wrap(get, ['channeldb', 'id']) def change(self, irc, msg, args, user, channel, id, replacer): """[] Changes the $type with id according to the regular expression . is only necessary if the message isn't sent in the channel itself. """ try: record = self.db.get(channel, id) self.checkChangeAllowed(irc, msg, channel, user, record) record.text = replacer(record.text) self.db.set(channel, id, record) irc.replySuccess() except KeyError: self.noSuchRecord(irc, channel, id) change = wrap(change, ['user', 'channeldb', 'id', 'regexpReplacer']) def stats(self, irc, msg, args, channel): """[] Returns the number of $types in the database for . is only necessary if the message isn't sent in the channel itself. """ n = self.db.size(channel) whats = self.name().lower() irc.reply(format('There %b %n in my database.', n, (n, whats))) stats = wrap(stats, ['channeldb']) class PeriodicFileDownloader(object): """A class to periodically download a file/files. A class-level dictionary 'periodicFiles' maps names of files to three-tuples of (url, seconds between downloads, function to run with downloaded file). 'url' should be in some form that urllib2.urlopen can handle (do note that urllib2.urlopen handles file:// links perfectly well.) 'seconds between downloads' is the number of seconds between downloads, obviously. An important point to remember, however, is that it is only engaged when a command is run. I.e., if you say you want the file downloaded every day, but no commands that use it are run in a week, the next time such a command is run, it'll be using a week-old file. If you don't want such behavior, you'll have to give an error mess age to the user and tell him to call you back in the morning. 'function to run with downloaded file' is a function that will be passed a string *filename* of the downloaded file. This will be some random filename probably generated via some mktemp-type-thing. You can do what you want with this; you may want to build a database, take some stats, or simply rename the file. You can pass None as your function and the file with automatically be renamed to match the filename you have it listed under. It'll be in conf.supybot.directories.data, of course. Aside from that dictionary, simply use self.getFile(filename) in any method that makes use of a periodically downloaded file, and you'll be set. """ periodicFiles = None def __init__(self): if self.periodicFiles is None: raise ValueError, 'You must provide files to download' self.lastDownloaded = {} self.downloadedCounter = {} for filename in self.periodicFiles: if self.periodicFiles[filename][-1] is None: fullname = os.path.join(conf.supybot.directories.data(), filename) if os.path.exists(fullname): self.lastDownloaded[filename] = os.stat(fullname).st_ctime else: self.lastDownloaded[filename] = 0 else: self.lastDownloaded[filename] = 0 self.currentlyDownloading = set() self.downloadedCounter[filename] = 0 self.getFile(filename) def _downloadFile(self, filename, url, f): self.currentlyDownloading.add(filename) try: try: infd = utils.web.getUrlFd(url) except IOError, e: self.log.warning('Error downloading %s: %s', url, e) return except utils.web.Error, e: self.log.warning('Error downloading %s: %s', url, e) return confDir = conf.supybot.directories.data() newFilename = os.path.join(confDir, utils.file.mktemp()) outfd = file(newFilename, 'wb') start = time.time() s = infd.read(4096) while s: outfd.write(s) s = infd.read(4096) infd.close() outfd.close() self.log.info('Downloaded %s in %s seconds', filename, time.time()-start) self.downloadedCounter[filename] += 1 self.lastDownloaded[filename] = time.time() if f is None: toFilename = os.path.join(confDir, filename) if os.name == 'nt': # Windows, grrr... if os.path.exists(toFilename): os.remove(toFilename) os.rename(newFilename, toFilename) else: start = time.time() f(newFilename) total = time.time() - start self.log.info('Function ran on %s in %s seconds', filename, total) finally: self.currentlyDownloading.remove(filename) def getFile(self, filename): if world.documenting: return (url, timeLimit, f) = self.periodicFiles[filename] if time.time() - self.lastDownloaded[filename] > timeLimit and \ filename not in self.currentlyDownloading: self.log.info('Beginning download of %s', url) args = (filename, url, f) name = '%s #%s' % (filename, self.downloadedCounter[filename]) t = threading.Thread(target=self._downloadFile, name=name, args=(filename, url, f)) t.setDaemon(True) t.start() world.threadsSpawned += 1 # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Todo/��������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014266� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Todo/test.py�������������������������������������������������������0000644�0000000�0000000�00000013763�11206611405�015631� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 TodoTestCase(PluginTestCase): plugins = ('Todo', 'User') _user1 = 'foo!bar@baz' _user2 = 'bar!foo@baz' def setUp(self): PluginTestCase.setUp(self) # Create a valid user to use self.prefix = self._user2 self.assertNotError('register testy oom') self.prefix = self._user1 self.assertNotError('register tester moo') def testTodo(self): # Should not error, but no tasks yet. self.assertNotError('todo') self.assertRegexp('todo', 'You have no tasks') # Add a task self.assertNotError('todo add wash my car') self.assertRegexp('todo', '#1: wash my car') # Check that task self.assertRegexp('todo 1', 'Todo for tester: wash my car \(Added .*?\)') # Check that it lists all my tasks when given my name self.assertResponse('todo tester', 'Todo for tester: #1: wash my car') # Check pluralization self.assertNotError('todo add moo') self.assertRegexp('todo tester', 'Todos for tester: #1: wash my car and #2: moo') # Check error self.assertError('todo asfas') self.assertRegexp('todo asfas', 'Error: \'asfas\' is not a valid task') # Check priority sorting self.assertNotError('todo setpriority 1 100') self.assertNotError('todo setpriority 2 10') self.assertRegexp('todo', '#2: moo and #1: wash my car') def testAddtodo(self): self.assertNotError('todo add code a new plugin') self.assertNotError('todo add --priority=1000 fix all bugs') def testRemovetodo(self): self.nick = 'testy' self.prefix = self._user2 self.assertNotError('todo add do something') self.assertNotError('todo add do something else') self.assertNotError('todo add do something again') self.assertNotError('todo remove 1') self.assertNotError('todo 1') self.nick = 'tester' self.prefix = self._user1 self.assertNotError('todo add make something') self.assertNotError('todo add make something else') self.assertNotError('todo add make something again') self.assertNotError('todo remove 1 3') self.assertRegexp('todo 1', r'Inactive') self.assertRegexp('todo 3', r'Inactive') self.assertNotError('todo') def testSearchtodo(self): self.assertNotError('todo add task number one') self.assertRegexp('todo search task*', '#1: task number one') self.assertRegexp('todo search number', '#1: task number one') self.assertNotError('todo add task number two is much longer than' ' task number one') self.assertRegexp('todo search task*', '#1: task number one and #2: task number two is ' 'much longer than task number...') self.assertError('todo search --regexp s/bustedregex') self.assertRegexp('todo search --regexp m/task/', '#1: task number one and #2: task number two is ' 'much longer than task number...') def testSetPriority(self): self.assertNotError('todo add --priority=1 moo') self.assertRegexp('todo 1', 'moo, priority: 1 \(Added at .*?\)') self.assertNotError('setpriority 1 50') self.assertRegexp('todo 1', 'moo, priority: 50 \(Added at .*?\)') self.assertNotError('setpriority 1 0') self.assertRegexp('todo 1', 'moo \(Added at .*?\)') def testChangeTodo(self): self.assertNotError('todo add moo') self.assertError('todo change 1 asdfas') self.assertError('todo change 1 m/asdfaf//') self.assertNotError('todo change 1 s/moo/foo/') self.assertRegexp('todo 1', 'Todo for tester: foo \(Added .*?\)') def testActiveInactiveTodo(self): self.assertNotError('todo add foo') self.assertNotError('todo add bar') self.assertRegexp('todo 1', 'Active') self.assertRegexp('todo 2', 'Active') self.assertNotError('todo remove 1') self.assertRegexp('todo 1', 'Inactive') self.assertRegexp('todo 2', 'Active') self.assertNotError('todo remove 2') self.assertRegexp('todo 2', 'Inactive') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������supybot-0.83.4.1.ds.orig/plugins/Todo/__init__.py���������������������������������������������������0000644�0000000�0000000�00000004642�11206611405�016405� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ The Todo plugin allows registered users to keep their own personal list of tasks to do, with an optional priority for each. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike # 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: ����������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Todo/README.txt����������������������������������������������������0000644�0000000�0000000�00000000117�11206611405�015763� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Todo/plugin.py�����������������������������������������������������0000644�0000000�0000000�00000022745�11206611405�016150� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 re import time import fnmatch import operator import supybot.dbi as dbi import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class TodoRecord(dbi.Record): __fields__ = [ ('priority', int), 'at', 'task', 'active', ] dataDir = conf.supybot.directories.data class FlatTodoDb(object): def __init__(self): self.directory = dataDir.dirize('Todo') if not os.path.exists(self.directory): os.mkdir(self.directory) self.dbs = {} def _getDb(self, uid): dbfile = os.path.join(self.directory, str(uid)) if uid not in self.dbs: self.dbs[uid] = dbi.DB(dbfile, Record=TodoRecord) return self.dbs[uid] def close(self): for db in self.dbs.itervalues(): db.close() def get(self, uid, tid): db = self._getDb(uid) return db.get(tid) def getTodos(self, uid): db = self._getDb(uid) L = [R for R in db.select(lambda r: r.active)] if not L: raise dbi.NoRecordError return L def add(self, priority, now, uid, task): db = self._getDb(uid) return db.add(TodoRecord(priority=priority, at=now, task=task, active=True)) def remove(self, uid, tid): db = self._getDb(uid) t = db.get(tid) t.active = False db.set(tid, t) def select(self, uid, criteria): db = self._getDb(uid) def match(todo): for p in criteria: if not p(todo.task): return False return True todos = db.select(lambda t: match(t)) if not todos: raise dbi.NoRecordError return todos def setpriority(self, uid, tid, priority): db = self._getDb(uid) t = db.get(tid) t.priority = priority db.set(tid, t) def change(self, uid, tid, replacer): db = self._getDb(uid) t = db.get(tid) t.task = replacer(t.task) db.set(tid, t) class Todo(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Todo, self) self.__parent.__init__(irc) self.db = FlatTodoDb() def die(self): self.__parent.die() self.db.close() def _shrink(self, s): return utils.str.ellipsisify(s, 50) def todo(self, irc, msg, args, user, taskid): """[ []|] Retrieves a task for the given task id. If no task id is given, it will return a list of task ids that that user has added to their todo list. """ try: u = ircdb.users.getUser(msg.prefix) except KeyError: u = None # List the active tasks for the given user if not taskid: try: tasks = self.db.getTodos(user.id) utils.sortBy(operator.attrgetter('priority'), tasks) tasks = [format('#%i: %s', t.id, self._shrink(t.task)) for t in tasks] Todo = 'Todo' if len(tasks) != 1: Todo = 'Todos' irc.reply(format('%s for %s: %L', Todo, user.name, tasks)) except dbi.NoRecordError: if u != user: irc.reply('That user has no tasks in their todo list.') else: irc.reply('You have no tasks in your todo list.') return # Reply with the user's task else: try: t = self.db.get(user.id, taskid) if t.active: active = 'Active' else: active = 'Inactive' if t.priority: t.task += format(', priority: %i', t.priority) at = time.strftime(conf.supybot.reply.format.time(), time.localtime(t.at)) s = format('%s todo for %s: %s (Added at %s)', active, user.name, t.task, at) irc.reply(s) except dbi.NoRecordError: irc.errorInvalid('task id', taskid) todo = wrap(todo, [first('otherUser', 'user'), additional(('id', 'task'))]) def add(self, irc, msg, args, user, optlist, text, now): """[--priority=] Adds as a task in your own personal todo list. The optional priority argument allows you to set a task as a high or low priority. Any integer is valid. """ priority = 0 for (option, arg) in optlist: if option == 'priority': priority = arg todoId = self.db.add(priority, now, user.id, text) irc.replySuccess(format('(Todo #%i added)', todoId)) add = wrap(add, ['user', getopts({'priority': ('int', 'priority')}), 'text', 'now']) def remove(self, irc, msg, args, user, tasks): """ [ ...] Removes from your personal todo list. """ invalid = [] for taskid in tasks: try: self.db.get(user.id, taskid) except dbi.NoRecordError: invalid.append(taskid) if invalid and len(invalid) == 1: irc.error(format('Task %i could not be removed either because ' 'that id doesn\'t exist or it has been removed ' 'already.', invalid[0])) elif invalid: irc.error(format('No tasks were removed because the following ' 'tasks could not be removed: %L.', invalid)) else: for taskid in tasks: self.db.remove(user.id, taskid) irc.replySuccess() remove = wrap(remove, ['user', many(('id', 'task'))]) def search(self, irc, msg, args, user, optlist, globs): """[--{regexp} ] [ ...] Searches your todos for tasks matching . If --regexp is given, its associated value is taken as a regexp and matched against the tasks. """ if not optlist and not globs: raise callbacks.ArgumentError criteria = [] for (option, arg) in optlist: if option == 'regexp': criteria.append(arg.search) for glob in globs: glob = fnmatch.translate(glob) criteria.append(re.compile(glob[:-1]).search) try: tasks = self.db.select(user.id, criteria) L = [format('#%i: %s', t.id, self._shrink(t.task)) for t in tasks] irc.reply(format('%L', L)) except dbi.NoRecordError: irc.reply('No tasks matched that query.') search = wrap(search, ['user', getopts({'regexp': 'regexpMatcher'}), any('glob')]) def setpriority(self, irc, msg, args, user, id, priority): """ Sets the priority of the todo with the given id to the specified value. """ try: self.db.setpriority(user.id, id, priority) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid('task id', id) setpriority = wrap(setpriority, ['user', ('id', 'task'), ('int', 'priority')]) def change(self, irc, msg, args, user, id, replacer): """ Modify the task with the given id using the supplied regexp. """ try: self.db.change(user.id, id, replacer) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid('task id', id) change = wrap(change, ['user', ('id', 'task'), 'regexpReplacer']) Class = Todo # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������supybot-0.83.4.1.ds.orig/plugins/Todo/config.py�����������������������������������������������������0000644�0000000�0000000�00000004461�11206611405�016112� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-2005, Daniel DiPaolo # 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('Todo', True) Todo = conf.registerPlugin('Todo') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Todo, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Internet/����������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�015151� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Internet/test.py���������������������������������������������������0000644�0000000�0000000�00000004502�11206611405�016503� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-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 * class InternetTestCase(PluginTestCase): plugins = ('Internet',) if network: def testDns(self): self.assertNotError('dns slashdot.org') self.assertResponse('dns alsdkjfaslkdfjaslkdfj.com', 'Host not found.') def testWhois(self): self.assertNotError('internet whois ohio-state.edu') self.assertError('internet whois www.ohio-state.edu') self.assertNotError('internet whois kuro5hin.org') self.assertError('internet whois www.kuro5hin.org') self.assertNotError('internet whois microsoft.com') self.assertNotError('internet whois inria.fr') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Internet/__init__.py�����������������������������������������������0000644�0000000�0000000�00000004566�11206611405�017275� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-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. ### """ Provides some internet-related commands. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {supybot.authors.jamessan: ['whois']} 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: ������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Internet/README.txt������������������������������������������������0000644�0000000�0000000�00000000117�11206611405�016646� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Internet/plugin.py�������������������������������������������������0000644�0000000�0000000�00000015333�11206611405�017026� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-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 socket import telnetlib import supybot.utils as utils from supybot.commands import * from supybot.utils.iter import any import supybot.callbacks as callbacks class Internet(callbacks.Plugin): """Add the help for "@help Internet" here.""" threaded = True def dns(self, irc, msg, args, host): """ Returns the ip of or the reverse DNS hostname of . """ if utils.net.isIP(host): hostname = socket.getfqdn(host) if hostname == host: irc.reply('Host not found.') else: irc.reply(hostname) else: try: ip = socket.gethostbyname(host) if ip == '64.94.110.11': # Verisign sucks! irc.reply('Host not found.') else: irc.reply(ip) except socket.error: irc.reply('Host not found.') dns = wrap(dns, ['something']) _tlds = set(['com', 'net', 'edu']) _domain = ['Domain Name', 'Server Name', 'domain'] _registrar = ['Sponsoring Registrar', 'Registrar', 'source'] _updated = ['Last Updated On', 'Domain Last Updated Date', 'Updated Date', 'Last Modified', 'changed'] _created = ['Created On', 'Domain Registration Date', 'Creation Date'] _expires = ['Expiration Date', 'Domain Expiration Date'] _status = ['Status', 'Domain Status', 'status'] def whois(self, irc, msg, args, domain): """ Returns WHOIS information on the registration of . """ usertld = domain.split('.')[-1] if '.' not in domain: irc.error(' must be in .com, .net, .edu, or .org.') return elif len(domain.split('.')) != 2: irc.error(' must be a domain, not a hostname.') return if usertld in self._tlds: server = 'rs.internic.net' search = '=%s' % domain else: server = '%s.whois-servers.net' % usertld search = domain try: t = telnetlib.Telnet(server, 43) except socket.error, e: irc.error(str(e)) return t.write(search) t.write('\n') s = t.read_all() server = registrar = updated = created = expires = status = '' for line in s.splitlines(): line = line.strip() if not line or ':' not in line: continue if not server and any(line.startswith, self._domain): server = ':'.join(line.split(':')[1:]).strip().lower() # Let's add this check so that we don't respond with info for # a different domain. E.g., doing a whois for microsoft.com # and replying with the info for microsoft.com.wanadoodoo.com if server != domain: server = '' continue if not server: continue if not registrar and any(line.startswith, self._registrar): registrar = ':'.join(line.split(':')[1:]).strip() elif not updated and any(line.startswith, self._updated): s = ':'.join(line.split(':')[1:]).strip() updated = 'updated %s' % s elif not created and any(line.startswith, self._created): s = ':'.join(line.split(':')[1:]).strip() created = 'registered %s' % s elif not expires and any(line.startswith, self._expires): s = ':'.join(line.split(':')[1:]).strip() expires = 'expires %s' % s elif not status and any(line.startswith, self._status): status = ':'.join(line.split(':')[1:]).strip().lower() if not status: status = 'unknown' try: t = telnetlib.Telnet('whois.pir.org', 43) except socket.error, e: irc.error(str(e)) return t.write('registrar ') t.write(registrar.split('(')[0].strip()) t.write('\n') s = t.read_all() url = '' for line in s.splitlines(): line = line.strip() if not line: continue if line.startswith('Email'): url = ' ' % line.split('@')[-1] elif line.startswith('Registrar Organization:'): url = ' ' % line.split(':')[1].strip() elif line == 'Not a valid ID pattern': url = '' if server and status: info = filter(None, [status, created, updated, expires]) s = format('%s%s is %L.', server, url, info) irc.reply(s) else: irc.error('I couldn\'t find such a domain.') whois = wrap(whois, ['lowered']) def hexip(self, irc, msg, args, ip): """ Returns the hexadecimal IP for that IP. """ quads = ip.split('.') ret = "" for quad in quads: i = int(quad) ret += '%02x' % i irc.reply(ret.upper()) hexip = wrap(hexip, ['ip']) Class = Internet # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Internet/config.py�������������������������������������������������0000644�0000000�0000000�00000004503�11206611405�016772� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2003-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('Internet', True) Internet = conf.registerPlugin('Internet') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Internet, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Dict/��������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014244� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Dict/test.py�������������������������������������������������������0000644�0000000�0000000�00000004153�11206611405�015600� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 DictTestCase(PluginTestCase): plugins = ('Dict',) if network: def testDict(self): self.assertNotError('dict slash') self.assertNotRegexp('dict web1913 slash', 'foldoc') self.assertError('dict ""') def testDictionaries(self): self.assertNotError('dictionaries') def testRandomDictionary(self): self.assertNotError('random') self.assertNotError('dict [random] moo') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Dict/__init__.py���������������������������������������������������0000644�0000000�0000000�00000004305�11206611405�016357� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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. ### """ Commands that use the dictd protocol to define word. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Dict/README.txt����������������������������������������������������0000644�0000000�0000000�00000000313�11206611405�015737� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������This is a simple plugin to allow querying dictionaries for word definitions. In order to use this plugin you must have the following modules installed: - dictclient: http://quux.org:70/devel/dictclient ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Dict/plugin.py�����������������������������������������������������0000644�0000000�0000000�00000011673�11206611405�016124� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 socket import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks try: dictclient = utils.python.universalImport('dictclient', 'local.dictclient') except ImportError: raise callbacks.Error, \ 'You need to have dictclient installed to use this plugin. ' \ 'Download it at ' class Dict(callbacks.Plugin): threaded = True def dictionaries(self, irc, msg, args): """takes no arguments Returns the dictionaries valid for the dict command. """ try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) dbs = list(conn.getdbdescs().keys()) dbs.sort() irc.reply(format('%L', dbs)) except socket.error, e: irc.error(utils.web.strError(e)) dictionaries = wrap(dictionaries) def random(self, irc, msg, args): """takes no arguments Returns a random valid dictionary. """ try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) dbs = conn.getdbdescs().keys() irc.reply(utils.iter.choice(dbs)) except socket.error, e: irc.error(utils.web.strError(e)) random = wrap(random) def dict(self, irc, msg, args, words): """[] Looks up the definition of on dict.org's dictd server. """ try: server = conf.supybot.plugins.Dict.server() conn = dictclient.Connection(server) except socket.error, e: irc.error(utils.web.strError(e), Raise=True) dbs = set(conn.getdbdescs()) if words[0] in dbs: dictionary = words.pop(0) else: default = self.registryValue('default', msg.args[0]) if default in dbs: dictionary = default else: if default: self.log.info('Default dict for %s is not a supported ' 'dictionary: %s.', msg.args[0], default) dictionary = '*' if not words: irc.error('You must give a word to define.', Raise=True) word = ' '.join(words) definitions = conn.define(dictionary, word) dbs = set() if not definitions: if dictionary == '*': irc.reply(format('No definition for %q could be found.', word)) else: irc.reply(format('No definition for %q could be found in %s', word, ircutils.bold(dictionary))) return L = [] for d in definitions: dbs.add(ircutils.bold(d.getdb().getname())) (db, s) = (d.getdb().getname(), d.getdefstr()) db = ircutils.bold(db) s = utils.str.normalizeWhitespace(s).rstrip(';.,') L.append('%s: %s' % (db, s)) utils.sortBy(len, L) if dictionary == '*' and len(dbs) > 1: s = format('%L responded: %s', list(dbs), '; '.join(L)) else: s = '; '.join(L) irc.reply(s) dict = wrap(dict, [many('something')]) Class = Dict # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Dict/config.py�����������������������������������������������������0000644�0000000�0000000�00000004676�11206611405�016100� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 def configure(advanced): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('Dict', True) output('The default dictd server is dict.org.') if yn('Would you like to specify a different dictd server?'): server = something('What server?') conf.supybot.plugins.Dict.server.set(server) Dict = conf.registerPlugin('Dict') conf.registerGlobalValue(Dict, 'server', registry.String('dict.org', """Determines what server the bot will retrieve definitions from.""")) conf.registerChannelValue(Dict, 'default', registry.String('', """Determines the default dictionary the bot will ask for definitions in. If this value is '*' (without the quotes) the bot will use all dictionaries to define words.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Misc/��������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014254� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Misc/test.py�������������������������������������������������������0000644�0000000�0000000�00000022765�11206611405�015621� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 from supybot.test import * class MiscTestCase(ChannelPluginTestCase): plugins = ('Misc', 'Utilities', 'Anonymous', 'Plugin', 'Channel', 'Dict', 'User', 'String') def testReplyWhenNotCommand(self): try: original = conf.supybot.reply.whenNotCommand() conf.supybot.reply.whenNotCommand.setValue(True) self.prefix = 'somethingElse!user@host.domain.tld' self.assertRegexp('foo', 'not.*command') self.assertRegexp('foo bar baz', 'not.*command') finally: conf.supybot.reply.whenNotCommand.setValue(original) def testReplyWhenNotCommandButFirstCommandIsPluginName(self): try: original = conf.supybot.reply.whenNotCommand() conf.supybot.reply.whenNotCommand.setValue(True) self.assertRegexp('misc foo', '"list Misc"') finally: conf.supybot.reply.whenNotCommand.setValue(original) # if network: # def testNotReplyWhenRegexpsMatch(self): # try: # orig = conf.supybot.reply.whenNotCommand() # gk = conf.supybot.plugins.Gameknot.gameSnarfer() # conf.supybot.reply.whenNotCommand.setValue(True) # conf.supybot.plugins.Gameknot.gameSnarfer.setValue(True) # self.prefix = 'somethingElse!user@host.domain.tld' # self.assertSnarfNotError( # 'http://gameknot.com/chess.pl?bd=1019508') # finally: # conf.supybot.reply.whenNotCommand.setValue(orig) # conf.supybot.plugins.Gameknot.gameSnarfer.setValue(gk) def testNotReplyWhenNotCanonicalName(self): try: original = str(conf.supybot.reply.whenNotCommand) conf.supybot.reply.whenNotCommand.set('True') self.prefix = 'somethingElse!user@host.domain.tld' self.assertNotRegexp('LeN foobar', 'command') self.assertResponse('lEn foobar', '6') finally: conf.supybot.reply.whenNotCommand.set(original) def testHelp(self): self.assertHelp('help list') self.assertRegexp('help help', r'^\(\x02help') #self.assertRegexp('help misc help', r'^\(\x02misc help') self.assertError('help nonExistentCommand') def testHelpIncludeFullCommandName(self): self.assertHelp('help channel capability add') m = self.getMsg('help channel capability add') self.failUnless('channel capability add' in m.args[1]) def testHelpDoesAmbiguityWithDefaultPlugins(self): m = self.getMsg('help list') # Misc.list and User.list. self.failIf(m.args[1].startswith('Error')) def testHelpIsCaseInsensitive(self): self.assertHelp('help LIST') def testList(self): self.assertNotError('list') self.assertNotError('list Misc') def testListIsCaseInsensitive(self): self.assertNotError('list misc') def testListPrivate(self): # If Anonymous changes to public, these tests will break. So if # the next assert fails, change the plugin we test for public/private # to some other non-public plugin. name = 'Anonymous' conf.supybot.plugins.Anonymous.public.setValue(False) self.assertNotRegexp('list', name) self.assertRegexp('list --private', name) conf.supybot.plugins.Anonymous.public.setValue(True) self.assertRegexp('list', name) self.assertNotRegexp('list --private', name) def testListDoesNotIncludeNonCanonicalName(self): self.assertNotRegexp('list Owner', '_exec') def testListNoIncludeDispatcher(self): self.assertNotRegexp('list Misc', 'misc') def testListIncludesDispatcherIfThereIsAnOriginalCommand(self): self.assertRegexp('list Dict', r'\bdict\b') if network: def testVersion(self): print '*** This test should start passing when we have our '\ 'threaded issues resolved.' self.assertNotError('version') def testSource(self): self.assertNotError('source') def testTell(self): # This test fails because the test is seeing us as owner and Misc.tell # allows the owner to send messages to people the bot hasn't seen. m = self.getMsg('tell aljsdkfh [plugin tell]') self.failUnless('let you do' in m.args[1]) m = self.getMsg('tell #foo [plugin tell]') self.failUnless('No need for' in m.args[1]) m = self.getMsg('tell me you love me') self.failUnless(m.args[0] == self.nick) def testTellDoesNotPropogateAction(self): m = self.getMsg('tell foo [action bar]') self.failIf(ircmsgs.isAction(m)) def testLast(self): orig = conf.supybot.plugins.Misc.timestampFormat() try: conf.supybot.plugins.Misc.timestampFormat.setValue('') self.feedMsg('foo bar baz') self.assertResponse('last', '<%s> foo bar baz' % self.nick) self.assertRegexp('last', '<%s> @last' % self.nick) self.assertResponse('last --with foo', '<%s> foo bar baz' % \ self.nick) self.assertResponse('last --without foo', '<%s> @last' % self.nick) self.assertRegexp('last --regexp m/\s+/', 'last --without foo') self.assertResponse('last --regexp m/bar/', '<%s> foo bar baz' % self.nick) self.assertResponse('last --from %s' % self.nick.upper(), '<%s> @last --regexp m/bar/' % self.nick) self.assertResponse('last --from %s*' % self.nick[0], '<%s> @last --from %s' % (self.nick, self.nick.upper())) conf.supybot.plugins.Misc.timestampFormat.setValue('foo') self.assertSnarfNoResponse('foo bar baz', 1) self.assertResponse('last', 'foo <%s> foo bar baz' % self.nick) finally: conf.supybot.plugins.Misc.timestampFormat.setValue(orig) def testNestedLastTimestampConfig(self): tsConfig = conf.supybot.plugins.Misc.last.nested.includeTimestamp orig = tsConfig() try: tsConfig.setValue(True) self.getMsg('foo bar baz') chars = conf.supybot.reply.whenAddressedBy.chars() chars = re.escape(chars) self.assertRegexp('echo [last]', r'[%s]foo bar baz' % chars) finally: tsConfig.setValue(orig) def testNestedLastNickConfig(self): nickConfig = conf.supybot.plugins.Misc.last.nested.includeNick orig = nickConfig() try: nickConfig.setValue(True) self.getMsg('foo bar baz') chars = conf.supybot.reply.whenAddressedBy.chars() chars = re.escape(chars) self.assertRegexp('echo [last]', '<%s> [%s]foo bar baz' % (self.nick, chars)) finally: nickConfig.setValue(orig) def testMore(self): self.assertRegexp('echo %s' % ('abc'*300), 'more') self.assertRegexp('more', 'more') self.assertNotRegexp('more', 'more') def testInvalidCommand(self): self.assertError('echo []') def testMoreIsCaseInsensitive(self): self.assertNotError('echo %s' % ('abc'*2000)) self.assertNotError('more') nick = ircutils.nickFromHostmask(self.prefix) self.assertNotError('more %s' % nick) self.assertNotError('more %s' % nick.upper()) self.assertNotError('more %s' % nick.lower()) def testApropos(self): self.assertNotError('apropos f') self.assertRegexp('apropos asldkfjasdlkfja', 'No appropriate commands') def testAproposIsNotCaseSensitive(self): self.assertNotRegexp('apropos LIST', 'No appropriate commands') def testAproposDoesntReturnNonCanonicalNames(self): self.assertNotRegexp('apropos exec', '_exec') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������supybot-0.83.4.1.ds.orig/plugins/Misc/__init__.py���������������������������������������������������0000644�0000000�0000000�00000004156�11206611405�016373� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-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. ### """ Miscellaneous commands. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Misc/plugin.py�����������������������������������������������������0000644�0000000�0000000�00000043245�11206611405�016134� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 sys import time import supybot import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircdb as ircdb import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.iter import ifilter class Misc(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Misc, self) self.__parent.__init__(irc) self.invalidCommands = ircutils.FloodQueue(60) def callPrecedence(self, irc): return ([cb for cb in irc.callbacks if cb is not self], []) def invalidCommand(self, irc, msg, tokens): assert not msg.repliedTo, 'repliedTo msg in Misc.invalidCommand.' assert self is irc.callbacks[-1], 'Misc isn\'t last callback.' self.log.debug('Misc.invalidCommand called (tokens %s)', tokens) # First, we check for invalidCommand floods. This is rightfully done # here since this will be the last invalidCommand called, and thus it # will only be called if this is *truly* an invalid command. maximum = conf.supybot.abuse.flood.command.invalid.maximum() self.invalidCommands.enqueue(msg) if self.invalidCommands.len(msg) > maximum and \ conf.supybot.abuse.flood.command.invalid() and \ not ircdb.checkCapability(msg.prefix, 'owner'): punishment = conf.supybot.abuse.flood.command.invalid.punishment() banmask = '*!%s@%s' % (msg.user, msg.host) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'invalid command flood.', banmask, punishment) if tokens and tokens[0] == 'Error:': self.log.warning('Apparent error loop with another Supybot ' 'observed. Consider ignoring this bot ' 'permanently.') ircdb.ignores.add(banmask, time.time() + punishment) irc.reply('You\'ve given me %s invalid commands within the last ' 'minute; I\'m now ignoring you for %s.' % (maximum, utils.timeElapsed(punishment, seconds=False))) return # Now, for normal handling. channel = msg.args[0] if conf.get(conf.supybot.reply.whenNotCommand, channel): if len(tokens) >= 2: cb = irc.getCallback(tokens[0]) if cb: plugin = cb.name() irc.error(format('The %q plugin is loaded, but there is ' 'no command named %q in it. Try "list ' '%s" to see the commands in the %q ' 'plugin.', plugin, tokens[1], plugin, plugin)) else: irc.errorInvalid('command', tokens[0], repr=False) else: command = tokens and tokens[0] or '' irc.errorInvalid('command', command, repr=False) else: if tokens: # echo [] will get us an empty token set, but there's no need # to log this in that case anyway, it being a nested command. self.log.info('Not replying to %s, not a command.', tokens[0]) if irc.nested: bracketConfig = conf.supybot.commands.nested.brackets brackets = conf.get(bracketConfig, channel) if brackets: (left, right) = brackets irc.reply(left + ' '.join(tokens) + right) else: pass # Let's just do nothing, I can't think of better. def list(self, irc, msg, args, optlist, cb): """[--private] [] Lists the commands available in the given plugin. If no plugin is given, lists the public plugins available. If --private is given, lists the private plugins. """ private = False for (option, argument) in optlist: if option == 'private': private = True if not self.registryValue('listPrivatePlugins') and \ not ircdb.checkCapability(msg.prefix, 'owner'): irc.errorNoCapability('owner') if not cb: def isPublic(cb): name = cb.name() return conf.supybot.plugins.get(name).public() names = [cb.name() for cb in irc.callbacks if (private and not isPublic(cb)) or (not private and isPublic(cb))] names.sort() if names: irc.reply(format('%L', names)) else: if private: irc.reply('There are no private plugins.') else: irc.reply('There are no public plugins.') else: commands = cb.listCommands() if commands: commands.sort() irc.reply(format('%L', commands)) else: irc.reply(format('That plugin exists, but has no commands. ' 'This probably means that it has some ' 'configuration variables that can be ' 'changed in order to modify its behavior. ' 'Try "config list supybot.plugins.%s" to see ' 'what configuration variables it has.', cb.name())) list = wrap(list, [getopts({'private':''}), additional('plugin')]) def apropos(self, irc, msg, args, s): """ Searches for in the commands currently offered by the bot, returning a list of the commands containing that string. """ commands = {} L = [] for cb in irc.callbacks: if isinstance(cb, callbacks.Plugin): for command in cb.listCommands(): if s in command: commands.setdefault(command, []).append(cb.name()) for (key, names) in commands.iteritems(): if len(names) == 1: L.append(key) else: for name in names: L.append('%s %s' % (name, key)) if L: L.sort() irc.reply(format('%L', L)) else: irc.reply('No appropriate commands were found.') apropos = wrap(apropos, ['lowered']) def help(self, irc, msg, args, command): """[] [] This command gives a useful description of what does. is only necessary if the command is in more than one plugin. """ command = map(callbacks.canonicalName, command) (maxL, cbs) = irc.findCallbacksForArgs(command) if maxL == command: if len(cbs) > 1: names = sorted([cb.name() for cb in cbs]) irc.error(format('That command exists in the %L plugins. ' 'Please specify exactly which plugin command ' 'you want help with.', names)) else: assert cbs, 'Odd, maxL == command, but no cbs.' irc.reply(cbs[0].getCommandHelp(command, False)) else: irc.error(format('There is no command %q.', callbacks.formatCommand(command))) help = wrap(help, [many('something')]) def version(self, irc, msg, args): """takes no arguments Returns the version of the current bot. """ try: newest = utils.web.getUrl('http://supybot.sf.net/version.txt') newest ='The newest version available online is %s.'%newest.strip() except utils.web.Error, e: self.log.info('Couldn\'t get website version: %s', e) newest = 'I couldn\'t fetch the newest version ' \ 'from the Supybot website.' s = 'The current (running) version of this Supybot is %s. %s' % \ (conf.version, newest) irc.reply(s) version = wrap(thread(version)) def source(self, irc, msg, args): """takes no arguments Returns a URL saying where to get Supybot. """ irc.reply('My source is at http://supybot.com/') source = wrap(source) def more(self, irc, msg, args, nick): """[] If the last command was truncated due to IRC message length limitations, returns the next chunk of the result of the last command. If is given, it takes the continuation of the last command from instead of the person sending this message. """ userHostmask = msg.prefix.split('!', 1)[1] if nick: try: (private, L) = irc._mores[nick] if not private: irc._mores[userHostmask] = L[:] else: irc.error('%s has no public mores.' % nick) return except KeyError: irc.error('Sorry, I can\'t find any mores for %s' % nick) return try: L = irc._mores[userHostmask] chunk = L.pop() if L: chunk += format(' \x02(%n)\x0F', (len(L), 'more', 'message')) irc.reply(chunk, True) except KeyError: irc.error('You haven\'t asked me a command; perhaps you want ' 'to see someone else\'s more. To do so, call this ' 'command with that person\'s nick.') except IndexError: irc.error('That\'s all, there is no more.') more = wrap(more, [additional('seenNick')]) def _validLastMsg(self, msg): return msg.prefix and \ msg.command == 'PRIVMSG' and \ ircutils.isChannel(msg.args[0]) def last(self, irc, msg, args, optlist): """[--{from,in,on,with,without,regexp} ] [--nolimit] Returns the last message matching the given criteria. --from requires a nick from whom the message came; --in requires a channel the message was sent to; --on requires a network the message was sent on; --with requires some string that had to be in the message; --regexp requires a regular expression the message must match; --nolimit returns all the messages that can be found. By default, the channel this command is given in is searched. """ predicates = {} nolimit = False skipfirst = True if ircutils.isChannel(msg.args[0]): predicates['in'] = lambda m: ircutils.strEqual(m.args[0], msg.args[0]) else: skipfirst = False for (option, arg) in optlist: if option == 'from': def f(m, arg=arg): return ircutils.hostmaskPatternEqual(arg, m.nick) predicates['from'] = f elif option == 'in': def f(m, arg=arg): return ircutils.strEqual(m.args[0], arg) predicates['in'] = f if arg != msg.args[0]: skipfirst = False elif option == 'on': def f(m, arg=arg): return m.receivedOn == arg predicates['on'] = f elif option == 'with': def f(m, arg=arg): return arg.lower() in m.args[1].lower() predicates.setdefault('with', []).append(f) elif option == 'without': def f(m, arg=arg): return arg.lower() not in m.args[1].lower() predicates.setdefault('without', []).append(f) elif option == 'regexp': def f(m, arg=arg): if ircmsgs.isAction(m): return arg.search(ircmsgs.unAction(m)) else: return arg.search(m.args[1]) predicates.setdefault('regexp', []).append(f) elif option == 'nolimit': nolimit = True iterable = ifilter(self._validLastMsg, reversed(irc.state.history)) if skipfirst: # Drop the first message only if our current channel is the same as # the channel we've been instructed to look at. iterable.next() predicates = list(utils.iter.flatten(predicates.itervalues())) # Make sure the user can't get messages from channels they aren't in def userInChannel(m): return m.args[0] in irc.state.channels \ and msg.nick in irc.state.channels[m.args[0]].users predicates.append(userInChannel) # Make sure the user can't get messages from a +s channel unless # they're calling the command from that channel or from a query def notSecretMsg(m): return not irc.isChannel(msg.args[0]) \ or msg.args[0] == m.args[0] \ or (m.args[0] in irc.state.channels \ and 's' not in irc.state.channels[m.args[0]].modes) predicates.append(notSecretMsg) resp = [] if irc.nested and not \ self.registryValue('last.nested.includeTimestamp'): tsf = None else: tsf = self.registryValue('timestampFormat') if irc.nested and not self.registryValue('last.nested.includeNick'): showNick = False else: showNick = True for m in iterable: for predicate in predicates: if not predicate(m): break else: if nolimit: resp.append(ircmsgs.prettyPrint(m, timestampFormat=tsf, showNick=showNick)) else: irc.reply(ircmsgs.prettyPrint(m, timestampFormat=tsf, showNick=showNick)) return if not resp: irc.error('I couldn\'t find a message matching that criteria in ' 'my history of %s messages.' % len(irc.state.history)) else: irc.reply(format('%L', resp)) last = wrap(last, [getopts({'nolimit': '', 'on': 'something', 'with': 'something', 'from': 'something', 'without': 'something', 'in': 'callerInGivenChannel', 'regexp': 'regexpMatcher',})]) def tell(self, irc, msg, args, target, text): """ Tells the whatever is. Use nested commands to your benefit here. """ if target.lower() == 'me': target = msg.nick if ircutils.isChannel(target): irc.error('Dude, just give the command. No need for the tell.') return if not ircutils.isNick(target): irc.errorInvalid('nick', target) if ircutils.nickEqual(target, irc.nick): irc.error('You just told me, why should I tell myself?',Raise=True) if target not in irc.state.nicksToHostmasks and \ not ircdb.checkCapability(msg.prefix, 'owner'): # We'll let owners do this. s = 'I haven\'t seen %s, I\'ll let you do the telling.' % target irc.error(s, Raise=True) if irc.action: irc.action = False text = '* %s %s' % (irc.nick, text) s = '%s wants me to tell you: %s' % (msg.nick, text) irc.reply(s, to=target, private=True) tell = wrap(tell, ['something', 'text']) def ping(self, irc, msg, args): """takes no arguments Checks to see if the bot is alive. """ irc.reply('pong', prefixNick=False) Class = Misc # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Misc/config.py�����������������������������������������������������0000644�0000000�0000000�00000006340�11206611405�016076� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-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('Misc', True) Misc = conf.registerPlugin('Misc') conf.registerGlobalValue(Misc, 'listPrivatePlugins', registry.Boolean(True, """Determines whether the bot will list private plugins with the list command if given the --private switch. If this is disabled, non-owner users should be unable to see what private plugins are loaded.""")) conf.registerGlobalValue(Misc, 'timestampFormat', registry.String('[%H:%M:%S]', """Determines the format string for timestamps in the Misc.last command. Refer to the Python documentation for the time module to see what formats are accepted. If you set this variable to the empty string, the timestamp will not be shown.""")) conf.registerGroup(Misc, 'last') conf.registerGroup(Misc.last, 'nested') conf.registerChannelValue(Misc.last.nested, 'includeTimestamp', registry.Boolean(False, """Determines whether or not the timestamp will be included in the output of last when it is part of a nested command""")) conf.registerChannelValue(Misc.last.nested, 'includeNick', registry.Boolean(False, """Determines whether or not the nick will be included in the output of last when it is part of a nested command""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Later/�������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014430� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Later/test.py������������������������������������������������������0000644�0000000�0000000�00000004243�11206611405�015764� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 LaterTestCase(PluginTestCase): plugins = ('Later',) def testLaterWorksTwice(self): self.assertNotError('later tell foo bar') self.assertNotError('later tell foo baz') def testLaterRemove(self): self.assertNotError('later tell foo 1') self.assertNotError('later tell bar 1') self.assertRegexp('later notes', 'bar.*foo') self.assertNotError('later remove bar') self.assertNotRegexp('later notes', 'bar.*foo') self.assertRegexp('later notes', 'foo') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Later/__init__.py��������������������������������������������������0000644�0000000�0000000�00000004715�11206611405�016550� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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. ### """ Informal notes, mostly for compatibility with other bots. Based entirely on nicks, it's an easy way to tell users who refuse to register notes when they arrive later. """ 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__ = "%%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: ���������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Later/README.txt���������������������������������������������������0000644�0000000�0000000�00000000117�11206611405�016125� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Later/plugin.py����������������������������������������������������0000644�0000000�0000000�00000015202�11206611405�016300� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 csv import time import supybot.log as log import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Later(callbacks.Plugin): """Used to do things later; currently, it only allows the sending of nick-based notes. Do note (haha!) that these notes are *not* private and don't even pretend to be; if you want such features, consider using the Note plugin.""" def __init__(self, irc): self.__parent = super(Later, self) self.__parent.__init__(irc) self._notes = ircutils.IrcDict() self.wildcards = [] self.filename = conf.supybot.directories.data.dirize('Later.db') self._openNotes() def die(self): self._flushNotes() def _flushNotes(self): fd = utils.file.AtomicFile(self.filename) writer = csv.writer(fd) for (nick, notes) in self._notes.iteritems(): for (time, whence, text) in notes: writer.writerow([nick, time, whence, text]) fd.close() def _openNotes(self): try: fd = file(self.filename) except EnvironmentError, e: self.log.warning('Couldn\'t open %s: %s', self.filename, e) return reader = csv.reader(fd) for (nick, time, whence, text) in reader: self._addNote(nick, whence, text, at=float(time), maximum=0) fd.close() def _timestamp(self, when): #format = conf.supybot.reply.format.time() diff = time.time() - when try: return utils.timeElapsed(diff, seconds=False) + ' ago' except ValueError: return 'just now' def _addNote(self, nick, whence, text, at=None, maximum=None): if at is None: at = time.time() if maximum is None: maximum = self.registryValue('maximum') try: notes = self._notes[nick] if maximum and len(notes) >= maximum: raise ValueError else: notes.append((at, whence, text)) except KeyError: self._notes[nick] = [(at, whence, text)] if '?' in nick or '*' in nick and nick not in self.wildcards: self.wildcards.append(nick) self._flushNotes() def tell(self, irc, msg, args, nick, text): """ Tells the next time is in seen. can contain wildcard characters, and the first matching nick will be given the note. """ if ircutils.strEqual(nick, irc.nick): irc.error('I can\'t send notes to myself.') return try: self._addNote(nick, msg.nick, text) irc.replySuccess() except ValueError: irc.error('That person\'s message queue is already full.') tell = wrap(tell, ['something', 'text']) def notes(self, irc, msg, args, nick): """[] If is given, replies with what notes are waiting on , otherwise, replies with the nicks that have notes waiting for them. """ if nick: if nick in self._notes: notes = [self._formatNote(when, whence, note) for (when, whence, note) in self._notes[nick]] irc.reply(format('%L', notes)) else: irc.error('I have no notes for that nick.') else: nicks = self._notes.keys() if nicks: utils.sortBy(ircutils.toLower, nicks) irc.reply(format('I currently have notes waiting for %L.', nicks)) else: irc.error('I have no notes waiting to be delivered.') notes = wrap(notes, [additional('something')]) def remove(self, irc, msg, args, nick): """ Removes the notes waiting on . """ try: del self._notes[nick] self._flushNotes() irc.replySuccess() except KeyError: irc.error('There were no notes for %r' % nick) remove = wrap(remove, [('checkCapability', 'admin'), 'something']) def doPrivmsg(self, irc, msg): notes = self._notes.pop(msg.nick, []) # Let's try wildcards. removals = [] for wildcard in self.wildcards: if ircutils.hostmaskPatternEqual(wildcard, msg.nick): removals.append(wildcard) notes.extend(self._notes.pop(wildcard)) for removal in removals: self.wildcards.remove(removal) if notes: irc = callbacks.SimpleProxy(irc, msg) private = self.registryValue('private') for (when, whence, note) in notes: s = self._formatNote(when, whence, note) irc.reply(s, private=private) self._flushNotes() def _formatNote(self, when, whence, note): return 'Sent %s: <%s> %s' % (self._timestamp(when), whence, note) Class = Later # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Later/config.py����������������������������������������������������0000644�0000000�0000000�00000004733�11206611405�016256� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 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('Later', True) Later = conf.registerPlugin('Later') conf.registerGlobalValue(Later, 'maximum', registry.NonNegativeInteger(0, """Determines the maximum number of messages to be queued for a user. If this value is 0, there is no maximum.""")) conf.registerGlobalValue(Later, 'private', registry.Boolean(True, """Determines whether users will be notified in the first place in which they're seen, or in private.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������supybot-0.83.4.1.ds.orig/plugins/Config/������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014566� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Config/test.py�����������������������������������������������������0000644�0000000�0000000�00000007451�11206611405�016126� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 supybot.conf as conf class ConfigTestCase(ChannelPluginTestCase): # We add utilities so there's something in supybot.plugins. plugins = ('Config', 'Utilities') def testGet(self): self.assertNotRegexp('config get supybot.reply', r'registry\.Group') self.assertResponse('config supybot.protocols.irc.throttleTime', '0.0') def testList(self): self.assertError('config list asldfkj') self.assertError('config list supybot.asdfkjsldf') self.assertNotError('config list supybot') self.assertNotError('config list supybot.replies') self.assertRegexp('config list supybot', r'@plugins.*@replies.*@reply') def testHelp(self): self.assertError('config help alsdkfj') self.assertError('config help supybot.alsdkfj') self.assertNotError('config help supybot') # We tell the user to list. self.assertNotError('config help supybot.plugins') self.assertNotError('config help supybot.replies.success') self.assertNotError('config help replies.success') def testHelpDoesNotAssertionError(self): self.assertNotRegexp('config help ' # Cont'd. 'supybot.commands.defaultPlugins.help', 'AssertionError') def testHelpExhaustively(self): L = conf.supybot.getValues(getChildren=True) for (name, v) in L: self.assertNotError('config help %s' % name) def testSearch(self): self.assertNotError('config search chars') self.assertNotError('config channel reply.whenAddressedBy.chars @') self.assertNotRegexp('config search chars', self.channel) def testDefault(self): self.assertNotError('config default ' 'supybot.replies.genericNoCapability') def testConfigErrors(self): self.assertRegexp('config supybot.replies.', 'not a valid') self.assertRegexp('config supybot.repl', 'not a valid') self.assertRegexp('config supybot.reply.withNickPrefix 123', 'True or False') self.assertRegexp('config supybot.replies foo', 'settable') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Config/__init__.py�������������������������������������������������0000644�0000000�0000000�00000004210�11206611405�016674� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-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. ### """ Handles configuration of the bot while it is running. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Config/plugin.py���������������������������������������������������0000644�0000000�0000000�00000025130�11206611405�016437� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 signal import supybot.log as log import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * from supybot.utils.iter import all import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks ### # Now, to setup the registry. ### def getWrapper(name): parts = registry.split(name) if not parts or parts[0] not in ('supybot', 'users'): raise InvalidRegistryName, name group = getattr(conf, parts.pop(0)) while parts: try: group = group.get(parts.pop(0)) # We'll catch registry.InvalidRegistryName and re-raise it here so # that we have a useful error message for the user. except (registry.NonExistentRegistryEntry, registry.InvalidRegistryName): raise registry.InvalidRegistryName, name return group def getCapability(name): capability = 'owner' # Default to requiring the owner capability. parts = registry.split(name) while parts: part = parts.pop() if ircutils.isChannel(part): # If a registry value has a channel in it, it requires a channel.op # capability, or so we assume. We'll see if we're proven wrong. capability = ircdb.makeChannelCapability(part, 'op') ### Do more later, for specific capabilities/sections. return capability def _reload(): ircdb.users.reload() ircdb.ignores.reload() ircdb.channels.reload() registry.open(world.registryFilename) def _hupHandler(sig, frame): log.info('Received SIGHUP, reloading configuration.') _reload() if os.name == 'posix': signal.signal(signal.SIGHUP, _hupHandler) def getConfigVar(irc, msg, args, state): name = args[0] if name.startswith('conf.'): name = name[5:] if not name.startswith('supybot') and not name.startswith('users'): name = 'supybot.' + name try: group = getWrapper(name) state.args.append(group) del args[0] except registry.InvalidRegistryName, e: state.errorInvalid('configuration variable', str(e)) addConverter('configVar', getConfigVar) def getSettableConfigVar(irc, msg, args, state): getConfigVar(irc, msg, args, state) if not hasattr(state.args[-1], 'set'): state.errorInvalid('settable configuration variable', state.args[-1]._name) addConverter('settableConfigVar', getSettableConfigVar) class Config(callbacks.Plugin): def callCommand(self, command, irc, msg, *args, **kwargs): try: super(Config, self).callCommand(command, irc, msg, *args, **kwargs) except registry.InvalidRegistryValue, e: irc.error(str(e)) def _list(self, group): L = [] for (vname, v) in group._children.iteritems(): if hasattr(group, 'channelValue') and group.channelValue and \ ircutils.isChannel(vname) and not v._children: continue if hasattr(v, 'channelValue') and v.channelValue: vname = '#' + vname if v._added and not all(ircutils.isChannel, v._added): vname = '@' + vname L.append(vname) utils.sortBy(str.lower, L) return L def list(self, irc, msg, args, group): """ Returns the configuration variables available under the given configuration . If a variable has values under it, it is preceded by an '@' sign. If a variable is a 'ChannelValue', that is, it can be separately configured for each channel using the 'channel' command in this plugin, it is preceded by an '#' sign. """ L = self._list(group) if L: irc.reply(format('%L', L)) else: irc.error('There don\'t seem to be any values in %s.' % group._name) list = wrap(list, ['configVar']) def search(self, irc, msg, args, word): """ Searches for in the current configuration variables. """ L = [] for (name, _) in conf.supybot.getValues(getChildren=True): if word in name.lower(): possibleChannel = registry.split(name)[-1] if not ircutils.isChannel(possibleChannel): L.append(name) if L: irc.reply(format('%L', L)) else: irc.reply('There were no matching configuration variables.') search = wrap(search, ['lowered']) # XXX compose with withoutSpaces? def _getValue(self, irc, msg, group, addChannel=False): value = str(group) or ' ' if addChannel and irc.isChannel(msg.args[0]) and not irc.nested: s = str(group.get(msg.args[0])) value = 'Global: %s; %s: %s' % (value, msg.args[0], s) if hasattr(group, 'value'): if not group._private: irc.reply(value) else: capability = getCapability(group._name) if ircdb.checkCapability(msg.prefix, capability): irc.reply(value, private=True) else: irc.errorNoCapability(capability) else: irc.error('That registry variable has no value. Use the list ' 'command in this plugin to see what variables are ' 'available in this group.') def _setValue(self, irc, msg, group, value): capability = getCapability(group._name) if ircdb.checkCapability(msg.prefix, capability): # I think callCommand catches exceptions here. Should it? group.set(value) irc.replySuccess() else: irc.errorNoCapability(capability) def channel(self, irc, msg, args, channel, group, value): """[] [] If is given, sets the channel configuration variable for to for . Otherwise, returns the current channel configuration value of . is only necessary if the message isn't sent in the channel itself.""" if not group.channelValue: irc.error('That configuration variable is not a channel-specific ' 'configuration variable.') return group = group.get(channel) if value is not None: self._setValue(irc, msg, group, value) else: self._getValue(irc, msg, group) channel = wrap(channel, ['channel', 'settableConfigVar', additional('text')]) def config(self, irc, msg, args, group, value): """ [] If is given, sets the value of to . Otherwise, returns the current value of . You may omit the leading "supybot." in the name if you so choose. """ if value is not None: self._setValue(irc, msg, group, value) else: self._getValue(irc, msg, group, addChannel=group.channelValue) config = wrap(config, ['settableConfigVar', additional('text')]) def help(self, irc, msg, args, group): """ Returns the description of the configuration variable . """ if hasattr(group, '_help'): s = group.help() if s: if hasattr(group, 'value') and not group._private: s += ' (Current value: %s)' % group irc.reply(s) else: irc.reply('That configuration group exists, but seems to have ' 'no help. Try "config list %s" to see if it has ' 'any children values.' % group._name) else: irc.error('%s has no help.' % group._name) help = wrap(help, ['configVar']) def default(self, irc, msg, args, group): """ Returns the default value of the configuration variable . """ v = group.__class__(group._default, '') irc.reply(str(v)) default = wrap(default, ['settableConfigVar']) def reload(self, irc, msg, args): """takes no arguments Reloads the various configuration files (user database, channel database, registry, etc.). """ _reload() # This was factored out for SIGHUP handling. irc.replySuccess() reload = wrap(reload, [('checkCapability', 'owner')]) def export(self, irc, msg, args, filename): """ Exports the public variables of your configuration to . If you want to show someone your configuration file, but you don't want that person to be able to see things like passwords, etc., this command will export a "sanitized" configuration file suitable for showing publicly. """ registry.close(conf.supybot, filename, private=False) irc.replySuccess() export = wrap(export, [('checkCapability', 'owner'), 'filename']) Class = Config # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Config/config.py���������������������������������������������������0000644�0000000�0000000�00000004473�11206611405�016415� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2004-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('Config', True) Config = conf.registerPlugin('Config') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Config, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ChannelLogger/�����������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�016071� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ChannelLogger/test.py����������������������������������������������0000644�0000000�0000000�00000003331�11206611405�017422� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 * class ChannelLoggerTestCase(PluginTestCase): plugins = ('ChannelLogger',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ChannelLogger/__init__.py������������������������������������������0000644�0000000�0000000�00000004526�11206611405�020211� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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. ### """ Logs each channel to its own individual logfile. """ 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__ = "%%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: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ChannelLogger/README.txt�������������������������������������������0000644�0000000�0000000�00000000117�11206611405�017566� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ChannelLogger/plugin.py��������������������������������������������0000644�0000000�0000000�00000023351�11206611405�017745� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 time from cStringIO import StringIO import supybot.conf as conf import supybot.world as world import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks class FakeLog(object): def flush(self): return def close(self): return def write(self, s): return class ChannelLogger(callbacks.Plugin): noIgnore = True def __init__(self, irc): self.__parent = super(ChannelLogger, self) self.__parent.__init__(irc) self.lastMsgs = {} self.lastStates = {} self.logs = {} self.flusher = self.flush world.flushers.append(self.flusher) def die(self): for log in self._logs(): log.close() world.flushers = [x for x in world.flushers if x is not self.flusher] def __call__(self, irc, msg): try: # I don't know why I put this in, but it doesn't work, because it # doesn't call doNick or doQuit. # if msg.args and irc.isChannel(msg.args[0]): self.__parent.__call__(irc, msg) if irc in self.lastMsgs: if irc not in self.lastStates: self.lastStates[irc] = irc.state.copy() self.lastStates[irc].addMsg(irc, self.lastMsgs[irc]) finally: # We must make sure this always gets updated. self.lastMsgs[irc] = msg def reset(self): for log in self._logs(): log.close() self.logs.clear() self.lastMsgs.clear() self.lastStates.clear() def _logs(self): for logs in self.logs.itervalues(): for log in logs.itervalues(): yield log def flush(self): self.checkLogNames() for log in self._logs(): try: log.flush() except ValueError, e: if e.args[0] != 'I/O operation on a closed file': self.log.exception('Odd exception:') def logNameTimestamp(self, channel): format = self.registryValue('filenameTimestamp', channel) return time.strftime(format) def getLogName(self, channel): if self.registryValue('rotateLogs', channel): return '%s.%s.log' % (channel, self.logNameTimestamp(channel)) else: return '%s.log' % channel def getLogDir(self, irc, channel): logDir = conf.supybot.directories.log.dirize(self.name()) if self.registryValue('directories'): if self.registryValue('directories.network'): logDir = os.path.join(logDir, irc.network) if self.registryValue('directories.channel'): logDir = os.path.join(logDir, channel) if self.registryValue('directories.timestamp'): format = self.registryValue('directories.timestamp.format') timeDir =time.strftime(format) logDir = os.path.join(logDir, timeDir) if not os.path.exists(logDir): os.makedirs(logDir) return logDir def checkLogNames(self): for (irc, logs) in self.logs.items(): for (channel, log) in logs.items(): if self.registryValue('rotateLogs', channel): name = self.getLogName(channel) if name != log.name: log.close() del logs[channel] def getLog(self, irc, channel): self.checkLogNames() try: logs = self.logs[irc] except KeyError: logs = ircutils.IrcDict() self.logs[irc] = logs if channel in logs: return logs[channel] else: try: name = self.getLogName(channel) logDir = self.getLogDir(irc, channel) log = file(os.path.join(logDir, name), 'a') logs[channel] = log return log except IOError: self.log.exception('Error opening log:') return FakeLog() def timestamp(self, log): format = conf.supybot.log.timestampFormat() if format: log.write(time.strftime(format)) log.write(' ') def normalizeChannel(self, irc, channel): return ircutils.toLower(channel) def doLog(self, irc, channel, s, *args): if not self.registryValue('enable', channel): return s = format(s, *args) channel = self.normalizeChannel(irc, channel) log = self.getLog(irc, channel) if self.registryValue('timestamp', channel): self.timestamp(log) if self.registryValue('stripFormatting', channel): s = ircutils.stripFormatting(s) log.write(s) if self.registryValue('flushImmediately'): log.flush() def doPrivmsg(self, irc, msg): (recipients, text) = msg.args for channel in recipients.split(','): if irc.isChannel(channel): noLogPrefix = self.registryValue('noLogPrefix', channel) if noLogPrefix and text.startswith(noLogPrefix): text = '-= THIS MESSAGE NOT LOGGED =-' nick = msg.nick or irc.nick if ircmsgs.isAction(msg): self.doLog(irc, channel, '* %s %s\n', nick, ircmsgs.unAction(msg)) else: self.doLog(irc, channel, '<%s> %s\n', nick, text) def doNotice(self, irc, msg): (recipients, text) = msg.args for channel in recipients.split(','): if irc.isChannel(channel): self.doLog(irc, channel, '-%s- %s\n', msg.nick, text) def doNick(self, irc, msg): oldNick = msg.nick newNick = msg.args[0] for (channel, c) in irc.state.channels.iteritems(): if newNick in c.users: self.doLog(irc, channel, '*** %s is now known as %s\n', oldNick, newNick) def doJoin(self, irc, msg): for channel in msg.args[0].split(','): self.doLog(irc, channel, '*** %s has joined %s\n', msg.nick or msg.prefix, channel) def doKick(self, irc, msg): if len(msg.args) == 3: (channel, target, kickmsg) = msg.args else: (channel, target) = msg.args kickmsg = '' if kickmsg: self.doLog(irc, channel, '*** %s was kicked by %s (%s)\n', target, msg.nick, kickmsg) else: self.doLog(irc, channel, '*** %s was kicked by %s\n', target, msg.nick) def doPart(self, irc, msg): for channel in msg.args[0].split(','): self.doLog(irc, channel, '*** %s has left %s\n', msg.nick, channel) def doMode(self, irc, msg): channel = msg.args[0] if irc.isChannel(channel) and msg.args[1:]: self.doLog(irc, channel, '*** %s sets mode: %s %s\n', msg.nick or msg.prefix, msg.args[1], ' '.join(msg.args[2:])) def doTopic(self, irc, msg): if len(msg.args) == 1: return # It's an empty TOPIC just to get the current topic. channel = msg.args[0] self.doLog(irc, channel, '*** %s changes topic to "%s"\n', msg.nick, msg.args[1]) def doQuit(self, irc, msg): if not isinstance(irc, irclib.Irc): irc = irc.getRealIrc() for (channel, chan) in self.lastStates[irc].channels.iteritems(): if msg.nick in chan.users: self.doLog(irc, channel, '*** %s has quit IRC\n', msg.nick) def outFilter(self, irc, msg): # Gotta catch my own messages *somehow* :) # Let's try this little trick... if msg.command in ('PRIVMSG', 'NOTICE'): # Other messages should be sent back to us. m = ircmsgs.IrcMsg(msg=msg, prefix=irc.prefix) self(irc, m) return msg Class = ChannelLogger # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ChannelLogger/config.py��������������������������������������������0000644�0000000�0000000�00000012022�11206611405�017705� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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('ChannelLogger', True) ChannelLogger = conf.registerPlugin('ChannelLogger') conf.registerChannelValue(ChannelLogger, 'enable', registry.Boolean(True, """Determines whether logging is enabled.""")) conf.registerGlobalValue(ChannelLogger, 'flushImmediately', registry.Boolean(False, """Determines whether channel logfiles will be flushed anytime they're written to, rather than being buffered by the operating system.""")) conf.registerChannelValue(ChannelLogger, 'stripFormatting', registry.Boolean(True, """Determines whether formatting characters (such as bolding, color, etc.) are removed when writing the logs to disk.""")) conf.registerChannelValue(ChannelLogger, 'timestamp', registry.Boolean(True, """Determines whether the logs for this channel are timestamped with the timestamp in supybot.log.timestampFormat.""")) conf.registerChannelValue(ChannelLogger, 'noLogPrefix', registry.String('[nolog]', """Determines what string a message should be prefixed with in order not to be logged. If you don't want any such prefix, just set it to the empty string.""")) conf.registerChannelValue(ChannelLogger, 'rotateLogs', registry.Boolean(False, """Determines whether the bot will automatically rotate the logs for this channel. The bot will rotate logs when the timestamp for the log changes. The timestamp is set according to the 'filenameTimestamp' configuration variable.""")) conf.registerChannelValue(ChannelLogger, 'filenameTimestamp', registry.String('%Y-%m-%d', """Determines how to represent the timestamp used for the filename in rotated logs. When this timestamp changes, the old logfiles will be closed and a new one started. The format characters for the timestamp are in the time.strftime docs at python.org. In order for your logs to be rotated, you'll also have to enable supybot.plugins.ChannelLogger.rotateLogs.""")) conf.registerGlobalValue(ChannelLogger, 'directories', registry.Boolean(True, """Determines whether the bot will partition its channel logs into separate directories based on different criteria.""")) conf.registerGlobalValue(ChannelLogger.directories, 'network', registry.Boolean(True, """Determines whether the bot will use a network directory if using directories.""")) conf.registerGlobalValue(ChannelLogger.directories, 'channel', registry.Boolean(True, """Determines whether the bot will use a channel directory if using directories.""")) conf.registerGlobalValue(ChannelLogger.directories, 'timestamp', registry.Boolean(False, """Determines whether the bot will use a timestamp (determined by supybot.plugins.ChannelLogger.directories.timestamp.format) if using directories.""")) conf.registerGlobalValue(ChannelLogger.directories.timestamp, 'format', registry.String('%B', """Determines what timestamp format will be used in the directory stucture for channel logs if supybot.plugins.ChannelLogger.directories.timestamp is True.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Topic/�������������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�014437� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Topic/test.py������������������������������������������������������0000644�0000000�0000000�00000025655�11206611405�016005� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 TopicTestCase(ChannelPluginTestCase): plugins = ('Topic',) def testRemove(self): self.assertError('topic remove 1') _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertError('topic remove 0') self.assertNotError('topic remove 3') self.assertNotError('topic remove 2') self.assertNotError('topic remove 1') self.assertError('topic remove 1') def testReplace(self): _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic replace 1 oof', 'oof.*bar.*baz') self.assertRegexp('topic replace -1 zab', 'oof.*bar.*zab') self.assertRegexp('topic replace 2 lorem ipsum', 'oof.*lorem ipsum.*zab') self.assertRegexp('topic replace 2 rab', 'oof.*rab.*zab') def testGet(self): self.assertError('topic get 1') _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic get 1', '^foo') self.assertError('topic get 0') def testAdd(self): self.assertError('topic add #floorgle') m = self.getMsg('topic add foo') self.assertEqual(m.command, 'TOPIC') self.assertEqual(m.args[0], self.channel) self.assertEqual(m.args[1], 'foo (test)') m = self.getMsg('topic add bar') self.assertEqual(m.command, 'TOPIC') self.assertEqual(m.args[0], self.channel) self.assertEqual(m.args[1], 'foo (test) || bar (test)') def testInsert(self): m = self.getMsg('topic add foo') self.assertEqual(m.args[1], 'foo (test)') m = self.getMsg('topic insert bar') self.assertEqual(m.args[1], 'bar (test) || foo (test)') def testChange(self): _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic change -1 s/baz/biff/', r'foo.*bar.*biff') self.assertRegexp('topic change 2 s/bar/baz/', r'foo.*baz.*biff') self.assertRegexp('topic change 1 s/foo/bar/', r'bar.*baz.*biff') self.assertRegexp('topic change -2 s/baz/bazz/', r'bar.*bazz.*biff') self.assertError('topic change 0 s/baz/biff/') def testConfig(self): try: original = conf.supybot.plugins.Topic.separator() conf.supybot.plugins.Topic.separator.setValue(' <==> ') _ = self.getMsg('topic add foo') m = self.getMsg('topic add bar') self.failUnless('<==>' in m.args[1]) finally: conf.supybot.plugins.Topic.separator.setValue(original) def testReorder(self): _ = self.getMsg('topic add foo') _ = self.getMsg('topic add bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic reorder 2 1 3', r'bar.*foo.*baz') self.assertRegexp('topic reorder 3 -2 1', r'baz.*foo.*bar') self.assertError('topic reorder 0 1 2') self.assertError('topic reorder 1 -2 2') self.assertError('topic reorder 1 2') self.assertError('topic reorder 2 3 4') self.assertError('topic reorder 1 2 2') self.assertError('topic reorder 1 1 2 3') _ = self.getMsg('topic remove 1') _ = self.getMsg('topic remove 1') self.assertError('topic reorder 1') _ = self.getMsg('topic remove 1') self.assertError('topic reorder 0') def testList(self): _ = self.getMsg('topic add foo') self.assertRegexp('topic list', '1: foo') _ = self.getMsg('topic add bar') self.assertRegexp('topic list', '1: foo .*2: bar') _ = self.getMsg('topic add baz') self.assertRegexp('topic list', '1: foo .* 2: bar .* and 3: baz') def testSet(self): _ = self.getMsg('topic add foo') self.assertRegexp('topic set -1 bar', 'bar') self.assertNotRegexp('topic set -1 baz', 'bar') self.assertResponse('topic set foo bar baz', 'foo bar baz') # Catch a bug we had where setting topic 1 would reset the whole topic orig = conf.supybot.plugins.Topic.format() sep = conf.supybot.plugins.Topic.separator() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic add baz', 'foo bar baz%sbaz' % sep) self.assertResponse('topic set 1 bar', 'bar%sbaz' % sep) finally: conf.supybot.plugins.Topic.format.setValue(orig) def testUndo(self): try: original = conf.supybot.plugins.Topic.format() conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic set ""', '') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo || bar') self.assertResponse('topic add baz', 'foo || bar || baz') self.assertResponse('topic undo', 'foo || bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic undo', '') finally: conf.supybot.plugins.Topic.format.setValue(original) def testUndoRedo(self): try: original = conf.supybot.plugins.Topic.format() conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic set ""', '') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo || bar') self.assertResponse('topic add baz', 'foo || bar || baz') self.assertResponse('topic undo', 'foo || bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic undo', '') self.assertResponse('topic redo', 'foo') self.assertResponse('topic redo', 'foo || bar') self.assertResponse('topic redo', 'foo || bar || baz') self.assertResponse('topic undo', 'foo || bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic redo', 'foo || bar') self.assertResponse('topic undo', 'foo') self.assertResponse('topic redo', 'foo || bar') finally: conf.supybot.plugins.Topic.format.setValue(original) def testSwap(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic set ""', '') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo || bar') self.assertResponse('topic add baz', 'foo || bar || baz') self.assertResponse('topic swap 1 2', 'bar || foo || baz') self.assertResponse('topic swap 1 -1', 'baz || foo || bar') self.assertError('topic swap -1 -1') self.assertError('topic swap 2 -2') self.assertError('topic swap 1 -3') self.assertError('topic swap -2 2') self.assertError('topic swap -3 1') finally: conf.supybot.plugins.Topic.format.setValue(original) def testDefault(self): self.assertError('topic default') try: original = conf.supybot.plugins.Topic.default() conf.supybot.plugins.Topic.default.setValue('foo bar baz') self.assertResponse('topic default', 'foo bar baz') finally: conf.supybot.plugins.Topic.default.setValue(original) def testTopic(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertError('topic addd') # Error to send too many args. self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo || bar') self.assertResponse('topic', 'foo || bar') finally: conf.supybot.plugins.Topic.format.setValue(original) def testSeparator(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.assertResponse('topic add foo', 'foo') self.assertResponse('topic add bar', 'foo || bar') self.assertResponse('topic add baz', 'foo || bar || baz') self.assertResponse('topic separator |', 'foo | bar | baz') self.assertResponse('topic separator ::', 'foo :: bar :: baz') self.assertResponse('topic separator ||', 'foo || bar || baz') finally: conf.supybot.plugins.Topic.format.setValue(original) def testFit(self): original = conf.supybot.plugins.Topic.format() try: conf.supybot.plugins.Topic.format.setValue('$topic') self.irc.state.supported['TOPICLEN'] = 20 self.assertResponse('topic fit foo', 'foo') self.assertResponse('topic fit bar', 'foo || bar') self.assertResponse('topic fit baz', 'foo || bar || baz') self.assertResponse('topic fit qux', 'bar || baz || qux') finally: conf.supybot.plugins.Topic.format.setValue(original) self.irc.state.supported.pop('TOPICLEN', None) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Topic/__init__.py��������������������������������������������������0000644�0000000�0000000�00000004530�11206611405�016552� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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. ### """ Provides commands for manipulating channel topics. """ 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__ = "%%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: ������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Topic/README.txt���������������������������������������������������0000644�0000000�0000000�00000000117�11206611405�016134� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/Topic/plugin.py����������������������������������������������������0000644�0000000�0000000�00000042743�11206611405�016321� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 random import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks def canChangeTopic(irc, msg, args, state): assert not state.channel callConverter('channel', irc, msg, args, state) callConverter('inChannel', irc, msg, args, state) if state.channel not in irc.state.channels: state.error(format('I\'m not currently in %s.', state.channel), Raise=True) c = irc.state.channels[state.channel] if irc.nick not in c.ops and 't' in c.modes: state.error(format('I can\'t change the topic, I\'m not opped ' 'and %s is +t.', state.channel), Raise=True) def getTopic(irc, msg, args, state, format=True): separator = state.cb.registryValue('separator', state.channel) if separator in args[0]: state.errorInvalid('topic', args[0], format('The topic must not include %q.', separator)) topic = args.pop(0) if format: env = {'topic': topic} formatter = state.cb.registryValue('format', state.channel) topic = ircutils.standardSubstitute(irc, msg, formatter, env) state.args.append(topic) def getTopicNumber(irc, msg, args, state): def error(s): state.errorInvalid('topic number', s) try: n = int(args[0]) if not n: raise ValueError except ValueError: error(args[0]) if n > 0: n -= 1 topic = irc.state.getTopic(state.channel) separator = state.cb.registryValue('separator', state.channel) topics = splitTopic(topic, separator) if not topics: state.error(format('There are no topics in %s.', state.channel), Raise=True) try: topics[n] except IndexError: error(str(n)) del args[0] while n < 0: n += len(topics) state.args.append(n) addConverter('topic', getTopic) addConverter('topicNumber', getTopicNumber) addConverter('canChangeTopic', canChangeTopic) def splitTopic(topic, separator): return filter(None, topic.split(separator)) class Topic(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Topic, self) self.__parent.__init__(irc) self.undos = ircutils.IrcDict() self.redos = ircutils.IrcDict() self.lastTopics = ircutils.IrcDict() self.watchingFor332 = ircutils.IrcSet() def _splitTopic(self, topic, channel): separator = self.registryValue('separator', channel) return splitTopic(topic, separator) def _joinTopic(self, channel, topics): separator = self.registryValue('separator', channel) return separator.join(topics) def _addUndo(self, channel, topics): stack = self.undos.setdefault(channel, []) stack.append(topics) maxLen = self.registryValue('undo.max', channel) del stack[:len(stack)-maxLen] def _addRedo(self, channel, topics): stack = self.redos.setdefault(channel, []) stack.append(topics) maxLen = self.registryValue('undo.max', channel) del stack[:len(stack)-maxLen] def _getUndo(self, channel): try: return self.undos[channel].pop() except (KeyError, IndexError): return None def _getRedo(self, channel): try: return self.redos[channel].pop() except (KeyError, IndexError): return None def _sendTopics(self, irc, channel, topics, isDo=False, fit=False): topics = [s for s in topics if s and not s.isspace()] self.lastTopics[channel] = topics newTopic = self._joinTopic(channel, topics) try: maxLen = irc.state.supported['topiclen'] if fit: while len(newTopic) > maxLen: topics.pop(0) self.lastTopics[channel] = topics newTopic = self._joinTopic(channel, topics) elif len(newTopic) > maxLen: if self.registryValue('recognizeTopiclen', channel): irc.error(format('That topic is too long for this server ' '(maximum length: %i; this topic: %i).', maxLen, len(newTopic)), Raise=True) except KeyError: pass self._addUndo(channel, topics) if not isDo and channel in self.redos: del self.redos[channel] irc.queueMsg(ircmsgs.topic(channel, newTopic)) irc.noReply() def doJoin(self, irc, msg): if ircutils.strEqual(msg.nick, irc.nick): # We're joining a channel, let's watch for the topic. self.watchingFor332.add(msg.args[0]) def do332(self, irc, msg): if msg.args[1] in self.watchingFor332: self.watchingFor332.remove(msg.args[1]) # Store an undo for the topic when we join a channel. This allows # us to undo the first topic change that takes place in a channel. self._addUndo(msg.args[1], [msg.args[2]]) def topic(self, irc, msg, args, channel): """[] Returns the topic for . is only necessary if the message isn't sent in the channel itself. """ topic = irc.state.channels[channel].topic irc.reply(topic) topic = wrap(topic, ['inChannel']) def add(self, irc, msg, args, channel, topic): """[] Adds to the topics for . is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) topics.append(topic) self._sendTopics(irc, channel, topics) add = wrap(add, ['canChangeTopic', rest('topic')]) def fit(self, irc, msg, args, channel, topic): """[] Adds to the topics for . If the topic is too long for the server, topics will be popped until there is enough room. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) topics.append(topic) self._sendTopics(irc, channel, topics, fit=True) fit = wrap(fit, ['canChangeTopic', rest('topic')]) def replace(self, irc, msg, args, channel, i, topic): """[] Replaces topic with . """ topics = self._splitTopic(irc.state.getTopic(channel), channel) topics[i] = topic self._sendTopics(irc, channel, topics) replace = wrap(replace, ['canChangeTopic', 'topicNumber', rest('topic')]) def insert(self, irc, msg, args, channel, topic): """[] Adds to the topics for at the beginning of the topics currently on . is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) topics.insert(0, topic) self._sendTopics(irc, channel, topics) insert = wrap(insert, ['canChangeTopic', rest('topic')]) def shuffle(self, irc, msg, args, channel): """[] Shuffles the topics in . is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) if len(topics) == 0 or len(topics) == 1: irc.error('I can\'t shuffle 1 or fewer topics.', Raise=True) elif len(topics) == 2: topics.reverse() else: original = topics[:] while topics == original: random.shuffle(topics) self._sendTopics(irc, channel, topics) shuffle = wrap(shuffle, ['canChangeTopic']) def reorder(self, irc, msg, args, channel, numbers): """[] [ ...] Reorders the topics from in the order of the specified arguments. is a one-based index into the topics. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) num = len(topics) if num == 0 or num == 1: irc.error('I cannot reorder 1 or fewer topics.', Raise=True) if len(numbers) != num: irc.error('All topic numbers must be specified.', Raise=True) if sorted(numbers) != range(num): irc.error('Duplicate topic numbers cannot be specified.') return newtopics = [topics[i] for i in numbers] self._sendTopics(irc, channel, newtopics) reorder = wrap(reorder, ['canChangeTopic', many('topicNumber')]) def list(self, irc, msg, args, channel): """[] Returns a list of the topics in , prefixed by their indexes. Mostly useful for topic reordering. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) L = [] for (i, t) in enumerate(topics): L.append(format('%i: %s', i+1, utils.str.ellipsisify(t, 30))) s = utils.str.commaAndify(L) irc.reply(s) list = wrap(list, ['inChannel']) def get(self, irc, msg, args, channel, number): """[] Returns topic number from . is a one-based index into the topics. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) irc.reply(topics[number]) get = wrap(get, ['inChannel', 'topicNumber']) def change(self, irc, msg, args, channel, number, replacer): """[] Changes the topic number on according to the regular expression . is the one-based index into the topics; is a regular expression of the form s/regexp/replacement/flags. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) topics[number] = replacer(topics[number]) self._sendTopics(irc, channel, topics) change = wrap(change, ['canChangeTopic', 'topicNumber', 'regexpReplacer']) def set(self, irc, msg, args, channel, number, topic): """[] [] Sets the topic to be . If no is given, this sets the entire topic. is only necessary if the message isn't sent in the channel itself. """ if number is not None: topics = self._splitTopic(irc.state.getTopic(channel), channel) topics[number] = topic else: topics = [topic] self._sendTopics(irc, channel, topics) set = wrap(set, ['canChangeTopic', optional('topicNumber'), rest(('topic', False))]) def remove(self, irc, msg, args, channel, number): """[] Removes topic from the topic for Topics are numbered starting from 1; you can also use negative indexes to refer to topics starting the from the end of the topic. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) topic = topics.pop(number) self._sendTopics(irc, channel, topics) remove = wrap(remove, ['canChangeTopic', 'topicNumber']) def lock(self, irc, msg, args, channel): """[] Locks the topic (sets the mode +t) in . is only necessary if the message isn't sent in the channel itself. """ irc.queueMsg(ircmsgs.mode(channel, '+t')) irc.noReply() lock = wrap(lock, ['channel', ('haveOp', 'lock the topic')]) def unlock(self, irc, msg, args, channel): """[] Locks the topic (sets the mode +t) in . is only necessary if the message isn't sent in the channel itself. """ irc.queueMsg(ircmsgs.mode(channel, '-t')) irc.noReply() unlock = wrap(unlock, ['channel', ('haveOp', 'unlock the topic')]) def restore(self, irc, msg, args, channel): """[] Restores the topic to the last topic set by the bot. is only necessary if the message isn't sent in the channel itself. """ try: topics = self.lastTopics[channel] except KeyError: irc.error(format('I haven\'t yet set the topic in %s.', channel)) return self._sendTopics(irc, channel, topics) restore = wrap(restore, ['canChangeTopic']) def undo(self, irc, msg, args, channel): """[] Restores the topic to the one previous to the last topic command that set it. is only necessary if the message isn't sent in the channel itself. """ self._addRedo(channel, self._getUndo(channel)) # current topic. topics = self._getUndo(channel) # This is the topic list we want. if topics is not None: self._sendTopics(irc, channel, topics, isDo=True) else: irc.error(format('There are no more undos for %s.', channel)) undo = wrap(undo, ['canChangetopic']) def redo(self, irc, msg, args, channel): """[] Undoes the last undo. is only necessary if the message isn't sent in the channel itself. """ topics = self._getRedo(channel) if topics is not None: self._sendTopics(irc, channel, topics, isDo=True) else: irc.error(format('There are no redos for %s.', channel)) redo = wrap(redo, ['canChangeTopic']) def swap(self, irc, msg, args, channel, first, second): """[] Swaps the order of the first topic number and the second topic number. is only necessary if the message isn't sent in the channel itself. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) if first == second: irc.error('I refuse to swap the same topic with itself.') return t = topics[first] topics[first] = topics[second] topics[second] = t self._sendTopics(irc, channel, topics) swap = wrap(swap, ['canChangeTopic', 'topicNumber', 'topicNumber']) def default(self, irc, msg, args, channel): """[] Sets the topic in to the default topic for . The default topic for a channel may be configured via the configuration variable supybot.plugins.Topic.default. """ topic = self.registryValue('default', channel) if topic: self._sendTopics(irc, channel, [topic]) else: irc.error(format('There is no default topic configured for %s.', channel)) default = wrap(default, ['canChangeTopic']) def separator(self, irc, msg, args, channel, separator): """[] Sets the topic separator for to Converts the current topic appropriately. """ topics = self._splitTopic(irc.state.getTopic(channel), channel) self.setRegistryValue('separator', separator, channel) self._sendTopics(irc, channel, topics) separator = wrap(separator, ['canChangeTopic', 'something']) Class = Topic # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: �����������������������������supybot-0.83.4.1.ds.orig/plugins/Topic/config.py����������������������������������������������������0000644�0000000�0000000�00000007005�11206611405�016260� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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('Topic', True) class TopicFormat(registry.String): "Value must include $topic, otherwise the actual topic would be left out." def setValue(self, v): if '$topic' in v or '${topic}' in v: registry.String.setValue(self, v) else: self.error() Topic = conf.registerPlugin('Topic') conf.registerChannelValue(Topic, 'separator', registry.StringSurroundedBySpaces(' || ', """Determines what separator is used between individually added topics in the channel topic.""")) conf.registerChannelValue(Topic, 'format', TopicFormat('$topic ($nick)', """Determines what format is used to add topics in the topic. All the standard substitutes apply, in addition to "$topic" for the topic itself.""")) conf.registerChannelValue(Topic, 'recognizeTopiclen', registry.Boolean(True, """Determines whether the bot will recognize the TOPICLEN value sent to it by the server and thus refuse to send TOPICs longer than the TOPICLEN. These topics are likely to be truncated by the server anyway, so this defaults to True.""")) conf.registerChannelValue(Topic, 'default', registry.String('', """Determines what the default topic for the channel is. This is used by the default command to set this topic.""")) conf.registerGroup(Topic, 'undo') conf.registerChannelValue(Topic.undo, 'max', registry.NonNegativeInteger(10, """Determines the number of previous topics to keep around in case the undo command is called.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ���������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ShrinkUrl/���������������������������������������������������������0000755�0000000�0000000�00000000000�11206611405�015302� 5����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ShrinkUrl/test.py��������������������������������������������������0000644�0000000�0000000�00000013406�11206611405�016637� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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 ShrinkUrlTestCase(ChannelPluginTestCase): plugins = ('ShrinkUrl',) if network: def testTinyurl(self): try: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(False) self.assertRegexp( 'shrinkurl tiny http://sourceforge.net/tracker/?' 'func=add&group_id=58965&atid=489447', r'http://tinyurl.com/rqac') conf.supybot.plugins.ShrinkUrl.default.setValue('tiny') conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) self.assertRegexp( 'shrinkurl tiny http://sourceforge.net/tracker/?' 'func=add&group_id=58965&atid=489447', r'http://tinyurl.com/rqac') finally: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(False) def testTinysnarf(self): try: conf.supybot.snarfThrottle.setValue(1) conf.supybot.plugins.ShrinkUrl.default.setValue('tiny') conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) self.assertSnarfRegexp( 'http://sourceforge.net/tracker/?func=add&' 'group_id=58965&atid=489447', r'http://tinyurl.com/rqac.* \(at') self.assertSnarfRegexp( 'http://www.urbandictionary.com/define.php?' 'term=all+your+base+are+belong+to+us', r'http://tinyurl.com/u479.* \(at') finally: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(False) def testLnurl(self): try: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(False) self.assertRegexp( 'shrinkurl ln http://sourceforge.net/tracker/?' 'func=add&group_id=58965&atid=489447', r'http://ln-s.net/25Z') conf.supybot.plugins.ShrinkUrl.default.setValue('ln') conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) self.assertRegexp( 'shrinkurl ln http://sourceforge.net/tracker/?' 'func=add&group_id=58965&atid=489447', r'http://ln-s.net/25Z') finally: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(False) def testLnsnarf(self): try: conf.supybot.snarfThrottle.setValue(1) conf.supybot.plugins.ShrinkUrl.default.setValue('ln') conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) self.assertSnarfRegexp( 'http://sourceforge.net/tracker/?func=add&' 'group_id=58965&atid=489447', r'http://ln-s.net/25Z.* \(at') self.assertSnarfRegexp( 'http://www.urbandictionary.com/define.php?' 'term=all+your+base+are+belong+to+us', r'http://ln-s.net/2\$K.* \(at') finally: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(False) def testNonSnarfing(self): tiny = conf.supybot.plugins.ShrinkUrl.shrinkSnarfer() snarf = conf.supybot.plugins.ShrinkUrl.nonSnarfingRegexp() try: conf.supybot.snarfThrottle.setValue(1) conf.supybot.plugins.ShrinkUrl.default.setValue('tiny') conf.supybot.plugins.ShrinkUrl.nonSnarfingRegexp.set('m/sf/') conf.supybot.plugins.ShrinkUrl.minimumLength.setValue(10) try: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) self.assertSnarfNoResponse('http://sf.net/', 2) self.assertSnarfRegexp('http://sourceforge.net/', r'http://tinyurl.com/7vm7.* \(at') finally: conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(tiny) finally: conf.supybot.plugins.ShrinkUrl.nonSnarfingRegexp.setValue(snarf) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ShrinkUrl/__init__.py����������������������������������������������0000644�0000000�0000000�00000004276�11206611405�017424� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # 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. ### """ Shrinks URLs using tinyurl.com and ln-s.net. """ import supybot import supybot.world as world __version__ = "%%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: ����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ShrinkUrl/README.txt�����������������������������������������������0000644�0000000�0000000�00000000117�11206611405�016777� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������Insert a description of your plugin here, with any notes, etc. about using it. �������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������supybot-0.83.4.1.ds.orig/plugins/ShrinkUrl/plugin.py������������������������������������������������0000644�0000000�0000000�00000017246�11206611405�017164� 0����������������������������������������������������������������������������������������������������ustar ��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class CdbShrunkenUrlDB(object): def __init__(self, filename): self.tinyDb = conf.supybot.databases.types.cdb.connect( filename.replace('.db', '.Tiny.db')) self.lnDb = conf.supybot.databases.types.cdb.connect( filename.replace('.db', '.Ln.db')) def getTiny(self, url): return self.tinyDb[url] def setTiny(self, url, tinyurl): self.tinyDb[url] = tinyurl def getLn(self, url): return self.lnDb[url] def setLn(self, url, lnurl): self.lnDb[url] = lnurl def close(self): self.tinyDb.close() self.lnDb.close() def flush(self): self.tinyDb.flush() self.lnDb.flush() ShrunkenUrlDB = plugins.DB('ShrinkUrl', {'cdb': CdbShrunkenUrlDB}) class ShrinkUrl(callbacks.PluginRegexp): regexps = ['shrinkSnarfer'] def __init__(self, irc): self.__parent = super(ShrinkUrl, self) self.__parent.__init__(irc) self.db = ShrunkenUrlDB() def die(self): self.db.close() def callCommand(self, command, irc, msg, *args, **kwargs): try: self.__parent.callCommand(command, irc, msg, *args, **kwargs) except utils.web.Error, e: irc.error(str(e)) def _outFilterThread(self, irc, msg): (channel, text) = msg.args for m in utils.web.httpUrlRe.finditer(text): url = m.group(1) if len(url) > self.registryValue('minimumLength', channel): cmd = self.registryValue('default', channel) try: if cmd == 'ln': (shortUrl, _) = self._getLnUrl(url) elif cmd == 'tiny': shortUrl = self._getTinyUrl(url) text = text.replace(url, shortUrl) except utils.web.Error: pass newMsg = ircmsgs.privmsg(channel, text, msg=msg) newMsg.tag('shrunken') irc.queueMsg(newMsg) def outFilter(self, irc, msg): channel = msg.args[0] if msg.command == 'PRIVMSG' and irc.isChannel(channel): if not msg.shrunken: if self.registryValue('outFilter', channel): if utils.web.httpUrlRe.search(msg.args[1]): self._outFilterThread(irc, msg) return None return msg def shrinkSnarfer(self, irc, msg, match): r"https?://[^\])>\s]{13,}" channel = msg.args[0] if not irc.isChannel(channel): return if self.registryValue('shrinkSnarfer', channel): url = match.group(0) r = self.registryValue('nonSnarfingRegexp', channel) if r and r.search(url) is not None: self.log.debug('Matched nonSnarfingRegexp: %u', url) return minlen = self.registryValue('minimumLength', channel) cmd = self.registryValue('default', channel) if len(url) >= minlen: shorturl = None if cmd == 'tiny': shorturl = self._getTinyUrl(url) elif cmd == 'ln': (shorturl, _) = self._getLnUrl(url) if shorturl is None: self.log.info('Couldn\'t get shorturl for %u', url) return if self.registryValue('shrinkSnarfer.showDomain', channel): domain = ' (at %s)' % utils.web.getDomain(url) else: domain = '' if self.registryValue('bold'): s = format('%u%s', ircutils.bold(shorturl), domain) else: s = format('%u%s', shorturl, domain) m = irc.reply(s, prefixNick=False) m.tag('shrunken') shrinkSnarfer = urlSnarfer(shrinkSnarfer) def _getLnUrl(self, url): url = utils.web.urlquote(url) try: return (self.db.getLn(url), '200') except KeyError: text = utils.web.getUrl('http://ln-s.net/home/api.jsp?url=' + url) (code, lnurl) = text.split(None, 1) lnurl = lnurl.strip() if code == '200': self.db.setLn(url, lnurl) else: lnurl = None return (lnurl, code) def ln(self, irc, msg, args, url): """ Returns an ln-s.net version of . """ if len(url) < 17: irc.error('Stop being a lazy-biotch and type the URL yourself.') return (lnurl, error) = self._getLnUrl(url) if lnurl is not None: domain = utils.web.getDomain(url) m = irc.reply(lnurl) m.tag('shrunken') else: irc.error(error) ln = thread(wrap(ln, ['url'])) _tinyRe = re.compile(r'
(http://tinyurl\.com/\w+)') def _getTinyUrl(self, url): try: return self.db.getTiny(url) except KeyError: s = utils.web.getUrl('http://tinyurl.com/create.php?url=' + url) m = self._tinyRe.search(s) if m is None: tinyurl = None else: tinyurl = m.group(1) self.db.setTiny(url, tinyurl) return tinyurl def tiny(self, irc, msg, args, url): """ Returns a TinyURL.com version of """ if len(url) < 20: irc.error('Stop being a lazy-biotch and type the URL yourself.') return tinyurl = self._getTinyUrl(url) if tinyurl is not None: m = irc.reply(tinyurl) if m is not None: m.tag('shrunken') else: s = 'Could not parse the TinyURL.com results page.' irc.errorPossibleBug(s) tiny = thread(wrap(tiny, ['url'])) Class = ShrinkUrl # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/ShrinkUrl/config.py0000644000000000000000000000750211206611405017125 0ustar ### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('ShrinkUrl', True) if yn("""This plugin offers a snarfer that will go retrieve a shorter version of long URLs that are sent to the channel. Would you like this snarfer to be enabled?""", default=False): conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) class ShrinkService(registry.OnlySomeStrings): validStrings = ('ln', 'tiny') ShrinkUrl = conf.registerPlugin('ShrinkUrl') conf.registerChannelValue(ShrinkUrl, 'shrinkSnarfer', registry.Boolean(False, """Determines whether the shrink snarfer is enabled. This snarfer will watch for URLs in the channel, and if they're sufficiently long (as determined by supybot.plugins.ShrinkUrl.minimumLength) it will post a smaller URL from either ln-s.net or tinyurl.com, as denoted in supybot.plugins.ShrinkUrl.default.""")) conf.registerChannelValue(ShrinkUrl.shrinkSnarfer, 'showDomain', registry.Boolean(True, """Determines whether the snarfer will show the domain of the URL being snarfed along with the shrunken URL.""")) conf.registerChannelValue(ShrinkUrl, 'minimumLength', registry.PositiveInteger(48, """The minimum length a URL must be before the bot will shrink it.""")) conf.registerChannelValue(ShrinkUrl, 'nonSnarfingRegexp', registry.Regexp(None, """Determines what URLs are to be snarfed; URLs matching the regexp given will not be snarfed. Give the empty string if you have no URLs that you'd like to exclude from being snarfed.""")) conf.registerChannelValue(ShrinkUrl, 'outFilter', registry.Boolean(False, """Determines whether the bot will shrink the URLs of outgoing messages if those URLs are longer than supybot.plugins.ShrinkUrl.minimumLength.""")) conf.registerChannelValue(ShrinkUrl, 'default', ShrinkService('ln', """Determines what website the bot will use when shrinking a URL.""")) conf.registerGlobalValue(ShrinkUrl, 'bold', registry.Boolean(True, """Determines whether this plugin will bold certain portions of its replies.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Google/0000755000000000000000000000000011206611405014575 5ustar supybot-0.83.4.1.ds.orig/plugins/Google/test.py0000644000000000000000000001356511206611405016140 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 GoogleTestCase(ChannelPluginTestCase): plugins = ('Google',) if network: def testCalcHandlesMultiplicationSymbol(self): self.assertNotRegexp('google calc seconds in a century', r'215') def testCalc(self): self.assertNotRegexp('google calc e^(i*pi)+1', r'didn\'t') def testHtmlHandled(self): self.assertNotRegexp('google calc ' 'the speed of light ' 'in microns / fortnight', '') self.assertNotRegexp('google calc ' 'the speed of light ' 'in microns / fortnight', '×') def testSearch(self): self.assertNotError('google foo') self.assertRegexp('google dupa', r'dupa') # Unicode check self.assertNotError('google ae') def testFight(self): self.assertRegexp('fight supybot moobot', r'.*supybot.*: \d+') def testTranslate(self): self.assertRegexp('translate en es hello world', 'mundo') def testCalcDoesNotHaveExtraSpaces(self): self.assertNotRegexp('google calc 1000^2', r'\s+,\s+') def testGroupsSnarfer(self): orig = conf.supybot.plugins.Google.groupsSnarfer() try: conf.supybot.plugins.Google.groupsSnarfer.setValue(True) # This should work, and does work in practice, but is failing # in the tests. #self.assertSnarfRegexp( # 'http://groups.google.com/groups?dq=&hl=en&lr=lang_en&' # 'ie=UTF-8&oe=UTF-8&selm=698f09f8.0310132012.738e22fc' # '%40posting.google.com', # r'comp\.lang\.python.*question: usage of __slots__') self.assertSnarfRegexp( 'http://groups.google.com/groups?selm=ExDm.8bj.23' '%40gated-at.bofh.it&oe=UTF-8&output=gplain', r'linux\.kernel.*NFS client freezes') self.assertSnarfRegexp( 'http://groups.google.com/groups?q=kernel+hot-pants&' 'hl=en&lr=&ie=UTF-8&oe=UTF-8&selm=1.5.4.32.199703131' '70853.00674d60%40adan.kingston.net&rnum=1', r'Madrid Bluegrass Ramble') self.assertSnarfRegexp( 'http://groups.google.com/groups?selm=1.5.4.32.19970' '313170853.00674d60%40adan.kingston.net&oe=UTF-8&' 'output=gplain', r'Madrid Bluegrass Ramble') self.assertSnarfRegexp( 'http://groups.google.com/groups?dq=&hl=en&lr=&' 'ie=UTF-8&threadm=mailman.1010.1069645289.702.' 'python-list%40python.org&prev=/groups%3Fhl%3Den' '%26lr%3D%26ie%3DUTF-8%26group%3Dcomp.lang.python', r'comp\.lang\.python.*What exactly are bound') # Test for Bug #1002547 self.assertSnarfRegexp( 'http://groups.google.com/groups?q=supybot+is+the&' 'hl=en&lr=&ie=UTF-8&c2coff=1&selm=1028329672' '%40freshmeat.net&rnum=9', r'fm\.announce.*SupyBot') finally: conf.supybot.plugins.Google.groupsSnarfer.setValue(orig) def testConfig(self): orig = conf.supybot.plugins.Google.groupsSnarfer() try: conf.supybot.plugins.Google.groupsSnarfer.setValue(False) self.assertSnarfNoResponse( 'http://groups.google.com/groups?dq=&hl=en&lr=lang_en&' 'ie=UTF-8&oe=UTF-8&selm=698f09f8.0310132012.738e22fc' '%40posting.google.com') conf.supybot.plugins.Google.groupsSnarfer.setValue(True) self.assertSnarfNotError( 'http://groups.google.com/groups?dq=&hl=en&lr=lang_en&' 'ie=UTF-8&oe=UTF-8&selm=698f09f8.0310132012.738e22fc' '%40posting.google.com') finally: conf.supybot.plugins.Google.groupsSnarfer.setValue(orig) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Google/__init__.py0000644000000000000000000000447611206611405016721 0ustar ### # 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. ### """ Accesses Google for various things. """ 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: supybot-0.83.4.1.ds.orig/plugins/Google/README.txt0000644000000000000000000000016111206611405016271 0ustar This is a simple plugin to provide access to the Google services we all know and love from our favorite IRC bot. supybot-0.83.4.1.ds.orig/plugins/Google/plugin.py0000644000000000000000000003714411206611405016456 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2008-2009, James Vega # 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 cgi import time import socket import urllib import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks try: simplejson = utils.python.universalImport('json', 'simplejson', 'local.simplejson') # The 3rd party simplejson module was included in Python 2.6 and renamed to # json. Unfortunately, this conflicts with the 3rd party json module. # Luckily, the 3rd party json module has a different interface so we test # to make sure we aren't using it. if hasattr(simplejson, 'read'): raise ImportError except ImportError: raise callbacks.Error, \ 'You need Python2.6 or the simplejson module installed to use ' \ 'this plugin. Download the module at ' \ '.' class Google(callbacks.PluginRegexp): threaded = True callBefore = ['Web'] regexps = ['googleSnarfer', 'googleGroups'] _colorGoogles = {} def _getColorGoogle(self, m): s = m.group(1) ret = self._colorGoogles.get(s) if not ret: L = list(s) L[0] = ircutils.mircColor(L[0], 'blue')[:-1] L[1] = ircutils.mircColor(L[1], 'red')[:-1] L[2] = ircutils.mircColor(L[2], 'yellow')[:-1] L[3] = ircutils.mircColor(L[3], 'blue')[:-1] L[4] = ircutils.mircColor(L[4], 'green')[:-1] L[5] = ircutils.mircColor(L[5], 'red') ret = ''.join(L) self._colorGoogles[s] = ret return ircutils.bold(ret) _googleRe = re.compile(r'\b(google)\b', re.I) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG' and \ self.registryValue('colorfulFilter', msg.args[0]): s = msg.args[1] s = re.sub(self._googleRe, self._getColorGoogle, s) msg = ircmsgs.privmsg(msg.args[0], s, msg=msg) return msg _gsearchUrl = 'http://ajax.googleapis.com/ajax/services/search/web' def search(self, query, channel, options={}): """Perform a search using Google's AJAX API. search("search phrase", options={}) Valid options are: smallsearch - True/False (Default: False) filter - {active,moderate,off} (Default: "moderate") language - Restrict search to documents in the given language (Default: "lang_en") """ ref = self.registryValue('referer') if not ref: ref = 'http://%s/%s' % (dynamic.irc.server, dynamic.irc.nick) headers = utils.web.defaultHeaders headers['Referer'] = ref opts = {'q': query, 'v': '1.0'} for (k, v) in options.iteritems(): if k == 'smallsearch': if v: opts['rsz'] = 'small' else: opts['rsz'] = 'large' elif k == 'filter': opts['safe'] = v elif k == 'language': opts['lr'] = v defLang = self.registryValue('defaultLanguage', channel) if 'lr' not in opts and defLang: opts['lr'] = defLang if 'safe' not in opts: opts['safe'] = self.registryValue('searchFilter', dynamic.channel) if 'rsz' not in opts: opts['rsz'] = 'large' fd = utils.web.getUrlFd('%s?%s' % (self._gsearchUrl, urllib.urlencode(opts)), headers) json = simplejson.load(fd) fd.close() if json['responseStatus'] != 200: raise callbacks.Error, 'We broke The Google!' return json def formatData(self, data, bold=True, max=0): if isinstance(data, basestring): return data results = [] if max: data = data[:max] for result in data: title = utils.web.htmlToText(result['titleNoFormatting']\ .encode('utf-8')) url = result['unescapedUrl'].encode('utf-8') if title: if bold: title = ircutils.bold(title) results.append(format('%s: %u', title, url)) else: results.append(url) if not results: return format('No matches found.') else: return format('; '.join(results)) def lucky(self, irc, msg, args, text): """ Does a google search, but only returns the first result. """ data = self.search(text, msg.args[0], {'smallsearch': True}) if data['responseData']['results']: url = data['responseData']['results'][0]['unescapedUrl'] irc.reply(url.encode('utf-8')) else: irc.reply('Google found nothing.') lucky = wrap(lucky, ['text']) def google(self, irc, msg, args, optlist, text): """ [--{filter,language} ] Searches google.com for the given string. As many results as can fit are included. --language accepts a language abbreviation; --filter accepts a filtering level ('active', 'moderate', 'off'). """ if 'language' in optlist and optlist['language'].lower() not in \ conf.supybot.plugins.Google.safesearch.validStrings: irc.errorInvalid('language') data = self.search(text, msg.args[0], dict(optlist)) if data['responseStatus'] != 200: irc.reply('We broke The Google!') return bold = self.registryValue('bold', msg.args[0]) max = self.registryValue('maximumResults', msg.args[0]) irc.reply(self.formatData(data['responseData']['results'], bold=bold, max=max)) google = wrap(google, [getopts({'language':'something', 'filter':''}), 'text']) def cache(self, irc, msg, args, url): """ Returns a link to the cached version of if it is available. """ data = self.search(url, msg.args[0], {'smallsearch': True}) if data['responseData']['results']: m = data['responseData']['results'][0] if m['cacheUrl']: url = m['cacheUrl'].encode('utf-8') irc.reply(url) return irc.error('Google seems to have no cache for that site.') cache = wrap(cache, ['url']) def fight(self, irc, msg, args): """ [ ...] Returns the results of each search, in order, from greatest number of results to least. """ channel = msg.args[0] results = [] for arg in args: data = self.search(arg, channel, {'smallsearch': True}) count = data['responseData']['cursor']['estimatedResultCount'] results.append((int(count), arg)) results.sort() results.reverse() if self.registryValue('bold', msg.args[0]): bold = ircutils.bold else: bold = repr s = ', '.join([format('%s: %i', bold(s), i) for (i, s) in results]) irc.reply(s) _gtranslateUrl='http://ajax.googleapis.com/ajax/services/language/translate' _transLangs = {'Arabic': 'ar', 'Bulgarian': 'bg', 'Chinese_simplified': 'zh-CN', 'Chinese_traditional': 'zh-TW', 'Croatian': 'hr', 'Czech': 'cs', 'Danish': 'da', 'Dutch': 'nl', 'English': 'en', 'Finnish': 'fi', 'French': 'fr', 'German': 'de', 'Greek': 'el', 'Hindi': 'hi', 'Italian': 'it', 'Japanese': 'ja', 'Korean': 'ko', 'Norwegian': 'no', 'Polish': 'pl', 'Portuguese': 'pt', 'Romanian': 'ro', 'Russian': 'ru', 'Spanish': 'es', 'Swedish': 'sv'} def translate(self, irc, msg, args, fromLang, toLang, text): """ [to] Returns translated from into . Beware that translating to or from languages that use multi-byte characters may result in some very odd results. """ channel = msg.args[0] ref = self.registryValue('referer') if not ref: ref = 'http://%s/%s' % (dynamic.irc.server, dynamic.irc.nick) headers = utils.web.defaultHeaders headers['Referer'] = ref opts = {'q': text, 'v': '1.0'} lang = conf.supybot.plugins.Google.defaultLanguage if fromLang.capitalize() in self._transLangs: fromLang = self._transLangs[fromLang.capitalize()] elif lang.normalize('lang_'+fromLang)[5:] \ not in self._transLangs.values(): irc.errorInvalid('from language', fromLang, format('Valid languages are: %L', self._transLangs.keys())) else: fromLang = lang.normalize('lang_'+fromLang)[5:] if toLang.capitalize() in self._transLangs: toLang = self._transLangs[toLang.capitalize()] elif lang.normalize('lang_'+toLang)[5:] \ not in self._transLangs.values(): irc.errorInvalid('to language', toLang, format('Valid languages are: %L', self._transLangs.keys())) else: toLang = lang.normalize('lang_'+toLang)[5:] opts['langpair'] = '%s|%s' % (fromLang, toLang) fd = utils.web.getUrlFd('%s?%s' % (self._gtranslateUrl, urllib.urlencode(opts)), headers) json = simplejson.load(fd) fd.close() if json['responseStatus'] != 200: raise callbacks.Error, 'We broke The Google!' irc.reply(json['responseData']['translatedText'].encode('utf-8')) translate = wrap(translate, ['something', 'to', 'something', 'text']) def googleSnarfer(self, irc, msg, match): r"^google\s+(.*)$" if not self.registryValue('searchSnarfer', msg.args[0]): return searchString = match.group(1) data = self.search(searchString, msg.args[0], {'smallsearch': True}) if data['responseData']['results']: url = data['responseData']['results'][0]['unescapedUrl'] irc.reply(url.encode('utf-8'), prefixNick=False) googleSnarfer = urlSnarfer(googleSnarfer) _ggThread = re.compile(r'Subject: ([^<]+)', re.I) _ggGroup = re.compile(r'Google Groups :\s*([^<]+)', re.I) _ggThreadm = re.compile(r'src="(/group[^"]+)">', re.I) _ggSelm = re.compile(r'selm=[^&]+', re.I) _threadmThread = re.compile(r'TITLE="([^"]+)">', re.I) _threadmGroup = re.compile(r'class=groupname[^>]+>([^<]+)<', re.I) def googleGroups(self, irc, msg, match): r"http://groups.google.[\w.]+/\S+\?(\S+)" if not self.registryValue('groupsSnarfer', msg.args[0]): return queries = cgi.parse_qsl(match.group(1)) queries = [q for q in queries if q[0] in ('threadm', 'selm')] if not queries: return queries.append(('hl', 'en')) url = 'http://groups.google.com/groups?' + urllib.urlencode(queries) text = utils.web.getUrl(url) mThread = None mGroup = None if 'threadm=' in url: path = self._ggThreadm.search(text) if path is not None: url = 'http://groups-beta.google.com' + path.group(1) text = utils.web.getUrl(url) mThread = self._threadmThread.search(text) mGroup = self._threadmGroup.search(text) else: mThread = self._ggThread.search(text) mGroup = self._ggGroup.search(text) if mThread and mGroup: irc.reply(format('Google Groups: %s, %s', mGroup.group(1), mThread.group(1)), prefixNick=False) else: self.log.debug('Unable to snarf. %s doesn\'t appear to be a ' 'proper Google Groups page.', match.group(1)) googleGroups = urlSnarfer(googleGroups) def _googleUrl(self, s): s = s.replace('+', '%2B') s = s.replace(' ', '+') url = r'http://google.com/search?q=' + s return url _calcRe = re.compile(r'(.*?)', re.I) _calcSupRe = re.compile(r'(.*?)', re.I) _calcFontRe = re.compile(r'(.*?)') _calcTimesRe = re.compile(r'&(?:times|#215);') def calc(self, irc, msg, args, expr): """ Uses Google's calculator to calculate the value of . """ url = self._googleUrl(expr) html = utils.web.getUrl(url) match = self._calcRe.search(html) if match is not None: s = match.group(1) s = self._calcSupRe.sub(r'^(\1)', s) s = self._calcFontRe.sub(r',', s) s = self._calcTimesRe.sub(r'*', s) irc.reply(s) else: irc.reply('Google\'s calculator didn\'t come up with anything.') calc = wrap(calc, ['text']) _phoneRe = re.compile(r'Phonebook.*?(.*?) Looks up on Google. """ url = self._googleUrl(phonenumber) html = utils.web.getUrl(url) m = self._phoneRe.search(html) if m is not None: s = m.group(1) s = s.replace('', '') s = s.replace('', '') s = utils.web.htmlToText(s) irc.reply(s) else: irc.reply('Google\'s phonebook didn\'t come up with anything.') phonebook = wrap(phonebook, ['text']) Class = Google # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Google/config.py0000644000000000000000000001214611206611405016420 0ustar ### # Copyright (c) 2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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): from supybot.questions import output, yn conf.registerPlugin('Google', True) output("""The Google plugin has the functionality to watch for URLs that match a specific pattern. (We call this a snarfer) When supybot sees such a URL, it will parse the web page for information and reply with the results. Google has two available snarfers: Google Groups link snarfing and a google search snarfer.""") if yn('Do you want the Google Groups link snarfer enabled by ' 'default?'): conf.supybot.plugins.Google.groupsSnarfer.setValue(True) if yn('Do you want the Google search snarfer enabled by default?'): conf.supybot.plugins.Google.searchSnarfer.setValue(True) class Language(registry.OnlySomeStrings): validStrings = ['lang_' + s for s in 'ar bg ca zh-CN zh-TW hr cs da nl en ' 'et fi fr de el iw hu is id it ja ko ' 'lv lt no pl pt ro ru sr sk sl es sv ' 'tr'.split()] validStrings.append('') def normalize(self, s): if s and not s.startswith('lang_'): s = 'lang_' + s if not s.endswith('CN') or s.endswith('TW'): s = s.lower() else: s = s.lower()[:-2] + s[-2:] return s class NumSearchResults(registry.PositiveInteger): """Value must be 1 <= n <= 8""" def setValue(self, v): if v > 8: self.error() super(NumSearchResults, self).setValue(v) class SafeSearch(registry.OnlySomeStrings): validStrings = ['active', 'moderate', 'off'] Google = conf.registerPlugin('Google') conf.registerGlobalValue(Google, 'referer', registry.String('', """Determines the URL that will be sent to Google for the Referer field of the search requests. If this value is empty, a Referer will be generated in the following format: http://$server/$botName""")) conf.registerChannelValue(Google, 'groupsSnarfer', registry.Boolean(False, """Determines whether the groups snarfer is enabled. If so, URLs at groups.google.com will be snarfed and their group/title messaged to the channel.""")) conf.registerChannelValue(Google, 'searchSnarfer', registry.Boolean(False, """Determines whether the search snarfer is enabled. If so, messages (even unaddressed ones) beginning with the word 'google' will result in the first URL Google returns being sent to the channel.""")) conf.registerChannelValue(Google, 'colorfulFilter', registry.Boolean(False, """Determines whether the word 'google' in the bot's output will be made colorful (like Google's logo).""")) conf.registerChannelValue(Google, 'bold', registry.Boolean(True, """Determines whether results are bolded.""")) conf.registerChannelValue(Google, 'maximumResults', NumSearchResults(8, """Determines the maximum number of results returned from the google command.""")) conf.registerChannelValue(Google, 'defaultLanguage', Language('lang_en', """Determines what default language is used in searches. If left empty, no specific language will be requested.""")) conf.registerChannelValue(Google, 'searchFilter', SafeSearch('moderate', """Determines what level of search filtering to use by default. 'active' - most filtering, 'moderate' - default filtering, 'off' - no filtering""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Unix/0000755000000000000000000000000011206611405014304 5ustar supybot-0.83.4.1.ds.orig/plugins/Unix/test.py0000644000000000000000000000573611206611405015650 0ustar ### # 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 os from supybot.test import * if os.name == 'posix': class UnixTestCase(PluginTestCase): plugins = ('Unix',) if utils.findBinaryInPath('aspell') is not None or \ utils.findBinaryInPath('ispell') is not None: def testSpell(self): self.assertRegexp('spell Strike', 'correctly') # ispell won't find any results. aspell will make some # suggestions. self.assertRegexp('spell z0opadfnaf83nflafl230kasdf023hflasdf', 'not find|Possible spellings') self.assertNotError('spell Strizzike') self.assertError('spell foo bar baz') self.assertError('spell -') self.assertError('spell .') self.assertError('spell ?') self.assertNotError('spell whereever') self.assertNotRegexp('spell foo', 'whatever') def testErrno(self): self.assertRegexp('errno 12', '^ENOMEM') self.assertRegexp('errno ENOMEM', '#12') def testProgstats(self): self.assertNotError('progstats') def testCrypt(self): self.assertNotError('crypt jemfinch') if utils.findBinaryInPath('fortune') is not None: def testFortune(self): self.assertNotError('fortune') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Unix/__init__.py0000644000000000000000000000474011206611405016422 0ustar ### # 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. ### """ Provides commands available only on Unix. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} # This is a url where the most recent plugin package can be downloaded. __url__ = '' # 'http://supybot.com/Members/yourname/Unix/download' 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: supybot-0.83.4.1.ds.orig/plugins/Unix/README.txt0000644000000000000000000000011711206611405016001 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Unix/plugin.py0000644000000000000000000002323511206611405016161 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2008, James Vega # 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 re import pwd import sys import crypt import errno import popen2 import random import select import struct import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks def progstats(): pw = pwd.getpwuid(os.getuid()) response = format('Process ID %i running as user %q and as group %q ' 'from directory %q with the command line %q. ' 'Running on Python %s.', os.getpid(), pw[0], pw[3], os.getcwd(), ' '.join(sys.argv), sys.version.translate(utils.str.chars, '\r\n')) return response class TimeoutError(IOError): pass def pipeReadline(fd, timeout=2): (r, _, _) = select.select([fd], [], [], timeout) if r: return r[0].readline() else: raise TimeoutError class Unix(callbacks.Plugin): def errno(self, irc, msg, args, s): """ Returns the number of an errno code, or the errno code of a number. """ try: i = int(s) name = errno.errorcode[i] except ValueError: name = s.upper() try: i = getattr(errno, name) except AttributeError: irc.reply('I can\'t find the errno number for that code.') return except KeyError: name = '(unknown)' irc.reply(format('%s (#%i): %s', name, i, os.strerror(i))) errno = wrap(errno, ['something']) def progstats(self, irc, msg, args): """takes no arguments Returns various unix-y information on the running supybot process. """ irc.reply(progstats()) def pid(self, irc, msg, args): """takes no arguments Returns the current pid of the process for this Supybot. """ irc.reply(format('%i', os.getpid()), private=True) pid = wrap(pid, [('checkCapability', 'owner')]) _cryptre = re.compile(r'[./0-9A-Za-z]') def crypt(self, irc, msg, args, password, salt): """ [] Returns the resulting of doing a crypt() on If is not given, uses a random salt. If running on a glibc2 system, prepending '$1$' to your salt will cause crypt to return an MD5sum based crypt rather than the standard DES based crypt. """ def makeSalt(): s = '\x00' while self._cryptre.sub('', s) != '': s = struct.pack(' Returns the result of passing to aspell/ispell. The results shown are sorted from best to worst in terms of being a likely match for the spelling of . """ # We are only checking the first word spellCmd = self.registryValue('spell.command') if not spellCmd: irc.error('A spell checking command doesn\'t seem to be ' 'installed on this computer. If one is installed, ' 'reconfigure supybot.plugins.Unix.spell.command ' 'appropriately.', Raise=True) if word and not word[0].isalpha(): irc.error(' must begin with an alphabet character.') return if ' ' in word: irc.error('Spaces aren\'t allowed in the word.') return inst = popen2.Popen4([spellCmd, '-a']) (r, w) = (inst.fromchild, inst.tochild) try: s = r.readline() # Banner, hopefully. if 'sorry' in s.lower(): irc.error(s) return w.write(word) w.write('\n') w.flush() try: line = pipeReadline(r) # aspell puts extra whitespace, ignore it while not line.strip('\r\n'): line = pipeReadline(r) # cache an extra line in case aspell's first line says the word # is spelled correctly, but subsequent lines offer spelling # suggestions line2 = pipeReadline(r) except TimeoutError: irc.error('The spell command timed out.') return finally: r.close() w.close() inst.wait() # parse the output # aspell will sometimes list spelling suggestions after a '*' or '+' # line for complex words. if line[0] in '*+' and line2.strip('\r\n'): line = line2 if line[0] in '*+': resp = format('%q may be spelled correctly.', word) elif line[0] == '#': resp = format('I could not find an alternate spelling for %q',word) elif line[0] == '&': matches = line.split(':')[1].strip() resp = format('Possible spellings for %q: %L.', word, matches.split(', ')) else: resp = 'Something unexpected was seen in the [ai]spell output.' irc.reply(resp) spell = wrap(spell, ['something']) def fortune(self, irc, msg, args): """takes no arguments Returns a fortune from the *nix fortune program. """ fortuneCmd = self.registryValue('fortune.command') if fortuneCmd: args = [fortuneCmd] if self.registryValue('fortune.short'): args.append('-s') if self.registryValue('fortune.equal'): args.append('-e') if self.registryValue('fortune.offensive'): args.append('-a') args.extend(self.registryValue('fortune.files')) inst = popen2.Popen4(args) (r, w) = (inst.fromchild, inst.tochild) try: lines = r.readlines() lines = map(str.rstrip, lines) lines = filter(None, lines) if lines: irc.replies(lines, joiner=' ') else: irc.error('It seems the configured fortune command was ' 'not available.') finally: w.close() r.close() inst.wait() else: irc.error('I couldn\'t find the fortune command on this system. ' 'If it is installed on this system, reconfigure the ' 'supybot.plugins.Unix.fortune.command configuration ' 'variable appropriately.') def wtf(self, irc, msg, args, _, something): """[is] Returns wtf is. 'wtf' is a *nix command that first appeared in NetBSD 1.5. In most *nices, it's available in some sort of 'bsdgames' package. """ wtfCmd = self.registryValue('wtf.command') if wtfCmd: def commandError(): irc.error('It seems the configured wtf command ' 'was not available.') something = something.rstrip('?') inst = popen2.Popen4([wtfCmd, something]) (r, w) = (inst.fromchild, inst.tochild) try: response = utils.str.normalizeWhitespace(r.readline().strip()) if response: irc.reply(response) else: commandError() finally: r.close() w.close() inst.wait() else: irc.error('I couldn\'t find the wtf command on this system. ' 'If it is installed on this system, reconfigure the ' 'supybot.plugins.Unix.wtf.command configuration ' 'variable appropriately.') wtf = wrap(wtf, [optional(('literal', ['is'])), 'something']) Class = Unix # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Unix/config.py0000644000000000000000000001040111206611405016117 0ustar ### # 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.conf as conf import supybot.utils as utils import supybot.registry as registry import plugin progstats = plugin.progstats 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 output, expect, anything, something, yn conf.registerPlugin('Unix', True) output("""The "progstats" command can reveal potentially sensitive information about your machine. Here's an example of its output: %s\n""" % progstats()) if yn('Would you like to disable this command for non-owner users?', default=True): conf.supybot.commands.disabled().add('Unix.progstats') Unix = conf.registerPlugin('Unix') conf.registerGroup(Unix, 'fortune') conf.registerGlobalValue(Unix.fortune, 'command', registry.String(utils.findBinaryInPath('fortune') or '', """Determines what command will be called for the fortune command.""")) conf.registerGlobalValue(Unix.fortune, 'short', registry.Boolean(True, """Determines whether only short fortunes will be used if possible. This sends the -s option to the fortune program.""")) conf.registerGlobalValue(Unix.fortune, 'equal', registry.Boolean(True, """Determines whether fortune will give equal weight to the different fortune databases. If false, then larger databases will be given more weight. This sends the -e option to the fortune program.""")) conf.registerGlobalValue(Unix.fortune, 'offensive', registry.Boolean(False, """Determines whether fortune will retrieve offensive fortunes along with the normal fortunes. This sends the -o option to the fortune program.""")) conf.registerGlobalValue(Unix.fortune, 'files', registry.SpaceSeparatedListOfStrings([], """Determines what specific file (if any) will be used with the fortune command; if none is given, the system-wide default will be used. Do note that this fortune file must be placed with the rest of your system's fortune files.""")) conf.registerGroup(Unix, 'spell') conf.registerGlobalValue(Unix.spell, 'command', registry.String(utils.findBinaryInPath('aspell') or utils.findBinaryInPath('ispell') or '', """Determines what command will be called for the spell command.""")) conf.registerGroup(Unix, 'wtf') conf.registerGlobalValue(Unix.wtf, 'command', registry.String(utils.findBinaryInPath('wtf') or '', """Determines what command will be called for the wtf command.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Lart/0000755000000000000000000000000011206611405014263 5ustar supybot-0.83.4.1.ds.orig/plugins/Lart/test.py0000644000000000000000000000475311206611405015625 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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 LartTestCase(ChannelPluginTestCase): plugins = ('Lart', 'User') def setUp(self): ChannelPluginTestCase.setUp(self) # Create a valid user to use self.prefix = 'mf!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) m = self.irc.takeMsg() # Response to register. def testAdd(self): self.assertError('lart add foo') # needs $who self.assertNotError('lart add smacks $who') def testLart(self): self.assertError('lart foo') # no praises! self.assertNotError('lart add smacks $who') self.assertAction('lart foo', 'smacks foo') def testMeInReason(self): self.assertNotError('lart add makes $who sit by me') self.assertAction('lart foo', 'makes foo sit by me') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Lart/__init__.py0000644000000000000000000000464511206611405016405 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ This plugin keeps a database of larts, and larts with it. """ 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__ = "%%VERSION%%" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.strike # 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: supybot-0.83.4.1.ds.orig/plugins/Lart/README.txt0000644000000000000000000000011711206611405015760 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Lart/plugin.py0000644000000000000000000000734411206611405016143 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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 from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils class Lart(plugins.ChannelIdDatabasePlugin): _meRe = re.compile(r'\bme\b', re.I) _myRe = re.compile(r'\bmy\b', re.I) def _replaceFirstPerson(self, s, nick): s = self._meRe.sub(nick, s) s = self._myRe.sub('%s\'s' % nick, s) return s def addValidator(self, irc, text): if '$who' not in text: irc.error('Larts must contain $who.', Raise=True) def lart(self, irc, msg, args, channel, id, text): """[] [] [for ] Uses the Luser Attitude Readjustment Tool on (for , if given). If is given, uses that specific lart. is only necessary if the message isn't sent in the channel itself. """ if ' for ' in text: (target, reason) = map(str.strip, text.split(' for ', 1)) else: (target, reason) = (text, '') if id is not None: try: lart = self.db.get(channel, id) except KeyError: irc.error(format('There is no lart with id #%i.', id)) return else: lart = self.db.random(channel) if not lart: irc.error(format('There are no larts in my database ' 'for %s.', channel)) return text = lart.text if ircutils.strEqual(target, irc.nick): target = msg.nick reason = self._replaceFirstPerson('trying to dis me', irc.nick) else: target = self._replaceFirstPerson(target, msg.nick) reason = self._replaceFirstPerson(reason, msg.nick) if target.endswith('.'): target = target.rstrip('.') text = text.replace('$who', target) if reason: text += ' for ' + reason if self.registryValue('showIds', channel): text += format(' (#%i)', lart.id) irc.reply(text, action=True) lart = wrap(lart, ['channeldb', optional('id'), 'text']) Class = Lart # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Lart/config.py0000644000000000000000000000471611206611405016112 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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('Lart', True) Lart = conf.registerPlugin('Lart') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Lart, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) conf.registerChannelValue(Lart, 'showIds', registry.Boolean(False, """Determines whether the bot will show the ids of a lart when the lart is given.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Alias/0000755000000000000000000000000011206611405014412 5ustar supybot-0.83.4.1.ds.orig/plugins/Alias/test.py0000644000000000000000000001262411206611405015750 0ustar ### # 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 * import supybot.plugin as plugin Alias = plugin.loadPluginModule('Alias') class FunctionsTest(SupyTestCase): def testFindBiggestDollar(self): self.assertEqual(Alias.findBiggestDollar(''), 0) self.assertEqual(Alias.findBiggestDollar('foo'), 0) self.assertEqual(Alias.findBiggestDollar('$0'), 0) self.assertEqual(Alias.findBiggestDollar('$1'), 1) self.assertEqual(Alias.findBiggestDollar('$2'), 2) self.assertEqual(Alias.findBiggestDollar('$2 $10'), 10) self.assertEqual(Alias.findBiggestDollar('$3'), 3) self.assertEqual(Alias.findBiggestDollar('$3 $2 $1'), 3) self.assertEqual(Alias.findBiggestDollar('foo bar $1'), 1) self.assertEqual(Alias.findBiggestDollar('foo $2 $1'), 2) self.assertEqual(Alias.findBiggestDollar('foo $0 $1'), 1) self.assertEqual(Alias.findBiggestDollar('foo $1 $3'), 3) self.assertEqual(Alias.findBiggestDollar('$10 bar $1'), 10) class AliasTestCase(ChannelPluginTestCase): plugins = ('Alias', 'Filter', 'Utilities', 'Format', 'Reply') def testNoAliasWithNestedCommandName(self): self.assertError('alias add foo "[bar] baz"') def testDoesNotOverwriteCommands(self): # We don't have dispatcher commands anymore #self.assertError('alias add alias "echo foo bar baz"') self.assertError('alias add add "echo foo bar baz"') self.assertError('alias add remove "echo foo bar baz"') self.assertError('alias add lock "echo foo bar baz"') self.assertError('alias add unlock "echo foo bar baz"') def testAliasHelp(self): self.assertNotError('alias add slashdot foo') self.assertRegexp('help slashdot', "Alias for .*foo") def testRemove(self): self.assertNotError('alias add foo echo bar') self.assertResponse('foo', 'bar') self.assertNotError('alias remove foo') self.assertError('foo') def testDollars(self): self.assertNotError('alias add rot26 "rot13 [rot13 $1]"') self.assertResponse('rot26 foobar', 'foobar') def testMoreDollars(self): self.assertNotError('alias add rev "echo $3 $2 $1"') self.assertResponse('rev foo bar baz', 'baz bar foo') def testAllArgs(self): self.assertNotError('alias add swap "echo $2 $1 $*"') self.assertResponse('swap 1 2 3 4 5', '2 1 3 4 5') self.assertError('alias add foo "echo $1 @1 $*"') def testChannel(self): self.assertNotError('alias add channel echo $channel') self.assertResponse('alias channel', self.channel) def testNick(self): self.assertNotError('alias add sendingnick "rot13 [rot13 $nick]"') self.assertResponse('sendingnick', self.nick) def testAddRemoveAlias(self): cb = self.irc.getCallback('Alias') cb.addAlias(self.irc, 'foobar', 'echo sbbone', lock=True) self.assertResponse('foobar', 'sbbone') self.assertRaises(Alias.AliasError, cb.removeAlias, 'foobar') cb.removeAlias('foobar', evenIfLocked=True) self.failIf('foobar' in cb.aliases) self.assertError('foobar') def testOptionalArgs(self): self.assertNotError('alias add myrepr "repr @1"') self.assertResponse('myrepr foo', '"foo"') self.assertResponse('myrepr ""', '""') def testNoExtraSpaces(self): self.assertNotError('alias add foo "action takes $1\'s money"') self.assertResponse('foo bar', '\x01ACTION takes bar\'s money\x01') def testNoExtraQuotes(self): self.assertNotError('alias add myre "echo s/$1/$2/g"') self.assertResponse('myre foo bar', 's/foo/bar/g') def testSimpleAliasWithoutArgsImpliesDollarStar(self): self.assertNotError('alias add exo echo') self.assertResponse('exo foo bar baz', 'foo bar baz') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Alias/__init__.py0000644000000000000000000000457711206611405016540 0ustar ### # 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. ### """ Allows aliases for other commands. """ 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! from plugin import findBiggestDollar, AliasError # for the tests. if world.testing: import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Alias/README.txt0000644000000000000000000000164511206611405016116 0ustar This plugin allows the user to create various aliases to other commands or combinations of other commands (via nested commands). It is a good idea to always quote the commands that are being aliased so that any nested commands are not immediately run. Basic usage ----------- Add an alias, trout, which expects a word as an argument @alias add trout "action slaps $1 with a large trout" jamessan: The operation succeeded. @trout me * bot slaps me with a large trout Add an alias, lastfm, which expects a last.fm user and replies with their recently played items. @alias add lastfm "rss [format concat http://ws.audioscrobbler.com/1.0/user/ [format concat [urlquote $1] /recenttracks.rss]]" Note that if the nested commands being aliased hadn't been quoted, then those commands would have been run immediately, and @lastfm would always reply with the same information, the result of those commands. supybot-0.83.4.1.ds.orig/plugins/Alias/plugin.py0000644000000000000000000003131211206611405016262 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 new import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils import supybot.registry as registry import supybot.callbacks as callbacks # Copied from the old privmsgs.py. def getChannel(msg, args=()): """Returns the channel the msg came over or the channel given in args. If the channel was given in args, args is modified (the channel is removed). """ if args and ircutils.isChannel(args[0]): if conf.supybot.reply.requireChannelCommandsToBeSentInChannel(): if args[0] != msg.args[0]: s = 'Channel commands must be sent in the channel to which ' \ 'they apply; if this is not the behavior you desire, ' \ 'ask the bot\'s administrator to change the registry ' \ 'variable ' \ 'supybot.reply.requireChannelCommandsToBeSentInChannel ' \ 'to False.' raise callbacks.Error, s return args.pop(0) elif ircutils.isChannel(msg.args[0]): return msg.args[0] else: raise callbacks.Error, 'Command must be sent in a channel or ' \ 'include a channel in its arguments.' def getArgs(args, required=1, optional=0): if len(args) < required: raise callbacks.ArgumentError if len(args) < required + optional: ret = list(args) + ([''] * (required + optional - len(args))) elif len(args) >= required + optional: ret = list(args[:required + optional - 1]) ret.append(' '.join(args[required + optional - 1:])) if len(ret) == 1: return ret[0] else: return ret class AliasError(Exception): pass class RecursiveAlias(AliasError): pass dollarRe = re.compile(r'\$(\d+)') def findBiggestDollar(alias): dollars = dollarRe.findall(alias) dollars = map(int, dollars) dollars.sort() if dollars: return dollars[-1] else: return 0 atRe = re.compile(r'@(\d+)') def findBiggestAt(alias): ats = atRe.findall(alias) ats = map(int, ats) ats.sort() if ats: return ats[-1] else: return 0 def makeNewAlias(name, alias): original = alias biggestDollar = findBiggestDollar(original) biggestAt = findBiggestAt(original) wildcard = '$*' in original if biggestAt and wildcard: raise AliasError, 'Can\'t mix $* and optional args (@1, etc.)' if original.count('$*') > 1: raise AliasError, 'There can be only one $* in an alias.' testTokens = callbacks.tokenize(original) if testTokens and isinstance(testTokens[0], list): raise AliasError, 'Commands may not be the result of nesting.' def f(self, irc, msg, args): alias = original.replace('$nick', msg.nick) if '$channel' in original: channel = getChannel(msg, args) alias = alias.replace('$channel', channel) tokens = callbacks.tokenize(alias) if not wildcard and biggestDollar or biggestAt: args = getArgs(args, required=biggestDollar, optional=biggestAt) # Gotta have a mutable sequence (for replace). if biggestDollar + biggestAt == 1: # We got a string, no tuple. args = [args] def regexpReplace(m): idx = int(m.group(1)) return args[idx-1] def replace(tokens, replacer): for (i, token) in enumerate(tokens): if isinstance(token, list): replace(token, replacer) else: tokens[i] = replacer(token) replace(tokens, lambda s: dollarRe.sub(regexpReplace, s)) if biggestAt: assert not wildcard args = args[biggestDollar:] replace(tokens, lambda s: atRe.sub(regexpReplace, s)) if wildcard: assert not biggestAt # Gotta remove the things that have already been subbed in. i = biggestDollar while i: args.pop(0) i -= 1 def everythingReplace(tokens): for (i, token) in enumerate(tokens): if isinstance(token, list): if everythingReplace(token): return if token == '$*': tokens[i:i+1] = args return True elif '$*' in token: tokens[i] = token.replace('$*', ' '.join(args)) return True return False everythingReplace(tokens) self.Proxy(irc, msg, tokens) doc =format('\n\nAlias for %q.', (biggestDollar, 'argument'), alias) f = utils.python.changeFunctionName(f, name, doc) return f class Alias(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Alias, self) self.__parent.__init__(irc) # Schema: {alias: [command, locked, commandMethod]} self.aliases = {} # XXX This should go. aliases should be a space separate list, etc. group = conf.supybot.plugins.Alias.aliases for (name, alias) in registry._cache.iteritems(): name = name.lower() if name.startswith('supybot.plugins.alias.aliases.'): name = name[len('supybot.plugins.alias.aliases.'):] if '.' in name: continue conf.registerGlobalValue(group, name, registry.String('', '')) conf.registerGlobalValue(group.get(name), 'locked', registry.Boolean(False, '')) for (name, value) in group.getValues(fullNames=False): name = name.lower() # Just in case. command = value() locked = value.locked() self.aliases[name] = [command, locked, None] for (alias, (command, locked, _)) in self.aliases.items(): try: self.addAlias(irc, alias, command, locked) except Exception, e: self.log.exception('Exception when trying to add alias %s. ' 'Removing from the Alias database.', alias) del self.aliases[alias] def isCommandMethod(self, name): if not self.__parent.isCommandMethod(name): if name in self.aliases: return True else: return False else: return True def listCommands(self): return self.__parent.listCommands(self.aliases.keys()) def getCommandMethod(self, command): try: return self.__parent.getCommandMethod(command) except AttributeError: return self.aliases[command[0]][2] def lock(self, irc, msg, args, name): """ Locks an alias so that no one else can change it. """ if name in self.aliases and self.isCommandMethod(name): self.aliases[name][1] = True conf.supybot.plugins.Alias.aliases.get(name).locked.setValue(True) irc.replySuccess() else: irc.error('There is no such alias.') lock = wrap(lock, [('checkCapability', 'admin'), 'commandName']) def unlock(self, irc, msg, args, name): """ Unlocks an alias so that people can define new aliases over it. """ if name in self.aliases and self.isCommandMethod(name): self.aliases[name][1] = False conf.supybot.plugins.Alias.aliases.get(name).locked.setValue(False) irc.replySuccess() else: irc.error('There is no such alias.') unlock = wrap(unlock, [('checkCapability', 'admin'), 'commandName']) _invalidCharsRe = re.compile(r'[\[\]\s]') def addAlias(self, irc, name, alias, lock=False): if self._invalidCharsRe.search(name): raise AliasError, 'Names cannot contain spaces or square brackets.' if '|' in name: raise AliasError, 'Names cannot contain pipes.' realName = callbacks.canonicalName(name) if name != realName: s = format('That name isn\'t valid. Try %q instead.', realName) raise AliasError, s name = realName if self.isCommandMethod(name): if realName not in self.aliases: s = 'You can\'t overwrite commands in this plugin.' raise AliasError, s if name in self.aliases: (currentAlias, locked, _) = self.aliases[name] if locked and currentAlias != alias: raise AliasError, format('Alias %q is locked.', name) try: f = makeNewAlias(name, alias) f = new.instancemethod(f, self, Alias) except RecursiveAlias: raise AliasError, 'You can\'t define a recursive alias.' if name in self.aliases: # We gotta remove it so its value gets updated. conf.supybot.plugins.Alias.aliases.unregister(name) conf.supybot.plugins.Alias.aliases.register(name, registry.String(alias, '')) conf.supybot.plugins.Alias.aliases.get(name).register('locked', registry.Boolean(lock, '')) self.aliases[name] = [alias, lock, f] def removeAlias(self, name, evenIfLocked=False): name = callbacks.canonicalName(name) if name in self.aliases and self.isCommandMethod(name): if evenIfLocked or not self.aliases[name][1]: del self.aliases[name] conf.supybot.plugins.Alias.aliases.unregister(name) else: raise AliasError, 'That alias is locked.' else: raise AliasError, 'There is no such alias.' def add(self, irc, msg, args, name, alias): """ Defines an alias that executes . The should be in the standard "command argument [nestedcommand argument]" arguments to the alias; they'll be filled with the first, second, etc. arguments. $1, $2, etc. can be used for required arguments. @1, @2, etc. can be used for optional arguments. $* simply means "all remaining arguments," and cannot be combined with optional arguments. """ if ' ' not in alias: # If it's a single word, they probably want $*. alias += ' $*' try: self.addAlias(irc, name, alias) self.log.info('Adding alias %q for %q (from %s)', name, alias, msg.prefix) irc.replySuccess() except AliasError, e: irc.error(str(e)) add = wrap(add, ['commandName', 'text']) def remove(self, irc, msg, args, name): """ Removes the given alias, if unlocked. """ try: self.removeAlias(name) self.log.info('Removing alias %q (from %s)', name, msg.prefix) irc.replySuccess() except AliasError, e: irc.error(str(e)) remove = wrap(remove, ['commandName']) Class = Alias # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Alias/config.py0000644000000000000000000000420311206611405016230 0ustar ### # 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('Alias', True) Alias = conf.registerPlugin('Alias') conf.registerGroup(Alias, 'aliases') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Channel/0000755000000000000000000000000011206611405014731 5ustar supybot-0.83.4.1.ds.orig/plugins/Channel/test.py0000644000000000000000000002400311206611405016261 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 supybot.conf as conf import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs class ChannelTestCase(ChannelPluginTestCase): plugins = ('Channel', 'User') def setUp(self): super(ChannelTestCase, self).setUp() self.irc.state.channels[self.channel].addUser('foo') self.irc.state.channels[self.channel].addUser('bar') def testLobotomies(self): self.assertRegexp('lobotomy list', 'not.*any') ## def testCapabilities(self): ## self.prefix = 'foo!bar@baz' ## self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, 'register foo bar', ## prefix=self.prefix)) ## u = ircdb.users.getUser(0) ## u.addCapability('%s.op' % self.channel) ## ircdb.users.setUser(u) ## self.assertNotError(' ') ## self.assertResponse('user capabilities foo', '[]') ## self.assertNotError('channel addcapability foo op') ## self.assertRegexp('channel capabilities foo', 'op') ## self.assertNotError('channel removecapability foo op') ## self.assertResponse('user capabilities foo', '[]') def testCapabilities(self): self.assertNotError('channel capability list') self.assertNotError('channel capability set -foo') self.assertNotError('channel capability unset -foo') self.assertError('channel capability unset -foo') self.assertNotError('channel capability set -foo bar baz') self.assertRegexp('channel capability list', 'baz') self.assertNotError('channel capability unset -foo baz') self.assertError('channel capability unset baz') def testEnableDisable(self): self.assertNotRegexp('channel capability list', '-Channel') self.assertError('channel enable channel') self.assertNotError('channel disable channel') self.assertRegexp('channel capability list', '-Channel') self.assertNotError('channel enable channel') self.assertNotRegexp('channel capability list', '-Channel') self.assertNotError('channel disable channel nicks') self.assertRegexp('channel capability list', '-Channel.nicks') self.assertNotError('channel enable channel nicks') self.assertNotRegexp('channel capability list', '-Channel.nicks') self.assertNotRegexp('channel capability list', 'nicks') self.assertNotError('channel disable nicks') self.assertRegexp('channel capability list', 'nicks') self.assertNotError('channel enable nicks') self.assertError('channel disable invalidPlugin') self.assertError('channel disable channel invalidCommand') def testUnban(self): self.assertError('unban foo!bar@baz') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) m = self.getMsg('unban foo!bar@baz') self.assertEqual(m.command, 'MODE') self.assertEqual(m.args, (self.channel, '-b', 'foo!bar@baz')) self.assertNoResponse(' ', 2) def testErrorsWithoutOps(self): for s in 'op deop halfop dehalfop voice devoice kick invite'.split(): self.assertError('%s foo' % s) self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('%s foo' % s) self.irc.feedMsg(ircmsgs.deop(self.channel, self.nick)) def testWontDeItself(self): for s in 'deop dehalfop devoice'.split(): self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertError('%s %s' % (s, self.nick)) def testOp(self): self.assertError('op') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('op') m = self.getMsg('op foo') self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+o', 'foo')) m = self.getMsg('op foo bar') self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+o', 'foo')) m = self.irc.takeMsg() self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+o', 'bar')) self.irc.state.supported['MODES'] = 2 m = self.getMsg('op foo bar') try: self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+oo', 'foo', 'bar')) finally: self.irc.state.supported['MODES'] = 1 def testHalfOp(self): self.assertError('halfop') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('halfop') m = self.getMsg('halfop foo') self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+h', 'foo')) m = self.getMsg('halfop foo bar') self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+h', 'foo')) m = self.irc.takeMsg() self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+h', 'bar')) def testVoice(self): self.assertError('voice') self.irc.feedMsg(ircmsgs.op(self.channel, self.nick)) self.assertNotError('voice') m = self.getMsg('voice foo') self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+v', 'foo')) m = self.getMsg('voice foo bar') self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+v', 'foo')) m = self.irc.takeMsg() self.failUnless(m.command == 'MODE' and m.args == (self.channel, '+v', 'bar')) def assertBan(self, query, hostmask, **kwargs): m = self.getMsg(query, **kwargs) self.assertEqual(m, ircmsgs.ban(self.channel, hostmask)) m = self.getMsg(' ') self.assertEqual(m.command, 'KICK') ## def testKban(self): ## self.irc.prefix = 'something!else@somehwere.else' ## self.irc.nick = 'something' ## self.irc.feedMsg(ircmsgs.join(self.channel, ## prefix='foobar!user@host.domain.tld')) ## self.assertError('kban foobar') ## self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick)) ## self.assertError('kban foobar -1') ## self.assertBan('kban foobar', '*!*@*.domain.tld') ## self.assertBan('kban --exact foobar', 'foobar!user@host.domain.tld') ## self.assertBan('kban --host foobar', '*!*@host.domain.tld') ## self.assertBan('kban --user foobar', '*!user@*') ## self.assertBan('kban --nick foobar', 'foobar!*@*') ## self.assertBan('kban --nick --user foobar', 'foobar!user@*') ## self.assertBan('kban --nick --host foobar', ## 'foobar!*@host.domain.tld') ## self.assertBan('kban --user --host foobar', '*!user@host.domain.tld') ## self.assertBan('kban --nick --user --host foobar', ## 'foobar!user@host.domain.tld') ## self.assertNotRegexp('kban adlkfajsdlfkjsd', 'KeyError') ## self.assertNotRegexp('kban foobar time', 'ValueError') ## self.assertError('kban %s' % self.irc.nick) def testBan(self): origban = conf.supybot.protocols.irc.banmask() try: conf.supybot.protocols.irc.banmask.setValue(['exact']) self.assertNotError('ban add foo!bar@baz') self.assertNotError('ban remove foo!bar@baz') orig = conf.supybot.protocols.irc.strictRfc() try: conf.supybot.protocols.irc.strictRfc.setValue(True) # something wonky is going on here. irc.error (src/Channel.py|449) # is being called but the assert is failing self.assertError('ban add not!a.hostmask') self.assertNotRegexp('ban add not!a.hostmask', 'KeyError') finally: conf.supybot.protocols.irc.strictRfc.setValue(orig) finally: conf.supybot.protocols.irc.banmask.setValue(origban) def testIgnore(self): orig = conf.supybot.protocols.irc.banmask() try: conf.supybot.protocols.irc.banmask.setValue(['exact']) self.assertNotError('channel ignore add foo!bar@baz') self.assertResponse('channel ignore list', "'foo!bar@baz'") self.assertNotError('channel ignore remove foo!bar@baz') self.assertError('ban add not!a.hostmask') finally: conf.supybot.protocols.irc.banmask.setValue(orig) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Channel/__init__.py0000644000000000000000000000444511206611405017051 0ustar ### # Copyright (c) 2004-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. ### """ Basic channel management commands. Many of these commands require their caller to have the .op capability. This plugin is loaded by default. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = { supybot.authors.skorobeus: ['enable', 'disable'], } import config import plugin reload(plugin) # In case we're being reloaded. if world.testing: import test Class = plugin.Class configure = config.configure supybot-0.83.4.1.ds.orig/plugins/Channel/plugin.py0000644000000000000000000010522311206611405016604 0ustar ### # Copyright (c) 2002-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 sys import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.schedule as schedule import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Channel(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Channel, self) self.__parent.__init__(irc) self.invites = {} def doKick(self, irc, msg): channel = msg.args[0] if msg.args[1] == irc.nick: if self.registryValue('alwaysRejoin', channel): networkGroup = conf.supybot.networks.get(irc.network) irc.sendMsg(networkGroup.channels.join(channel)) def _sendMsg(self, irc, msg): irc.queueMsg(msg) irc.noReply() def _sendMsgs(self, irc, nicks, f): numModes = irc.state.supported.get('modes', 1) for i in range(0, len(nicks), numModes): irc.queueMsg(f(nicks[i:i + numModes])) irc.noReply() def mode(self, irc, msg, args, channel, modes): """[] [ ...] Sets the mode in to , sending the arguments given. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.mode(channel, modes)) mode = wrap(mode, ['op', ('haveOp', 'change the mode'), many('something')]) def limit(self, irc, msg, args, channel, limit): """[] [] Sets the channel limit to . If is 0, or isn't given, removes the channel limit. is only necessary if the message isn't sent in the channel itself. """ if limit: self._sendMsg(irc, ircmsgs.mode(channel, ['+l', limit])) else: self._sendMsg(irc, ircmsgs.mode(channel, ['-l'])) limit = wrap(limit, ['op', ('haveOp', 'change the limit'), additional('nonNegativeInt', 0)]) def moderate(self, irc, msg, args, channel): """[] Sets +m on , making it so only ops and voiced users can send messages to the channel. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.mode(channel, ['+m'])) moderate = wrap(moderate, ['op', ('haveOp', 'moderate the channel')]) def unmoderate(self, irc, msg, args, channel): """[] Sets -m on , making it so everyone can send messages to the channel. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.mode(channel, ['-m'])) unmoderate = wrap(unmoderate, ['op', ('haveOp', 'unmoderate the channel')]) def key(self, irc, msg, args, channel, key): """[] [] Sets the keyword in to . If is not given, removes the keyword requirement to join . is only necessary if the message isn't sent in the channel itself. """ networkGroup = conf.supybot.networks.get(irc.network) networkGroup.channels.key.get(channel).setValue(key) if key: self._sendMsg(irc, ircmsgs.mode(channel, ['+k', key])) else: self._sendMsg(irc, ircmsgs.mode(channel, ['-k'])) key = wrap(key, ['op', ('haveOp', 'change the keyword'), additional('somethingWithoutSpaces', '')]) def op(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will give all the s you provide ops. If you don't provide any s, this will op you. is only necessary if the message isn't sent in the channel itself. """ if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.ops(channel, L) self._sendMsgs(irc, nicks, f) op = wrap(op, ['op', ('haveOp', 'op someone'), any('nickInChannel')]) def halfop(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,halfop capability, this will give all the s you provide halfops. If you don't provide any s, this will give you halfops. is only necessary if the message isn't sent in the channel itself. """ if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.halfops(channel, L) self._sendMsgs(irc, nicks, f) halfop = wrap(halfop, ['halfop', ('haveOp', 'halfop someone'), any('nickInChannel')]) def voice(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,voice capability, this will voice all the s you provide. If you don't provide any s, this will voice you. is only necessary if the message isn't sent in the channel itself. """ if nicks: if len(nicks) == 1 and msg.nick in nicks: capability = 'voice' else: capability = 'op' else: nicks = [msg.nick] capability = 'voice' capability = ircdb.makeChannelCapability(channel, capability) if ircdb.checkCapability(msg.prefix, capability): def f(L): return ircmsgs.voices(channel, L) self._sendMsgs(irc, nicks, f) else: irc.errorNoCapability(capability) voice = wrap(voice, ['channel', ('haveOp', 'voice someone'), any('nickInChannel')]) def deop(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will remove operator privileges from all the nicks given. If no nicks are given, removes operator privileges from the person sending the message. """ if irc.nick in nicks: irc.error('I cowardly refuse to deop myself. If you really want ' 'me deopped, tell me to op you and then deop me ' 'yourself.', Raise=True) if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.deops(channel, L) self._sendMsgs(irc, nicks, f) deop = wrap(deop, ['op', ('haveOp', 'deop someone'), any('nickInChannel')]) def dehalfop(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will remove half-operator privileges from all the nicks given. If no nicks are given, removes half-operator privileges from the person sending the message. """ if irc.nick in nicks: irc.error('I cowardly refuse to dehalfop myself. If you really ' 'want me dehalfopped, tell me to op you and then ' 'dehalfop me yourself.', Raise=True) if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.dehalfops(channel, L) self._sendMsgs(irc, nicks, f) dehalfop = wrap(dehalfop, ['halfop', ('haveOp', 'dehalfop someone'), any('nickInChannel')]) def devoice(self, irc, msg, args, channel, nicks): """[] [ ...] If you have the #channel,op capability, this will remove voice from all the nicks given. If no nicks are given, removes voice from the person sending the message. """ if irc.nick in nicks: irc.error('I cowardly refuse to devoice myself. If you really ' 'want me devoiced, tell me to op you and then devoice ' 'me yourself.', Raise=True) if not nicks: nicks = [msg.nick] def f(L): return ircmsgs.devoices(channel, L) self._sendMsgs(irc, nicks, f) devoice = wrap(devoice, ['voice', ('haveOp', 'devoice someone'), any('nickInChannel')]) def cycle(self, irc, msg, args, channel): """[] If you have the #channel,op capability, this will cause the bot to "cycle", or PART and then JOIN the channel. is only necessary if the message isn't sent in the channel itself. """ self._sendMsg(irc, ircmsgs.part(channel, msg.nick)) networkGroup = conf.supybot.networks.get(irc.network) self._sendMsg(irc, networkGroup.channels.join(channel)) cycle = wrap(cycle, ['op']) def kick(self, irc, msg, args, channel, nick, reason): """[] [] Kicks from for . If isn't given, uses the nick of the person making the command as the reason. is only necessary if the message isn't sent in the channel itself. """ if ircutils.strEqual(nick, irc.nick): irc.error('I cowardly refuse to kick myself.', Raise=True) if not reason: reason = msg.nick kicklen = irc.state.supported.get('kicklen', sys.maxint) if len(reason) > kicklen: irc.error('The reason you gave is longer than the allowed ' 'length for a KICK reason on this server.', Raise=True) self._sendMsg(irc, ircmsgs.kick(channel, nick, reason)) kick = wrap(kick, ['op', ('haveOp', 'kick someone'), 'nickInChannel', additional('text')]) def kban(self, irc, msg, args, channel, optlist, bannedNick, expiry, reason): """[] [--{exact,nick,user,host}] [] [] If you have the #channel,op capability, this will kickban for as many seconds as you specify, or else (if you specify 0 seconds or don't specify a number of seconds) it will ban the person indefinitely. --exact bans only the exact hostmask; --nick bans just the nick; --user bans just the user, and --host bans just the host. You can combine these options as you choose. is a reason to give for the kick. is only necessary if the message isn't sent in the channel itself. """ # Check that they're not trying to make us kickban ourself. if not irc.isNick(bannedNick): self.log.warning('%q tried to kban a non nick: %q', msg.prefix, bannedNick) raise callbacks.ArgumentError elif bannedNick == irc.nick: self.log.warning('%q tried to make me kban myself.', msg.prefix) irc.error('I cowardly refuse to kickban myself.') return if not reason: reason = msg.nick try: bannedHostmask = irc.state.nickToHostmask(bannedNick) except KeyError: irc.error(format('I haven\'t seen %s.', bannedNick), Raise=True) capability = ircdb.makeChannelCapability(channel, 'op') banmaskstyle = conf.supybot.protocols.irc.banmask banmask = banmaskstyle.makeBanmask(bannedHostmask, [o[0] for o in optlist]) # Check (again) that they're not trying to make us kickban ourself. if ircutils.hostmaskPatternEqual(banmask, irc.prefix): if ircutils.hostmaskPatternEqual(bannedHostmask, irc.prefix): self.log.warning('%q tried to make me kban myself.',msg.prefix) irc.error('I cowardly refuse to ban myself.') return else: self.log.warning('Using exact hostmask since banmask would ' 'ban myself.') banmask = bannedHostmask # Now, let's actually get to it. Check to make sure they have # #channel,op and the bannee doesn't have #channel,op; or that the # bannee and the banner are both the same person. def doBan(): if irc.state.channels[channel].isOp(bannedNick): irc.queueMsg(ircmsgs.deop(channel, bannedNick)) irc.queueMsg(ircmsgs.ban(channel, banmask)) irc.queueMsg(ircmsgs.kick(channel, bannedNick, reason)) if expiry > 0: def f(): if channel in irc.state.channels and \ banmask in irc.state.channels[channel].bans: irc.queueMsg(ircmsgs.unban(channel, banmask)) schedule.addEvent(f, expiry) if bannedNick == msg.nick: doBan() elif ircdb.checkCapability(msg.prefix, capability): if ircdb.checkCapability(bannedHostmask, capability): self.log.warning('%s tried to ban %q, but both have %s', msg.prefix, bannedHostmask, capability) irc.error(format('%s has %s too, you can\'t ban him/her/it.', bannedNick, capability)) else: doBan() else: self.log.warning('%q attempted kban without %s', msg.prefix, capability) irc.errorNoCapability(capability) exact,nick,user,host kban = wrap(kban, ['op', getopts({'exact':'', 'nick':'', 'user':'', 'host':''}), ('haveOp', 'kick or ban someone'), 'nickInChannel', optional('expiry', 0), additional('text')]) def unban(self, irc, msg, args, channel, hostmask): """[] [] Unbans on . If is not given, unbans any hostmask currently banned on that matches your current hostmask. Especially useful for unbanning yourself when you get unexpectedly (or accidentally) banned from the channel. is only necessary if the message isn't sent in the channel itself. """ if hostmask: self._sendMsg(irc, ircmsgs.unban(channel, hostmask)) else: bans = [] for banmask in irc.state.channels[channel].bans: if ircutils.hostmaskPatternEqual(banmask, msg.prefix): bans.append(banmask) if bans: irc.queueMsg(ircmsgs.unbans(channel, bans)) irc.replySuccess(format('All bans on %s matching %s ' 'have been removed.', channel, msg.prefix)) else: irc.error('No bans matching %s were found on %s.' % (msg.prefix, channel)) unban = wrap(unban, ['op', ('haveOp', 'unban someone'), additional('hostmask')]) def invite(self, irc, msg, args, channel, nick): """[] If you have the #channel,op capability, this will invite to join . is only necessary if the message isn't sent in the channel itself. """ nick = nick or msg.nick self._sendMsg(irc, ircmsgs.invite(nick, channel)) self.invites[(irc.getRealIrc(), ircutils.toLower(nick))] = irc invite = wrap(invite, ['op', ('haveOp', 'invite someone'), additional('nick')]) def do341(self, irc, msg): (_, nick, channel) = msg.args nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: self.log.info('Inviting %s to %s by command of %s.', nick, channel, replyIrc.msg.prefix) replyIrc.replySuccess() else: self.log.info('Inviting %s to %s.', nick, channel) def do443(self, irc, msg): (_, nick, channel, _) = msg.args nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: replyIrc.error(format('%s is already in %s.', nick, channel)) def do401(self, irc, msg): nick = msg.args[1] nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyIrc is not None: replyIrc.error(format('There is no %s on this network.', nick)) def do504(self, irc, msg): nick = msg.args[1] nick = ircutils.toLower(nick) replyIrc = self.invites.pop((irc, nick), None) if replyirc is not None: replyIrc.error(format('There is no %s on this server.', nick)) class lobotomy(callbacks.Commands): def add(self, irc, msg, args, channel): """[] If you have the #channel,op capability, this will "lobotomize" the bot, making it silent and unanswering to all requests made in the channel. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.lobotomized = True ircdb.channels.setChannel(channel, c) irc.replySuccess() add = wrap(add, ['op']) def remove(self, irc, msg, args, channel): """[] If you have the #channel,op capability, this will unlobotomize the bot, making it respond to requests made in the channel again. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.lobotomized = False ircdb.channels.setChannel(channel, c) irc.replySuccess() remove = wrap(remove, ['op']) def list(self, irc, msg, args): """takes no arguments Returns the channels in which this bot is lobotomized. """ L = [] for (channel, c) in ircdb.channels.iteritems(): if c.lobotomized: chancap = ircdb.makeChannelCapability(channel, 'op') if ircdb.checkCapability(msg.prefix, 'admin') or \ ircdb.checkCapability(msg.prefix, chancap) or \ (channel in irc.state.channels and \ msg.nick in irc.state.channels[channel].users): L.append(channel) if L: L.sort() s = format('I\'m currently lobotomized in %L.', L) irc.reply(s) else: irc.reply('I\'m not currently lobotomized in any channels ' 'that you\'re in.') list = wrap(list) class ban(callbacks.Commands): def add(self, irc, msg, args, channel, banmask, expires): """[] [] If you have the #channel,op capability, this will effect a persistent ban from interacting with the bot on the given (or the current hostmask associated with . Other plugins may enforce this ban by actually banning users with matching hostmasks when they join. is an optional argument specifying when (in "seconds from now") the ban should expire; if none is given, the ban will never automatically expire. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.addBan(banmask, expires) ircdb.channels.setChannel(channel, c) irc.replySuccess() add = wrap(add, ['op', 'banmask', additional('expiry', 0)]) def remove(self, irc, msg, args, channel, banmask): """[] If you have the #channel,op capability, this will remove the persistent ban on . is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) try: c.removeBan(banmask) ircdb.channels.setChannel(channel, c) irc.replySuccess() except KeyError: irc.error('There are no persistent bans for that hostmask.') remove = wrap(remove, ['op', 'hostmask']) def list(self, irc, msg, args, channel): """[] If you have the #channel,op capability, this will show you the current persistent bans on #channel. """ c = ircdb.channels.getChannel(channel) if c.bans: bans = [] for ban in c.bans: if c.bans[ban]: bans.append(format('%q (expires %t)', ban, c.bans[ban])) else: bans.append(format('%q (never expires)', ban, c.bans[ban])) irc.reply(format('%L', bans)) else: irc.reply(format('There are no persistent bans on %s.', channel)) list = wrap(list, ['op']) class ignore(callbacks.Commands): def add(self, irc, msg, args, channel, banmask, expires): """[] [] If you have the #channel,op capability, this will set a persistent ignore on or the hostmask currently associated with . is an optional argument specifying when (in "seconds from now") the ignore will expire; if it isn't given, the ignore will never automatically expire. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) c.addIgnore(banmask, expires) ircdb.channels.setChannel(channel, c) irc.replySuccess() add = wrap(add, ['op', 'banmask', additional('expiry', 0)]) def remove(self, irc, msg, args, channel, banmask): """[] If you have the #channel,op capability, this will remove the persistent ignore on in the channel. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) try: c.removeIgnore(banmask) ircdb.channels.setChannel(channel, c) irc.replySuccess() except KeyError: irc.error('There are no ignores for that hostmask.') remove = wrap(remove, ['op', 'hostmask']) def list(self, irc, msg, args, channel): """[] Lists the hostmasks that the bot is ignoring on the given channel. is only necessary if the message isn't sent in the channel itself. """ # XXX Add the expirations. c = ircdb.channels.getChannel(channel) if len(c.ignores) == 0: s = format('I\'m not currently ignoring any hostmasks in %q', channel) irc.reply(s) else: L = sorted(c.ignores) irc.reply(utils.str.commaAndify(map(repr, L))) list = wrap(list, ['op']) class capability(callbacks.Commands): def add(self, irc, msg, args, channel, user, capabilities): """[] [ ...] If you have the #channel,op capability, this will give the user (or the user to whom maps) the capability in the channel. is only necessary if the message isn't sent in the channel itself. """ for c in capabilities.split(): c = ircdb.makeChannelCapability(channel, c) user.addCapability(c) ircdb.users.setUser(user) irc.replySuccess() add = wrap(add, ['op', 'otherUser', 'capability']) def remove(self, irc, msg, args, channel, user, capabilities): """[] [ ...] If you have the #channel,op capability, this will take from the user currently identified as (or the user to whom maps) the capability in the channel. is only necessary if the message isn't sent in the channel itself. """ fail = [] for c in capabilities.split(): cap = ircdb.makeChannelCapability(channel, c) try: user.removeCapability(cap) except KeyError: fail.append(c) ircdb.users.setUser(user) if fail: s = 'capability' if len(fail) > 1: s = utils.str.pluralize(s) irc.error(format('That user didn\'t have the %L %s.', fail, s), Raise=True) irc.replySuccess() remove = wrap(remove, ['op', 'otherUser', 'capability']) # XXX This needs to be fix0red to be like Owner.defaultcapability. Or # something else. This is a horrible interface. def setdefault(self, irc, msg, args, channel, v): """[] {True|False} If you have the #channel,op capability, this will set the default response to non-power-related (that is, not {op, halfop, voice} capabilities to be the value you give. is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) if v: c.setDefaultCapability(True) else: c.setDefaultCapability(False) ircdb.channels.setChannel(channel, c) irc.replySuccess() setdefault = wrap(setdefault, ['op', 'boolean']) def set(self, irc, msg, args, channel, capabilities): """[] [ ...] If you have the #channel,op capability, this will add the channel capability for all users in the channel. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) for c in capabilities: chan.addCapability(c) ircdb.channels.setChannel(channel, chan) irc.replySuccess() set = wrap(set, ['op', many('capability')]) def unset(self, irc, msg, args, channel, capabilities): """[] [ ...] If you have the #channel,op capability, this will unset the channel capability so each user's specific capability or the channel default capability will take precedence. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) fail = [] for c in capabilities: try: chan.removeCapability(c) except KeyError: fail.append(c) ircdb.channels.setChannel(channel, chan) if fail: s = 'capability' if len(fail) > 1: s = utils.str.pluralize(s) irc.error(format('I do not know about the %L %s.', fail, s), Raise=True) irc.replySuccess() unset = wrap(unset, ['op', many('capability')]) def list(self, irc, msg, args, channel): """[] Returns the capabilities present on the . is only necessary if the message isn't sent in the channel itself. """ c = ircdb.channels.getChannel(channel) L = sorted(c.capabilities) irc.reply(' '.join(L)) list = wrap(list, ['channel']) def disable(self, irc, msg, args, channel, plugin, command): """[] [] [] If you have the #channel,op capability, this will disable the in . If is provided, will be disabled only for that plugin. If only is provided, all commands in the given plugin will be disabled. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) failMsg = '' if plugin: s = '-%s' % plugin.name() if command: if plugin.isCommand(command): s = '-%s.%s' % (plugin.name(), command) else: failMsg = format('The %s plugin does not have a command ' 'called %s.', plugin.name(), command) elif command: # findCallbackForCommand if filter(None, irc.findCallbacksForArgs([command])): s = '-%s' % command else: failMsg = format('No plugin or command named %s could be ' 'found.', command) else: raise callbacks.ArgumentError if failMsg: irc.error(failMsg) else: chan.addCapability(s) ircdb.channels.setChannel(channel, chan) irc.replySuccess() disable = wrap(disable, ['op', optional(('plugin', False)), additional('commandName')]) def enable(self, irc, msg, args, channel, plugin, command): """[] [] [] If you have the #channel,op capability, this will enable the in if it has been disabled. If is provided, will be enabled only for that plugin. If only is provided, all commands in the given plugin will be enabled. is only necessary if the message isn't sent in the channel itself. """ chan = ircdb.channels.getChannel(channel) failMsg = '' if plugin: s = '-%s' % plugin.name() if command: if plugin.isCommand(command): s = '-%s.%s' % (plugin.name(), command) else: failMsg = format('The %s plugin does not have a command ' 'called %s.', plugin.name(), command) elif command: # findCallbackForCommand if filter(None, irc.findCallbacksForArgs([command])): s = '-%s' % command else: failMsg = format('No plugin or command named %s could be ' 'found.', command) else: raise callbacks.ArgumentError if failMsg: irc.error(failMsg) else: fail = [] try: chan.removeCapability(s) except KeyError: fail.append(s) ircdb.channels.setChannel(channel, chan) if fail: irc.error(format('%s was not disabled.', s[1:])) else: irc.replySuccess() enable = wrap(enable, ['op', optional(('plugin', False)), additional('commandName')]) def nicks(self, irc, msg, args, channel): """[] Returns the nicks in . is only necessary if the message isn't sent in the channel itself. """ # Make sure we don't elicit information about private channels to # people or channels that shouldn't know if 's' in irc.state.channels[channel].modes and \ msg.args[0] != channel and \ (ircutils.isChannel(msg.args[0]) or \ msg.nick not in irc.state.channels[channel].users): irc.error('You don\'t have access to that information.') L = list(irc.state.channels[channel].users) utils.sortBy(str.lower, L) irc.reply(utils.str.commaAndify(L)) nicks = wrap(nicks, ['inChannel']) def alertOps(self, irc, channel, s, frm=None): """Internal message for notifying all the #channel,ops in a channel of a given situation.""" capability = ircdb.makeChannelCapability(channel, 'op') s = format('Alert to all %s ops: %s', channel, s) if frm is not None: s += format(' (from %s)', frm) for nick in irc.state.channels[channel].users: hostmask = irc.state.nickToHostmask(nick) if ircdb.checkCapability(hostmask, capability): irc.reply(s, to=nick, private=True) def alert(self, irc, msg, args, channel, text): """[] Sends to all the users in who have the ,op capability. """ self.alertOps(irc, channel, text, frm=msg.nick) alert = wrap(alert, ['op', 'text']) Class = Channel # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Channel/config.py0000644000000000000000000000455211206611405016556 0ustar ### # Copyright (c) 2004-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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.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('Channel', True) Channel = conf.registerPlugin('Channel') conf.registerChannelValue(Channel, 'alwaysRejoin', registry.Boolean(True, """Determines whether the bot will always try to rejoin a channel whenever it's kicked from the channel.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/News/0000755000000000000000000000000011206611405014275 5ustar supybot-0.83.4.1.ds.orig/plugins/News/test.py0000644000000000000000000000723311206611405015633 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 from supybot.test import * class NewsTestCase(ChannelPluginTestCase): plugins = ('News','User') def setUp(self): ChannelPluginTestCase.setUp(self) # Create a valid user to use self.prefix = 'news!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.nick, 'register tester moo', prefix=self.prefix)) m = self.irc.takeMsg() # Response to register. def testAddnews(self): self.assertNotError('add 0 subject: foo') self.assertRegexp('news', 'subject') self.assertNotError('add 0 subject2: foo2') self.assertRegexp('news', 'subject.*subject2') self.assertNotError('add 5 subject3: foo3') self.assertRegexp('news', 'subject3') print print 'Sleeping to expire the news item (testAddnews)' time.sleep(6) print 'Done sleeping.' self.assertNotRegexp('news', 'subject3') def testNews(self): # These should both fail first, as they will have nothing in the DB self.assertRegexp('news', 'no news') self.assertRegexp('news #channel', 'no news') # Now we'll add news and make sure listnews doesn't fail self.assertNotError('add #channel 0 subject: foo') self.assertNotError('news #channel') self.assertNotError('add 0 subject: foo') self.assertRegexp('news', '#1') self.assertNotError('news 1') def testChangenews(self): self.assertNotError('add 0 Foo: bar') self.assertNotError('change 1 s/bar/baz/') self.assertNotRegexp('news 1', 'bar') self.assertRegexp('news 1', 'baz') def testOldnews(self): self.assertRegexp('old', 'No old news') self.assertNotError('add 0 a: b') self.assertRegexp('old', 'No old news') self.assertNotError('add 5 foo: bar') self.assertRegexp('old', 'No old news') print print 'Sleeping to expire the news item (testOldnews)' time.sleep(6) print 'Done sleeping.' self.assertNotError('old') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/News/__init__.py0000644000000000000000000000501011206611405016402 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 allow each channel to have "news". News items may have expiration dates. """ 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__ = "%%VERSION%%" __author__ = supybot.authors.strike # This is a dictionary mapping supybot.Author instances to lists of # contributions. __contributors__ = {} # This is a url where the most recent plugin package can be downloaded. __url__ = '' # 'http://supybot.com/Members/yourname/News/download' 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: supybot-0.83.4.1.ds.orig/plugins/News/README.txt0000644000000000000000000000020511206611405015770 0ustar This plugin provides a means of maintaining News for a channel. It was partially inspired by the news system used on #debian's bot. supybot-0.83.4.1.ds.orig/plugins/News/plugin.py0000644000000000000000000002150311206611405016146 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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.dbi as dbi import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks class DbiNewsDB(plugins.DbiChannelDB): class DB(dbi.DB): class Record(dbi.Record): __fields__ = [ 'subject', 'text', 'at', 'expires', 'by', ] def __str__(self): user = plugins.getUserName(self.by) if self.expires == 0: s = format('%s (Subject: %q, added by %s on %s)', self.text, self.subject, self.by, utils.str.timestamp(self.at)) else: s = format('%s (Subject: %q, added by %s on %s, ' 'expires at %s)', self.text, self.subject, user, utils.str.timestamp(self.at), utils.str.timestamp(self.expires)) return s def __init__(self, filename): # We use self.__class__ here because apparently DB isn't in our # scope. python-- self.__parent = super(self.__class__, self) self.__parent.__init__(filename) def add(self, subject, text, at, expires, by): return self.__parent.add(self.Record(at=at, by=by, text=text, subject=subject, expires=expires)) def getOld(self, id=None): now = time.time() if id: return self.get(id) else: L = [R for R in self if R.expires < now and R.expires != 0] if not L: raise dbi.NoRecordError else: return L def get(self, id=None): now = time.time() if id: return self.__parent.get(id) else: L = [R for R in self if R.expires >= now or R.expires == 0] if not L: raise dbi.NoRecordError return L def change(self, id, f): news = self.get(id) s = '%s: %s' % (news.subject, news.text) s = f(s) (news.subject, news.text) = s.split(': ', 1) self.set(id, news) NewsDB = plugins.DB('News', {'flat': DbiNewsDB}) class News(callbacks.Plugin): def __init__(self, irc): self.__parent = super(News, self) self.__parent.__init__(irc) self.db = NewsDB() def die(self): self.__parent.die() self.db.close() def add(self, irc, msg, args, channel, user, at, expires, news): """[] : Adds a given news item of to a channel with the given . If isn't 0, that news item will expire seconds from now. is only necessary if the message isn't sent in the channel itself. """ try: (subject, text) = news.split(': ', 1) except ValueError: raise callbacks.ArgumentError id = self.db.add(channel, subject, text, at, expires, user.id) irc.replySuccess(format('(News item #%i added)', id)) add = wrap(add, ['channeldb', 'user', 'now', 'expiry', 'text']) def news(self, irc, msg, args, channel, id): """[] [] Display the news items for in the format of '(#id) subject'. If is given, retrieve only that news item; otherwise retrieve all news items. is only necessary if the message isn't sent in the channel itself. """ if not id: try: records = self.db.get(channel) items = [format('(#%i) %s', R.id, R.subject) for R in records] s = format('News for %s: %s', channel, '; '.join(items)) irc.reply(s) except dbi.NoRecordError: irc.reply(format('No news for %s.', channel)) else: try: if id < 1: raise ValueError record = self.db.get(channel, id) irc.reply(str(record)) except dbi.NoRecordError, id: irc.errorInvalid('news item id', id) except ValueError: irc.errorInvalid('news item id', id, ' must be a positive integer.') news = wrap(news, ['channeldb', additional('int')]) def remove(self, irc, msg, args, channel, id): """[] Removes the news item with from . is only necessary if the message isn't sent in the channel itself. """ try: if id < 1: raise ValueError self.db.remove(channel, id) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid('news item id', id) except ValueError: irc.errorInvalid('news item id', id, ' must be a positive integer.') remove = wrap(remove, ['channeldb', 'int']) def change(self, irc, msg, args, channel, id, replacer): """[] Changes the news item with from according to the regular expression . should be of the form s/text/replacement/flags. is only necessary if the message isn't sent on the channel itself. """ try: if id < 1: raise ValueError self.db.change(channel, id, replacer) irc.replySuccess() except dbi.NoRecordError: irc.errorInvalid('news item id', id) except ValueError: irc.errorInvalid('news item id', id, ' must be a positive integer.') change = wrap(change, ['channeldb', 'int', 'regexpReplacer']) def old(self, irc, msg, args, channel, id): """[] [] Returns the old news item for with . If no number is given, returns all the old news items in reverse order. is only necessary if the message isn't sent in the channel itself. """ if id: try: if id < 1: raise ValueError record = self.db.getOld(channel, id) irc.reply(str(record)) except dbi.NoRecordError, id: irc.errorInvalid('news item id', id) except ValueError: irc.errorInvalid('news item id', id, ' must be a positive integer.') else: try: records = self.db.getOld(channel) items = [format('(#%i) %s', R.id, R.subject) for R in records] s = format('Old news for %s: %s', channel, '; '.join(items)) irc.reply(s) except dbi.NoRecordError: irc.reply(format('No old news for %s.', channel)) old = wrap(old, ['channeldb', additional('int')]) Class = News # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/News/config.py0000644000000000000000000000446111206611405016121 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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('News', True) News = conf.registerPlugin('News') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(News, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Herald/0000755000000000000000000000000011206611405014560 5ustar supybot-0.83.4.1.ds.orig/plugins/Herald/test.py0000644000000000000000000000332111206611405016110 0ustar ### # 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 * class HeraldTestCase(PluginTestCase): plugins = ('Herald',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Herald/__init__.py0000644000000000000000000000435211206611405016675 0ustar ### # 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. ### """ Greets users who join the channel with a recognized hostmask with a nice little greeting. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Herald/README.txt0000644000000000000000000000011711206611405016255 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Herald/plugin.py0000644000000000000000000002337411206611405016441 0ustar ### # 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 os import time import supybot.conf as conf import supybot.utils as utils import supybot.world as world import supybot.ircdb as ircdb from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.structures import TimeoutQueue filename = conf.supybot.directories.data.dirize('Herald.db') class HeraldDB(plugins.ChannelUserDB): def serialize(self, v): return [v] def deserialize(self, channel, id, L): if len(L) != 1: raise ValueError return L[0] class Herald(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Herald, self) self.__parent.__init__(irc) self.db = HeraldDB(filename) world.flushers.append(self.db.flush) self.lastParts = plugins.ChannelUserDictionary() splitTimeout = conf.supybot.plugins.Herald.throttle.afterSplit self.splitters = TimeoutQueue(splitTimeout) self.lastHerald = plugins.ChannelUserDictionary() def die(self): if self.db.flush in world.flushers: world.flushers.remove(self.db.flush) self.db.close() self.__parent.die() def doQuit(self, irc, msg): # We want to observe netsplits and keep from heralding users rejoining # after one. if ircmsgs.isSplit(msg): self.splitters.enqueue(msg.nick) try: id = ircdb.users.getUserId(msg.prefix) self.splitters.enqueue(id) except KeyError: pass def doJoin(self, irc, msg): if ircutils.strEqual(irc.nick, msg.nick): return # It's us. if msg.nick in self.splitters: self.log.debug('Not heralding %s, recent split.', msg.nick) return # Recently split. channel = msg.args[0] irc = callbacks.SimpleProxy(irc, msg) if self.registryValue('heralding', channel): try: id = ircdb.users.getUserId(msg.prefix) if id in self.splitters: self.log.debug('Not heralding id #%s, recent split.', id) return herald = self.db[channel, id] except KeyError: default = self.registryValue('default', channel) if default: default = ircutils.standardSubstitute(irc, msg, default) msgmaker = ircmsgs.privmsg if self.registryValue('default.notice', channel): msgmaker = ircmsgs.notice target = msg.nick if self.registryValue('default.public', channel): target = channel irc.queueMsg(msgmaker(target, default)) return now = time.time() throttle = self.registryValue('throttle', channel) if now - self.lastHerald.get((channel, id), 0) > throttle: if (channel, id) in self.lastParts: i = self.registryValue('throttle.afterPart', channel) if now - self.lastParts[channel, id] < i: return self.lastHerald[channel, id] = now herald = ircutils.standardSubstitute(irc, msg, herald) irc.reply(herald, prefixNick=False) def doPart(self, irc, msg): try: id = self._getId(irc, msg.prefix) self.lastParts[msg.args[0], id] = time.time() except KeyError: pass def _getId(self, irc, userNickHostmask): try: id = ircdb.users.getUserId(userNickHostmask) except KeyError: if not ircutils.isUserHostmask(userNickHostmask): hostmask = irc.state.nickToHostmask(userNickHostmask) id = ircdb.users.getUserId(hostmask) else: raise KeyError return id def default(self, irc, msg, args, channel, optlist, text): """[] [--remove|] If is given, sets the default herald to . A of "" will remove the default herald. If is not given, returns the current default herald. is only necessary if the message isn't sent in the channel itself. """ if optlist and text: raise callbacks.ArgumentError for (option, _) in optlist: if option == 'remove': self.setRegistryValue('default', '', channel) irc.replySuccess() return if text: self.setRegistryValue('default', text, channel) irc.replySuccess() else: resp = self.registryValue('default', channel) or \ 'I do not have a default herald set for %s.' % channel irc.reply(resp) default = wrap(default, ['channel', getopts({'remove': ''}), additional('text')]) def get(self, irc, msg, args, channel, user): """[] [] Returns the current herald message for (or the user is currently identified or recognized as). If is not given, defaults to the user giving the command. is only necessary if the message isn't sent in the channel itself. """ try: herald = self.db[channel, user.id] irc.reply(herald) except KeyError: irc.error('I have no herald for %s.' % user.name) get = wrap(get, ['channel', first('otherUser', 'user')]) def _preCheck(self, irc, msg, user): capability = self.registryValue('requireCapability') if capability: try: u = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) else: if u != user: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) # I chose not to make optional in this command because # if it's not a valid username (e.g., if the user tyops and misspells a # username), it may be nice not to clobber the user's herald. def add(self, irc, msg, args, channel, user, herald): """[] Sets the herald message for (or the user is currently identified or recognized as) to . is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) self.db[channel, user.id] = herald irc.replySuccess() add = wrap(add, ['channel', 'otherUser', 'text']) def remove(self, irc, msg, args, channel, user): """[] [] Removes the herald message set for , or the user is currently identified or recognized as. If is not given, defaults to the user giving the command. is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) try: del self.db[channel, user.id] irc.replySuccess() except KeyError: irc.error('I have no herald for that user.') remove = wrap(remove, ['channel', first('otherUser', 'user')]) def change(self, irc, msg, args, channel, user, changer): """[] [] Changes the herald message for , or the user is currently identified or recognized as, according to . If is not given, defaults to the calling user. is only necessary if the message isn't sent in the channel itself. """ self._preCheck(irc, msg, user) s = self.db[channel, user.id] newS = changer(s) self.db[channel, user.id] = newS irc.replySuccess() change = wrap(change, ['channel', first('otherUser', 'user'), 'regexpReplacer']) Class = Herald # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Herald/config.py0000644000000000000000000000721411206611405016403 0ustar ### # 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 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('Herald', True) Herald = conf.registerPlugin('Herald') conf.registerChannelValue(Herald, 'heralding', registry.Boolean(True, """Determines whether messages will be sent to the channel when a recognized user joins; basically enables or disables the plugin.""")) conf.registerGlobalValue(Herald, 'requireCapability', registry.String('', """Determines what capability (if any) is required to add/change/remove the herald of another user.""")) conf.registerChannelValue(Herald, 'throttle', registry.PositiveInteger(600, """Determines the minimum number of seconds between heralds.""")) conf.registerChannelValue(Herald.throttle, 'afterPart', registry.NonNegativeInteger(0, """Determines the minimum number of seconds after parting that the bot will not herald the person when he or she rejoins.""")) conf.registerChannelValue(Herald.throttle, 'afterSplit', registry.NonNegativeInteger(60, """Determines the minimum number of seconds after a netsplit that the bot will not herald the users that split.""")) conf.registerChannelValue(Herald, 'default', registry.String('', """Sets the default herald to use. If a user has a personal herald specified, that will be used instead. If set to the empty string, the default herald will be disabled.""")) conf.registerChannelValue(Herald.default, 'notice', registry.Boolean(True, """Determines whether the default herald will be sent as a NOTICE instead of a PRIVMSG.""")) conf.registerChannelValue(Herald.default, 'public', registry.Boolean(False, """Determines whether the default herald will be sent publicly.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Limiter/0000755000000000000000000000000011206611405014766 5ustar supybot-0.83.4.1.ds.orig/plugins/Limiter/test.py0000644000000000000000000000542111206611405016321 0ustar ### # Copyright (c) 2004-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 * class LimiterTestCase(ChannelPluginTestCase): plugins = ('Limiter',) config = {'supybot.plugins.Limiter.enable': True} def testEnforceLimit(self): origMin = conf.supybot.plugins.Limiter.minimumExcess() origMax = conf.supybot.plugins.Limiter.maximumExcess() try: conf.supybot.plugins.Limiter.minimumExcess.setValue(5) conf.supybot.plugins.Limiter.maximumExcess.setValue(10) self.irc.feedMsg(ircmsgs.join('#foo', prefix='foo!root@host')) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.limit('#foo', 1+10)) self.irc.feedMsg(ircmsgs.join('#foo', prefix='bar!root@host')) m = self.irc.takeMsg() self.failIf(m is not None) conf.supybot.plugins.Limiter.maximumExcess.setValue(7) self.irc.feedMsg(ircmsgs.part('#foo', prefix='bar!root@host')) m = self.irc.takeMsg() self.assertEqual(m, ircmsgs.limit('#foo', 1-5)) finally: conf.supybot.plugins.Limiter.minimumExcess.setValue(origMin) conf.supybot.plugins.Limiter.maximumExcess.setValue(origMax) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Limiter/__init__.py0000644000000000000000000000453011206611405017101 0ustar ### # Copyright (c) 2004-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. ### """ This plugin handles channel limits (MODE +l). """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Limiter/README.txt0000644000000000000000000000011711206611405016463 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Limiter/plugin.py0000644000000000000000000000651411206611405016644 0ustar ### # Copyright (c) 2004-2005, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Limiter(callbacks.Plugin): """In order to use this plugin, its config values need to be properly setup. supybot.plugins.Limiter.enable needs to be set to True and supybot.plugins.Limiter.{maximumExcess,minimumExcess} should be set to values appropriate to your channel (if the defaults aren't satisfactory. Once these are set, and someone enters/leaves the channel, Supybot will start setting the proper +l modes. """ def _enforce(self, irc, limit): irc.queueMsg(limit) irc.noReply() def _enforceLimit(self, irc, channel): if self.registryValue('enable', channel): maximum = self.registryValue('maximumExcess', channel) minimum = self.registryValue('minimumExcess', channel) assert maximum > minimum currentUsers = len(irc.state.channels[channel].users) currentLimit = irc.state.channels[channel].modes.get('l', 0) if currentLimit - currentUsers < minimum: self._enforce(irc, ircmsgs.limit(channel,currentUsers+maximum)) elif currentLimit - currentUsers > maximum: self._enforce(irc, ircmsgs.limit(channel,currentUsers+minimum)) def doJoin(self, irc, msg): if not ircutils.strEqual(msg.nick, irc.nick): irc = callbacks.SimpleProxy(irc, msg) self._enforceLimit(irc, msg.args[0]) doPart = doJoin doKick = doJoin def doQuit(self, irc, msg): for channel in irc.state.channels: self._enforceLimit(irc, channel) Class = Limiter # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Limiter/config.py0000644000000000000000000000562611206611405016616 0ustar ### # Copyright (c) 2004-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('Limiter', True) Limiter = conf.registerPlugin('Limiter') conf.registerChannelValue(Limiter, 'enable', registry.Boolean(False, """Determines whether the bot will maintain the channel limit to be slightly above the current number of people in the channel, in order to make clone/drone attacks harder.""")) conf.registerChannelValue(Limiter, 'minimumExcess', registry.PositiveInteger(5, """Determines the minimum number of free spots that will be saved when limits are being enforced. This should always be smaller than supybot.plugins.Limiter.limit.maximumExcess.""")) conf.registerChannelValue(Limiter, 'maximumExcess', registry.PositiveInteger(10, """Determines the maximum number of free spots that will be saved when limits are being enforced. This should always be larger than supybot.plugins.Limiter.limit.minimumExcess.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/MoobotFactoids/0000755000000000000000000000000011206611405016275 5ustar supybot-0.83.4.1.ds.orig/plugins/MoobotFactoids/test.py0000644000000000000000000003610211206611405017630 0ustar # -*- encoding: utf-8 -*- ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 supybot.plugin as plugin import supybot.ircutils as ircutils try: import sqlite except ImportError: sqlite = None MF = plugin.loadPluginModule('MoobotFactoids') MFconf = conf.supybot.plugins.MoobotFactoids class OptionListTestCase(SupyTestCase): maxIterations = 267 def _testOptions(self, s, L): max = self.maxIterations original = L[:] while max and L: max -= 1 option = MF.plugin.pickOptions(s) self.failUnless(option in original, 'Option %s not in %s' % (option, original)) if option in L: L.remove(option) self.failIf(L, 'Some options never seen: %s' % L) def testPickOptions(self): self._testOptions('(a|b)', ['a', 'b']) self._testOptions('a', ['a']) self._testOptions('(a|b (c|d))', ['a', 'b c', 'b d']) self._testOptions('(a|(b|)c)', ['a', 'bc', 'c']) self._testOptions('(a(b|)|(c|)d)', ['a', 'ab', 'cd', 'd']) self._testOptions('(a|)', ['a', '']) self._testOptions('(|a)', ['a', '']) self._testOptions('((a)|(b))', ['(a)', '(b)']) self._testOptions('^\\%(\\%(foo\\)\\@moo is moo') self.assertResponse('bar', 'moo is moo') # Check substitution self.assertNotError('who is $who') self.assertResponse('who', ircutils.nickFromHostmask(self.prefix)) # Check that actions ("\x01ACTION...") don't match m = ircmsgs.action(self.channel, 'is doing something') self.irc.feedMsg(m) self.assertNoResponse(' ', 1) def testLiteral(self): self.assertError('literal moo') # no factoids yet self.assertNotError('moo is foo') self.assertResponse('literal moo', 'foo') self.assertNotError('moo2 is moo!') self.assertResponse('literal moo2', 'moo!') self.assertNotError('moo3 is foo') self.assertResponse('literal moo3', 'foo') def testGetFactoid(self): self.assertNotError('moo is foo') self.assertResponse('moo', 'foo') self.assertNotError('moo2 is moo!') self.assertResponse('moo2', 'moo2 is moo!') self.assertNotError('moo3 is foo') self.assertAction('moo3', 'foo') # Test and make sure it's parsing self.assertNotError('moo4 is (1|2|3)') self.assertRegexp('moo4', '^(1|2|3)$') # Check case-insensitivity self.assertResponse('MOO', 'foo') self.assertResponse('mOo', 'foo') self.assertResponse('MoO', 'foo') # Check the "_is_" ability self.assertNotError('remove moo') self.assertNotError('moo _is_ foo') self.assertResponse('moo', 'foo') self.assertNotError('foo is bar _is_ baz') self.assertResponse('foo is bar', 'foo is bar is baz') def testFactinfo(self): self.assertNotError('moo is foo') self.assertRegexp('factinfo moo', '^moo: Created by tester on.*$') self.assertNotError('moo') self.assertRegexp('factinfo moo', self.prefix + '.*1 time') self.assertNotError('moo') self.assertRegexp('factinfo moo', self.prefix + '.*2 times') self.assertNotError('moo =~ s/foo/bar/') self.assertRegexp('factinfo moo', '^moo: Created by tester on' '.*?\. Last modified by tester on .*?\. ' 'Last requested by %s on .*?, ' 'requested 2 times.$' % self.prefix) self.assertNotError('lock moo') self.assertRegexp('factinfo moo', '^moo: Created by tester on' '.*?\. Last modified by tester on .*?\. ' 'Last requested by %s on .*?, ' 'requested 2 times. ' 'Locked by tester on .*\.$' % self.prefix) self.assertNotError('unlock moo') self.assertRegexp('factinfo moo', '^moo: Created by tester on' '.*?\. Last modified by tester on .*?\. ' 'Last requested by %s on .*?, ' 'requested 2 times.$' % self.prefix) # Make sure I solved this bug # Check and make sure all the other stuff is reset self.assertNotError('foo is bar') self.assertNotError('foo =~ s/bar/blah/') self.assertNotError('foo') self.assertNotError('no foo is baz') self.assertRegexp('factinfo foo', '^foo: Created by tester on' '(?!(request|modif)).*?\.$') def testLockUnlock(self): # disable world.testing since we want new users to not # magically be endowed with the admin capability try: world.testing = False self.assertNotError('moo is moo') self.assertNotError('lock moo') self.assertRegexp('factinfo moo', '^moo: Created by tester on' '.*?\. Locked by tester on .*?\.') # switch user original = self.prefix self.prefix = 'moo!moo@moo' self.assertNotError('register nottester moo', private=True) self.assertError('unlock moo') self.assertRegexp('factinfo moo', '^moo: Created by tester on' '.*?\. Locked by tester on .*?\.') # switch back self.prefix = original self.assertNotError('identify tester moo', private=True) self.assertNotError('unlock moo') self.assertRegexp('factinfo moo', '^moo: Created by tester on.*?\.') finally: world.testing = True def testChangeFactoid(self): self.assertNotError('moo is moo') self.assertNotError('moo =~ s/moo/moos/') self.assertResponse('moo', 'moos') self.assertNotError('moo =~ s/reply/action/') self.assertAction('moo', 'moos') self.assertNotError('moo =~ s/moos/(moos|woofs)/') self.assertActionRegexp('moo', '^(moos|woofs)$') self.assertError('moo =~ s/moo/') def testMost(self): userPrefix1 = 'moo!bar@baz'; userNick1 = 'moo' userPrefix2 = 'boo!bar@baz'; userNick2 = 'boo' self.assertNotError('register %s bar' % userNick1, frm=userPrefix1, private=True) self.assertNotError('register %s bar' % userNick2, frm=userPrefix2, private=True) # Check an empty database self.assertError('most popular') self.assertError('most authored') self.assertError('most recent') # Check singularity response self.prefix = userPrefix1 self.assertNotError('moogle is moo') self.assertError('most popular') self.assertResponse('most authored', 'Most prolific author: moo (1)') self.assertRegexp('most recent', "1 latest factoid:.*moogle") self.assertResponse('moogle', 'moo') self.assertRegexp('most popular', "Top 1 requested factoid:.*moogle.*(1)") # Check plural response self.prefix = userPrefix2 self.assertNotError('mogle is mo') self.assertRegexp('most authored', 'Most prolific authors:.*moo.*(1).*boo.*(1)') self.assertRegexp('most recent', "2 latest factoids:.*mogle.*moogle.*") self.assertResponse('moogle', 'moo') self.assertRegexp('most popular', "Top 1 requested factoid:.*moogle.*(2)") self.assertResponse('mogle', 'mo') self.assertRegexp('most popular', "Top 2 requested factoids:.*" "moogle.*(2).*mogle.*(1)") # Check most author ordering self.assertNotError('moo is oom') self.assertRegexp('most authored', 'Most prolific authors:.*boo.*(2).*moo.*(1)') def testListkeys(self): self.assertResponse('listkeys %', 'No keys matching "%" found.') self.assertNotError('moo is moo') # With this set, if only one key matches, it should respond with # the factoid orig = MFconf.showFactoidIfOnlyOneMatch() try: MFconf.showFactoidIfOnlyOneMatch.setValue(True) self.assertResponse('listkeys moo', 'moo') self.assertResponse('listkeys foo', 'No keys matching "foo" ' 'found.') # Throw in a bunch more for i in range(10): self.assertNotError('moo%s is moo' % i) self.assertRegexp('listkeys moo', '^Key search for "moo" ' '\(11 found\): ("moo\d*", )+and "moo9"$') self.assertNotError('foo is bar') self.assertRegexp('listkeys %', '^Key search for "\%" ' '\(12 found\): "foo", ("moo\d*", )+and ' '"moo9"$') # Check quoting self.assertNotError('foo\' is bar') self.assertResponse('listkeys foo', 'Key search for "foo" ' '(2 found): "foo" and "foo\'"') # Check unicode stuff self.assertResponse('listkeys Б', 'No keys matching "Б" found.') self.assertNotError('АБВГДЕЖ is foo') self.assertNotError('АБВГДЕЖЗИ is foo') self.assertResponse('listkeys Б', 'Key search for "Б" ' '(2 found): "АБВГДЕЖ" and "АБВГДЕЖЗИ"') finally: MFconf.showFactoidIfOnlyOneMatch.setValue(orig) def testListvalues(self): self.assertNotError('moo is moo') self.assertResponse('listvalues moo', 'Value search for "moo" (1 found): "moo"') def testListauth(self): self.assertNotError('moo is moo') self.assertRegexp('listauth tester', 'tester.*\(1 found\):.*moo') self.assertError('listauth moo') def testRemove(self): self.assertNotError('moo is moo') self.assertNotError('lock moo') self.assertError('remove moo') self.assertNotError('unlock moo') self.assertNotError('remove moo') def testAugmentFactoid(self): self.assertNotError('moo is foo') self.assertNotError('moo is also bar') self.assertResponse('moo', 'moo is foo, or bar') self.assertNotError('moo is bar _is_ foo') self.assertNotError('moo is bar is also foo') self.assertResponse('moo is bar', 'moo is bar is foo, or foo') def testReplaceFactoid(self): self.assertNotError('moo is foo') self.assertNotError('no moo is bar') self.assertResponse('moo', 'moo is bar') self.assertNotError('no, moo is baz') self.assertResponse('moo', 'moo is baz') self.assertNotError('lock moo') self.assertError('no moo is qux') self.assertNotError('foo is bar _is_ foo') self.assertNotError('no foo is bar _is_ baz') self.assertResponse('foo is bar', 'foo is bar is baz') def testRegexpNotCalledIfAlreadyHandled(self): self.assertResponse('echo foo is bar', 'foo is bar') self.assertNoResponse(' ', 3) def testNoResponseToCtcp(self): self.assertNotError('foo is bar') self.assertResponse('foo', 'foo is bar') self.irc.feedMsg(ircmsgs.privmsg(self.irc.nick, '\x01VERSION\x01')) m = self.irc.takeMsg() self.failIf(m) def testAddFactoidNotCalledWithBadNestingSyntax(self): self.assertError('re s/Error:.*/jbm is a tard/ ]') self.assertNoResponse(' ', 3) def testConfigShowFactoidIfOnlyOneMatch(self): # man these are long MFconf = conf.supybot.plugins.MoobotFactoids self.assertNotError('foo is bar') # Default to saying the factoid value self.assertResponse('listkeys foo', 'foo is bar') # Check the False setting MFconf.showFactoidIfOnlyOneMatch.setValue(False) self.assertResponse('listkeys foo', 'Key search for "foo" ' '(1 found): "foo"') def testRandom(self): self.assertNotError('foo is bar') self.assertNotError('bar is baz') self.assertRegexp('random', r'bar|baz') # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/plugins/MoobotFactoids/__init__.py0000644000000000000000000000471311206611405020413 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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. ### """ Moobot factoid compatibility module. Moobot's factoids were originally designed to emulate Blootbot's factoids, so in either case, you should find this plugin comfortable. """ 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__ = "0.1" __author__ = supybot.authors.strike # 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=8 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/plugins/MoobotFactoids/README.txt0000644000000000000000000000011711206611405017772 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/MoobotFactoids/plugin.py0000644000000000000000000006561111206611405020156 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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 time import shlex import string from cStringIO import StringIO import supybot.conf as conf import supybot.ircdb as ircdb import supybot.utils as utils from supybot.commands import * import supybot.plugins as plugins import supybot.ircutils as ircutils import supybot.callbacks as callbacks allchars = string.maketrans('', '') class OptionList(object): validChars = allchars.translate(allchars, '|()') def _insideParens(self, lexer): ret = [] while True: token = lexer.get_token() if not token: return '(%s' % ''.join(ret) #) elif token == ')': if '|' in ret: L = map(''.join, utils.iter.split('|'.__eq__, ret, yieldEmpty=True)) return utils.iter.choice(L) else: return '(%s)' % ''.join(ret) elif token == '(': ret.append(self._insideParens(lexer)) elif token == '|': ret.append(token) else: ret.append(token) def tokenize(self, s): lexer = shlex.shlex(StringIO(s)) lexer.commenters = '' lexer.quotes = '' lexer.whitespace = '' lexer.wordchars = self.validChars ret = [] while True: token = lexer.get_token() if not token: break elif token == '(': ret.append(self._insideParens(lexer)) else: ret.append(token) return ''.join(ret) def pickOptions(s): return OptionList().tokenize(s) class SqliteMoobotDB(object): def __init__(self, filename): self.filename = filename self.dbs = ircutils.IrcDict() def close(self): for db in self.dbs.itervalues(): db.close() self.dbs.clear() 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 ' if channel in self.dbs: return self.dbs[channel] filename = plugins.makeChannelFilename(self.filename, channel) if os.path.exists(filename): self.dbs[channel] = sqlite.connect(filename) return self.dbs[channel] db = sqlite.connect(filename) self.dbs[channel] = db cursor = db.cursor() cursor.execute("""CREATE TABLE factoids ( key TEXT PRIMARY KEY, created_by INTEGER, created_at TIMESTAMP, modified_by INTEGER, modified_at TIMESTAMP, locked_at TIMESTAMP, locked_by INTEGER, last_requested_by TEXT, last_requested_at TIMESTAMP, fact TEXT, requested_count INTEGER )""") db.commit() return db def getFactoid(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact FROM factoids WHERE key LIKE %s""", key) if cursor.rowcount == 0: return None else: return cursor.fetchall()[0] def getFactinfo(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT created_by, created_at, modified_by, modified_at, last_requested_by, last_requested_at, requested_count, locked_by, locked_at FROM factoids WHERE key LIKE %s""", key) if cursor.rowcount == 0: return None else: return cursor.fetchone() def randomFactoid(self, channel): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT fact, key FROM factoids ORDER BY random() LIMIT 1""") if cursor.rowcount == 0: return None else: return cursor.fetchone() def addFactoid(self, channel, key, value, creator_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""INSERT INTO factoids VALUES (%s, %s, %s, NULL, NULL, NULL, NULL, NULL, NULL, %s, 0)""", key, creator_id, int(time.time()), value) db.commit() def updateFactoid(self, channel, key, newvalue, modifier_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids SET fact=%s, modified_by=%s, modified_at=%s WHERE key LIKE %s""", newvalue, modifier_id, int(time.time()), key) db.commit() def updateRequest(self, channel, key, hostmask): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids SET last_requested_by = %s, last_requested_at = %s, requested_count = requested_count + 1 WHERE key = %s""", hostmask, int(time.time()), key) db.commit() def removeFactoid(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""DELETE FROM factoids WHERE key LIKE %s""", key) db.commit() def locked(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute ("""SELECT locked_by FROM factoids WHERE key LIKE %s""", key) if cursor.fetchone()[0] is None: return False else: return True def lock(self, channel, key, locker_id): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids SET locked_by=%s, locked_at=%s WHERE key LIKE %s""", locker_id, int(time.time()), key) db.commit() def unlock(self, channel, key): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""UPDATE factoids SET locked_by=%s, locked_at=%s WHERE key LIKE %s""", None, None, key) db.commit() def mostAuthored(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT created_by, count(key) FROM factoids GROUP BY created_by ORDER BY count(key) DESC LIMIT %s""", limit) return cursor.fetchall() def mostRecent(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT key FROM factoids ORDER BY created_at DESC LIMIT %s""", limit) return cursor.fetchall() def mostPopular(self, channel, limit): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT key, requested_count FROM factoids WHERE requested_count > 0 ORDER BY requested_count DESC LIMIT %s""", limit) if cursor.rowcount == 0: return [] else: return cursor.fetchall() def getKeysByAuthor(self, channel, authorId): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT key FROM factoids WHERE created_by=%s ORDER BY key""", authorId) if cursor.rowcount == 0: return [] else: return cursor.fetchall() def getKeysByGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() glob = '%%%s%%' % glob cursor.execute("""SELECT key FROM factoids WHERE key LIKE %s ORDER BY key""", glob) if cursor.rowcount == 0: return [] else: return cursor.fetchall() def getKeysByValueGlob(self, channel, glob): db = self._getDb(channel) cursor = db.cursor() glob = '%%%s%%' % glob cursor.execute("""SELECT key FROM factoids WHERE fact LIKE %s ORDER BY key""", glob) if cursor.rowcount == 0: return [] else: return cursor.fetchall() MoobotDB = plugins.DB('MoobotFactoids', {'sqlite': SqliteMoobotDB}) class MoobotFactoids(callbacks.Plugin): """Add the help for "@help MoobotFactoids" here (assuming you don't implement a MoobotFactoids command). This should describe *how* to use this plugin.""" callBefore = ['Dunno'] def __init__(self, irc): self.db = MoobotDB() self.__parent = super(MoobotFactoids, self) self.__parent.__init__(irc) def die(self): self.__parent.die() self.db.close() def reset(self): self.db.close() _replyTag = '' _actionTag = '' def _parseFactoid(self, irc, msg, fact): type = 'define' # Default is to just spit the factoid back as a # definition of what the key is (i.e., "foo is bar") newfact = pickOptions(fact) if newfact.startswith(self._replyTag): newfact = newfact[len(self._replyTag):] type = 'reply' elif newfact.startswith(self._actionTag): newfact = newfact[len(self._actionTag):] type = 'action' newfact = newfact.strip() newfact = ircutils.standardSubstitute(irc, msg, newfact) return (type, newfact) def invalidCommand(self, irc, msg, tokens): if '=~' in tokens: self.changeFactoid(irc, msg, tokens) elif tokens and tokens[0] in ('no', 'no,'): self.replaceFactoid(irc, msg, tokens) elif ['is', 'also'] in utils.seq.window(tokens, 2): self.augmentFactoid(irc, msg, tokens) else: key = ' '.join(tokens) key = self._sanitizeKey(key) channel = plugins.getChannel(msg.args[0]) fact = self.db.getFactoid(channel, key) if fact: self.db.updateRequest(channel, key, msg.prefix) # getFactoid returns "all results", so we need to extract the # first one. fact = fact[0] # Update the requested count/requested by for this key hostmask = msg.prefix # Now actually get the factoid and respond accordingly (type, text) = self._parseFactoid(irc, msg, fact) if type == 'action': irc.reply(text, action=True) elif type == 'reply': irc.reply(text, prefixNick=False) elif type == 'define': irc.reply(format('%s is %s', key, text), prefixNick=False) else: assert False, 'Spurious type from _parseFactoid' else: if 'is' in tokens or '_is_' in tokens: self.addFactoid(irc, msg, tokens) def _getUserId(self, irc, prefix): try: return ircdb.users.getUserId(prefix) except KeyError: irc.errorNotRegistered(Raise=True) def _sanitizeKey(self, key): return key.rstrip('!? ') def _checkNotLocked(self, irc, channel, key): if self.db.locked(channel, key): irc.error(format('Factoid %q is locked.', key), Raise=True) def _getFactoid(self, irc, channel, key): fact = self.db.getFactoid(channel, key) if fact is not None: return fact else: irc.error(format('Factoid %q not found.', key), Raise=True) def _getKeyAndFactoid(self, tokens): if '_is_' in tokens: p = '_is_'.__eq__ elif 'is' in tokens: p = 'is'.__eq__ else: self.log.debug('Invalid tokens for {add,replace}Factoid: %s.', tokens) s = 'Missing an \'is\' or \'_is_\'.' raise ValueError, s (key, newfact) = map(' '.join, utils.iter.split(p, tokens, maxsplit=1)) key = self._sanitizeKey(key) return (key, newfact) def addFactoid(self, irc, msg, tokens): # First, check and see if the entire message matches a factoid key channel = plugins.getChannel(msg.args[0]) id = self._getUserId(irc, msg.prefix) try: (key, fact) = self._getKeyAndFactoid(tokens) except ValueError, e: irc.error(str(e), Raise=True) # Check and make sure it's not in the DB already if self.db.getFactoid(channel, key): irc.error(format('Factoid %q already exists.', key), Raise=True) self.db.addFactoid(channel, key, fact, id) irc.replySuccess() def changeFactoid(self, irc, msg, tokens): id = self._getUserId(irc, msg.prefix) (key, regexp) = map(' '.join, utils.iter.split('=~'.__eq__, tokens, maxsplit=1)) channel = plugins.getChannel(msg.args[0]) # Check and make sure it's in the DB fact = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) # It's fair game if we get to here try: r = utils.str.perlReToReplacer(regexp) except ValueError, e: irc.errorInvalid('regexp', regexp, Raise=True) fact = fact[0] new_fact = r(fact) self.db.updateFactoid(channel, key, new_fact, id) irc.replySuccess() def augmentFactoid(self, irc, msg, tokens): # Must be registered! id = self._getUserId(irc, msg.prefix) pairs = list(utils.seq.window(tokens, 2)) isAlso = pairs.index(['is', 'also']) key = ' '.join(tokens[:isAlso]) new_text = ' '.join(tokens[isAlso+2:]) channel = plugins.getChannel(msg.args[0]) fact = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) # It's fair game if we get to here fact = fact[0] new_fact = format('%s, or %s', fact, new_text) self.db.updateFactoid(channel, key, new_fact, id) irc.replySuccess() def replaceFactoid(self, irc, msg, tokens): # Must be registered! channel = plugins.getChannel(msg.args[0]) id = self._getUserId(irc, msg.prefix) del tokens[0] # remove the "no," try: (key, fact) = self._getKeyAndFactoid(tokens) except ValueError, e: irc.error(str(e), Raise=True) _ = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) self.db.removeFactoid(channel, key) self.db.addFactoid(channel, key, fact, id) irc.replySuccess() def literal(self, irc, msg, args, channel, key): """[] Returns the literal factoid for the given factoid key. No parsing of the factoid value is done as it is with normal retrieval. is only necessary if the message isn't sent in the channel itself. """ fact = self._getFactoid(irc, channel, key) fact = fact[0] irc.reply(fact) literal = wrap(literal, ['channeldb', 'text']) def factinfo(self, irc, msg, args, channel, key): """[] Returns the various bits of info on the factoid for the given key. is only necessary if the message isn't sent in the channel itself. """ # Start building the response string s = key + ': ' # Next, get all the info and build the response piece by piece info = self.db.getFactinfo(channel, key) if not info: irc.error(format('No such factoid: %q', key)) return (created_by, created_at, modified_by, modified_at, last_requested_by, last_requested_at, requested_count, locked_by, locked_at) = info # First, creation info. # Map the integer created_by to the username created_by = plugins.getUserName(created_by) created_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(created_at))) s += format('Created by %s on %s.', created_by, created_at) # Next, modification info, if any. if modified_by is not None: modified_by = plugins.getUserName(modified_by) modified_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(modified_at))) s += format(' Last modified by %s on %s.',modified_by, modified_at) # Next, last requested info, if any if last_requested_by is not None: last_by = last_requested_by # not an int user id last_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(last_requested_at))) req_count = requested_count s += format(' Last requested by %s on %s, requested %n.', last_by, last_at, (requested_count, 'time')) # Last, locked info if locked_at is not None: lock_at = time.strftime(conf.supybot.reply.format.time(), time.localtime(int(locked_at))) lock_by = plugins.getUserName(locked_by) s += format(' Locked by %s on %s.', lock_by, lock_at) irc.reply(s) factinfo = wrap(factinfo, ['channeldb', 'text']) def _lock(self, irc, msg, channel, user, key, locking=True): #self.log.debug('in _lock') #self.log.debug('id: %s', id) id = user.id info = self.db.getFactinfo(channel, key) if not info: irc.error(format('No such factoid: %q', key)) return (created_by, _, _, _, _, _, _, locked_by, _) = info # Don't perform redundant operations if locking and locked_by is not None: irc.error(format('Factoid %q is already locked.', key)) return if not locking and locked_by is None: irc.error(format('Factoid %q is not locked.', key)) return # Can only lock/unlock own factoids unless you're an admin #self.log.debug('admin?: %s', ircdb.checkCapability(id, 'admin')) #self.log.debug('created_by: %s', created_by) if not (ircdb.checkCapability(id, 'admin') or created_by == id): if locking: s = 'lock' else: s = 'unlock' irc.error(format('Cannot %s someone else\'s factoid unless you ' 'are an admin.', s)) return # Okay, we're done, ready to lock/unlock if locking: self.db.lock(channel, key, id) else: self.db.unlock(channel, key) irc.replySuccess() def lock(self, irc, msg, args, channel, user, key): """[] Locks the factoid with the given factoid key. Requires that the user be registered and have created the factoid originally. is only necessary if the message isn't sent in the channel itself. """ self._lock(irc, msg, channel, user, key, True) lock = wrap(lock, ['channeldb', 'user', 'text']) def unlock(self, irc, msg, args, channel, user, key): """[] Unlocks the factoid with the given factoid key. Requires that the user be registered and have locked the factoid. is only necessary if the message isn't sent in the channel itself. """ self._lock(irc, msg, channel, user, key, False) unlock = wrap(unlock, ['channeldb', 'user', 'text']) def most(self, irc, msg, args, channel, method): """[] {popular|authored|recent} Lists the most {popular|authored|recent} factoids. "popular" lists the most frequently requested factoids. "authored" lists the author with the most factoids. "recent" lists the most recently created factoids. is only necessary if the message isn't sent in the channel itself. """ method = method.capitalize() method = getattr(self, '_most%s' % method, None) if method is None: raise callbacks.ArgumentError limit = self.registryValue('mostCount', channel) method(irc, channel, limit) most = wrap(most, ['channeldb', ('literal', ('popular', 'authored', 'recent'))]) def _mostAuthored(self, irc, channel, limit): results = self.db.mostAuthored(channel, limit) L = ['%s (%s)' % (plugins.getUserName(t[0]), int(t[1])) for t in results] if L: author = 'author' if len(L) != 1: author = 'authors' irc.reply(format('Most prolific %s: %L', author, L)) else: irc.error('There are no factoids in my database.') def _mostRecent(self, irc, channel, limit): results = self.db.mostRecent(channel, limit) L = [format('%q', t[0]) for t in results] if L: irc.reply(format('%n: %L', (len(L), 'latest', 'factoid'), L)) else: irc.error('There are no factoids in my database.') def _mostPopular(self, irc, channel, limit): results = self.db.mostPopular(channel, limit) L = [format('%q (%s)', t[0], t[1]) for t in results] if L: irc.reply( format('Top %n: %L', (len(L), 'requested', 'factoid'), L)) else: irc.error('No factoids have been requested from my database.') def listauth(self, irc, msg, args, channel, author): """[] Lists the keys of the factoids with the given author. Note that if an author has an integer name, you'll have to use that author's id to use this function (so don't use integer usernames!). is only necessary if the message isn't sent in the channel itself. """ try: id = ircdb.users.getUserId(author) except KeyError: irc.errorNoUser(name=author, Raise=True) results = self.db.getKeysByAuthor(channel, id) if not results: irc.reply(format('No factoids by %q found.', author)) return keys = [format('%q', t[0]) for t in results] s = format('Author search for %q (%i found): %L', author, len(keys), keys) irc.reply(s) listauth = wrap(listauth, ['channeldb', 'something']) def listkeys(self, irc, msg, args, channel, search): """[] Lists the keys of the factoids whose key contains the provided text. is only necessary if the message isn't sent in the channel itself. """ results = self.db.getKeysByGlob(channel, search) if not results: irc.reply(format('No keys matching %q found.', search)) elif len(results) == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel): key = results[0][0] self.invalidCommand(irc, msg, [key]) else: keys = [format('%q', tup[0]) for tup in results] s = format('Key search for %q (%i found): %L', search, len(keys), keys) irc.reply(s) listkeys = wrap(listkeys, ['channeldb', 'text']) def listvalues(self, irc, msg, args, channel, search): """[] Lists the keys of the factoids whose value contains the provided text. is only necessary if the message isn't sent in the channel itself. """ results = self.db.getKeysByValueGlob(channel, search) if not results: irc.reply(format('No values matching %q found.', search)) return keys = [format('%q', tup[0]) for tup in results] s = format('Value search for %q (%i found): %L', search, len(keys), keys) irc.reply(s) listvalues = wrap(listvalues, ['channeldb', 'text']) def remove(self, irc, msg, args, channel, _, key): """[] Deletes the factoid with the given key. is only necessary if the message isn't sent in the channel itself. """ _ = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) self.db.removeFactoid(channel, key) irc.replySuccess() remove = wrap(remove, ['channeldb', 'user', 'text']) def random(self, irc, msg, args, channel): """[] Displays a random factoid (along with its key) from the database. is only necessary if the message isn't sent in the channel itself. """ results = self.db.randomFactoid(channel) if not results: irc.error('No factoids in the database.') return (fact, key) = results irc.reply(format('Random factoid: %q is %q', key, fact)) random = wrap(random, ['channeldb']) Class = MoobotFactoids # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78: supybot-0.83.4.1.ds.orig/plugins/MoobotFactoids/config.py0000644000000000000000000000500111206611405020110 0ustar ### # Copyright (c) 2003-2005, Daniel DiPaolo # 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('MoobotFactoids', True) MoobotFactoids = conf.registerPlugin('MoobotFactoids') conf.registerChannelValue(MoobotFactoids, 'showFactoidIfOnlyOneMatch', registry.Boolean(True, """Determines whether or not the factoid value will be shown when a listkeys search returns only one factoid key.""")) conf.registerChannelValue(MoobotFactoids, 'mostCount', registry.Integer(10, """Determines how many items are shown when the 'most' command is called.""")) # vim:set shiftwidth=4 softtabstop=8 expandtab textwidth=78 supybot-0.83.4.1.ds.orig/plugins/BadWords/0000755000000000000000000000000011206611405015066 5ustar supybot-0.83.4.1.ds.orig/plugins/BadWords/test.py0000644000000000000000000000641011206611405016420 0ustar ### # 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: supybot-0.83.4.1.ds.orig/plugins/BadWords/__init__.py0000644000000000000000000000457211206611405017207 0ustar ### # 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: supybot-0.83.4.1.ds.orig/plugins/BadWords/README.txt0000644000000000000000000000032511206611405016564 0ustar 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. supybot-0.83.4.1.ds.orig/plugins/BadWords/plugin.py0000644000000000000000000001310011206611405016731 0ustar ### # Copyright (c) 2002-2004, Jeremiah Fincher # Copyright (c) 2009, James Vega # 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 # We need to check for bad words here rather than in doPrivmsg because # messages don't get to doPrivmsg is the user is ignored. if msg.command == 'PRIVMSG': self.updateRegexp() s = ircutils.stripFormatting(msg.args[1]) channel = msg.args[0] if ircutils.isChannel(channel) and self.registryValue('kick', channel): if self.regexp.search(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.warning('Should kick %s from %s, but not opped.', msg.nick, channel) return msg def updateRegexp(self): if self.lastModified < self.words.lastModified: self.makeRegexp(self.words()) self.lastModified = time.time() def outFilter(self, irc, msg): if self.filtering and msg.command == 'PRIVMSG': self.updateRegexp() s = msg.args[1] if self.registryValue('stripFormatting'): s = ircutils.stripFormatting(s) t = self.regexp.sub(self.sub, s) if t != s: msg = ircmsgs.privmsg(msg.args[0], t, msg=msg) return 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: supybot-0.83.4.1.ds.orig/plugins/BadWords/config.py0000644000000000000000000001167411206611405016716 0ustar ### # 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.NormalizedString("""You have been kicked for using a word prohibited in the presence of this bot. Please use more appropriate language in the future.""", """Determines the kick message used by the bot when kicking users for saying bad words.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Relay/0000755000000000000000000000000011206611405014435 5ustar supybot-0.83.4.1.ds.orig/plugins/Relay/test.py0000644000000000000000000000331711206611405015772 0ustar ### # 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 RelayTestCase(PluginTestCase): plugins = ('Relay',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Relay/__init__.py0000644000000000000000000000451011206611405016546 0ustar ### # 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. ### """ Handles relaying between networks. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Relay/README.txt0000644000000000000000000000011711206611405016132 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Relay/plugin.py0000644000000000000000000005062411206611405016314 0ustar ### # 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 time import supybot.conf as conf import supybot.utils as utils import supybot.world as world from supybot.commands import * import supybot.irclib as irclib import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks from supybot.utils.structures import MultiSet, TimeoutQueue class Relay(callbacks.Plugin): noIgnore = True def __init__(self, irc): self.__parent = super(Relay, self) self.__parent.__init__(irc) self._whois = {} self.lastmsg = {} self.ircstates = {} self.queuedTopics = MultiSet() self.lastRelayMsgs = ircutils.IrcDict() def __call__(self, irc, msg): try: irc = self._getRealIrc(irc) if irc not in self.ircstates: self._addIrc(irc) self.ircstates[irc].addMsg(irc, self.lastmsg[irc]) finally: self.lastmsg[irc] = msg self.__parent.__call__(irc, msg) def do376(self, irc, msg): networkGroup = conf.supybot.networks.get(irc.network) for channel in self.registryValue('channels'): if self.registryValue('channels.joinOnAllNetworks', channel): if channel not in irc.state.channels: irc.queueMsg(networkGroup.channels.join(channel)) do377 = do422 = do376 def _getRealIrc(self, irc): if isinstance(irc, irclib.Irc): return irc else: return irc.getRealIrc() def _getIrcName(self, irc): # We should allow abbreviations at some point. return irc.network def _addIrc(self, irc): # Let's just be extra-special-careful here. if irc not in self.ircstates: self.ircstates[irc] = irclib.IrcState() if irc not in self.lastmsg: self.lastmsg[irc] = ircmsgs.ping('this is just a fake message') if irc.afterConnect: # We've probably been reloaded. Let's send some messages to get # our IrcState objects up to current. for channel in self.registryValue('channels'): irc.queueMsg(ircmsgs.who(channel)) irc.queueMsg(ircmsgs.names(channel)) def join(self, irc, msg, args, channel): """[] Starts relaying between the channel on all networks. If on a network the bot isn't in , he'll join. This commands is required even if the bot is in the channel on both networks; he won't relay between those channels unless he's told to join both channels. If is not given, starts relaying on the channel the message was sent in. """ self.registryValue('channels').add(channel) for otherIrc in world.ircs: if channel not in otherIrc.state.channels: networkGroup = conf.supybot.networks.get(otherIrc.network) otherIrc.queueMsg(networkGroup.channels.join(channel)) irc.replySuccess() join = wrap(join, ['channel', 'admin']) def part(self, irc, msg, args, channel): """ Ceases relaying between the channel on all networks. The bot will part from the channel on all networks in which it is on the channel. """ self.registryValue('channels').discard(channel) for otherIrc in world.ircs: if channel in otherIrc.state.channels: otherIrc.queueMsg(ircmsgs.part(channel)) irc.replySuccess() part = wrap(part, ['channel', 'admin']) def nicks(self, irc, msg, args, channel): """[] Returns the nicks of the people in the channel on the various networks the bot is connected to. is only necessary if the message isn't sent on the channel itself. """ realIrc = self._getRealIrc(irc) if channel not in self.registryValue('channels'): irc.error(format('I\'m not relaying in %s.', channel)) return users = [] for otherIrc in world.ircs: network = self._getIrcName(otherIrc) ops = [] halfops = [] voices = [] usersS = [] if network != self._getIrcName(realIrc): try: Channel = otherIrc.state.channels[channel] except KeyError: users.append(format('(not in %s on %s)',channel,network)) continue numUsers = 0 for s in Channel.users: s = s.strip() if not s: continue numUsers += 1 if s in Channel.ops: ops.append('@' + s) elif s in Channel.halfops: halfops.append('%' + s) elif s in Channel.voices: voices.append('+' + s) else: usersS.append(s) utils.sortBy(ircutils.toLower, ops) utils.sortBy(ircutils.toLower, voices) utils.sortBy(ircutils.toLower, halfops) utils.sortBy(ircutils.toLower, usersS) usersS = ', '.join(filter(None, map(', '.join, (ops,halfops,voices,usersS)))) users.append(format('%s (%i): %s', ircutils.bold(network), numUsers, usersS)) users.sort() irc.reply('; '.join(users)) nicks = wrap(nicks, ['channel']) def do311(self, irc, msg): irc = self._getRealIrc(irc) nick = ircutils.toLower(msg.args[1]) if (irc, nick) not in self._whois: return else: self._whois[(irc, nick)][-1][msg.command] = msg # These are all sent by a WHOIS response. do301 = do311 do312 = do311 do317 = do311 do319 = do311 do320 = do311 def do318(self, irc, msg): irc = self._getRealIrc(irc) nick = msg.args[1] loweredNick = ircutils.toLower(nick) if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] hostmask = '@'.join(d['311'].args[2:4]) user = d['311'].args[-1] if '319' in d: channels = d['319'].args[-1].split() ops = [] voices = [] normal = [] halfops = [] for channel in channels: if channel.startswith('@'): ops.append(channel[1:]) elif channel.startswith('%'): halfops.append(channel[1:]) elif channel.startswith('+'): voices.append(channel[1:]) else: normal.append(channel) L = [] if ops: L.append(format('is an op on %L', ops)) if halfops: L.append(format('is a halfop on %L', halfups)) if voices: L.append(format('is voiced on %L', voices)) if normal: if L: L.append(format('is also on %L', normal)) else: L.append(format('is on %L', normal)) else: L = ['isn\'t on any non-secret channels'] channels = format('%L', L) if '317' in d: idle = utils.timeElapsed(d['317'].args[2]) signon = time.strftime(conf.supybot.reply.format.time(), time.localtime(float(d['317'].args[3]))) else: idle = '' signon = '' if '312' in d: server = d['312'].args[2] else: server = '' if '301' in d: away = format(' %s is away: %s.', nick, d['301'].args[2]) else: away = '' if '320' in d: if d['320'].args[2]: identify = ' identified' else: identify = '' else: identify = '' s = format('%s (%s) has been%s on server %s since %s (idle for %s) ' 'and %s.%s', user, hostmask, identify, server, signon, idle, channels, away) replyIrc.reply(s) del self._whois[(irc, loweredNick)] def do402(self, irc, msg): irc = self._getRealIrc(irc) nick = msg.args[1] loweredNick = ircutils.toLower(nick) if (irc, loweredNick) not in self._whois: return (replyIrc, replyMsg, d) = self._whois[(irc, loweredNick)] del self._whois[(irc, loweredNick)] s = format('There is no %s on %s.', nick, self._getIrcName(irc)) replyIrc.reply(s) do401 = do402 def _formatPrivmsg(self, nick, network, msg): channel = msg.args[0] if self.registryValue('includeNetwork', channel): network = '@' + network else: network = '' # colorize nicks color = self.registryValue('color', channel) # Also used further down. if color: nick = ircutils.IrcString(nick) newnick = ircutils.mircColor(nick, *ircutils.canonicalColor(nick)) colors = ircutils.canonicalColor(nick, shift=4) nick = newnick if ircmsgs.isAction(msg): if color: t = ircutils.mircColor('*', *colors) else: t = '*' s = format('%s %s%s %s', t, nick, network, ircmsgs.unAction(msg)) else: if color: lt = ircutils.mircColor('<', *colors) gt = ircutils.mircColor('>', *colors) else: lt = '<' gt = '>' s = format('%s%s%s%s %s', lt, nick, network, gt, msg.args[1]) return s def _sendToOthers(self, irc, msg): assert msg.command in ('PRIVMSG', 'NOTICE', 'TOPIC') for otherIrc in world.ircs: if otherIrc != irc and not otherIrc.zombie: if msg.args[0] in otherIrc.state.channels: msg.tag('relayedMsg') otherIrc.queueMsg(msg) def _checkRelayMsg(self, msg): channel = msg.args[0] if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] unformatted = ircutils.stripFormatting(msg.args[1]) normalized = utils.str.normalizeWhitespace(unformatted) for s in q: if s in normalized: return True return False def _punishRelayers(self, msg): assert self._checkRelayMsg(msg), 'Punishing without checking.' who = msg.prefix channel = msg.args[0] def notPunishing(irc, s, *args): self.log.info('Not punishing %s in %s on %s: %s.', msg.prefix, channel, irc.network, s, *args) for irc in world.ircs: if channel in irc.state.channels: if irc.nick in irc.state.channels[channel].ops: if who in irc.state.channels[channel].bans: notPunishing(irc, 'already banned') else: self.log.info('Punishing %s in %s on %s for relaying.', who, channel, irc.network) irc.sendMsg(ircmsgs.ban(channel, who)) kmsg = 'You seem to be relaying, punk.' irc.sendMsg(ircmsgs.kick(channel, msg.nick, kmsg)) else: notPunishing(irc, 'not opped') def doPrivmsg(self, irc, msg): (channel, text) = msg.args if irc.isChannel(channel): irc = self._getRealIrc(irc) if channel not in self.registryValue('channels'): return ignores = self.registryValue('ignores', channel) for ignore in ignores: if ircutils.hostmaskPatternEqual(ignore, msg.prefix): self.log.debug('Refusing to relay %s, ignored by %s.', msg.prefix, ignore) return if ircmsgs.isCtcp(msg) and \ 'AWAY' not in text and 'ACTION' not in text: return # Let's try to detect other relay bots. if self._checkRelayMsg(msg): if self.registryValue('punishOtherRelayBots', channel): self._punishRelayers(msg) # Either way, we don't relay the message. else: self.log.warning('Refusing to relay message from %s, ' 'it appears to be a relay message.', msg.prefix) else: network = self._getIrcName(irc) s = self._formatPrivmsg(msg.nick, network, msg) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def _msgmaker(self, target, s): msg = dynamic.msg channel = dynamic.channel if self.registryValue('noticeNonPrivmsgs', dynamic.channel) and \ msg.command != 'PRIVMSG': return ircmsgs.notice(target, s) else: return ircmsgs.privmsg(target, s) def doJoin(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if self.registryValue('hostmasks', channel): hostmask = format(' (%s)', msg.prefix) else: hostmask = '' s = format('%s%s has joined on %s', msg.nick, hostmask, network) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doPart(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if self.registryValue('hostmasks', channel): hostmask = format(' (%s)', msg.prefix) else: hostmask = '' if len(msg.args) > 1: s = format('%s%s has left on %s (%s)', msg.nick, hostmask, network, msg.args[1]) else: s = format('%s%s has left on %s', msg.nick, hostmask, network) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doMode(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) s = format('mode change by %s on %s: %s', msg.nick, network, ' '.join(msg.args[1:])) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doKick(self, irc, msg): irc = self._getRealIrc(irc) channel = msg.args[0] if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if len(msg.args) == 3: s = format('%s was kicked by %s on %s (%s)', msg.args[1], msg.nick, network, msg.args[2]) else: s = format('%s was kicked by %s on %s', msg.args[1], msg.nick, network) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doNick(self, irc, msg): irc = self._getRealIrc(irc) newNick = msg.args[0] network = self._getIrcName(irc) s = format('nick change by %s to %s on %s', msg.nick,newNick,network) for channel in self.registryValue('channels'): if channel in irc.state.channels: if newNick in irc.state.channels[channel].users: m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doTopic(self, irc, msg): irc = self._getRealIrc(irc) (channel, newTopic) = msg.args if channel not in self.registryValue('channels'): return network = self._getIrcName(irc) if self.registryValue('topicSync', channel): m = ircmsgs.topic(channel, newTopic) for otherIrc in world.ircs: if irc != otherIrc: try: if otherIrc.state.getTopic(channel) != newTopic: if (otherIrc, newTopic) not in self.queuedTopics: self.queuedTopics.add((otherIrc, newTopic)) otherIrc.queueMsg(m) else: self.queuedTopics.remove((otherIrc, newTopic)) except KeyError: self.log.warning('Not on %s on %s, ' 'can\'t sync topics.', channel, otherIrc.network) else: s = format('topic change by %s on %s: %s', msg.nick, network, newTopic) m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doQuit(self, irc, msg): irc = self._getRealIrc(irc) network = self._getIrcName(irc) if msg.args: s = format('%s has quit %s (%s)', msg.nick, network, msg.args[0]) else: s = format('%s has quit %s.', msg.nick, network) for channel in self.registryValue('channels'): if channel in self.ircstates[irc].channels: if msg.nick in self.ircstates[irc].channels[channel].users: m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def doError(self, irc, msg): irc = self._getRealIrc(irc) network = self._getIrcName(irc) s = format('disconnected from %s: %s', network, msg.args[0]) for channel in self.registryValue('channels'): m = self._msgmaker(channel, s) self._sendToOthers(irc, m) def outFilter(self, irc, msg): irc = self._getRealIrc(irc) if msg.command == 'PRIVMSG': if msg.relayedMsg: self._addRelayMsg(msg) else: channel = msg.args[0] if channel in self.registryValue('channels'): network = self._getIrcName(irc) s = self._formatPrivmsg(irc.nick, network, msg) relayMsg = self._msgmaker(channel, s) self._sendToOthers(irc, relayMsg) return msg def _addRelayMsg(self, msg): channel = msg.args[0] if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] else: q = TimeoutQueue(60) # XXX Make this configurable. self.lastRelayMsgs[channel] = q unformatted = ircutils.stripFormatting(msg.args[1]) normalized = utils.str.normalizeWhitespace(unformatted) q.enqueue(normalized) Class = Relay # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Relay/config.py0000644000000000000000000001066311206611405016262 0ustar ### # 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.ircutils as ircutils import supybot.registry as registry def configure(advanced): from supybot.questions import output, expect, anything, something, yn conf.registerPlugin('Relay', True) if yn('Would you like to relay between any channels?'): channels = anything('What channels? Separated them by spaces.') conf.supybot.plugins.Relay.channels.set(channels) if yn('Would you like to use color to distinguish between nicks?'): conf.supybot.plugins.Relay.color.setValue(True) output("""Right now there's no way to configure the actual connection to the server. What you'll need to do when the bot finishes starting up is use the 'start' command followed by the 'connect' command. Use the 'help' command to see how these two commands should be used.""") class Ignores(registry.SpaceSeparatedListOf): List = ircutils.IrcSet Value = conf.ValidHostmask class Networks(registry.SpaceSeparatedListOf): List = ircutils.IrcSet Value = registry.String Relay = conf.registerPlugin('Relay') conf.registerChannelValue(Relay, 'color', registry.Boolean(False, """Determines whether the bot will color relayed PRIVMSGs so as to make the messages easier to read.""")) conf.registerChannelValue(Relay, 'topicSync', registry.Boolean(True, """Determines whether the bot will synchronize topics between networks in the channels it relays.""")) conf.registerChannelValue(Relay, 'hostmasks', registry.Boolean(False, """Determines whether the bot will relay the hostmask of the person joining or parting the channel when he or she joins or parts.""")) conf.registerChannelValue(Relay, 'includeNetwork', registry.Boolean(True, """Determines whether the bot will include the network in relayed PRIVMSGs; if you're only relaying between two networks, it's somewhat redundant, and you may wish to save the space.""")) conf.registerChannelValue(Relay, 'punishOtherRelayBots', registry.Boolean(False, """Determines whether the bot will detect other bots relaying and respond by kickbanning them.""")) conf.registerGlobalValue(Relay, 'channels', conf.SpaceSeparatedSetOfChannels([], """Determines which channels the bot will relay in.""")) conf.registerChannelValue(Relay.channels, 'joinOnAllNetworks', registry.Boolean(True, """Determines whether the bot will always join the channel(s) it relays for on all networks the bot is connected to.""")) conf.registerChannelValue(Relay, 'ignores', Ignores([], """Determines what hostmasks will not be relayed on a channel.""")) conf.registerChannelValue(Relay, 'noticeNonPrivmsgs', registry.Boolean(False, """Determines whether the bot will used NOTICEs rather than PRIVMSGs for non-PRIVMSG relay messages (i.e., joins, parts, nicks, quits, modes, etc.)""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Protector/0000755000000000000000000000000011206611405015342 5ustar supybot-0.83.4.1.ds.orig/plugins/Protector/test.py0000644000000000000000000000332111206611405016672 0ustar ### # 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 * class ProtectorTestCase(PluginTestCase): plugins = ('Protector',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Protector/__init__.py0000644000000000000000000000457111206611405017462 0ustar ### # 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. ### """ Defends a channel against actions by people who don't have the proper capabilities. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Protector/README.txt0000644000000000000000000000011711206611405017037 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Protector/plugin.py0000644000000000000000000001544411206611405017222 0ustar ### # 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.utils as utils import supybot.ircdb as ircdb import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Protector(callbacks.Plugin): def isImmune(self, irc, msg): if not ircutils.isUserHostmask(msg.prefix): self.log.debug('%q is immune, it\'s a server.', msg) return True # It's a server prefix. if ircutils.strEqual(msg.nick, irc.nick): self.log.debug('%q is immune, it\'s me.', msg) return True # It's the bot itself. if msg.nick in self.registryValue('immune', msg.args[0]): self.log.debug('%q is immune, it\'s configured to be immune.', msg) return True return False def isOp(self, irc, channel, hostmask): cap = ircdb.makeChannelCapability(channel, 'op') if ircdb.checkCapability(hostmask, cap): self.log.debug('%s is an op on %s, it has %s.', hostmask, channel, cap) return True if ircutils.strEqual(hostmask, irc.prefix): return True return False def isProtected(self, irc, channel, hostmask): cap = ircdb.makeChannelCapability(channel, 'protected') if ircdb.checkCapability(hostmask, cap): self.log.debug('%s is protected on %s, it has %s.', hostmask, channel, cap) return True if ircutils.strEqual(hostmask, irc.prefix): return True return False def demote(self, channel, nick): irc.queueMsg(ircmsgs.deop(channel, nick)) def __call__(self, irc, msg): def ignore(reason): self.log.debug('Ignoring %q, %s.', msg, reason) if not msg.args: ignore('no msg.args') elif not irc.isChannel(msg.args[0]): ignore('not on a channel') elif not self.registryValue('enable', msg.args[0]): ignore('supybot.plugins.Protector.enable is False.') elif msg.args[0] not in irc.state.channels: # One has to wonder how this would happen, but just in case... ignore('bot isn\'t in channel') elif irc.nick not in irc.state.channels[msg.args[0]].ops: ignore('bot is not opped') elif msg.nick not in irc.state.channels[msg.args[0]].users: ignore('sender is not in channel (ChanServ, maybe?)') elif msg.nick not in irc.state.channels[msg.args[0]].ops: ignore('sender is not an op in channel (IRCOP, maybe?)') elif self.isImmune(irc, msg): ignore('sender is immune') else: super(Protector, self).__call__(irc, msg) def doMode(self, irc, msg): channel = msg.args[0] chanOp = ircdb.makeChannelCapability(channel, 'op') chanVoice = ircdb.makeChannelCapability(channel, 'voice') chanhalfop = ircdb.makeChannelCapability(channel, 'halfop') if not ircdb.checkCapability(msg.prefix, chanOp): irc.sendMsg(ircmsgs.deop(channel, msg.nick)) for (mode, value) in ircutils.separateModes(msg.args[1:]): if not value: continue if ircutils.strEqual(value, msg.nick): # We allow someone to mode themselves to oblivion. continue if irc.isNick(value): hostmask = irc.state.nickToHostmask(value) if mode == '+o': if not self.isOp(irc, channel, hostmask): irc.queueMsg(ircmsgs.deop(channel, value)) elif mode == '+h': if not ircdb.checkCapability(hostmask, chanHalfOp): irc.queueMsg(ircmsgs.dehalfop(channel, value)) elif mode == '+v': if not ircdb.checkCapability(hostmask, chanVoice): irc.queueMsg(ircmsgs.devoice(channel, value)) elif mode == '-o': if ircdb.checkCapability(hostmask, chanOp): irc.queueMsg(ircmsgs.op(channel, value)) elif mode == '-h': if ircdb.checkCapability(hostmask, chanOp): irc.queueMsg(ircmsgs.halfop(channel, value)) elif mode == '-v': if ircdb.checkCapability(hostmask, chanOp): irc.queueMsg(ircmsgs.voice(channel, value)) else: assert ircutils.isUserHostmask(value) # Handle bans. def doKick(self, irc, msg): channel = msg.args[0] kicked = msg.args[1].split(',') protected = [] for nick in kicked: if ircutils.strEqual(nick, irc.nick): return # Channel will handle the rejoin. for nick in kicked: hostmask = irc.state.nickToHostmask(nick) if self.isProtected(irc, channel, hostmask): self.log.info('%s was kicked from %s and is protected; ' 'inviting back.', hostmask, channel) irc.queueMsg(ircmsgs.invite(nick, channel)) protected.append(nick) if not self.isOp(irc, channel, msg.prefix): self.demote(channel, msg.nick) Class = Protector # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Protector/config.py0000644000000000000000000000522411206611405017164 0ustar ### # 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.ircutils as ircutils 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('Protector', True) Protector = conf.registerPlugin('Protector') conf.registerChannelValue(Protector, 'enable', registry.Boolean(True, """Determines whether this plugin is enabled in a given channel.""")) class ImmuneNicks(conf.ValidNicks): List = ircutils.IrcSet conf.registerChannelValue(Protector, 'immune', ImmuneNicks([], """Determines what nicks the bot will consider to be immune from enforcement. These nicks will not even have their actions watched by this plugin. In general, only the ChanServ for this network will be in this list.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Quote/0000755000000000000000000000000011206611405014456 5ustar supybot-0.83.4.1.ds.orig/plugins/Quote/test.py0000644000000000000000000000330711206611405016012 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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 QuoteTestCase(PluginTestCase): plugins = ('Quote',) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Quote/__init__.py0000644000000000000000000000463111206611405016573 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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. ### """ Maintains a Quotes database for each 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__ = "%%VERSION%%" # XXX Replace this with an appropriate author or supybot.Author instance. __author__ = supybot.authors.strike # 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: supybot-0.83.4.1.ds.orig/plugins/Quote/README.txt0000644000000000000000000000011711206611405016153 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Quote/plugin.py0000644000000000000000000000425511206611405016334 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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.commands import * import supybot.plugins as plugins class Quote(plugins.ChannelIdDatabasePlugin): def random(self, irc, msg, args, channel): """[] Returns a random quote from . is only necessary if the message isn't sent in the channel itself. """ quote = self.db.random(channel) if quote: irc.reply(self.showRecord(quote)) else: irc.error('I have no quotes in my database for %s.' % channel) random = wrap(random, ['channeldb']) Class = Quote # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Quote/config.py0000644000000000000000000000446011206611405016301 0ustar ### # Copyright (c) 2005, Daniel DiPaolo # 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('Quote', True) Quote = conf.registerPlugin('Quote') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Quote, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Games/0000755000000000000000000000000011206611405014415 5ustar supybot-0.83.4.1.ds.orig/plugins/Games/test.py0000644000000000000000000000531111206611405015746 0ustar ### # Copyright (c) 2003-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 * class GamesTestCase(ChannelPluginTestCase): plugins = ('Games',) _nonKickRe = re.compile(r'bang|click|spin', re.I) def testRoulette(self): self.irc.feedMsg(ircmsgs.op(self.channel, self.irc.nick)) sawKick = False for i in xrange(100): m = self.getMsg('roulette', frm='someoneElse') if m.command == 'PRIVMSG': self.failUnless(self._nonKickRe.search(m.args[1]), 'Got a msg without bang|click|spin: %r' % m) elif m.command == 'KICK': sawKick = True self.failUnless('bang' in m.args[2].lower(), 'Got a KICK without bang in it.') else: self.fail('Got something other than a kick or a privmsg.') self.failUnless(sawKick, 'Didn\'t get a kick in %s iterations!' % i) def testEightball(self): self.assertNotError('eightball') self.assertNotError('eightball a') self.assertNotError('eightball ab') self.assertNotError('eightball abc') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Games/__init__.py0000644000000000000000000000452211206611405016531 0ustar ### # Copyright (c) 2003-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. ### """ Provides various game-related commands. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Games/README.txt0000644000000000000000000000011711206611405016112 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Games/plugin.py0000644000000000000000000001550311206611405016271 0ustar ### # Copyright (c) 2003-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 re import random import supybot.utils as utils from supybot.commands import * import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks class Games(callbacks.Plugin): def coin(self, irc, msg, args): """takes no arguments Flips a coin and returns the result. """ if random.randrange(0, 2): irc.reply('heads') else: irc.reply('tails') coin = wrap(coin) def dice(self, irc, msg, args, m): """d Rolls a die with number of sides times. For example, 2d6 will roll 2 six-sided dice; 10d10 will roll 10 ten-sided dice. """ (dice, sides) = utils.iter.imap(int, m.groups()) if dice > 6: irc.error('You can\'t roll more than 6 dice.') elif sides > 100: irc.error('Dice can\'t have more than 100 sides.') elif sides < 3: irc.error('Dice can\'t have fewer than 3 sides.') else: L = [0] * dice for i in xrange(dice): L[i] = random.randrange(1, sides+1) irc.reply(format('%L', [str(x) for x in L])) _dicere = re.compile(r'^(\d+)d(\d+)$') dice = wrap(dice, [('matches', _dicere, 'Dice must be of the form d')]) # The list of words and algorithm are pulled straight the mozbot # MagicEightBall.bm module: http://tinyurl.com/7ytg7 _responses = {'positive': ['It is possible.', 'Yes!', 'Of course.', 'Naturally.', 'Obviously.', 'It shall be.', 'The outlook is good.', 'It is so.', 'One would be wise to think so.', 'The answer is certainly yes.'], 'negative': ['In your dreams.', 'I doubt it very much.', 'No chance.', 'The outlook is poor.', 'Unlikely.', 'About as likely as pigs flying.', 'You\'re kidding, right?', 'NO!', 'NO.', 'No.', 'The answer is a resounding no.', ], 'unknown' : ['Maybe...', 'No clue.', '_I_ don\'t know.', 'The outlook is hazy, please ask again later.', 'What are you asking me for?', 'Come again?', 'You know the answer better than I.', 'The answer is def-- oooh! shiny thing!'], } def _checkTheBall(self, questionLength): if questionLength % 3 == 0: category = 'positive' elif questionLength % 3 == 1: category = 'negative' else: category = 'unknown' return utils.iter.choice(self._responses[category]) def eightball(self, irc, msg, args, text): """[] Ask a question and the answer shall be provided. """ if text: irc.reply(self._checkTheBall(len(text))) else: irc.reply(self._checkTheBall(random.randint(0, 2))) eightball = wrap(eightball, [additional('text')]) _rouletteChamber = random.randrange(0, 6) _rouletteBullet = random.randrange(0, 6) def roulette(self, irc, msg, args, spin): """[spin] Fires the revolver. If the bullet was in the chamber, you're dead. Tell me to spin the chambers and I will. """ if spin: self._rouletteBullet = random.randrange(0, 6) irc.reply('*SPIN* Are you feeling lucky?', prefixNick=False) return channel = msg.args[0] if self._rouletteChamber == self._rouletteBullet: self._rouletteBullet = random.randrange(0, 6) self._rouletteChamber = random.randrange(0, 6) if irc.nick in irc.state.channels[channel].ops: irc.queueMsg(ircmsgs.kick(channel, msg.nick, 'BANG!')) else: irc.reply('*BANG* Hey, who put a blank in here?!', prefixNick=False) irc.reply('reloads and spins the chambers.', action=True) else: irc.reply('*click*') self._rouletteChamber += 1 self._rouletteChamber %= 6 roulette = wrap(roulette, ['public', additional(('literal', 'spin'))]) def monologue(self, irc, msg, args, channel): """[] Returns the number of consecutive lines you've sent in without being interrupted by someone else (i.e. how long your current 'monologue' is). is only necessary if the message isn't sent in the channel itself. """ i = 0 for m in reversed(irc.state.history): if m.command != 'PRIVMSG': continue if not m.prefix: continue if not ircutils.strEqual(m.args[0], channel): continue if msg.prefix == m.prefix: i += 1 else: break irc.reply(format('Your current monologue is at least %n long.', (i, 'line'))) monologue = wrap(monologue, ['channel']) Class = Games # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Games/config.py0000644000000000000000000000414511206611405016240 0ustar ### # Copyright (c) 2003-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('Games', True) Games = conf.registerPlugin('Games') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Filter/0000755000000000000000000000000011206611405014606 5ustar supybot-0.83.4.1.ds.orig/plugins/Filter/test.py0000644000000000000000000001612011206611405016137 0ustar ### # 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', 'Reply') 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.Plugin._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>] [] 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 = utils.iter.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 = utils.iter.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 utils.iter.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 = utils.iter.choice([' fagot1', ' fagorts', ' jerks', 'fagot' ' jerk', 'dumbshoes', ' dumbshoe']) else: insult = '' laugh1 = utils.iter.choice(['ha', 'hah', 'lol', 'l0l', 'ahh']) laugh2 = utils.iter.choice(['ha', 'hah', 'lol', 'l0l', 'ahh']) laugh1 = laugh1 * random.randrange(1, 5) laugh2 = laugh2 * random.randrange(1, 5) exclaim = utils.iter.choice(['!', '~', '!~', '~!!~~', '!!~', '~~~!']) exclaim += utils.iter.choice(['!', '~', '!~', '~!!~~', '!!~', '~~~!']) if random.random() < 0.5: exclaim += utils.iter.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']) _azn_trans = string.maketrans('rlRL', 'lrLR') def azn(self, irc, msg, args, text): """ Returns with the l's made into r's and r's made into l's. """ text = text.translate(self._azn_trans) irc.reply(text) azn = wrap(azn, ['text']) _uniudMap = { ' ': u' ', '!': u'\u00a1', '"': u'\u201e', '#': u'#', '$': u'$', '%': u'%', '&': u'\u214b', "'": u'\u0375', '(': u')', ')': u'(', '*': u'*', '+': u'+', ',': u'\u2018', '-': u'-', '.': u'\u02d9', '/': u'/', '0': u'0', '1': u'1', '2': u'', '3': u'', '4': u'', '5': u'\u1515', '6': u'9', '7': u'', '8': u'8', '9': u'6', ':': u':', ';': u'\u22c5\u0315', '<': u'>', '=': u'=', '>': u'<', '?': u'\u00bf', '@': u'@', 'A': u'\u13cc', 'B': u'\u03f4', 'C': u'\u0186', 'D': u'p', 'E': u'\u018e', 'F': u'\u2132', 'G': u'\u2141', 'H': u'H', 'I': u'I', 'J': u'\u017f\u0332', 'K': u'\u029e', 'L': u'\u2142', 'M': u'\u019c', 'N': u'N', 'O': u'O', 'P': u'd', 'Q': u'\u053e', 'R': u'\u0222', 'S': u'S', 'T': u'\u22a5', 'U': u'\u144e', 'V': u'\u039b', 'W': u'M', 'X': u'X', 'Y': u'\u2144', 'Z': u'Z', '[': u']', '\\': u'\\', ']': u'[', '^': u'\u203f', '_': u'\u203e', '`': u'\u0020\u0316', 'a': u'\u0250', 'b': u'q', 'c': u'\u0254', 'd': u'p', 'e': u'\u01dd', 'f': u'\u025f', 'g': u'\u0253', 'h': u'\u0265', 'i': u'\u0131\u0323', 'j': u'\u017f\u0323', 'k': u'\u029e', 'l': u'\u01ae', 'm': u'\u026f', 'n': u'u', 'o': u'o', 'p': u'd', 'q': u'b', 'r': u'\u0279', 's': u's', 't': u'\u0287', 'u': u'n', 'v': u'\u028c', 'w': u'\u028d', 'x': u'x', 'y': u'\u028e', 'z': u'z', '{': u'}', '|': u'|', '}': u'{', '~': u'\u223c', } def uniud(self, irc, msg, args, text): """ Returns rotated 180 degrees. """ turned = [] tlen = 0 for c in text: if c in self._uniudMap: tmp = self._uniudMap[c] if not len(tmp): tmp = u'\ufffd' turned.insert(0, tmp) tlen += 1 elif c == '\t': tablen = 8 - tlen % 8 turned.insert(0, ' ' * tablen) tlen += tablen elif ord(c) >= 32: turned.insert(0, c) tlen += 1 s = '%s \x02 \x02' % ''.join(map(lambda x: x.encode('utf-8'), turned)) irc.reply(s) uniud = wrap(uniud, ['text']) Class = Filter # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Filter/config.py0000644000000000000000000000557511206611405016441 0ustar ### # 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 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Format/0000755000000000000000000000000011206611405014611 5ustar supybot-0.83.4.1.ds.orig/plugins/Format/test.py0000644000000000000000000000703411206611405016146 0ustar ### # 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 FormatTestCase(PluginTestCase): plugins = ('Format',) def testBold(self): self.assertResponse('bold foobar', '\x02foobar\x02') def testUnderline(self): self.assertResponse('underline foobar', '\x1ffoobar\x1f') def testReverse(self): self.assertResponse('reverse foobar', '\x16foobar\x16') def testFormat(self): self.assertResponse('format %s foo', 'foo') self.assertResponse('format %s%s foo bar', 'foobar') self.assertResponse('format "%sbaz%s" "foo bar" 1', 'foo barbaz1') self.assertError('format %s foo bar') self.assertError('format %s%s foo') def testJoin(self): self.assertResponse('join + foo bar baz', 'foo+bar+baz') self.assertResponse('join "" foo bar baz', 'foobarbaz') def testTranslate(self): self.assertResponse('translate 123 456 1234567890', '4564567890') def testUpper(self): self.assertResponse('upper foo', 'FOO') self.assertResponse('upper FOO', 'FOO') def testLower(self): self.assertResponse('lower foo', 'foo') self.assertResponse('lower FOO', 'foo') def testCapitalize(self): self.assertResponse('capitalize foo', 'Foo') self.assertResponse('capitalize foo bar', 'Foo bar') def testTitle(self): self.assertResponse('title foo', 'Foo') self.assertResponse('title foo bar', 'Foo Bar') def testRepr(self): self.assertResponse('repr foo bar baz', '"foo bar baz"') def testConcat(self): self.assertResponse('concat foo bar baz', 'foobar baz') def testCut(self): self.assertResponse('cut 5 abcdefgh', 'abcde') self.assertResponse('cut 5 abcd', 'abcd') self.assertResponse('cut -1 abcde', 'abcd') def testField(self): self.assertResponse('field 2 foo bar baz', 'bar') self.assertResponse('field -1 foo bar baz', 'baz') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Format/__init__.py0000644000000000000000000000430511206611405016724 0ustar ### # 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 simple commands for formatting text on IRC. """ 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__ = "%%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. if world.testing: import test Class = plugin.Class configure = config.configure # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Format/README.txt0000644000000000000000000000011711206611405016306 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Format/plugin.py0000644000000000000000000001502711206611405016466 0ustar ### # Copyright (c) 2004-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 string import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils 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('Format', True) class Format(callbacks.Plugin): def bold(self, irc, msg, args, text): """ Returns bolded. """ irc.reply(ircutils.bold(text)) bold = wrap(bold, ['text']) def reverse(self, irc, msg, args, text): """ Returns in reverse-video. """ irc.reply(ircutils.reverse(text)) reverse = wrap(reverse, ['text']) def underline(self, irc, msg, args, text): """ Returns underlined. """ irc.reply(ircutils.underline(text)) underline = wrap(underline, ['text']) def color(self, irc, msg, args, fg, bg, text): """ [] Returns with foreground color and background color (if given) """ irc.reply(ircutils.mircColor(text, fg=fg, bg=bg)) color = wrap(color, ['color', optional('color'), 'text']) def join(self, irc, msg, args, sep): """ [ ...] Joins all the arguments together with . """ irc.reply(sep.join(args)) join = wrap(join, ['anything'], allowExtra=True) def translate(self, irc, msg, args, bad, good, text): """ Replaces with in . The first and second arguments must necessarily be the same length. """ if len(bad) != len(good): irc.error(' must be the same length as ' '.', Raise=True) irc.reply(text.translate(string.maketrans(bad, good))) translate = wrap(translate, ['something', 'something', 'text']) def upper(self, irc, msg, args, text): """ Returns uppercased. """ irc.reply(text.upper()) upper = wrap(upper, ['text']) def lower(self, irc, msg, args, text): """ Returns lowercased. """ irc.reply(text.lower()) lower = wrap(lower, ['text']) def capitalize(self, irc, msg, args, text): """ Returns capitalized. """ irc.reply(text.capitalize()) capitalize = wrap(capitalize, ['text']) def title(self, irc, msg, args, text): """ Returns titlecased. """ irc.reply(text.title()) title = wrap(title, ['text']) def repr(self, irc, msg, args, text): """ Returns the text surrounded by double quotes. """ irc.reply(utils.str.dqrepr(text)) repr = wrap(repr, ['text']) def concat(self, irc, msg, args, first, second): """ Concatenates two strings. Do keep in mind that this is *not* the same thing as join "", since if contains spaces, they won't be removed by concat. """ irc.reply(first+second) concat = wrap(concat, ['something', 'text']) def cut(self, irc, msg, args, size, text): """ Cuts down to by chopping off the rightmost characters in excess of . If is a negative number, it chops that many characters off the end of . """ irc.reply(text[:size]) cut = wrap(cut, ['int', 'text']) def field(self, irc, msg, args, index, text): """ Returns the th space-separated field of . I.e., if text is "foo bar baz" and is 2, "bar" is returned. """ try: irc.reply(text.split()[index]) except IndexError: irc.errorInvalid('field') field = wrap(field, ['index', 'text']) def format(self, irc, msg, args): """ [ ...] Expands a Python-style format string using the remaining args. Just be sure always to use %s, not %d or %f or whatever, because all the args are strings. """ if not args: raise callbacks.ArgumentError s = args.pop(0) try: s %= tuple(args) irc.reply(s) except TypeError, e: self.log.debug(utils.exnToString(e)) irc.error('Not enough arguments for the format string.',Raise=True) Class = Format # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Format/config.py0000644000000000000000000000446511206611405016441 0ustar ### # 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 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('Format', True) Format = conf.registerPlugin('Format') # This is where your configuration variables (if any) should go. For example: # conf.registerGlobalValue(Format, 'someConfigVariableName', # registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79 supybot-0.83.4.1.ds.orig/plugins/Time/0000755000000000000000000000000011206611405014257 5ustar supybot-0.83.4.1.ds.orig/plugins/Time/test.py0000644000000000000000000000533011206611405015611 0ustar ### # 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. ### from supybot.test import * class TimeTestCase(PluginTestCase): plugins = ('Time','Utilities') def testSeconds(self): self.assertResponse('seconds 1s', '1') self.assertResponse('seconds 10s', '10') self.assertResponse('seconds 1m', '60') self.assertResponse('seconds 1m 1s', '61') self.assertResponse('seconds 1h', '3600') self.assertResponse('seconds 1h 1s', '3601') self.assertResponse('seconds 1d', '86400') self.assertResponse('seconds 1d 1s', '86401') self.assertResponse('seconds 2s', '2') self.assertResponse('seconds 2m', '120') self.assertResponse('seconds 2d 2h 2m 2s', '180122') self.assertResponse('seconds 1s', '1') self.assertResponse('seconds 1y 1s', '31536001') self.assertResponse('seconds 1w 1s', '604801') def testNoErrors(self): self.assertNotError('ctime') self.assertNotError('time %Y') def testNoNestedErrors(self): self.assertNotError('echo [until 4:00]') self.assertNotError('echo [at now]') self.assertNotError('echo [seconds 4m]') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: supybot-0.83.4.1.ds.orig/plugins/Time/__init__.py0000644000000000000000000000451211206611405016372 0ustar ### # 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. ### """ A plugin for time-related functions. """ 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__ = "%%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: supybot-0.83.4.1.ds.orig/plugins/Time/README.txt0000644000000000000000000000011711206611405015754 0ustar Insert a description of your plugin here, with any notes, etc. about using it. supybot-0.83.4.1.ds.orig/plugins/Time/plugin.py0000644000000000000000000001330211206611405016126 0ustar ### # 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 time TIME = time # For later use. import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.callbacks as callbacks parser = utils.python.universalImport('dateutil.parser', 'local.dateutil.parser') def parse(s): todo = [] s = s.replace('noon', '12:00') s = s.replace('midnight', '00:00') if 'tomorrow' in s: todo.append(lambda i: i + 86400) s = s.replace('tomorrow', '') if 'next week' in s: todo.append(lambda i: i + 86400*7) s = s.replace('next week', '') i = int(time.mktime(parser.parse(s, fuzzy=True).timetuple())) for f in todo: i = f(i) return i class Time(callbacks.Plugin): def seconds(self, irc, msg, args): """[y] [w] [d] [h] [m] [s] Returns the number of seconds in the number of , , , , , and given. An example usage is "seconds 2h 30m", which would return 9000, which is '3600*2 + 30*60'. Useful for scheduling events at a given number of seconds in the future. """ if not args: raise callbacks.ArgumentError seconds = 0 for arg in args: if not arg or arg[-1] not in 'ywdhms': raise callbacks.ArgumentError (s, kind) = arg[:-1], arg[-1] try: i = int(s) except ValueError: irc.errorInvalid('argument', arg, Raise=True) if kind == 'y': seconds += i*31536000 elif kind == 'w': seconds += i*604800 elif kind == 'd': seconds += i*86400 elif kind == 'h': seconds += i*3600 elif kind == 'm': seconds += i*60 elif kind == 's': seconds += i irc.reply(str(seconds)) def at(self, irc, msg, args, s): """