diff --git a/.gitignore b/.gitignore index 17dd40d60..9cc03ecd6 100644 --- a/.gitignore +++ b/.gitignore @@ -23,3 +23,4 @@ supybot.egg-info/ test-conf/ test-data/ test-logs/ +src/version.py diff --git a/.mailmap b/.mailmap new file mode 100644 index 000000000..cfb26288b --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +James McCoy diff --git a/INSTALL b/INSTALL new file mode 100644 index 000000000..b3bd5d224 --- /dev/null +++ b/INSTALL @@ -0,0 +1,80 @@ +Common + + First things first: Supybot *requires* at least Python 2.6 and +setuptools. There ain't no getting around it. You can get Python from +http://www.python.org/ and setuptools from +https://pypi.python.org/pypi/setuptools. + + 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 '--user' + option when running 'setup.py'. E.g., 'python setup.py install + --user' to install into your home directory. You'll now have + a $HOME/.local/bin directory containing Supybot programs ('supybot', + 'supybot-wizard', etc.) and a $HOME/.local/lib directory containing the + Supybot libraries. + +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. diff --git a/plugins/ChannelLogger/plugin.py b/plugins/ChannelLogger/plugin.py index 8faaca62a..c2d2488a7 100644 --- a/plugins/ChannelLogger/plugin.py +++ b/plugins/ChannelLogger/plugin.py @@ -220,7 +220,7 @@ class ChannelLogger(callbacks.Plugin): def doNick(self, irc, msg): oldNick = msg.nick newNick = msg.args[0] - for (channel, c) in irc.state.channels.iteritems(): + for (channel, c) in irc.state.channels.items(): if newNick in c.users: self.doLog(irc, channel, '*** %s is now known as %s\n', oldNick, newNick) @@ -278,7 +278,9 @@ class ChannelLogger(callbacks.Plugin): reason = "" if not isinstance(irc, irclib.Irc): irc = irc.getRealIrc() - for (channel, chan) in self.lastStates[irc].channels.iteritems(): + if irc not in self.lastStates: + return + for (channel, chan) in self.lastStates[irc].channels.items(): if(self.registryValue('showJoinParts', channel)): if msg.nick in chan.users: self.doLog(irc, channel, diff --git a/plugins/Config/plugin.py b/plugins/Config/plugin.py index d7fa3e435..f065474d2 100644 --- a/plugins/Config/plugin.py +++ b/plugins/Config/plugin.py @@ -239,7 +239,19 @@ class Config(callbacks.Plugin): s = group.help() if s: if hasattr(group, 'value') and not group._private: - s += _(' (Current value: %s)') % group + channel = msg.args[0] + if irc.isChannel(channel) and \ + channel in group._children: + globvalue = str(group) + chanvalue = str(group.get(channel)) + if chanvalue != globvalue: + s += _(' (Current global value: %s; ' + 'current channel value: %s)') % \ + (globvalue, chanvalue) + else: + s += _(' (Current value: %s)') % group + else: + s += _(' (Current value: %s)') % group irc.reply(s) else: irc.reply(_('That configuration group exists, but seems to ' diff --git a/plugins/PluginDownloader/plugin.py b/plugins/PluginDownloader/plugin.py index 86f6c1465..2eced820d 100644 --- a/plugins/PluginDownloader/plugin.py +++ b/plugins/PluginDownloader/plugin.py @@ -301,6 +301,10 @@ repositories = { 'GLolol', 'SupyPlugins', ), + 'Iota': GithubRepository( + 'IotaSpencer', + 'supyplugins', + ), } class PluginDownloader(callbacks.Plugin): diff --git a/plugins/RSS/config.py b/plugins/RSS/config.py index 53c5ff7b6..26665a827 100644 --- a/plugins/RSS/config.py +++ b/plugins/RSS/config.py @@ -97,7 +97,7 @@ conf.registerGlobalValue(RSS, 'defaultNumberOfHeadlines', registry.PositiveInteger(1, _("""Indicates how many headlines an rss feed will output by default, if no number is provided."""))) conf.registerChannelValue(RSS, 'initialAnnounceHeadlines', - registry.PositiveInteger(5, _("""Indicates how many headlines an rss feed + registry.Integer(5, _("""Indicates how many headlines an rss feed will output when it is first added to announce for a channel."""))) conf.registerChannelValue(RSS, 'keywordWhitelist', registry.SpaceSeparatedSetOfStrings([], _("""Space separated list of diff --git a/plugins/Seen/plugin.py b/plugins/Seen/plugin.py index aa8d62cd7..8bafb2e31 100644 --- a/plugins/Seen/plugin.py +++ b/plugins/Seen/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2010-2011, James McCoy +# Copyright (c) 2010-2011, 2013, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without diff --git a/plugins/Seen/test.py b/plugins/Seen/test.py index 6c49d2524..23027940c 100644 --- a/plugins/Seen/test.py +++ b/plugins/Seen/test.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2013, James McCoy # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -63,12 +64,24 @@ class ChannelDBTestCase(ChannelPluginTestCase): with conf.supybot.plugins.seen.showLastMessage.context(False): self.assertRegexp('seen any %s' % self.nick, '^%s was last seen[^:]*' % self.nick) + self.assertNotError('config plugins.Seen.minimumNonWildcard 0') + orig = conf.supybot.protocols.irc.strictRfc() + try: + for state in (True, False): + conf.supybot.protocols.irc.strictRfc.setValue(state) + for wildcard in self.wildcardTest: + self.assertRegexp('seen any %s' % wildcard, + '^%s was last seen' % self.nick) + self.assertRegexp('seen any bar*', '^I haven\'t seen anyone matching') + finally: + conf.supybot.protocols.irc.strictRfc.setValue(orig) def testSeen(self): self.irc.feedMsg(ircmsgs.join(self.channel, self.irc.nick, prefix=self.prefix)) self.assertNotError('seen last') self.assertNotError('list') + self.assertNotError('config plugins.Seen.minimumNonWildcard 2') self.assertError('seen *') self.assertNotError('seen %s' % self.nick) m = self.assertNotError('seen %s' % self.nick.upper()) @@ -76,10 +89,16 @@ class ChannelDBTestCase(ChannelPluginTestCase): self.assertRegexp('seen user %s' % self.nick, '^%s was last seen' % self.nick) self.assertNotError('config plugins.Seen.minimumNonWildcard 0') - 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') + orig = conf.supybot.protocols.irc.strictRfc() + try: + for state in (True, False): + conf.supybot.protocols.irc.strictRfc.setValue(state) + 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') + finally: + conf.supybot.protocols.irc.strictRfc.setValue(orig) def testSeenNoUser(self): self.irc.feedMsg(ircmsgs.join(self.channel, self.irc.nick, diff --git a/plugins/Web/plugin.py b/plugins/Web/plugin.py index 20bb233b8..b95f97494 100644 --- a/plugins/Web/plugin.py +++ b/plugins/Web/plugin.py @@ -114,7 +114,6 @@ def catch_web_errors(f): class Web(callbacks.PluginRegexp): """Add the help for "@help Web" here.""" - threaded = True regexps = ['titleSnarfer'] @fetch_sandbox diff --git a/setup.py b/setup.py index d7462f2e5..c1e219609 100644 --- a/setup.py +++ b/setup.py @@ -212,7 +212,8 @@ setup( url='https://github.com/ProgVal/Limnoria', author_email='progval+limnoria@progval.net', download_url='http://builds.progval.net/limnoria/', - description='A modified version of Supybot (an IRC bot)', + description='A modified version of Supybot (an IRC bot and framework)', + platforms=['linux', 'linux2', 'win32', 'cygwin', 'darwin'], long_description=normalizeWhitespace("""A robust, full-featured Python IRC bot with a clean and flexible plugin API. Equipped with a complete ACL system for specifying user permissions with as much as per-command diff --git a/src/commands.py b/src/commands.py index 7f93d8326..793023843 100644 --- a/src/commands.py +++ b/src/commands.py @@ -336,16 +336,14 @@ def getNetworkIrc(irc, msg, args, state, errorIfNoMatch=False): state.args.append(irc) def getHaveVoice(irc, msg, args, state, action=_('do that')): - if not state.channel: - getChannel(irc, msg, args, state) + 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].isVoice(irc.nick): state.error(_('I need to be voiced to %s.') % action, Raise=True) def getHaveVoicePlus(irc, msg, args, state, action=_('do that')): - if not state.channel: - getChannel(irc, msg, args, state) + 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].isVoicePlus(irc.nick): @@ -354,16 +352,14 @@ def getHaveVoicePlus(irc, msg, args, state, action=_('do that')): Raise=True) def getHaveHalfop(irc, msg, args, state, action=_('do that')): - if not state.channel: - getChannel(irc, msg, args, state) + 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].isHalfop(irc.nick): state.error(_('I need to be halfopped to %s.') % action, Raise=True) def getHaveHalfopPlus(irc, msg, args, state, action=_('do that')): - if not state.channel: - getChannel(irc, msg, args, state) + 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].isHalfopPlus(irc.nick): @@ -372,8 +368,7 @@ def getHaveHalfopPlus(irc, msg, args, state, action=_('do that')): Raise=True) def getHaveOp(irc, msg, args, state, action=_('do that')): - if not state.channel: - getChannel(irc, msg, args, state) + 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): @@ -400,8 +395,7 @@ def getHostmask(irc, msg, args, state): def getBanmask(irc, msg, args, state): getHostmask(irc, msg, args, state) - if not state.channel: - getChannel(irc, msg, args, state) + getChannel(irc, msg, args, state) channel = state.channel banmaskstyle = conf.supybot.protocols.irc.banmask state.args[-1] = banmaskstyle.makeBanmask(state.args[-1]) @@ -477,6 +471,8 @@ def getSeenNick(irc, msg, args, state, errmsg=None): state.error(errmsg, Raise=True) def getChannel(irc, msg, args, state): + if state.channel: + return if args and irc.isChannel(args[0]): channel = args.pop(0) elif irc.isChannel(msg.args[0]): @@ -508,8 +504,7 @@ def getChannelDb(irc, msg, args, state, **kwargs): state.args.append(channel) def inChannel(irc, msg, args, state): - if not state.channel: - getChannel(irc, msg, args, state) + getChannel(irc, msg, args, state) if state.channel not in irc.state.channels: state.error(_('I\'m not in %s.') % state.channel, Raise=True) @@ -564,8 +559,7 @@ def getChannelOrGlobal(irc, msg, args, state): state.args.append(channel) def checkChannelCapability(irc, msg, args, state, cap): - if not state.channel: - getChannel(irc, msg, args, state) + getChannel(irc, msg, args, state) cap = ircdb.canonicalCapability(cap) cap = ircdb.makeChannelCapability(state.channel, cap) if not ircdb.checkCapability(msg.prefix, cap): diff --git a/src/httpserver.py b/src/httpserver.py index b07d25e03..688349417 100644 --- a/src/httpserver.py +++ b/src/httpserver.py @@ -361,15 +361,15 @@ class Favicon(SupyHTTPServerCallback): file_path = conf.supybot.servers.http.favicon() found = False if file_path: + response = None try: - icon = open(file_path, 'r') - found = True + icon = open(file_path, 'rb') + response = icon.read() except IOError: pass finally: icon.close() - if found: - response = icon.read() + if response is not None: filename = file_path.rsplit(os.sep, 1)[1] if '.' in filename: ext = filename.rsplit('.', 1)[1] diff --git a/src/log.py b/src/log.py index b5425e4e9..63abafeba 100644 --- a/src/log.py +++ b/src/log.py @@ -209,7 +209,10 @@ class ValidLogLevel(registry.String): def set(self, s): s = s.upper() try: - level = logging._levelNames[s] + try: + level = logging._levelNames[s] + except AttributeError: + level = logging._nameToLevel[s] except KeyError: try: level = int(s)