From 024a77af484f05ee4aaa162d74f03dccc0d6a29c Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 25 May 2009 13:38:22 -0400 Subject: [PATCH 01/67] Updated to 0.83.4.1+git. Signed-off-by: James Vega (cherry picked from commit 332a614eefb3a8b2fbdfd37446f5caa940b459f1) --- scripts/supybot | 2 +- setup.py | 2 +- src/conf.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/supybot b/scripts/supybot index 3f9d56b21..c8b5da92c 100644 --- a/scripts/supybot +++ b/scripts/supybot @@ -124,7 +124,7 @@ def main(): log.info('Total CPU time taken: %s seconds.', user+system) log.info('No more Irc objects, exiting.') -version = '0.83.4.1' +version = '0.83.4.1+git' if __name__ == '__main__': ### # Options: diff --git a/setup.py b/setup.py index dcdb2a4f5..e19de1248 100644 --- a/setup.py +++ b/setup.py @@ -116,7 +116,7 @@ package_dir = {'supybot': 'src', for plugin in plugins: package_dir['supybot.plugins.' + plugin] = 'plugins/' + plugin -version = '0.83.4.1' +version = '0.83.4.1+git' setup( # Metadata name='supybot', diff --git a/src/conf.py b/src/conf.py index c7b0ba40a..2340cb2d8 100644 --- a/src/conf.py +++ b/src/conf.py @@ -40,7 +40,7 @@ import supybot.ircutils as ircutils ### # version: This should be pretty obvious. ### -version = '0.83.4.1' +version = '0.83.4.1+git' ### # *** The following variables are affected by command-line options. They are From a1286f8f43859b14f951913c52bdb6e3e3100614 Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 25 May 2009 13:50:05 -0400 Subject: [PATCH 02/67] release.py: Remove the archives after uploading to Sourceforge Signed-off-by: James Vega (cherry picked from commit b170d5f9c36f3812953eab92b13f0fe21a283d65) --- sandbox/release.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sandbox/release.py b/sandbox/release.py index f39acbcce..0538e6c30 100644 --- a/sandbox/release.py +++ b/sandbox/release.py @@ -130,12 +130,15 @@ if __name__ == '__main__': print 'Uploading package files to upload.sf.net.' system('scp Supybot-%s.tar.gz Supybot-%s.tar.bz2 Supybot-%s.zip ' '%s@frs.sourceforge.net:uploads' % (v, v, v, u)) + os.unlink('Supybot-%s.tar.gz' % v) + os.unlink('Supybot-%s.tar.bz2' % v) + os.unlink('Supybot-%s.zip' % v) print 'Copying new version.txt over to project webserver.' system('echo %s > version.txt' % v) system('scp version.txt %s@web.sf.net:/home/groups/s/su/supybot/htdocs' %u) - system('rm version.txt') + os.unlink('version.txt') # print 'Generating documentation.' # # docFiles is in the format {directory: files} From 8bb49e88731720f7bee65162c92ad8e488d18a97 Mon Sep 17 00:00:00 2001 From: James Vega Date: Tue, 26 May 2009 16:27:53 -0400 Subject: [PATCH 03/67] supybot-wizard: Handle os.makedirs errors on Windows Signed-off-by: James Vega (cherry picked from commit 3c898fa483b143ddec557a1b7fea9df088357431) --- scripts/supybot-wizard | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/supybot-wizard b/scripts/supybot-wizard index f23bf6294..184c5d50c 100644 --- a/scripts/supybot-wizard +++ b/scripts/supybot-wizard @@ -140,13 +140,15 @@ def getDirectoryName(default, basedir=os.curdir, prompt=True): os.makedirs(dir) done = True except OSError, e: - if e.args[0] != 17: # File exists. + # 17 is File exists for Linux (and likely other POSIX systems) + # 183 is the same for Windows + if e.args[0] == 17 or (os.name == 'nt' and e.args[0] == 183): + done = True + else: 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(): From 4eb30069bfb2e3144dce52a4ae3906a332dd01a2 Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Wed, 3 Jun 2009 02:55:40 -0400 Subject: [PATCH 04/67] Try using simplejson instead if json-py is installed. Signed-off-by: James Vega (cherry picked from commit 416a6e8dd2ffed0320be02ee80e21f2e6910d573) --- plugins/Google/plugin.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index e1b6bee9e..98fd7dd1b 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -42,15 +42,21 @@ import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils import supybot.callbacks as callbacks +simplejson = None + +try: + simplejson = utils.python.universalImport('json') +except ImportError: + pass + 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 + if simplejson is None or hasattr(simplejson, 'read'): + simplejson = utils.python.universalImport('simplejson', + 'local.simplejson') except ImportError: raise callbacks.Error, \ 'You need Python2.6 or the simplejson module installed to use ' \ From 65a180798fe0c58fd051d29f6b622c5254148a72 Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 26 Jun 2009 16:58:46 -0400 Subject: [PATCH 05/67] QuoteGrabs: Fix incorrect argument order to _grab Signed-off-by: James Vega (cherry picked from commit 874508867fb98b6cdd9846136c681cae388a0741) --- plugins/QuoteGrabs/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index dfd49911e..b3deefc49 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2004, Daniel DiPaolo -# Copyright (c) 2008, James Vega +# Copyright (c) 2008-2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -202,7 +202,7 @@ class QuoteGrabs(callbacks.Plugin): try: last = int(self.db.select(channel, msg.nick)) except dbi.NoRecordError: - self._grab(irc, channel, msg, irc.prefix) + self._grab(channel, irc, msg, irc.prefix) self._sendGrabMsg(irc, msg) else: elapsed = int(time.time()) - last From 2b0b74dfe0bc9f3a42ea41f510a0f14d4f79e4aa Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 26 Jun 2009 18:21:16 -0400 Subject: [PATCH 06/67] Google: Keep the list of supported languages in one place. Signed-off-by: James Vega (cherry picked from commit 461f943d9799f69b332cd07fdd8418a4dbad356e) --- plugins/Google/config.py | 38 ++++++++++++++++++++++++++++++++------ plugins/Google/plugin.py | 26 ++++++++------------------ 2 files changed, 40 insertions(+), 24 deletions(-) diff --git a/plugins/Google/config.py b/plugins/Google/config.py index 21f58ef35..d34f530a1 100644 --- a/plugins/Google/config.py +++ b/plugins/Google/config.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2005, Jeremiah Fincher -# Copyright (c) 2008, James Vega +# Copyright (c) 2008-2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -48,15 +48,41 @@ def configure(advanced): 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()] + transLangs = {'Afrikaans': 'af', 'Albanian': 'sq', 'Amharic': 'am', + 'Arabic': 'ar', 'Armenian': 'hy', 'Azerbaijani': 'az', + 'Basque': 'eu', 'Belarusian': 'be', 'Bengali': 'bn', + 'Bulgarian': 'bg', 'Burmese': 'my', 'Catalan': 'ca', + 'Chinese': 'zh', 'Chinese_simplified': 'zh-CN', + 'Chinese_traditional': 'zh-TW', 'Croatian': 'hr', + 'Czech': 'cs', 'Danish': 'da', 'Dhivehi': 'dv', + 'Dutch': 'nl', 'English': 'en', 'Esperanto': 'eo', + 'Estonian': 'et', 'Filipino': 'tl', 'Finnish': 'fi', + 'French': 'fr', 'Galician': 'gl', 'Georgian': 'ka', + 'German': 'de', 'Greek': 'el', 'Gujarati': 'gu', + 'Hebrew': 'iw', 'Hindi': 'hi', 'Hungarian': 'hu', + 'Icelandic': 'is', 'Indonesian': 'id', 'Inuktitut': 'iu', + 'Italian': 'it', 'Japanese': 'ja', 'Kannada': 'kn', + 'Kazakh': 'kk', 'Khmer': 'km', 'Korean': 'ko', + 'Kurdish': 'ku', 'Kyrgyz': 'ky', 'Laothian': 'lo', + 'Latvian': 'lv', 'Lithuanian': 'lt', 'Macedonian': 'mk', + 'Malay': 'ms', 'Malayalam': 'ml', 'Maltese': 'mt', + 'Marathi': 'mr', 'Mongolian': 'mn', 'Nepali': 'ne', + 'Norwegian': 'no', 'Oriya': 'or', 'Pashto': 'ps', + 'Persian': 'fa', 'Polish': 'pl', 'Portuguese': 'pt-PT', + 'Punjabi': 'pa', 'Romanian': 'ro', 'Russian': 'ru', + 'Sanskrit': 'sa', 'Serbian': 'sr', 'Sindhi': 'sd', + 'Sinhalese': 'si', 'Slovak': 'sk', 'Slovenian': 'sl', + 'Spanish': 'es', 'Swedish': 'sv', 'Tajik': 'tg', + 'Tamil': 'ta', 'Tagalog': 'tl', 'Telugu': 'te', + 'Thai': 'th', 'Tibetan': 'bo', 'Turkish': 'tr', + 'Ukranian': 'uk', 'Urdu': 'ur', 'Uzbek': 'uz', + 'Uighur': 'ug', 'Vietnamese': 'vi'} + validStrings = ['lang_' + s for s in transLangs.values()] 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'): + if not s.endswith('CN') or s.endswith('TW') or s.endswith('PT'): s = s.lower() else: s = s.lower()[:-2] + s[-2:] diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 98fd7dd1b..1d889edba 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -231,16 +231,6 @@ class Google(callbacks.PluginRegexp): 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] @@ -257,22 +247,22 @@ class Google(callbacks.PluginRegexp): 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()] + if fromLang.capitalize() in lang.transLangs: + fromLang = lang.transLangs[fromLang.capitalize()] elif lang.normalize('lang_'+fromLang)[5:] \ - not in self._transLangs.values(): + not in lang.transLangs.values(): irc.errorInvalid('from language', fromLang, format('Valid languages are: %L', - self._transLangs.keys())) + lang.transLangs.keys())) else: fromLang = lang.normalize('lang_'+fromLang)[5:] - if toLang.capitalize() in self._transLangs: - toLang = self._transLangs[toLang.capitalize()] + if toLang.capitalize() in lang.transLangs: + toLang = lang.transLangs[toLang.capitalize()] elif lang.normalize('lang_'+toLang)[5:] \ - not in self._transLangs.values(): + not in lang.transLangs.values(): irc.errorInvalid('to language', toLang, format('Valid languages are: %L', - self._transLangs.keys())) + lang.transLangs.keys())) else: toLang = lang.normalize('lang_'+toLang)[5:] opts['langpair'] = '%s|%s' % (fromLang, toLang) From 26cefc78f432c0b93c86e026c21960dafab1ceb7 Mon Sep 17 00:00:00 2001 From: James Vega Date: Tue, 30 Jun 2009 11:06:17 -0400 Subject: [PATCH 07/67] Channel: alert should not require the caller to have op capability Signed-off-by: James Vega (cherry picked from commit ba29f0787c86d5cf19b545e9e9a31cbdca5e9a95) --- plugins/Channel/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Channel/plugin.py b/plugins/Channel/plugin.py index 4b7758f8d..7cb51f937 100644 --- a/plugins/Channel/plugin.py +++ b/plugins/Channel/plugin.py @@ -813,7 +813,7 @@ class Channel(callbacks.Plugin): capability. """ self.alertOps(irc, channel, text, frm=msg.nick) - alert = wrap(alert, ['op', 'text']) + alert = wrap(alert, ['inChannel', 'text']) Class = Channel From 7aeedea6f610950518361111605823f4ebf3a7ad Mon Sep 17 00:00:00 2001 From: James Vega Date: Tue, 14 Jul 2009 20:30:57 -0400 Subject: [PATCH 08/67] Updated ChannelIdDatabasePlugin's getCommandHelp signature Signed-off-by: James Vega (cherry picked from commit c9329303d3affdba1e73284cad5b70d023701516) --- plugins/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/__init__.py b/plugins/__init__.py index 71a66ded0..c177eb840 100644 --- a/plugins/__init__.py +++ b/plugins/__init__.py @@ -352,8 +352,8 @@ class ChannelIdDatabasePlugin(callbacks.Plugin): self.db.close() self.__parent.die() - def getCommandHelp(self, name): - help = self.__parent.getCommandHelp(name) + def getCommandHelp(self, name, simpleSyntax=None): + help = self.__parent.getCommandHelp(name, simpleSyntax) help = help.replace('$Types', format('%p', self.name())) help = help.replace('$Type', self.name()) help = help.replace('$types', format('%p', self.name().lower())) From 965f4e79b8732c5412bf140ba96674e9a6ecfa7c Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 16 Jul 2009 11:28:43 -0400 Subject: [PATCH 09/67] User: Fix hostmask.list to show the user's name not the repr user object Signed-off-by: James Vega (cherry picked from commit 9dccada152fb3d3b3bcb9eaa1b71c680d6106403) --- plugins/User/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index ad04768aa..a23228f78 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -254,7 +254,8 @@ class User(callbacks.Plugin): hostmasks.sort() return format('%L', hostmasks) else: - irc.reply(format('%s has no registered hostmasks.', user)) + irc.reply(format('%s has no registered hostmasks.', + user.name)) try: user = ircdb.users.getUser(msg.prefix) if name: From d43d083f44c49b5dea63793b2b2b13baaffe09aa Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 16 Jul 2009 11:39:20 -0400 Subject: [PATCH 10/67] User: getHostmasks should always return a string Since it was calling irc.reply() in one case, we had both that irc.reply being sent and the irc.reply() of what getHostmasks returned (None in that case). Bad! Signed-off-by: James Vega (cherry picked from commit dcb247494e4fd6433865e8c94cf56414fb253192) --- plugins/User/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index a23228f78..cf1342027 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -254,8 +254,7 @@ class User(callbacks.Plugin): hostmasks.sort() return format('%L', hostmasks) else: - irc.reply(format('%s has no registered hostmasks.', - user.name)) + return format('%s has no registered hostmasks.', user.name) try: user = ircdb.users.getUser(msg.prefix) if name: From 885d60a1321ceee277d0f53db58dc79318800dd2 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 18 Jul 2009 20:27:45 -0400 Subject: [PATCH 11/67] QuoteGrabs: QuoteGrabsRecord at arg needs to be an int. Signed-off-by: James Vega (cherry picked from commit cbf68e53e35fe0281724674b0f3ce153334c87f9) --- plugins/QuoteGrabs/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index b3deefc49..04abf7a44 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -105,7 +105,7 @@ class SqliteQuoteGrabsDB(object): raise dbi.NoRecordError (id, by, quote, hostmask, at, grabber) = cursor.fetchone() return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask, - at=at, grabber=grabber) + at=int(at), grabber=grabber) def random(self, channel, nick): db = self._getDb(channel) From 6299ef5c2226b756a4320f9240504cff52e4464f Mon Sep 17 00:00:00 2001 From: Ricky Zhou Date: Fri, 17 Jul 2009 17:08:20 -0400 Subject: [PATCH 12/67] Account for negative times in timeElapsed. Signed-off-by: James Vega (cherry picked from commit aa2337791a75ea47f4a3a7838e177a2ef8c3b621) --- src/utils/gen.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/utils/gen.py b/src/utils/gen.py index bcdd3997e..2f05a859e 100644 --- a/src/utils/gen.py +++ b/src/utils/gen.py @@ -77,6 +77,7 @@ def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True, be used. """ ret = [] + before = False def Format(s, i): if i or leadingZeroes or ret: if short: @@ -84,6 +85,12 @@ def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True, else: ret.append(format('%n', (i, s))) elapsed = int(elapsed) + + # Handle negative times + if elapsed < 0: + before = True + elapsed = -elapsed + assert years or weeks or days or \ hours or minutes or seconds, 'One flag must be True' if years: @@ -107,10 +114,14 @@ def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True, Format('second', secs) if not ret: raise ValueError, 'Time difference not great enough to be noted.' + result = '' if short: - return ' '.join(ret) + result = ' '.join(ret) else: - return format('%L', ret) + result = format('%L', ret) + if before: + result += ' ago' + return result def findBinaryInPath(s): """Return full path of a binary if it's in PATH, otherwise return None.""" From 5b8bae5d5fafaac01c98ec820df73614bd9f8546 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 25 Jul 2009 15:12:30 +0200 Subject: [PATCH 13/67] Escape '-' where necessary in supybot.1 Signed-off-by: James Vega (cherry picked from commit aa9db7ba23a400e0506711ef5171371cb070e290) --- docs/man/supybot.1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/man/supybot.1 b/docs/man/supybot.1 index 1203d3c8a..08385010c 100644 --- a/docs/man/supybot.1 +++ b/docs/man/supybot.1 @@ -1,9 +1,9 @@ .\" Process this file with .\" groff -man -Tascii supybot.1 .\" -.TH SUPYBOT 1 "APRIL 2005" +.TH SUPYBOT 1 "JULY 2009" .SH NAME -supybot \- A robust and user friendly Python IRC bot +supybot - A robust and user friendly Python IRC bot .SH SYNOPSIS .B supybot .RI [ options ] " configFile @@ -40,7 +40,7 @@ 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 +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 From 6bebc383b45c0de6e27ae339f6cf87480d8edd0b Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 20 Aug 2009 10:48:41 -0400 Subject: [PATCH 14/67] ChannelStats.rank now starts from 1 instead of 0 Signed-off-by: James Vega (cherry picked from commit 169aee3bc0ba39c5bea37c6798f62ab21e9b6b30) --- plugins/ChannelStats/plugin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/ChannelStats/plugin.py b/plugins/ChannelStats/plugin.py index d5c77ab30..baf768007 100644 --- a/plugins/ChannelStats/plugin.py +++ b/plugins/ChannelStats/plugin.py @@ -1,5 +1,6 @@ ### # 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 @@ -60,6 +61,7 @@ class ChannelStat(irclib.IrcCommandDispatcher): self.smileys = smileys self.topics = topics self.words = words + def values(self): return [getattr(self, s) for s in self._values] @@ -311,7 +313,7 @@ class ChannelStats(callbacks.Plugin): users.append((v, ircdb.users.getUser(id).name)) users.sort() users.reverse() - s = utils.str.commaAndify(['#%s %s (%.3g)' % (i, u, v) + s = utils.str.commaAndify(['#%s %s (%.3g)' % (i+1, u, v) for (i, (v, u)) in enumerate(users)]) irc.reply(s) rank = wrap(rank, ['channeldb', 'text']) From 4cc8fdecdc248ccb58eb7df76f387677bda9784f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20N=C4=9Bmec?= Date: Sun, 16 Aug 2009 22:38:46 +0200 Subject: [PATCH 15/67] Note plugin: Fix erroneous use of __contributors__. Signed-off-by: James Vega (cherry picked from commit a693162059cd6c90634d8f6846d0168622fa558b) --- plugins/Note/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Note/__init__.py b/plugins/Note/__init__.py index 3144a9821..53a4cd79d 100644 --- a/plugins/Note/__init__.py +++ b/plugins/Note/__init__.py @@ -43,7 +43,7 @@ __author__ = supybot.authors.jemfinch # This is a dictionary mapping supybot.Author instances to lists of # contributions. -__contributors__ = {'inkedmn': ['Original implementation.']} +__contributors__ = { supybot.authors.inkedmn: ['Original implementation.'] } import config import plugin From d7d5ccea505b7698898300b59e36804c1fcf3b70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20N=C4=9Bmec?= Date: Sun, 16 Aug 2009 23:29:04 +0200 Subject: [PATCH 16/67] scripts/supybot: Unify the `os.linesep' vs '\n' usage. Signed-off-by: James Vega (cherry picked from commit 2242b2602531ac7438138968ba3e771b136f5cb6) --- scripts/supybot | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/scripts/supybot b/scripts/supybot index c8b5da92c..9b66e0f70 100644 --- a/scripts/supybot +++ b/scripts/supybot @@ -45,7 +45,8 @@ 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.stderr.write('This program requires Python >= 2.3.0') + sys.stderr.write(os.linesep) sys.exit(-1) def _termHandler(signalNumber, stackFrame): @@ -171,7 +172,8 @@ if __name__ == '__main__': 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.stderr.write('Dude, don\'t even try to run this as root.') + sys.stderr.write(os.linesep) sys.exit(-1) if len(args) > 1: @@ -208,9 +210,10 @@ if __name__ == '__main__': errmsg = textwrap.fill('%s: %s' % (name, e), width=78, subsequent_indent=' '*len(name)) sys.stderr.write(errmsg) - sys.stderr.write('\n') + sys.stderr.write(os.linesep) sys.stderr.write('Please fix this error in your configuration file ' - 'and restart your bot.\n') + 'and restart your bot.') + sys.stderr.write(os.linesep) sys.exit(-1) import supybot.conf as conf import supybot.world as world @@ -292,7 +295,7 @@ if __name__ == '__main__': try: fd = file(pidFile, 'w') pid = os.getpid() - fd.write('%s\n' % pid) + fd.write('%s%s' % (pid, os.linesep)) fd.close() def removePidFile(): try: From bc1c159d273fdfe53145a59f80878d661ffdec2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0t=C4=9Bp=C3=A1n=20N=C4=9Bmec?= Date: Wed, 19 Aug 2009 16:00:57 +0200 Subject: [PATCH 17/67] QuoteGrabs plugin: Add an `ungrab' command. Also add a missing error check in the `list' db method. Signed-off-by: James Vega (cherry picked from commit e92291856423fde6f5e949efceebba97e7566a4c) --- plugins/QuoteGrabs/plugin.py | 40 ++++++++++++++++++++++++++++++++++++ plugins/QuoteGrabs/test.py | 33 ++++++++++++++++++++++++++++- 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 04abf7a44..a077878d5 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -128,6 +128,8 @@ class SqliteQuoteGrabsDB(object): cursor.execute("""SELECT id, quote FROM quotegrabs WHERE nickeq(nick, %s) ORDER BY id DESC""", nick) + if cursor.rowcount == 0: + raise dbi.NoRecordError return [QuoteGrabsRecord(id, text=quote) for (id, quote) in cursor.fetchall()] @@ -167,6 +169,27 @@ class SqliteQuoteGrabsDB(object): msg.nick, msg.prefix, by, int(time.time()), text) db.commit() + def remove(self, channel, grab=None): + db = self._getDb(channel) + cursor = db.cursor() + if grab is not None: + # the testing if there actually *is* the to-be-deleted record is + # strictly unnecessary -- the DELETE operation would "succeed" + # anyway, but it's silly to just keep saying 'OK' no matter what, + # so... + cursor.execute("""SELECT * FROM quotegrabs WHERE id = %s""", grab) + if cursor.rowcount == 0: + raise dbi.NoRecordError + cursor.execute("""DELETE FROM quotegrabs WHERE id = %s""", grab) + else: + cursor.execute("""SELECT * FROM quotegrabs WHERE id = (SELECT MAX(id) + FROM quotegrabs)""") + if cursor.rowcount == 0: + raise dbi.NoRecordError + cursor.execute("""DELETE FROM quotegrabs WHERE id = (SELECT MAX(id) + FROM quotegrabs)""") + db.commit() + def search(self, channel, text): db = self._getDb(channel) cursor = db.cursor() @@ -241,6 +264,23 @@ class QuoteGrabs(callbacks.Plugin): irc.error('I couldn\'t find a proper message to grab.') grab = wrap(grab, ['channeldb', 'nick']) + def ungrab(self, irc, msg, args, channel, grab): + """[] + + Removes the grab (the last by default) on . + is only necessary if the message isn't sent in the channel + itself. + """ + try: + self.db.remove(channel, grab) + irc.replySuccess() + except dbi.NoRecordError: + if grab is None: + irc.error('Nothing to ungrab.') + else: + irc.error('Invalid grab number.') + ungrab = wrap(ungrab, ['channeldb', optional('id')]) + def quote(self, irc, msg, args, channel, nick): """[] diff --git a/plugins/QuoteGrabs/test.py b/plugins/QuoteGrabs/test.py index d26eed18d..42d967851 100644 --- a/plugins/QuoteGrabs/test.py +++ b/plugins/QuoteGrabs/test.py @@ -51,6 +51,38 @@ class QuoteGrabsTestCase(ChannelPluginTestCase): self.assertNotError('grab foo') self.assertResponse('quote foo', '* foo moos') + def testUngrab(self): + testPrefix = 'foo!bar@baz' + # nothing yet + self.assertError('ungrab') + self.assertError('ungrab 2') + self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', + prefix=testPrefix)) + # still not grabbed + self.assertError('ungrab') + self.assertError('ungrab 3') + # grab and ungrab a quote + self.assertNotError('grab foo') + self.assertNotError('ungrab') + + self.assertNotError('grab foo') + # this is not there... + self.assertError('ungrab 8883') + # ...unlike this... + self.assertNotError('ungrab 1') + # ...but not now anymore :-D + self.assertError('ungrab') + # grab two quotes and ungrab them by id + self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'something', + prefix=testPrefix)) + self.assertNotError('grab foo') + self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'another', + prefix=testPrefix)) + self.assertNotError('grab foo') + self.assertNotError('ungrab 1') + self.assertNotError('ungrab 2') + self.assertError('ungrab') + def testList(self): testPrefix = 'foo!bar@baz' self.irc.feedMsg(ircmsgs.privmsg(self.channel, 'testList', @@ -111,7 +143,6 @@ class QuoteGrabsTestCase(ChannelPluginTestCase): self.assertNotError('grab foo') self.assertNotError('quotegrabs search test') - class QuoteGrabsNonChannelTestCase(QuoteGrabsTestCase): config = { 'databases.plugins.channelSpecific' : False } From bdf9e8836b770da60c0040899ded9e795bcfb910 Mon Sep 17 00:00:00 2001 From: James Vega Date: Tue, 1 Sep 2009 23:29:42 -0400 Subject: [PATCH 18/67] Ensure getBanmask has a channel variable as per makeBanmask's requirement Signed-off-by: James Vega (cherry picked from commit 260570bd4fccfb8bae470f49449592c229c235df) --- src/commands.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/commands.py b/src/commands.py index b22068f1a..eee35c6c2 100644 --- a/src/commands.py +++ b/src/commands.py @@ -278,6 +278,7 @@ def getBanmask(irc, msg, args, state): getHostmask(irc, msg, args, state) if not state.channel: getChannel(irc, msg, args, state) + channel = state.channel banmaskstyle = conf.supybot.protocols.irc.banmask state.args[-1] = banmaskstyle.makeBanmask(state.args[-1]) From 5a0f96fc8eae06d3d74f014c7d22c1789f851d8f Mon Sep 17 00:00:00 2001 From: James Vega Date: Wed, 2 Sep 2009 07:40:33 -0400 Subject: [PATCH 19/67] Reply with an error when getBanmask doesn't have a valid channel. Signed-off-by: James Vega (cherry picked from commit 459bc616b1c19b6d5f96a7a3ae5916777637f42f) --- src/commands.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/commands.py b/src/commands.py index eee35c6c2..ef7a61466 100644 --- a/src/commands.py +++ b/src/commands.py @@ -280,7 +280,10 @@ def getBanmask(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]) + try: + state.args[-1] = banmaskstyle.makeBanmask(state.args[-1]) + except AssertionError: + state.errorInvalid('channel', channel) def getUser(irc, msg, args, state): try: From 3e984c71d0c1763bef36bc83b55af976cfc33134 Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 11 Sep 2009 18:09:38 -0400 Subject: [PATCH 20/67] Clarify databases.types.cdb.maximumModifications' help and use a proper type. The code expects a float between 0 and 1 inclusive but was simply using registry.Float. registry.Probability matches the behavior we want. Signed-off-by: James Vega (cherry picked from commit e9a896c736e8eac8402d6d94bce0c3962950ef62) --- src/conf.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/conf.py b/src/conf.py index 2340cb2d8..720f0e38b 100644 --- a/src/conf.py +++ b/src/conf.py @@ -864,9 +864,9 @@ 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 + registry.Probability(0.5, """Determines how often CDB databases will have + their modifications flushed to disk. When the number of modified records + is greater than this fraction of the total number of records, the database will be entirely flushed to disk.""")) # XXX Configuration variables for dbi, sqlite, flat, mysql, etc. From 750fb2ccdc63b7ba46835e547aba55e195fdd7a3 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Burhans" Date: Thu, 3 Sep 2009 20:57:45 -0700 Subject: [PATCH 21/67] Check for empty word list in BadWords plugin before filtering. Signed-off-by: James Vega (cherry picked from commit 6418b3d8c3395f90244b782ecd9553fb642a4d7e) --- plugins/BadWords/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/BadWords/plugin.py b/plugins/BadWords/plugin.py index 27b105036..e373567a4 100644 --- a/plugins/BadWords/plugin.py +++ b/plugins/BadWords/plugin.py @@ -87,7 +87,7 @@ class BadWords(callbacks.Privmsg): self.lastModified = time.time() def outFilter(self, irc, msg): - if self.filtering and msg.command == 'PRIVMSG': + if self.filtering and msg.command == 'PRIVMSG' and self.words(): self.updateRegexp() s = msg.args[1] if self.registryValue('stripFormatting'): From bb4e09886a8b118a71c23843a5101ea45a35a745 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 4 Oct 2009 21:41:05 -0400 Subject: [PATCH 22/67] Use utils.web.httpUrlRe for the Web/ShrinkUrl snarfer regexes. Signed-off-by: James Vega (cherry picked from commit ca917d352836ff623cf859ea51beea2242defc60) --- plugins/ShrinkUrl/plugin.py | 2 +- plugins/Web/plugin.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/ShrinkUrl/plugin.py b/plugins/ShrinkUrl/plugin.py index 48c408bd3..41c3248d5 100644 --- a/plugins/ShrinkUrl/plugin.py +++ b/plugins/ShrinkUrl/plugin.py @@ -112,7 +112,6 @@ class ShrinkUrl(callbacks.PluginRegexp): return msg def shrinkSnarfer(self, irc, msg, match): - r"https?://[^\])>\s]{13,}" channel = msg.args[0] if not irc.isChannel(channel): return @@ -144,6 +143,7 @@ class ShrinkUrl(callbacks.PluginRegexp): m = irc.reply(s, prefixNick=False) m.tag('shrunken') shrinkSnarfer = urlSnarfer(shrinkSnarfer) + shrinkSnarfer.__doc__ = utils.web.httpUrlRe def _getLnUrl(self, url): url = utils.web.urlquote(url) diff --git a/plugins/Web/plugin.py b/plugins/Web/plugin.py index 2e1d1787f..94744018c 100644 --- a/plugins/Web/plugin.py +++ b/plugins/Web/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2005, Jeremiah Fincher +# Copyright (c) 2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -75,7 +76,6 @@ class Web(callbacks.PluginRegexp): irc.reply(str(e)) def titleSnarfer(self, irc, msg, match): - r"https?://[^\])>\s]+" channel = msg.args[0] if not irc.isChannel(channel): return @@ -105,6 +105,7 @@ class Web(callbacks.PluginRegexp): s = format('Title: %s (at %s)', title, domain) irc.reply(s, prefixNick=False) titleSnarfer = urlSnarfer(titleSnarfer) + titleSnarfer.__doc__ = utils.web.httpUrlRe def headers(self, irc, msg, args, url): """ From f0852a9e45a3e3256523be0f2d1729bc8545cc57 Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 15 Oct 2009 21:56:26 -0400 Subject: [PATCH 23/67] utils.web: Provide access to the raw httpUrlRe/urlRe strings Using the compiled regexps for a PluginRegexp method's __doc__ doesn't work. Closes Sourceforge #2879862 Signed-off-by: James Vega (cherry picked from commit 25fc2de6430ca76b93054fc440f41e3cd93e67dd) --- plugins/ShrinkUrl/plugin.py | 2 +- plugins/Web/plugin.py | 2 +- src/utils/web.py | 17 +++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/ShrinkUrl/plugin.py b/plugins/ShrinkUrl/plugin.py index 41c3248d5..780b4134f 100644 --- a/plugins/ShrinkUrl/plugin.py +++ b/plugins/ShrinkUrl/plugin.py @@ -143,7 +143,7 @@ class ShrinkUrl(callbacks.PluginRegexp): m = irc.reply(s, prefixNick=False) m.tag('shrunken') shrinkSnarfer = urlSnarfer(shrinkSnarfer) - shrinkSnarfer.__doc__ = utils.web.httpUrlRe + shrinkSnarfer.__doc__ = utils.web._httpUrlRe def _getLnUrl(self, url): url = utils.web.urlquote(url) diff --git a/plugins/Web/plugin.py b/plugins/Web/plugin.py index 94744018c..6a332c792 100644 --- a/plugins/Web/plugin.py +++ b/plugins/Web/plugin.py @@ -105,7 +105,7 @@ class Web(callbacks.PluginRegexp): s = format('Title: %s (at %s)', title, domain) irc.reply(s, prefixNick=False) titleSnarfer = urlSnarfer(titleSnarfer) - titleSnarfer.__doc__ = utils.web.httpUrlRe + titleSnarfer.__doc__ = utils.web._httpUrlRe def headers(self, irc, msg, args, url): """ diff --git a/src/utils/web.py b/src/utils/web.py index 03bc3cb74..e05648560 100644 --- a/src/utils/web.py +++ b/src/utils/web.py @@ -45,15 +45,16 @@ 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) +_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) +_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 = r'(\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (_domain, _ipAddr) +urlRe = re.compile(_urlRe, re.I) +_httpUrlRe = r'(https?://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (_domain, + _ipAddr) +httpUrlRe = re.compile(_httpUrlRe, re.I) REFUSED = 'Connection refused.' TIMED_OUT = 'Connection timed out.' From dcdbd5bea4cb4725e34385f718fba7d123ffb517 Mon Sep 17 00:00:00 2001 From: James Vega Date: Wed, 21 Oct 2009 22:43:26 -0400 Subject: [PATCH 24/67] Update references to PySqlite URL Signed-off-by: James Vega (cherry picked from commit f475525237ad53a705d3e6e327b6ca19850c47e1) --- plugins/Factoids/plugin.py | 3 ++- plugins/Karma/plugin.py | 2 +- plugins/MoobotFactoids/plugin.py | 3 ++- plugins/QuoteGrabs/plugin.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 390092aef..7c8a0c601 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -44,7 +44,8 @@ try: import sqlite except ImportError: raise callbacks.Error, 'You need to have PySQLite installed to use this ' \ - 'plugin. Download it at ' + 'plugin. Download it at ' \ + '' def getFactoid(irc, msg, args, state): assert not state.channel diff --git a/plugins/Karma/plugin.py b/plugins/Karma/plugin.py index cc298a62f..a24548326 100644 --- a/plugins/Karma/plugin.py +++ b/plugins/Karma/plugin.py @@ -53,7 +53,7 @@ class SqliteKarmaDB(object): 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] diff --git a/plugins/MoobotFactoids/plugin.py b/plugins/MoobotFactoids/plugin.py index 10acf0d21..030f735d0 100644 --- a/plugins/MoobotFactoids/plugin.py +++ b/plugins/MoobotFactoids/plugin.py @@ -102,7 +102,8 @@ class SqliteMoobotDB(object): except ImportError: raise callbacks.Error, \ 'You need to have PySQLite installed to use this ' \ - 'plugin. Download it at ' + 'plugin. Download it at ' \ + '' if channel in self.dbs: return self.dbs[channel] filename = plugins.makeChannelFilename(self.filename, channel) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index a077878d5..65c2e4b52 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -70,7 +70,7 @@ class SqliteQuoteGrabsDB(object): 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)) From 0bd9a8b6f4fcbedf32ea2bdb4b581b100ef99493 Mon Sep 17 00:00:00 2001 From: James Vega Date: Wed, 21 Oct 2009 22:48:04 -0400 Subject: [PATCH 25/67] Use the correct URLs for the PySqlite website. Signed-off-by: James Vega (cherry picked from commit 927c8c963a2ddbd43e2890c8cf254e7bc9922183) --- plugins/Factoids/plugin.py | 2 +- plugins/Karma/plugin.py | 2 +- plugins/MoobotFactoids/plugin.py | 2 +- plugins/QuoteGrabs/plugin.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 7c8a0c601..71eb9704f 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -45,7 +45,7 @@ try: 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 diff --git a/plugins/Karma/plugin.py b/plugins/Karma/plugin.py index a24548326..36532b286 100644 --- a/plugins/Karma/plugin.py +++ b/plugins/Karma/plugin.py @@ -53,7 +53,7 @@ class SqliteKarmaDB(object): 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] diff --git a/plugins/MoobotFactoids/plugin.py b/plugins/MoobotFactoids/plugin.py index 030f735d0..c0818bc77 100644 --- a/plugins/MoobotFactoids/plugin.py +++ b/plugins/MoobotFactoids/plugin.py @@ -103,7 +103,7 @@ class SqliteMoobotDB(object): 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) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 65c2e4b52..8fe6714bb 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -70,7 +70,7 @@ class SqliteQuoteGrabsDB(object): 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)) From 2688467a31fc94c718268f04cd90a6e179e0dea5 Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 23 Oct 2009 20:05:55 -0400 Subject: [PATCH 26/67] Re-word the private message FAQ since +E UMODE is no longer default. Signed-off-by: James Vega (cherry picked from commit dc3cdbd84980bff3b5087f89dee79d50be0aa62f) --- docs/FAQ | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/docs/FAQ b/docs/FAQ index 70de4d407..df7b724ab 100644 --- a/docs/FAQ +++ b/docs/FAQ @@ -109,18 +109,16 @@ Why doesn't `Karma` seem to work for me? 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: + network. Around Sept. 2005, Freenode added a user mode which + registered user could set that `blocks`_ private messages from + unregistered users. So, the reason you aren't seeing a response from + your Supybot is: - * Your Supybot is not registered with NickServ and + * Your Supybot is not registered with NickServ, you are registered, + and you have set the +E user mode for yourself. - o you haven't registered with NickServ - - * or you have registered with NickServ - - o but you haven't allowed `unregistered users`_ to message you + * or you have registered your Supybot with NickServ, you aren't + registered, and your Supybot has the +E user mode set. Can users with the "admin" capability change configuration? @@ -192,8 +190,7 @@ Is 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 +.. _blocks: http://freenode.net/faq.shtml#blockingmessages .. _tsocks: http://tsocks.sourceforge.net .. _Sourceforge: http://sourceforge.net/ .. _project page: http://sourceforge.net/projects/supybot From c5df44204dbfb03edbd3dc394fe4492d69e234b6 Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 23 Oct 2009 20:07:48 -0400 Subject: [PATCH 27/67] Use '2x' to reference Python version in README. Signed-off-by: James Vega (cherry picked from commit 96aaf159f6e882129922ed0bf628856cd30db30b) --- README | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README b/README index e4223bae1..ed1f6ab72 100644 --- a/README +++ b/README @@ -3,7 +3,6 @@ EVERYONE: Read LICENSE. It's a 2-clause BSD license, but you should read it anyway. - USERS: ------ If you're upgrading, read RELNOTES. There is also much documentation @@ -22,8 +21,7 @@ WINDOWS USERS: The wizards (supybot-wizard, supybot-newplugin, and supybot-adduser) are all installed to your Python directory's \Scripts. What that *probably* means is that you'll run them like -this: C:\Python23\python C:\Python23\Scripts\supybot-wizard - +this: C:\Python2x\python C:\Python2x\Scripts\supybot-wizard DEVELOPERS: ----------- From 56d97e0eeb0645946769761ccc459410b2b667dd Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 23 Oct 2009 20:09:54 -0400 Subject: [PATCH 28/67] Remove supybot-plugin-package. It was never fully fleshed out. If someone wants to finish it, they can rewrite it or dig it out of the history and finish it. Signed-off-by: James Vega (cherry picked from commit 899391e4dc589b1147f01e9e7ebc6314813caba9) --- scripts/supybot-plugin-package | 54 ---------------------------------- 1 file changed, 54 deletions(-) delete mode 100644 scripts/supybot-plugin-package diff --git a/scripts/supybot-plugin-package b/scripts/supybot-plugin-package deleted file mode 100644 index 10bed7911..000000000 --- a/scripts/supybot-plugin-package +++ /dev/null @@ -1,54 +0,0 @@ -#!/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: From d95cdfec34167bca5a41480c97b03e00e638476a Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 25 Oct 2009 09:20:45 -0400 Subject: [PATCH 29/67] Remove supybot-plugin-package from the set of installed files. Signed-off-by: James Vega (cherry picked from commit e393ea59178182a05d08c01534abcef80abab509) --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index e19de1248..de6b483ad 100644 --- a/setup.py +++ b/setup.py @@ -158,7 +158,6 @@ setup( 'scripts/supybot-adduser', 'scripts/supybot-plugin-doc', 'scripts/supybot-plugin-create', - 'scripts/supybot-plugin-package', ] ) From c53f8cd5105a965e4f1d4db1a82b54dd4c2649c2 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 31 Oct 2009 18:22:14 -0400 Subject: [PATCH 30/67] Rename Owner.log to Owner.logmark Since every plugin has a log method (to do actual logging), the log command was conflicting with that. The attempted workaround was overly complicated and broken. Simply renaming the command to logmark simplifies everything. Closes Sf #2889709 Signed-off-by: James Vega (cherry picked from commit acaa9b1fd6cc9a137db9f97e446ff994c0499273) --- plugins/Owner/plugin.py | 45 ++++++++++------------------------------- plugins/Owner/test.py | 4 ++-- 2 files changed, 13 insertions(+), 36 deletions(-) diff --git a/plugins/Owner/plugin.py b/plugins/Owner/plugin.py index cd44ec15c..5c3d895f8 100644 --- a/plugins/Owner/plugin.py +++ b/plugins/Owner/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2008, James Vega +# Copyright (c) 2008-2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -100,32 +100,6 @@ 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. @@ -134,8 +108,6 @@ class Owner(callbacks.Plugin): 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. @@ -185,10 +157,6 @@ class Owner(callbacks.Plugin): 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() @@ -298,6 +266,16 @@ class Owner(callbacks.Plugin): except SyntaxError, e: irc.queueMsg(callbacks.error(msg, str(e))) + def logmark(self, irc, msg, args, text): + """ + + Logs to the global Supybot log at critical priority. Useful for + marking logfiles for later searching. + """ + self.log.critical(text) + irc.replySuccess() + logmark = wrap(logmark, ['text']) + def announce(self, irc, msg, args, text): """ @@ -622,4 +600,3 @@ class Owner(callbacks.Plugin): Class = Owner # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: - diff --git a/plugins/Owner/test.py b/plugins/Owner/test.py index 625332827..61b5f2531 100644 --- a/plugins/Owner/test.py +++ b/plugins/Owner/test.py @@ -1,5 +1,6 @@ ### # 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 @@ -33,10 +34,9 @@ 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') + self.assertHelp('help logmark') def testSrcAmbiguity(self): self.assertError('capability add foo bar') From 9ddf07ce97e4afd9012e5737648dac00d9d459ba Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 1 Nov 2009 10:26:08 -0500 Subject: [PATCH 31/67] Allow Banmask.makeBanmask to work when dynamic.channel is None. This gives back the ability to generate a banmask based on the global banmask settings instead of per-channel settings. Signed-off-by: James Vega (cherry picked from commit 8a98653d3bc9cc638a1739e3d134ebdea8aa0112) --- src/commands.py | 5 +---- src/conf.py | 9 +++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/commands.py b/src/commands.py index ef7a61466..eee35c6c2 100644 --- a/src/commands.py +++ b/src/commands.py @@ -280,10 +280,7 @@ def getBanmask(irc, msg, args, state): getChannel(irc, msg, args, state) channel = state.channel banmaskstyle = conf.supybot.protocols.irc.banmask - try: - state.args[-1] = banmaskstyle.makeBanmask(state.args[-1]) - except AssertionError: - state.errorInvalid('channel', channel) + state.args[-1] = banmaskstyle.makeBanmask(state.args[-1]) def getUser(irc, msg, args, state): try: diff --git a/src/conf.py b/src/conf.py index 720f0e38b..1e620e858 100644 --- a/src/conf.py +++ b/src/conf.py @@ -926,20 +926,17 @@ class Banmask(registry.SpaceSeparatedSetOfStrings): 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) + channel = dynamic.channel + assert channel is None or ircutils.isChannel(channel) (nick, user, host) = ircutils.splitHostmask(hostmask) bnick = '*' buser = '*' bhost = '*' if not options: - options = get(supybot.protocols.irc.banmask, dynamic.channel) + options = get(supybot.protocols.irc.banmask, channel) for option in options: if option == 'nick': bnick = nick From 2a18d07a2e028a22f555c98f2d20830cf035d7bd Mon Sep 17 00:00:00 2001 From: James Vega Date: Wed, 18 Nov 2009 20:02:53 -0500 Subject: [PATCH 32/67] Default to strict RFC compliance. It's better to force people to use an RFC nick and change it after they connect than to let non-RFC nicks get used and not be able to connect to the network. Signed-off-by: James Vega (cherry picked from commit 07e283f4502db87cac55217f4567ad4feb418d44) --- src/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf.py b/src/conf.py index 1e620e858..b00b495c4 100644 --- a/src/conf.py +++ b/src/conf.py @@ -953,7 +953,7 @@ registerChannelValue(supybot.protocols.irc, 'banmask', default banmask style.""")) registerGlobalValue(supybot.protocols.irc, 'strictRfc', - registry.Boolean(False, """Determines whether the bot will strictly follow + registry.Boolean(True, """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 From a7c924b46656602591a67ef8f470e9375e177566 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 22 Nov 2009 14:31:58 -0500 Subject: [PATCH 33/67] Switch from using the various popen flavors to subprocess.Popen Signed-off-by: James Vega (cherry picked from commit fbdc44ca521acc45f26807b04b1ef1d84312501b) --- plugins/Status/plugin.py | 17 ++++-- plugins/Unix/plugin.py | 128 +++++++++++++++++++-------------------- scripts/supybot-botchk | 14 +++-- 3 files changed, 81 insertions(+), 78 deletions(-) diff --git a/plugins/Status/plugin.py b/plugins/Status/plugin.py index 1b033b243..fccd58336 100644 --- a/plugins/Status/plugin.py +++ b/plugins/Status/plugin.py @@ -1,5 +1,6 @@ ### # 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 @@ -31,6 +32,7 @@ import os import sys import time import threading +import subprocess import supybot.conf as conf import supybot.utils as utils @@ -142,12 +144,17 @@ class Status(callbacks.Plugin): if plat.startswith('linux') or plat.startswith('sunos') or \ plat.startswith('freebsd') or plat.startswith('openbsd') or \ plat.startswith('darwin'): + cmd = 'ps -o rss -p %s' % pid try: - r = os.popen('ps -o rss -p %s' % pid) - r.readline() # VSZ Header. - mem = r.readline().strip() - finally: - r.close() + inst = subprocess.Popen(cmd.split(), close_fds=True, + stdin=file(os.devnull), + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + except OSError: + irc.error('Unable to run ps command.', Raise=True) + (out, _) = inst.communicate() + inst.wait() + mem = out.splitlines()[1] 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 diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index f754e2d92..d4da8e070 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2008, James Vega +# Copyright (c) 2008-2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -34,10 +34,10 @@ import pwd import sys import crypt import errno -import popen2 import random import select import struct +import subprocess import supybot.utils as utils from supybot.commands import * @@ -130,46 +130,45 @@ class Unix(callbacks.Plugin): # 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) + irc.error('The spell checking command is not configured. 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() + inst = subprocess.Popen([spellCmd, '-a'], close_fds=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=subprocess.PIPE) + except OSError, e: + irc.error(e, Raise=True) + ret = inst.poll() + if ret is not None: + s = inst.stderr.readline() + if not s: + s = inst.stdout.readline() + s = s.rstrip('\r\n') + s = s.lstrip('Error: ') + irc.error(s, Raise=True) + (out, err) = inst.communicate(word) + inst.wait() + lines = filter(None, out.splitlines()) + lines.pop(0) # Banner + if not lines: + irc.error('No results found.', Raise=True) + line = lines.pop(0) + line2 = '' + if lines: + line2 = lines.pop(0) # 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'): + if line[0] in '*+' and line2: line = line2 if line[0] in '*+': resp = format('%q may be spelled correctly.', word) @@ -199,24 +198,23 @@ class Unix(callbacks.Plugin): 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() + inst = subprocess.Popen(args, close_fds=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + stdin=file(os.devnull)) + except OSError, e: + irc.error('It seems the configured fortune command was ' + 'not available.', Raise=True) + (out, err) = inst.communicate() + inst.wait() + lines = out.splitlines() + lines = map(str.rstrip, lines) + lines = filter(None, lines) + irc.replies(lines, joiner=' ') else: - irc.error('I couldn\'t find the fortune command on this system. ' - 'If it is installed on this system, reconfigure the ' + irc.error('The fortune command is not configured. If fortune is ' + 'installed on this system, reconfigure the ' 'supybot.plugins.Unix.fortune.command configuration ' 'variable appropriately.') @@ -229,31 +227,27 @@ class Unix(callbacks.Plugin): """ 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() + inst = subprocess.Popen([wtfCmd, something], close_fds=True, + stdout=subprocess.PIPE, + stderr=file(os.devnull), + stdin=file(os.devnull)) + except OSError: + irc.error('It seems the configured wtf command was not ' + 'available.', Raise=True) + (out, _) = inst.communicate() + inst.wait() + if out: + response = out.splitlines()[0].strip() + response = utils.str.normalizeWhitespace(response) + irc.reply(response) else: - irc.error('I couldn\'t find the wtf command on this system. ' - 'If it is installed on this system, reconfigure the ' + irc.error('The wtf command is not configured. 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: diff --git a/scripts/supybot-botchk b/scripts/supybot-botchk index f2924f49a..9020f7273 100644 --- a/scripts/supybot-botchk +++ b/scripts/supybot-botchk @@ -2,6 +2,7 @@ ### # Copyright (c) 2005, Jeremiah Fincher +# Copyright (c) 2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -59,8 +60,8 @@ if __name__ == '__main__': # import supybot.conf as conf import os import sys - import popen2 import optparse + import subprocess parser = optparse.OptionParser(usage='Usage: %prog [options]') parser.add_option('', '--verbose', action='store_true', @@ -120,17 +121,18 @@ if __name__ == '__main__': sys.exit(-1) debug('Bot not found, starting.') home = os.environ['HOME'] - inst = popen2.Popen4('sh') + inst = subprocess.Popen('sh', close_fds=True, stderr=subprocess.STDOUT, + stdin=subprocess.PIPE, stdout=subprocess.PIPE) 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) + inst.communicate('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()) + (stdout, _) = inst.communicate(cmdline + os.linesep) + inst.stdin.close() + debug('Received from sh process: %r' % stdout) ret = inst.wait() debug('Bot started, command line %r returned %s.' % (cmdline, ret)) sys.exit(ret) From 2a79c0cab9e531d998948915ae2eb6f34cd5fa01 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 28 Nov 2009 17:48:03 -0500 Subject: [PATCH 34/67] Fix supybot-botchk's use of subprocess Signed-off-by: James Vega (cherry picked from commit a48cd109a37ce9d164cd20f96e9aef65d8355417) --- scripts/supybot-botchk | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/supybot-botchk b/scripts/supybot-botchk index 9020f7273..ab5366c7a 100644 --- a/scripts/supybot-botchk +++ b/scripts/supybot-botchk @@ -127,12 +127,12 @@ if __name__ == '__main__': filename = os.path.join(home, filename) if os.path.exists(filename): debug('Found %s, sourcing.' % filename) - inst.communicate('source %s' % filename + os.linesep) - cmdline = "%s --daemon %s" % (options.supybot, options.conffile) + inst.stdin.write('source %s' % filename + os.linesep) + cmdline = '%s --daemon %s' % (options.supybot, options.conffile) debug('Sending cmdline to sh process.') - (stdout, _) = inst.communicate(cmdline + os.linesep) + inst.stdin.write(cmdline + os.linesep) inst.stdin.close() - debug('Received from sh process: %r' % stdout) + debug('Received from sh process: %r' % inst.stdout.read()) ret = inst.wait() debug('Bot started, command line %r returned %s.' % (cmdline, ret)) sys.exit(ret) From afb4e1e07ff5846729ad9f2325505d29eb55611e Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 28 Nov 2009 21:21:00 -0500 Subject: [PATCH 35/67] Use 0 when no results are returned for Google.fight Signed-off-by: James Vega (cherry picked from commit 3689460d0fa2e7ef8a6c95feda217b2fa6f4e73d) --- plugins/Google/plugin.py | 3 ++- plugins/Google/test.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 1d889edba..a78b1d981 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -219,7 +219,8 @@ class Google(callbacks.PluginRegexp): results = [] for arg in args: data = self.search(arg, channel, {'smallsearch': True}) - count = data['responseData']['cursor']['estimatedResultCount'] + count = data['responseData']['cursor'].get('estimatedResultCount', + 0) results.append((int(count), arg)) results.sort() results.reverse() diff --git a/plugins/Google/test.py b/plugins/Google/test.py index 83d498530..695e95037 100644 --- a/plugins/Google/test.py +++ b/plugins/Google/test.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2008, James Vega +# Copyright (c) 2008-2009, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -55,6 +55,7 @@ class GoogleTestCase(ChannelPluginTestCase): def testFight(self): self.assertRegexp('fight supybot moobot', r'.*supybot.*: \d+') + self.assertNotError('fight ... !') def testTranslate(self): self.assertRegexp('translate en es hello world', 'mundo') From 4de4717f4329493cf23be0c276ecb3bc2222668f Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 10 Dec 2009 08:09:11 -0500 Subject: [PATCH 36/67] PLUGIN_TUTORIAL: Remove references to the old website. Signed-off-by: James Vega (cherry picked from commit 0806c0bbc3b887c3d6b7210083504611a4e37b0c) --- docs/PLUGIN_TUTORIAL | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/docs/PLUGIN_TUTORIAL b/docs/PLUGIN_TUTORIAL index 8cf8af186..e1b766209 100644 --- a/docs/PLUGIN_TUTORIAL +++ b/docs/PLUGIN_TUTORIAL @@ -144,10 +144,7 @@ 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 +plugin. Since this is just an example, we'll leave this blank. 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 @@ -549,7 +546,7 @@ wisdom with regards to Supybot plugin-writing. 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. + aren't very well-documented. * Hack new functionality into existing plugins first if writing a new plugin is too daunting. @@ -558,14 +555,10 @@ wisdom with regards to Supybot plugin-writing. 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. + * 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. + * Read, read, read all the documentation. * And of course, have fun writing your plugins. From f35e68cd65a31f595237d99774976f56d299b411 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 10 Jan 2010 20:17:10 -0500 Subject: [PATCH 37/67] Network: Properly parse WHOIS response The 319 message that indicates which channel(s) a user is in prefix the channel name with the modes (@, +, !, etc.) applied to that user. These need to be stripped from the channel name before we feed it to irc.state.channels.get(), otherwise when irc.state.channels.get() returns None we assume the channel can't be private and leak information. (cherry picked from commit 408ab6f88a04f593f55ab38e33e558c5297d5d77) --- plugins/Network/plugin.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/plugins/Network/plugin.py b/plugins/Network/plugin.py index 10dbdc354..2eb405f01 100644 --- a/plugins/Network/plugin.py +++ b/plugins/Network/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -166,7 +167,19 @@ class Network(callbacks.Plugin): normal = [] halfops = [] for channel in channels: + origchan = channel + channel = channel.lstrip('@%+~!') + # UnrealIRCd uses & for user modes and disallows it as a + # channel-prefix, flying in the face of the RFC. Have to + # handle this specially when processing WHOIS response. + testchan = channel.lstrip('&') + if testchan != channel and irc.isChannel(testchan): + channel = testchan + diff = len(channel) - len(origchan) + modes = origchan[:diff] chan = irc.state.channels.get(channel) + # The user is in a channel the bot is in, so the ircd may have + # responded with otherwise private data. if chan: # Skip channels the callee isn't in. This helps prevents # us leaking information when the channel is +s or the @@ -178,14 +191,14 @@ class Network(callbacks.Plugin): if 's' in chan.modes and \ not ircutils.strEqual(replyMsg.args[0], channel): continue - if channel.startswith('@'): - ops.append(channel[1:]) - elif channel.startswith('%'): - halfops.append(channel[1:]) - elif channel.startswith('+'): - voices.append(channel[1:]) - else: + if not modes: normal.append(channel) + elif utils.iter.any(lambda c: c in modes,('@', '&', '~', '!')): + ops.append(channel[1:]) + elif utils.iter.any(lambda c: c in modes, ('%',)): + halfops.append(channel[1:]) + elif utils.iter.any(lambda c: c in modes, ('+',)): + voices.append(channel[1:]) L = [] if ops: L.append(format('is an op on %L', ops)) From e2cafb2e3dd338d506eafde739aaab9bbe5de84f Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Thu, 28 Jan 2010 06:35:53 -0600 Subject: [PATCH 38/67] Don't bother snarfing URLs from non-Action CTCP messages. (cherry picked from commit 3282e3407ede364acbc92b4e9a6319800d50d46a) --- plugins/Web/plugin.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/Web/plugin.py b/plugins/Web/plugin.py index 6a332c792..bf9b66bda 100644 --- a/plugins/Web/plugin.py +++ b/plugins/Web/plugin.py @@ -36,6 +36,7 @@ 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 @@ -79,6 +80,8 @@ class Web(callbacks.PluginRegexp): channel = msg.args[0] if not irc.isChannel(channel): return + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return if callbacks.addressed(irc.nick, msg): return if self.registryValue('titleSnarfer', channel): From 455b5631bc935f3d3689a535976c249b4cbab70d Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 28 Jan 2010 08:14:44 -0500 Subject: [PATCH 39/67] Update plugins to ignore all non-ACTION CTCP messages. Also update commands.urlSnarfer to do the same, which allows us to revert "Don't bother snarfing URLs from non-Action CTCP messages." This reverts commit 3282e3407ede364acbc92b4e9a6319800d50d46a. Signed-off-by: James Vega (cherry picked from commit 288d7c6e0216baa6445cbd90bb819d1fd0f113c4) --- plugins/ChannelStats/plugin.py | 7 +++++-- plugins/Karma/plugin.py | 2 ++ plugins/Later/plugin.py | 4 ++++ plugins/Note/plugin.py | 5 ++++- plugins/QuoteGrabs/plugin.py | 4 +++- plugins/Relay/plugin.py | 6 +++--- plugins/Seen/plugin.py | 3 +++ plugins/URL/plugin.py | 3 +++ plugins/Web/plugin.py | 3 --- src/commands.py | 7 ++++--- 10 files changed, 31 insertions(+), 13 deletions(-) diff --git a/plugins/ChannelStats/plugin.py b/plugins/ChannelStats/plugin.py index baf768007..adbf0fd91 100644 --- a/plugins/ChannelStats/plugin.py +++ b/plugins/ChannelStats/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2009, James Vega +# Copyright (c) 2009-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -81,8 +81,11 @@ class ChannelStat(irclib.IrcCommandDispatcher): self.smileys += len(sRe.findall(payload)) def doPrivmsg(self, msg): + isAction = ircmsgs.isAction(msg) + if ircmsgs.isCtcp(msg) and not isAction: + return self.doPayload(*msg.args) - if ircmsgs.isAction(msg): + if isAction: self.actions += 1 def doTopic(self, msg): diff --git a/plugins/Karma/plugin.py b/plugins/Karma/plugin.py index 36532b286..d7a33ec41 100644 --- a/plugins/Karma/plugin.py +++ b/plugins/Karma/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2005, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -265,6 +266,7 @@ class Karma(callbacks.Plugin): if not (msg.addressed or msg.repliedTo): channel = msg.args[0] if irc.isChannel(channel) and \ + not ircmsgs.isCtcp(msg) and \ self.registryValue('allowUnaddressedKarma', channel): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() diff --git a/plugins/Later/plugin.py b/plugins/Later/plugin.py index ca964bc7b..3babf6365 100644 --- a/plugins/Later/plugin.py +++ b/plugins/Later/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -34,6 +35,7 @@ import supybot.log as log 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.callbacks as callbacks @@ -152,6 +154,8 @@ class Later(callbacks.Plugin): remove = wrap(remove, [('checkCapability', 'admin'), 'something']) def doPrivmsg(self, irc, msg): + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return notes = self._notes.pop(msg.nick, []) # Let's try wildcards. removals = [] diff --git a/plugins/Note/plugin.py b/plugins/Note/plugin.py index 525430122..95a53bbea 100644 --- a/plugins/Note/plugin.py +++ b/plugins/Note/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2004, Brett Kelly +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -121,7 +122,7 @@ class DbiNoteDB(dbi.DB): for (to, ids) in cache.items(): while id in ids: ids.remove(id) - + NoteDB = plugins.DB('Note', {'flat': DbiNoteDB}) class Note(callbacks.Plugin): @@ -135,6 +136,8 @@ class Note(callbacks.Plugin): self.db.close() def doPrivmsg(self, irc, msg): + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return self._notify(irc, msg) def doJoin(self, irc, msg): diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 8fe6714bb..03d3a98d6 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2004, Daniel DiPaolo -# Copyright (c) 2008-2009, James Vega +# Copyright (c) 2008-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -212,6 +212,8 @@ class QuoteGrabs(callbacks.Plugin): self.db = QuoteGrabsDB() def doPrivmsg(self, irc, msg): + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return irc = callbacks.SimpleProxy(irc, msg) if irc.isChannel(msg.args[0]): (chan, payload) = msg.args diff --git a/plugins/Relay/plugin.py b/plugins/Relay/plugin.py index c19f836f4..ffd31f80f 100644 --- a/plugins/Relay/plugin.py +++ b/plugins/Relay/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -339,6 +340,8 @@ class Relay(callbacks.Plugin): notPunishing(irc, 'not opped') def doPrivmsg(self, irc, msg): + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return (channel, text) = msg.args if irc.isChannel(channel): irc = self._getRealIrc(irc) @@ -350,9 +353,6 @@ class Relay(callbacks.Plugin): 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): diff --git a/plugins/Seen/plugin.py b/plugins/Seen/plugin.py index 291027b54..d5bc6275e 100644 --- a/plugins/Seen/plugin.py +++ b/plugins/Seen/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -136,6 +137,8 @@ class Seen(callbacks.Plugin): irc.queueMsg(ircmsgs.names(channel)) def doPrivmsg(self, irc, msg): + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return if irc.isChannel(msg.args[0]): channel = msg.args[0] said = ircmsgs.prettyPrint(msg) diff --git a/plugins/URL/plugin.py b/plugins/URL/plugin.py index 4914ef22a..50787ca68 100644 --- a/plugins/URL/plugin.py +++ b/plugins/URL/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -65,6 +66,8 @@ class URL(callbacks.Plugin): self.db = URLDB() def doPrivmsg(self, irc, msg): + if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): + return channel = msg.args[0] if irc.isChannel(channel): if ircmsgs.isAction(msg): diff --git a/plugins/Web/plugin.py b/plugins/Web/plugin.py index bf9b66bda..6a332c792 100644 --- a/plugins/Web/plugin.py +++ b/plugins/Web/plugin.py @@ -36,7 +36,6 @@ 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 @@ -80,8 +79,6 @@ class Web(callbacks.PluginRegexp): channel = msg.args[0] if not irc.isChannel(channel): return - if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): - return if callbacks.addressed(irc.nick, msg): return if self.registryValue('titleSnarfer', channel): diff --git a/src/commands.py b/src/commands.py index eee35c6c2..764df1135 100644 --- a/src/commands.py +++ b/src/commands.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2009, James Vega +# Copyright (c) 2009-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -109,10 +109,11 @@ def urlSnarfer(f): def newf(self, irc, msg, match, *L, **kwargs): url = match.group(0) channel = msg.args[0] - if not irc.isChannel(channel): + if not irc.isChannel(channel) or (ircmsgs.isCtcp(msg) and not + ircmsgs.isAction(msg)): return if ircdb.channels.getChannel(channel).lobotomized: - self.log.info('Not snarfing in %s: lobotomized.', channel) + self.log.debug('Not snarfing in %s: lobotomized.', channel) return if _snarfed.has(channel, url): self.log.info('Throttling snarf of %s in %s.', url, channel) From e13d3d69438aa318a0d5371cf46af8cbd8cc39ce Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 30 Jan 2010 23:03:35 -0500 Subject: [PATCH 40/67] Decode/encode as 'string_escape' when (de)serializing the registry. Signed-off-by: James Vega --- src/callbacks.py | 2 +- src/registry.py | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/callbacks.py b/src/callbacks.py index 1e6ff556b..d12f1ea5a 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -275,7 +275,7 @@ class Tokenizer(object): def _handleToken(self, token): if token[0] == token[-1] and token[0] in self.quotes: token = token[1:-1] - token = token.decode('string-escape') + token = token.decode('string_escape') return token def _insideBrackets(self, lexer): diff --git a/src/registry.py b/src/registry.py index 63dab3e9a..afaf876fe 100644 --- a/src/registry.py +++ b/src/registry.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2004-2005, Jeremiah Fincher +# Copyright (c) 2009-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -87,7 +88,7 @@ def open(filename, clear=False): try: (key, value) = re.split(r'(? Date: Fri, 5 Feb 2010 18:50:03 -0500 Subject: [PATCH 41/67] Factoids: Prevent empty key or value when adding a factoid. Signed-off-by: James Vega (cherry picked from commit 10f5a12b7e6b33000dc8616ba5e6b57e25ab9f08) --- plugins/Factoids/plugin.py | 4 +++- plugins/Factoids/test.py | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 71eb9704f..f86c937cd 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher -# Copyright (c) 2009, James Vega +# Copyright (c) 2009-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -63,6 +63,8 @@ def getFactoid(irc, msg, args, state): key.append(args.pop(0)) else: value.append(args.pop(0)) + if not key or not value: + raise callbacks.ArgumentError state.args.append(' '.join(key)) state.args.append(' '.join(value)) diff --git a/plugins/Factoids/test.py b/plugins/Factoids/test.py index a2c1f4a1a..fb8fa7e35 100644 --- a/plugins/Factoids/test.py +++ b/plugins/Factoids/test.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -43,6 +44,8 @@ if sqlite: self.assertRegexp('random', 'primary author') def testLearn(self): + self.assertError('learn as my primary author') + self.assertError('learn jemfinch as') self.assertNotError('learn jemfinch as my primary author') self.assertNotError('info jemfinch') self.assertRegexp('whatis jemfinch', 'my primary author') From 8d94ff743c35335ec44c84c3ef97587db6d99b18 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 28 Feb 2010 21:17:35 -0500 Subject: [PATCH 42/67] Include hostmasks in JOIN, PART, QUIT logs. Signed-off-by: James Vega (cherry picked from commit 4a9596608abfbacf75d46237b29fee18ec2e9205) --- plugins/ChannelLogger/plugin.py | 13 ++++++++----- src/ircmsgs.py | 11 ++++++++--- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/plugins/ChannelLogger/plugin.py b/plugins/ChannelLogger/plugin.py index 719e306cc..93f9b3c06 100644 --- a/plugins/ChannelLogger/plugin.py +++ b/plugins/ChannelLogger/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2009, James Vega +# Copyright (c) 2009-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -206,8 +206,8 @@ class ChannelLogger(callbacks.Plugin): 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) + '*** %s <%s> has joined %s\n', + msg.nick, msg.prefix, channel) def doKick(self, irc, msg): if len(msg.args) == 3: @@ -226,7 +226,8 @@ class ChannelLogger(callbacks.Plugin): def doPart(self, irc, msg): for channel in msg.args[0].split(','): self.doLog(irc, channel, - '*** %s has left %s\n', msg.nick, channel) + '*** %s <%s> has left %s\n', + msg.nick, msg.prefix, channel) def doMode(self, irc, msg): channel = msg.args[0] @@ -248,7 +249,9 @@ class ChannelLogger(callbacks.Plugin): 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) + self.doLog(irc, channel, + '*** %s <%s> has quit IRC\n', + msg.nick, msg.prefix) def outFilter(self, irc, msg): # Gotta catch my own messages *somehow* :) diff --git a/src/ircmsgs.py b/src/ircmsgs.py index 3d5a47e90..f2fec7aec 100644 --- a/src/ircmsgs.py +++ b/src/ircmsgs.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2005, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -292,13 +293,17 @@ def prettyPrint(msg, addRecipients=False, timestampFormat=None, showNick=True): else: s = '-%s- %s' % (nick(), msg.args[1]) elif msg.command == 'JOIN': - s = '*** %s has joined %s' % (msg.nick, msg.args[0]) + prefix = msg.prefix + if msg.nick: + prefix = '%s <%s>' % (msg.nick, prefix) + s = '*** %s has joined %s' % (prefix, 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) + s = '*** %s <%s> has parted %s%s' % (msg.nick, msg.prefix, + msg.args[0], partmsg) elif msg.command == 'KICK': if len(msg.args) > 2: kickmsg = ' (%s)' % msg.args[1] @@ -312,7 +317,7 @@ def prettyPrint(msg, addRecipients=False, timestampFormat=None, showNick=True): quitmsg = ' (%s)' % msg.args[0] else: quitmsg = '' - s = '*** %s has quit IRC%s' % (msg.nick, quitmsg) + s = '*** %s <%s> has quit IRC%s' % (msg.nick, msg.prefix, quitmsg) elif msg.command == 'TOPIC': s = '*** %s changes topic to %s' % (nickorprefix(), msg.args[1]) elif msg.command == 'NICK': From be9128b3ec4b61170eebd98b97e33db8d338dc9b Mon Sep 17 00:00:00 2001 From: James Vega Date: Wed, 3 Mar 2010 08:33:44 -0500 Subject: [PATCH 43/67] Internet: Use whois-servers.net for all whois lookups. rs.internic.net seems to be broken and using $tld.whois-servers.net looks to be working for everything. Also need to update the line termination string to use '\r\n' instead of '\n' since some servers are strict about receiving the former. Signed-off-by: James Vega (cherry picked from commit 397cbbe0d3d8ac2ae6ec17ff4f9bfd4fc3297912) --- plugins/Internet/plugin.py | 19 +++++-------------- plugins/Internet/test.py | 4 ++-- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/plugins/Internet/plugin.py b/plugins/Internet/plugin.py index d2ca86839..490c9165c 100644 --- a/plugins/Internet/plugin.py +++ b/plugins/Internet/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2003-2005, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -61,7 +62,6 @@ class Internet(callbacks.Plugin): 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', @@ -76,24 +76,15 @@ class Internet(callbacks.Plugin): """ usertld = domain.split('.')[-1] if '.' not in domain: - irc.error(' must be in .com, .net, .edu, or .org.') + irc.errorInvalid('domain') 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) + t = telnetlib.Telnet('%s.whois-servers.net' % usertld, 43) except socket.error, e: irc.error(str(e)) return - t.write(search) - t.write('\n') + t.write(domain) + t.write('\r\n') s = t.read_all() server = registrar = updated = created = expires = status = '' for line in s.splitlines(): diff --git a/plugins/Internet/test.py b/plugins/Internet/test.py index b4bca2114..e098fdeee 100644 --- a/plugins/Internet/test.py +++ b/plugins/Internet/test.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2003-2005, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -39,11 +40,10 @@ class InternetTestCase(PluginTestCase): 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') + self.assertNotError('internet whois slime.com.au') # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From fa199022e1aae642bd8e2a5fe8da99b39e1d6e97 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 11 Apr 2010 10:15:39 -0400 Subject: [PATCH 44/67] Make registry.Regexp.error mimic registry.Value.error Regexp.error can't directly call Value.error because it's providing extra information, so it needs to build the InvalidRegistryValue exception itself and raise it. Closes: Sf#2985241 Signed-off-by: James Vega (cherry picked from commit ef8bd817e8b62ede76aa7501a9a8d69af7408efc) --- src/registry.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/registry.py b/src/registry.py index afaf876fe..48f94caba 100644 --- a/src/registry.py +++ b/src/registry.py @@ -539,7 +539,10 @@ class Regexp(Value): self.__parent.__init__(*args, **kwargs) def error(self, e): - self.__parent.error('Value must be a regexp of the form %s' % e) + s = 'Value must be a regexp of the form m/.../ or /.../. %s' % e + e = InvalidRegistryValue(s) + e.value = self + raise e def set(self, s): try: From b2c45caa66898b6e76e549901058c16c3a0361d6 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 11 Apr 2010 10:36:55 -0400 Subject: [PATCH 45/67] Factoids: Pass channel to whatis when being called from search Thanks to Daniel Folkinshteyn for the fix. Closes: Sf#2965589 Signed-off-by: James Vega (cherry picked from commit 209facd242ebcf0fc9974f01b87cf47a8e25098f) --- plugins/Factoids/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index f86c937cd..8460e09ee 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -417,7 +417,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.reply('No keys matched that query.') elif cursor.rowcount == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel): - self.whatis(irc, msg, [cursor.fetchone()[0]]) + self.whatis(irc, msg, channel, [cursor.fetchone()[0]]) elif cursor.rowcount > 100: irc.reply('More than 100 keys matched that query; ' 'please narrow your query.') From 7869b962d1dc695eb2673478b197c9e19139987f Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 11 Apr 2010 11:04:45 -0400 Subject: [PATCH 46/67] Fix the previous Factoids fix. channel needs to be part of the args list. Signed-off-by: James Vega (cherry picked from commit fe07ea11465785db69e13f034ea8f2e219b5c64c) --- plugins/Factoids/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 8460e09ee..30d366fce 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -417,7 +417,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.reply('No keys matched that query.') elif cursor.rowcount == 1 and \ self.registryValue('showFactoidIfOnlyOneMatch', channel): - self.whatis(irc, msg, channel, [cursor.fetchone()[0]]) + self.whatis(irc, msg, [channel, cursor.fetchone()[0]]) elif cursor.rowcount > 100: irc.reply('More than 100 keys matched that query; ' 'please narrow your query.') From fb8d0d320a8d9838a4b23e48213d7bd65063c266 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 14 Apr 2010 10:27:56 -0400 Subject: [PATCH 47/67] fix alias bug https://sourceforge.net/tracker/?func=detail&aid=2987147&group_id=58965&atid=489447 add tests for appropriate behavior Signed-off-by: James Vega (cherry picked from commit 8d64d08645319e57f7bee8f9176cae63c67fe1f3) --- plugins/Alias/plugin.py | 22 ++++++++++------------ plugins/Alias/test.py | 6 +++++- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/plugins/Alias/plugin.py b/plugins/Alias/plugin.py index 02b66ad07..9021b6809 100644 --- a/plugins/Alias/plugin.py +++ b/plugins/Alias/plugin.py @@ -62,18 +62,18 @@ def getChannel(msg, args=()): raise callbacks.Error, 'Command must be sent in a channel or ' \ 'include a channel in its arguments.' -def getArgs(args, required=1, optional=0): +def getArgs(args, required=1, optional=0, wildcard=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 + if not wildcard: + ret = list(args[:required + optional - 1]) + ret.append(' '.join(args[required + optional - 1:])) + else: + ret = list(args) + return ret class AliasError(Exception): pass @@ -119,11 +119,9 @@ def makeNewAlias(name, alias): 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] + if biggestDollar or biggestAt: + args = getArgs(args, required=biggestDollar, optional=biggestAt, + wildcard=wildcard) def regexpReplace(m): idx = int(m.group(1)) return args[idx-1] diff --git a/plugins/Alias/test.py b/plugins/Alias/test.py index a4de21c70..57649dfa6 100644 --- a/plugins/Alias/test.py +++ b/plugins/Alias/test.py @@ -85,7 +85,11 @@ class AliasTestCase(ChannelPluginTestCase): 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 $*"') - + self.assertNotError('alias add moo echo $1 $*') + self.assertError('moo') + self.assertResponse('moo foo', 'foo') + self.assertResponse('moo foo bar', 'foo bar') + def testChannel(self): self.assertNotError('alias add channel echo $channel') self.assertResponse('alias channel', self.channel) From 4f2279fc92508369c6007ff768cd4ac5bb44c4af Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Sun, 25 Apr 2010 23:24:42 -0400 Subject: [PATCH 48/67] Improve supybot-botchk documentation. Make a note that supybot.pidFile config must be set for it to work. Signed-off-by: James Vega (cherry picked from commit 79c7514f1a94b0f1792991a85a8b2fe907cffdb5) --- scripts/supybot-botchk | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/supybot-botchk b/scripts/supybot-botchk index ab5366c7a..468bead4f 100644 --- a/scripts/supybot-botchk +++ b/scripts/supybot-botchk @@ -72,7 +72,9 @@ if __name__ == '__main__': 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.') + 'given bot directory. Note that for this to actually ' + 'work, you have to make a matching entry in the ' + 'supybot.pidFile config in the supybot registry.') parser.add_option('', '--supybot', default='supybot', help='Determines where the supybot executable is ' 'located. If not given, assumes that supybot is ' From e9d55d4bbde8dbb29774a94cad7cb3f077de0383 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Mon, 26 Apr 2010 19:50:08 -0400 Subject: [PATCH 49/67] fix bug in RSS.announce.list: Because the 'channel' argument was declared optional, calling announce.list off-channel without a channel argument caused an error. Signed-off-by: James Vega (cherry picked from commit 40941e044acf5a79cc9d95f5bf49b742243fc410) --- plugins/RSS/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/RSS/plugin.py b/plugins/RSS/plugin.py index ae0efb0cd..6163965fb 100644 --- a/plugins/RSS/plugin.py +++ b/plugins/RSS/plugin.py @@ -337,7 +337,7 @@ class RSS(callbacks.Plugin): 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')]) + list = wrap(list, ['channel',]) def add(self, irc, msg, args, channel, feeds): """[] [ ...] From 246e09cc99cb7ae0d3188eb3f73a09e2a3e364f5 Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 24 May 2010 15:21:58 -0400 Subject: [PATCH 50/67] Anonymous: Implement support for allowPrivateTarget config. Closes: Sf#2991515 Signed-off-by: James Vega (cherry picked from commit 57e894de589dcdf9c7a63f8279105420e0890674) --- plugins/Anonymous/plugin.py | 48 +++++++++++++++++++++---------------- plugins/Anonymous/test.py | 13 +++++++--- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/plugins/Anonymous/plugin.py b/plugins/Anonymous/plugin.py index 18205306e..9eb95260d 100644 --- a/plugins/Anonymous/plugin.py +++ b/plugins/Anonymous/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2005, Daniel DiPaolo +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -45,7 +46,7 @@ class Anonymous(callbacks.Plugin): that the user be registered by setting supybot.plugins.Anonymous.requireRegistration. """ - def _preCheck(self, irc, msg, channel): + def _preCheck(self, irc, msg, target, action): if self.registryValue('requireRegistration'): try: _ = ircdb.users.getUser(msg.prefix) @@ -55,35 +56,42 @@ class Anonymous(callbacks.Plugin): 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) + if irc.isChannel(target): + if self.registryValue('requirePresenceInChannel', target) and \ + msg.nick not in irc.state.channels[target].users: + irc.error(format('You must be in %s to %q in there.', + target, action), Raise=True) + c = ircdb.channels.getChannel(target) + if c.lobotomized: + irc.error(format('I\'m lobotomized in %s.', target), + 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) + elif action == 'say' and not self.registryValue('allowPrivateTarget'): + irc.error(format('%q cannot be used to send private messages.', + action), + Raise=True) - def say(self, irc, msg, args, channel, text): - """ + def say(self, irc, msg, args, target, text): + """ - Sends to . + Sends to . Can only send to if + supybot.plugins.Anonymous.allowPrivateTarget is True. """ - 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)) + self._preCheck(irc, msg, target, 'say') + self.log.info('Saying %q to %s due to %s.', + text, target, msg.prefix) + irc.queueMsg(ircmsgs.privmsg(target, text)) irc.noReply() - say = wrap(say, ['inChannel', 'text']) + say = wrap(say, [first('nick', 'inChannel'), 'text']) def do(self, irc, msg, args, channel, text): """ Performs in . """ - self._preCheck(irc, msg, channel) + self._preCheck(irc, msg, channel, 'do') self.log.info('Performing %q in %s due to %s.', text, channel, msg.prefix) irc.queueMsg(ircmsgs.action(channel, text)) diff --git a/plugins/Anonymous/test.py b/plugins/Anonymous/test.py index e2a9ab05b..b65baa581 100644 --- a/plugins/Anonymous/test.py +++ b/plugins/Anonymous/test.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2005, Daniel DiPaolo +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -32,14 +33,20 @@ from supybot.test import * class AnonymousTestCase(ChannelPluginTestCase): plugins = ('Anonymous',) def testSay(self): - m = self.assertError('anonymous say %s I love you!' % self.channel) + self.assertError('anonymous say %s I love you!' % self.channel) + self.assertError('anonymous say %s I love you!' % self.nick) + origreg = conf.supybot.plugins.Anonymous.requireRegistration() + origpriv = conf.supybot.plugins.Anonymous.allowPrivateTarget() 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!') + conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(True) + m = self.assertNotError('anonymous say %s foo!' % self.nick) + self.failUnless(m.args[1] == 'foo!') finally: - conf.supybot.plugins.Anonymous.requireRegistration.setValue(orig) + conf.supybot.plugins.Anonymous.requireRegistration.setValue(origreg) + conf.supybot.plugins.Anonymous.allowPrivateTarget.setValue(origpriv) def testAction(self): m = self.assertError('anonymous do %s loves you!' % self.channel) From a6857ce9bf4a9f25cd7a6fd0005793b64f7c67dd Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 24 May 2010 15:44:25 -0400 Subject: [PATCH 51/67] utils/web.py: Only try catching socket.sslerror if built with SSL support Closes: Sf#2998820 Signed-off-by: James Vega (cherry picked from commit f03a3f6c8529c15042908d826235e46123cd29e2) --- src/utils/web.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/utils/web.py b/src/utils/web.py index e05648560..f9d9bdd79 100644 --- a/src/utils/web.py +++ b/src/utils/web.py @@ -36,6 +36,12 @@ import sgmllib import urlparse import htmlentitydefs +sockerrors = (socket.error,) +try: + sockerrors += (socket.sslerror,) +except AttributeError: + pass + from str import normalizeWhitespace Request = urllib2.Request @@ -106,7 +112,7 @@ def getUrlFd(url, headers=None): return fd except socket.timeout, e: raise Error, TIMED_OUT - except (socket.error, socket.sslerror), e: + except sockerrors, e: raise Error, strError(e) except httplib.InvalidURL, e: raise Error, 'Invalid URL: %s' % e From ff760d1c690ef81e3d74737d1f306daf99ef5742 Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 24 May 2010 23:36:29 -0400 Subject: [PATCH 52/67] Socket: Ensure driver is flagged as disconnected after a socket error. Users were occasionally hitting a situation where the socket had errored, causing a reconnect, but the socket wasn't closed nor the driver marked as disconnected. This resulted in run() continuing to try and use the driver, which would cause another error, schedule another reconnect, log an error, ad infinitum. Closes: Sf#2965530 Signed-off-by: James Vega (cherry picked from commit a278d17f2bec3697987cbd403594393a4fb30021) --- src/drivers/Socket.py | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/src/drivers/Socket.py b/src/drivers/Socket.py index 86c6d6ada..6db605a59 100644 --- a/src/drivers/Socket.py +++ b/src/drivers/Socket.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -56,8 +57,9 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): self.inbuffer = '' self.outbuffer = '' self.zombie = False - self.scheduled = None self.connected = False + self.writeCheckTime = None + self.nextReconnectTime = None self.resetDelay() # Only connect to non-SSL servers if self.networkGroup.get('ssl').value: @@ -87,6 +89,11 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): # 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) + try: + self.conn.close() + except: + pass + self.connected = False self.scheduleReconnect() else: log.debug('Got EAGAIN, current count: %s.', self.eagains) @@ -110,6 +117,11 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): self._reallyDie() def run(self): + now = time.time() + if self.nextReconnectTime is not None and now > self.nextReconnectTime: + self.reconnect() + elif self.writeCheckTime is not None and now > self.writeCheckTime: + self._checkAndWriteOrReconnect() 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. @@ -137,7 +149,7 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): self.reconnect(reset=False, **kwargs) def reconnect(self, reset=True): - self.scheduled = None + self.nextReconnectTime = None if self.connected: drivers.log.reconnect(self.irc.network) self.conn.close() @@ -172,13 +184,14 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): whenS = log.timestamp(when) drivers.log.debug('Connection in progress, scheduling ' 'connectedness check for %s', whenS) - schedule.addEvent(self._checkAndWriteOrReconnect, when) + self.writeCheckTime = when else: drivers.log.connectError(self.currentServer, e) self.scheduleReconnect() return def _checkAndWriteOrReconnect(self): + self.writeCheckTime = None drivers.log.debug('Checking whether we are connected.') (_, w, _) = select.select([], [self.conn], [], 0) if w: @@ -193,18 +206,19 @@ class SocketDriver(drivers.IrcDriver, drivers.ServersMixin): 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 ' + if self.nextReconnectTime: + drivers.log.error('Updating next reconnect time when one is ' + 'already present. 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) + self.nextReconnectTime = when def die(self): self.zombie = True - if self.scheduled: - schedule.removeEvent(self.scheduled) + if self.nextReconnectTime is not None: + self.nextReconnectTime = None + if self.writeCheckTime is not None: + self.writeCheckTime = None drivers.log.die(self.irc) def _reallyDie(self): From 8d5e4ba6240d8bb33b36071a0d45d51c769d4575 Mon Sep 17 00:00:00 2001 From: James Vega Date: Fri, 18 Jun 2010 20:33:43 -0400 Subject: [PATCH 53/67] Twisted: Send all available ircmsgs and reduce delay between checks All ircmsgs that takeMsg will return should be processed each time checkIrcForMsgs is called since there may be multiple available in the fastqueue. Reduced the time between calls of checkIrcForMsgs so the delay between normally queued ircmsgs stays close to the configured throttleTime. Closes: Sf#3018148 (cherry picked from commit adc5d62bbf6b89631ab25e9b5f5707bde0b06709) --- src/drivers/Twisted.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/drivers/Twisted.py b/src/drivers/Twisted.py index 7b41b7c4f..c91d80d6b 100644 --- a/src/drivers/Twisted.py +++ b/src/drivers/Twisted.py @@ -66,7 +66,7 @@ class SupyIrcProtocol(LineReceiver): delimiter = '\n' MAX_LENGTH = 1024 def __init__(self): - self.mostRecentCall = reactor.callLater(1, self.checkIrcForMsgs) + self.mostRecentCall = reactor.callLater(0.1, self.checkIrcForMsgs) def lineReceived(self, line): msg = drivers.parseMsg(line) @@ -76,9 +76,10 @@ class SupyIrcProtocol(LineReceiver): def checkIrcForMsgs(self): if self.connected: msg = self.irc.takeMsg() - if msg: + while msg: self.transport.write(str(msg)) - self.mostRecentCall = reactor.callLater(1, self.checkIrcForMsgs) + msg = self.irc.takeMsg() + self.mostRecentCall = reactor.callLater(0.1, self.checkIrcForMsgs) def connectionLost(self, r): self.mostRecentCall.cancel() From 976567c1ac64d1eb67101cb210f088514a6eada3 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 19 Jun 2010 16:59:13 -0400 Subject: [PATCH 54/67] Services: Disable most of the plugin on networks in the disabled list. Notify the user when trying to use the commands on a disabled network, ignore noJoinsUntilIdentified, and don't try communicating with services. Closes: Sf#3018464 Signed-off-by: James Vega (cherry picked from commit 9e73f4482c32b517f9927931831e3c8d15c285ab) --- plugins/Services/plugin.py | 43 +++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/plugins/Services/plugin.py b/plugins/Services/plugin.py index cca8ef5b7..2e50f7b0d 100644 --- a/plugins/Services/plugin.py +++ b/plugins/Services/plugin.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -62,6 +63,13 @@ class Services(callbacks.Plugin): self.identified = False self.waitingJoins = [] + def isDisabled(self, irc): + disabled = self.registryValue('disabledNetworks') + if irc.network in disabled or \ + irc.state.supported.get('NETWORK', '') in disabled: + return True + return False + def outFilter(self, irc, msg): if msg.command == 'JOIN': if not self.identified: @@ -75,9 +83,6 @@ class Services(callbacks.Plugin): def _getNick(self): return conf.supybot.nick() -## def _getNickServ(self, network): -## return self.registryValue('NickServ', network) - def _getNickServPassword(self, nick): # This should later be nick-specific. assert nick in self.registryValue('nicks') @@ -89,6 +94,8 @@ class Services(callbacks.Plugin): self.setRegistryValue('NickServ.password.%s' % nick, password) def _doIdentify(self, irc, nick=None): + if self.isDisabled(irc): + return if nick is None: nick = self._getNick() if nick not in self.registryValue('nicks'): @@ -109,6 +116,8 @@ class Services(callbacks.Plugin): irc.sendMsg(ircmsgs.privmsg(nickserv, identify)) def _doGhost(self, irc, nick=None): + if self.isDisabled(irc): + return if nick is None: nick = self._getNick() if nick not in self.registryValue('nicks'): @@ -135,11 +144,9 @@ class Services(callbacks.Plugin): self.sentGhost = time.time() def __call__(self, irc, msg): - disabled = self.registryValue('disabledNetworks') - if irc.network in disabled or \ - irc.state.supported.get('NETWORK', '') in disabled: - return self.__parent.__call__(irc, msg) + if self.isDisabled(irc): + return nick = self._getNick() if nick not in self.registryValue('nicks'): return @@ -160,6 +167,8 @@ class Services(callbacks.Plugin): self.sentGhost = None def do376(self, irc, msg): + if self.isDisabled(irc): + return nick = self._getNick() if nick not in self.registryValue('nicks'): return @@ -182,6 +191,8 @@ class Services(callbacks.Plugin): do422 = do377 = do376 def do433(self, irc, msg): + if self.isDisabled(irc): + return nick = self._getNick() if nick not in self.registryValue('nicks'): return @@ -219,6 +230,8 @@ class Services(callbacks.Plugin): _chanRe = re.compile('\x02(.*?)\x02') def doChanservNotice(self, irc, msg): + if self.isDisabled(irc): + return s = msg.args[1].lower() channel = None m = self._chanRe.search(s) @@ -249,6 +262,8 @@ class Services(callbacks.Plugin): on, msg) def doNickservNotice(self, irc, msg): + if self.isDisabled(irc): + return nick = self._getNick() s = ircutils.stripFormatting(msg.args[1].lower()) on = 'on %s' % irc.network @@ -310,6 +325,8 @@ class Services(callbacks.Plugin): self.log.debug('Unexpected notice from NickServ %s: %q.', on, s) def checkPrivileges(self, irc, channel): + if self.isDisabled(irc): + return chanserv = self.registryValue('ChanServ') on = 'on %s' % irc.network if chanserv and self.registryValue('ChanServ.op', channel): @@ -329,6 +346,8 @@ class Services(callbacks.Plugin): irc.sendMsg(ircmsgs.privmsg(chanserv, 'voice %s' % channel)) def doMode(self, irc, msg): + if self.isDisabled(irc): + return chanserv = self.registryValue('ChanServ') on = 'on %s' % irc.network if ircutils.strEqual(msg.nick, chanserv): @@ -352,6 +371,12 @@ class Services(callbacks.Plugin): channel = msg.args[1] # nick is msg.args[0]. self.checkPrivileges(irc, channel) + def callCommand(self, command, irc, msg, *args, **kwargs): + if self.isDisabled(irc): + irc.error('Services plugin is disabled on this network', + Raise=True) + self.__parent.callCommand(command, irc, msg, *args, **kwargs) + def _chanservCommand(self, irc, channel, command, log=False): chanserv = self.registryValue('ChanServ') if chanserv: @@ -394,6 +419,8 @@ class Services(callbacks.Plugin): voice = wrap(voice, [('checkChannelCapability', 'op'), 'inChannel']) def do474(self, irc, msg): + if self.isDisabled(irc): + return channel = msg.args[1] on = 'on %s' % irc.network self.log.info('Banned from %s, attempting ChanServ unban %s.', @@ -414,6 +441,8 @@ class Services(callbacks.Plugin): unban = wrap(unban, [('checkChannelCapability', 'op')]) def do473(self, irc, msg): + if self.isDisabled(irc): + return channel = msg.args[1] on = 'on %s' % irc.network self.log.info('%s is +i, attempting ChanServ invite %s.', channel, on) From 9e1ba9910a7598c7f62a1d5c48bfaa52ca76ff9b Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Tue, 27 Apr 2010 12:46:22 -0400 Subject: [PATCH 55/67] make Misc.apropos return plugin name even if command is in only one plugin. Signed-off-by: James Vega (cherry picked from commit 8daebd1240ca73c3eba98c3401c1bcc6a3e37335) --- plugins/Misc/plugin.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/plugins/Misc/plugin.py b/plugins/Misc/plugin.py index 5680589fb..91f944f2e 100644 --- a/plugins/Misc/plugin.py +++ b/plugins/Misc/plugin.py @@ -169,11 +169,8 @@ class Misc(callbacks.Plugin): 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)) + for name in names: + L.append('%s %s' % (name, key)) if L: L.sort() irc.reply(format('%L', L)) From abf6df9e138ce79421fa3fd785ad7906415f53d3 Mon Sep 17 00:00:00 2001 From: Daniel Folkinshteyn Date: Wed, 2 Jun 2010 18:36:27 -0400 Subject: [PATCH 56/67] fix google calc to work when doing a currency conversion. made the calcre more generic, so it finds stuff on both math and currency. nothing a little exploration of google html page source couldn't solve. Signed-off-by: James Vega (cherry picked from commit 432228c736572433734e62b881be5c3fbedf24ed) --- plugins/Google/plugin.py | 2 +- plugins/Google/test.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index a78b1d981..f9b2fa0fd 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -332,7 +332,7 @@ class Google(callbacks.PluginRegexp): url = r'http://google.com/search?q=' + s return url - _calcRe = re.compile(r'(.*?)', re.I) + _calcRe = re.compile(r'(.*?)', re.I) _calcSupRe = re.compile(r'(.*?)', re.I) _calcFontRe = re.compile(r'(.*?)') _calcTimesRe = re.compile(r'&(?:times|#215);') diff --git a/plugins/Google/test.py b/plugins/Google/test.py index 695e95037..92b7aeee0 100644 --- a/plugins/Google/test.py +++ b/plugins/Google/test.py @@ -38,6 +38,7 @@ class GoogleTestCase(ChannelPluginTestCase): def testCalc(self): self.assertNotRegexp('google calc e^(i*pi)+1', r'didn\'t') + self.assertNotRegexp('google calc 1 usd in gbp', r'didn\'t') def testHtmlHandled(self): self.assertNotRegexp('google calc ' From 0f877166ade25e372e8990c6bac5386f677befba Mon Sep 17 00:00:00 2001 From: James Vega Date: Sat, 19 Jun 2010 22:38:27 -0400 Subject: [PATCH 57/67] Services: Fix conflict with callbacks.Commands.isDisabled Signed-off-by: James Vega (cherry picked from commit f926804f4071ac0c59a67d915ca66ad19c28e6c0) --- plugins/Services/plugin.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/plugins/Services/plugin.py b/plugins/Services/plugin.py index 2e50f7b0d..0a6dfe505 100644 --- a/plugins/Services/plugin.py +++ b/plugins/Services/plugin.py @@ -63,7 +63,7 @@ class Services(callbacks.Plugin): self.identified = False self.waitingJoins = [] - def isDisabled(self, irc): + def disabled(self, irc): disabled = self.registryValue('disabledNetworks') if irc.network in disabled or \ irc.state.supported.get('NETWORK', '') in disabled: @@ -94,7 +94,7 @@ class Services(callbacks.Plugin): self.setRegistryValue('NickServ.password.%s' % nick, password) def _doIdentify(self, irc, nick=None): - if self.isDisabled(irc): + if self.disabled(irc): return if nick is None: nick = self._getNick() @@ -116,7 +116,7 @@ class Services(callbacks.Plugin): irc.sendMsg(ircmsgs.privmsg(nickserv, identify)) def _doGhost(self, irc, nick=None): - if self.isDisabled(irc): + if self.disabled(irc): return if nick is None: nick = self._getNick() @@ -145,7 +145,7 @@ class Services(callbacks.Plugin): def __call__(self, irc, msg): self.__parent.__call__(irc, msg) - if self.isDisabled(irc): + if self.disabled(irc): return nick = self._getNick() if nick not in self.registryValue('nicks'): @@ -167,7 +167,7 @@ class Services(callbacks.Plugin): self.sentGhost = None def do376(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return nick = self._getNick() if nick not in self.registryValue('nicks'): @@ -191,7 +191,7 @@ class Services(callbacks.Plugin): do422 = do377 = do376 def do433(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return nick = self._getNick() if nick not in self.registryValue('nicks'): @@ -230,7 +230,7 @@ class Services(callbacks.Plugin): _chanRe = re.compile('\x02(.*?)\x02') def doChanservNotice(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return s = msg.args[1].lower() channel = None @@ -262,7 +262,7 @@ class Services(callbacks.Plugin): on, msg) def doNickservNotice(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return nick = self._getNick() s = ircutils.stripFormatting(msg.args[1].lower()) @@ -325,7 +325,7 @@ class Services(callbacks.Plugin): self.log.debug('Unexpected notice from NickServ %s: %q.', on, s) def checkPrivileges(self, irc, channel): - if self.isDisabled(irc): + if self.disabled(irc): return chanserv = self.registryValue('ChanServ') on = 'on %s' % irc.network @@ -346,7 +346,7 @@ class Services(callbacks.Plugin): irc.sendMsg(ircmsgs.privmsg(chanserv, 'voice %s' % channel)) def doMode(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return chanserv = self.registryValue('ChanServ') on = 'on %s' % irc.network @@ -372,7 +372,7 @@ class Services(callbacks.Plugin): self.checkPrivileges(irc, channel) def callCommand(self, command, irc, msg, *args, **kwargs): - if self.isDisabled(irc): + if self.disabled(irc): irc.error('Services plugin is disabled on this network', Raise=True) self.__parent.callCommand(command, irc, msg, *args, **kwargs) @@ -419,7 +419,7 @@ class Services(callbacks.Plugin): voice = wrap(voice, [('checkChannelCapability', 'op'), 'inChannel']) def do474(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return channel = msg.args[1] on = 'on %s' % irc.network @@ -441,7 +441,7 @@ class Services(callbacks.Plugin): unban = wrap(unban, [('checkChannelCapability', 'op')]) def do473(self, irc, msg): - if self.isDisabled(irc): + if self.disabled(irc): return channel = msg.args[1] on = 'on %s' % irc.network From 31d5191dcd1e5b8f3515f97f05578595c045fee5 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 20 Jun 2010 09:40:57 -0400 Subject: [PATCH 58/67] supybot: Remove extraneous sys.stdin.close() Signed-off-by: James Vega (cherry picked from commit 0e22e218f0d48d82eb7728a7cd3c1bb4ef052b80) --- scripts/supybot | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/supybot b/scripts/supybot index 9b66e0f70..ebd85e3f7 100644 --- a/scripts/supybot +++ b/scripts/supybot @@ -270,7 +270,6 @@ if __name__ == '__main__': 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() From 8c280369a61e6007b4c147a8f140a508fb37c682 Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 21 Jun 2010 19:35:35 -0400 Subject: [PATCH 59/67] Services: Properly register the NickServ.password group and child values. Closes: Sf#3019174 Signed-off-by: James Vega (cherry picked from commit d78f7b6ac556293739f7334a56bd5b9f67742516) --- plugins/Services/config.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/Services/config.py b/plugins/Services/config.py index 6fe798925..cfa8081fa 100644 --- a/plugins/Services/config.py +++ b/plugins/Services/config.py @@ -1,5 +1,6 @@ ### # Copyright (c) 2005, Jeremiah Fincher +# Copyright (c) 2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -33,7 +34,10 @@ import supybot.registry as registry def registerNick(nick, password=''): p = conf.supybot.plugins.Services.Nickserv.get('password') - v = p.register(nick, registry.String(password, '', private=True)) + h = 'Determines what password the bot will use with NickServ when ' \ + 'identifying as %s.' % nick + v = p.register(nick, registry.String(password, h, private=True)) + v.channelValue = False if password: v.setValue(password) @@ -82,9 +86,7 @@ conf.registerGlobalValue(Services, 'ghostDelay', conf.registerGlobalValue(Services, 'NickServ', ValidNickOrEmptyString('', """Determines what nick the 'NickServ' service has.""")) -conf.registerGroup(Services.NickServ, 'password', - registry.String('', """Determines what password the bot will use with - NickServ.""", private=True)) +conf.registerGroup(Services.NickServ, 'password') conf.registerGlobalValue(Services, 'ChanServ', ValidNickOrEmptyString('', """Determines what nick the 'ChanServ' service has.""")) From 3f63917dc274ad91235d8e11464a8363d40d45bc Mon Sep 17 00:00:00 2001 From: James Vega Date: Thu, 24 Jun 2010 00:37:40 -0400 Subject: [PATCH 60/67] Use conf.registerGlobalValue to ensure generated values are properly setup. Signed-off-by: James Vega (cherry picked from commit 0c6220480941a199334828d79189429afe1a0452) --- plugins/Alias/plugin.py | 12 ++++++------ plugins/RSS/plugin.py | 4 ++-- plugins/Services/config.py | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/plugins/Alias/plugin.py b/plugins/Alias/plugin.py index 9021b6809..af22ab55f 100644 --- a/plugins/Alias/plugin.py +++ b/plugins/Alias/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2009, James Vega +# Copyright (c) 2009-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -260,13 +260,13 @@ class Alias(callbacks.Plugin): f = new.instancemethod(f, self, Alias) except RecursiveAlias: raise AliasError, 'You can\'t define a recursive alias.' + aliasGroup = self.registryValue('aliases', value=False) 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, '')) + aliasGroup.unregister(name) + conf.registerGlobalValue(aliasGroup, name, registry.String(alias, '')) + conf.registerGlobalValue(aliasGroup.get(name), 'locked', + registry.Boolean(lock, '')) self.aliases[name] = [alias, lock, f] def removeAlias(self, name, evenIfLocked=False): diff --git a/plugins/RSS/plugin.py b/plugins/RSS/plugin.py index 6163965fb..b2fb31b7b 100644 --- a/plugins/RSS/plugin.py +++ b/plugins/RSS/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2002-2004, Jeremiah Fincher -# Copyright (c) 2008-2009, James Vega +# Copyright (c) 2008-2010, James Vega # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -102,7 +102,7 @@ class RSS(callbacks.Plugin): def _registerFeed(self, name, url=''): self.registryValue('feeds').add(name) group = self.registryValue('feeds', value=False) - group.register(name, registry.String(url, '')) + conf.registerGlobalValue(group, name, registry.String(url, '')) def __call__(self, irc, msg): self.__parent.__call__(irc, msg) diff --git a/plugins/Services/config.py b/plugins/Services/config.py index cfa8081fa..aa09131ea 100644 --- a/plugins/Services/config.py +++ b/plugins/Services/config.py @@ -36,8 +36,8 @@ def registerNick(nick, password=''): p = conf.supybot.plugins.Services.Nickserv.get('password') h = 'Determines what password the bot will use with NickServ when ' \ 'identifying as %s.' % nick - v = p.register(nick, registry.String(password, h, private=True)) - v.channelValue = False + v = conf.registerGlobalValue(p, nick, + registry.String(password, h, private=True)) if password: v.setValue(password) From 0ff414b993a45a47d035700a98c2410a62319639 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 27 Jun 2010 19:48:36 -0400 Subject: [PATCH 61/67] Services: Don't filter outgoing JOIN messages on disabled networks Signed-off-by: James Vega (cherry picked from commit acffde68abb28606371634d7f52fe8991e3cbf9e) --- plugins/Services/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Services/plugin.py b/plugins/Services/plugin.py index 0a6dfe505..e5882573b 100644 --- a/plugins/Services/plugin.py +++ b/plugins/Services/plugin.py @@ -71,7 +71,7 @@ class Services(callbacks.Plugin): return False def outFilter(self, irc, msg): - if msg.command == 'JOIN': + if msg.command == 'JOIN' and not self.disabled(irc): if not self.identified: if self.registryValue('noJoinsUntilIdentified'): self.log.info('Holding JOIN to %s until identified.', From e7ef97e52954ac28d88c03723d54d1456cd216a1 Mon Sep 17 00:00:00 2001 From: James Vega Date: Mon, 26 Jul 2010 19:48:37 -0400 Subject: [PATCH 62/67] Ensure channel-specific reply.whenNotAddressed works. Signed-off-by: James Vega (cherry picked from commit 166f32dcb02eab58659882fb003502c1e990797a) --- src/callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/callbacks.py b/src/callbacks.py index d12f1ea5a..a2afdcd46 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -126,7 +126,7 @@ def _addressed(nick, msg, prefixChars=None, nicks=None, # There should be some separator between the nick and the # previous alphanumeric character. return possiblePayload - if conf.supybot.reply.whenNotAddressed(): + if get(conf.supybot.reply.whenNotAddressed): return payload else: return '' From 25b987cc581ff33dc561325138f6e2937597ab95 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 29 Aug 2010 10:26:59 -0400 Subject: [PATCH 63/67] Model Admin's ignore help after Channel's. Closes: Sf#3054919 Signed-off-by: James Vega --- plugins/Admin/plugin.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/plugins/Admin/plugin.py b/plugins/Admin/plugin.py index 781570695..149d08235 100644 --- a/plugins/Admin/plugin.py +++ b/plugins/Admin/plugin.py @@ -298,12 +298,10 @@ class Admin(callbacks.Plugin): 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. + 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. """ ircdb.ignores.add(hostmask, expires) irc.replySuccess() @@ -312,8 +310,8 @@ class Admin(callbacks.Plugin): def remove(self, irc, msg, args, hostmask): """ - Ignores or, if a nick is given, ignores whatever - hostmask that nick is currently using. + This will remove the persistent ignore on or the + hostmask currently associated with . """ try: ircdb.ignores.remove(hostmask) @@ -325,7 +323,7 @@ class Admin(callbacks.Plugin): def list(self, irc, msg, args): """takes no arguments - Returns the hostmasks currently being globally ignored. + Lists the hostmasks that the bot is ignoring. """ # XXX Add the expirations. if ircdb.ignores.hostmasks: From 07da8cab138ae53e168b7aa5097fd752d04a6476 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 29 Aug 2010 10:49:13 -0400 Subject: [PATCH 64/67] User: Specify chanagename must be used in private in its help. Closes: Sf#3055353 Signed-off-by: James Vega --- plugins/User/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index cf1342027..c102ea0e4 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -150,8 +150,8 @@ class User(callbacks.Plugin): 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). + This message must be sent to the bot privately (not on a channel) since + it may contain a password. """ try: id = ircdb.users.getUserId(newname) From f977a3a260f6c0853501196b15665425c2163649 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 29 Aug 2010 11:03:05 -0400 Subject: [PATCH 65/67] User: Require set.password be sent in private. Closes: Sf#3055365 Signed-off-by: James Vega --- plugins/User/plugin.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index c102ea0e4..df5353e88 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -187,7 +187,8 @@ class User(callbacks.Plugin): irc.replySuccess() else: irc.error(conf.supybot.replies.incorrectAuthentication()) - password = wrap(password, ['otherUser', 'something', 'something']) + password = wrap(password, ['private', 'otherUser', 'something', + 'something']) def secure(self, irc, msg, args, user, password, value): """ [] From de726f90f395c0ec0d705a7e582fe4f643898e86 Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 29 Aug 2010 11:10:54 -0400 Subject: [PATCH 66/67] User: Only require name for set.password when changing other user's password. Closes: Sf#3055358 Signed-off-by: James Vega --- plugins/User/plugin.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index df5353e88..e08348239 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -168,7 +168,7 @@ class User(callbacks.Plugin): 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 @@ -180,6 +180,10 @@ class User(callbacks.Plugin): u = ircdb.users.getUser(msg.prefix) except KeyError: u = None + if user is None: + if u is None: + irc.errorNotRegistered(Raise=True) + user = u if user.checkPassword(password) or \ (u and u._checkCapability('owner') and not u == user): user.setPassword(newpassword) @@ -187,8 +191,8 @@ class User(callbacks.Plugin): irc.replySuccess() else: irc.error(conf.supybot.replies.incorrectAuthentication()) - password = wrap(password, ['private', 'otherUser', 'something', - 'something']) + password = wrap(password, ['private', optional('otherUser'), + 'something', 'something']) def secure(self, irc, msg, args, user, password, value): """ [] From 577294f48942efeda3b5ee09038dc0d1fa51deab Mon Sep 17 00:00:00 2001 From: James Vega Date: Sun, 29 Aug 2010 11:24:54 -0400 Subject: [PATCH 67/67] User: Handle DuplicateHostmask exception in hostmask.add. Signed-off-by: James Vega --- plugins/User/plugin.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index e08348239..e9eddc3b6 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -327,6 +327,8 @@ class User(callbacks.Plugin): ircdb.users.setUser(user) except ValueError, e: irc.error(str(e), Raise=True) + except ircdb.DuplicateHostmask: + irc.error('That hostmask is already registered.', Raise=True) irc.replySuccess() add = wrap(add, ['private', first('otherUser', 'user'), optional('something'), additional('something', '')])