From 1499141f091204af1d61379b64a55dcaba314467 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 6 May 2016 21:19:28 -0700 Subject: [PATCH 01/32] Import SedRegex plugin as of https://github.com/jlu5/SupyPlugins/commit/2a556a1b84db3e634754846760ec5b4fb1c0545a Co-authored-by: Michael Daniel Telatynski Co-authored-by: nyuszika7h --- plugins/SedRegex/README.md | 1 + plugins/SedRegex/__init__.py | 61 +++++++++++ plugins/SedRegex/config.py | 60 +++++++++++ plugins/SedRegex/local/__init__.py | 1 + plugins/SedRegex/plugin.py | 163 +++++++++++++++++++++++++++++ plugins/SedRegex/test.py | 36 +++++++ 6 files changed, 322 insertions(+) create mode 100644 plugins/SedRegex/README.md create mode 100644 plugins/SedRegex/__init__.py create mode 100644 plugins/SedRegex/config.py create mode 100644 plugins/SedRegex/local/__init__.py create mode 100644 plugins/SedRegex/plugin.py create mode 100644 plugins/SedRegex/test.py diff --git a/plugins/SedRegex/README.md b/plugins/SedRegex/README.md new file mode 100644 index 000000000..5127f0f52 --- /dev/null +++ b/plugins/SedRegex/README.md @@ -0,0 +1 @@ +History replacer using sed-style expressions. diff --git a/plugins/SedRegex/__init__.py b/plugins/SedRegex/__init__.py new file mode 100644 index 000000000..25b4daa63 --- /dev/null +++ b/plugins/SedRegex/__init__.py @@ -0,0 +1,61 @@ +### +# Copyright (c) 2015, Michael Daniel Telatynski +# Copyright (c) 2015-2016, James Lu +# 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. + +### + +""" +History replacer using sed-style expressions. +""" + +import supybot +import supybot.world as world + +__version__ = "2016.02.28.1+git" +__author__ = supybot.Author("Michael Daniel Telatynski", "t3chguy", "postmaster@webdevguru.co.uk") +__contributors__ = {supybot.Author("James Lu", "GLolol", "glolol@overdrivenetworks.com"): + ["options bolding the replacement text", "misc. bug fixes and enhancements"], + supybot.Author('nyuszika7h', 'nyuszika7h', 'nyuszika7h@openmailbox.org'): + ["_unpack_sed method within plugin.py"] + } +__url__ = 'https://github.com/GLolol/SupyPlugins' + +from . import config +from . import plugin +from imp import reload + +reload(config) +reload(plugin) + +if world.testing: + from . import test + +Class = plugin.Class +configure = config.configure + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/SedRegex/config.py b/plugins/SedRegex/config.py new file mode 100644 index 000000000..0a64864c7 --- /dev/null +++ b/plugins/SedRegex/config.py @@ -0,0 +1,60 @@ +### +# Copyright (c) 2015, Michael Daniel Telatynski +# Copyright (c) 2015, James Lu +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +import supybot.conf as conf +import supybot.registry as registry +try: + from supybot.i18n import PluginInternationalization + _ = PluginInternationalization('SedRegex') +except: + _ = lambda x: x + +def configure(advanced): + from supybot.questions import expect, anything, something, yn + conf.registerPlugin('SedRegex', True) + if advanced: + output("""The SedRegex plugin allows you to make Perl/sed-style regex + replacements to your chat history.""") + +SedRegex = conf.registerPlugin('SedRegex') + +conf.registerChannelValue(SedRegex, 'displayErrors', + registry.Boolean(True, _("""Should errors be displayed?"""))) +conf.registerChannelValue(SedRegex, 'boldReplacementText', + registry.Boolean(True, _("""Should the replacement text be bolded?"""))) +conf.registerChannelValue(SedRegex, 'enable', + registry.Boolean(False, _("""Should Perl/sed-style regex replacing + work in this channel?"""))) +conf.registerChannelValue(SedRegex, 'ignoreRegex', + registry.Boolean(True, _("""Should Perl/sed regex replacing + ignore messages which look like valid regex?"""))) + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/SedRegex/local/__init__.py b/plugins/SedRegex/local/__init__.py new file mode 100644 index 000000000..e86e97b86 --- /dev/null +++ b/plugins/SedRegex/local/__init__.py @@ -0,0 +1 @@ +# Stub so local is a module, used for third-party modules diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py new file mode 100644 index 000000000..acd7bb67c --- /dev/null +++ b/plugins/SedRegex/plugin.py @@ -0,0 +1,163 @@ +### +# Copyright (c) 2015, Michael Daniel Telatynski +# Copyright (c) 2015-2016, James Lu +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +from supybot.commands import * +import supybot.plugins as plugins +import supybot.ircmsgs as ircmsgs +import supybot.callbacks as callbacks +import supybot.ircutils as ircutils +import supybot.ircdb as ircdb + +import re + +try: + from supybot.i18n import PluginInternationalization + _ = PluginInternationalization('SedRegex') +except ImportError: + _ = lambda x: x + + +SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" + r"(?P.*?)(?:(?P=delim)(?P[gi]*))?$") + +class SedRegex(callbacks.PluginRegexp): + """History replacer using sed-style regex syntax.""" + threaded = True + public = True + unaddressedRegexps = ['replacer'] + + @staticmethod + def _unpack_sed(expr): + if '\0' in expr: + raise ValueError('Expression can\'t contain NUL') + + delim = expr[1] + escaped_expr = '' + + for (i, c) in enumerate(expr): + if c == delim and i > 0: + if expr[i - 1] == '\\': + escaped_expr = escaped_expr[:-1] + '\0' + continue + + escaped_expr += c + + match = SED_REGEX.search(escaped_expr) + + if not match: + return + + groups = match.groupdict() + pattern = groups['pattern'].replace('\0', delim) + replacement = groups['replacement'].replace('\0', delim) + + if groups['flags']: + raw_flags = set(groups['flags']) + else: + raw_flags = set() + + flags = 0 + count = 1 + + for flag in raw_flags: + if flag == 'g': + count = 0 + if flag == 'i': + flags |= re.IGNORECASE + + pattern = re.compile(pattern, flags) + + return (pattern, replacement, count) + + def replacer(self, irc, msg, regex): + if not self.registryValue('enable', msg.args[0]): + return + iterable = reversed(irc.state.history) + msg.tag('Replacer') + + try: + (pattern, replacement, count) = self._unpack_sed(msg.args[1]) + except (ValueError, re.error) as e: + self.log.warning(_("SedRegex error: %s"), e) + if self.registryValue('displayErrors', msg.args[0]): + irc.error(_("SedRegex error: %s" % e), Raise=True) + return + + next(iterable) + for m in iterable: + if m.command in ('PRIVMSG', 'NOTICE') and \ + m.args[0] == msg.args[0]: + target = regex.group('nick') + if not ircutils.isNick(str(target), strictRfc=True): + return + if target and m.nick != target: + continue + # Don't snarf ignored users' messages unless specifically + # told to. + if ircdb.checkIgnored(m.prefix) and not target: + continue + # When running substitutions, ignore the "* nick" part of any actions. + action = ircmsgs.isAction(m) + if action: + text = ircmsgs.unAction(m) + else: + text = m.args[1] + + if self.registryValue('ignoreRegex', msg.args[0]) and \ + m.tagged('Replacer'): + continue + if m.nick == msg.nick: + messageprefix = msg.nick + else: + messageprefix = '%s thinks %s' % (msg.nick, m.nick) + if regexp_wrapper(text, pattern, timeout=0.05, plugin_name=self.name(), + fcn_name='replacer'): + if self.registryValue('boldReplacementText', msg.args[0]): + replacement = ircutils.bold(replacement) + subst = process(pattern.sub, replacement, + text, count, timeout=0.05) + if action: # If the message was an ACTION, prepend the nick back. + subst = '* %s %s' % (m.nick, subst) + irc.reply(_("%s meant to say: %s") % + (messageprefix, subst), prefixNick=False) + return + + self.log.debug(_("SedRegex: Search %r not found in the last %i messages of %s."), + msg.args[1], len(irc.state.history), msg.args[0]) + if self.registryValue("displayErrors", msg.args[0]): + irc.error(_("Search not found in the last %i messages.") % + len(irc.state.history), Raise=True) + replacer.__doc__ = SED_REGEX.pattern + +Class = SedRegex + + +# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py new file mode 100644 index 000000000..0655e0453 --- /dev/null +++ b/plugins/SedRegex/test.py @@ -0,0 +1,36 @@ +### +# Copyright (c) 2015, Michael Daniel Telatynski +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions, and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions, and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the author of this software nor the name of +# contributors to this software may be used to endorse or promote products +# derived from this software without specific prior written consent. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +### + +from supybot.test import * + +class SedRegexTestCase(PluginTestCase): + plugins = ('SedRegex',) + +# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From fa13c68ebcec6e3dd6a3955f292224befffeb037 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 15 Jul 2016 23:44:25 -0700 Subject: [PATCH 02/32] SedRegex: work around "nothing to repeat" errors on Python < 2.7.6 Source: https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat and https://bugs.python.org/issue18647 From: https://github.com/jlu5/SupyPlugins/commit/335fc6e3da685e1c899ca41f370383603924ce8f --- plugins/SedRegex/plugin.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index acd7bb67c..153c431c6 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -44,9 +44,11 @@ try: except ImportError: _ = lambda x: x - +# Note: {0,2} is used for matching the flags instead of "*" to work around a Python bug in versions +# lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat +# and https://bugs.python.org/issue18647 SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?:(?P=delim)(?P[gi]*))?$") + r"(?P.*?)(?:(?P=delim)(?P[gi]{0,2}))?$") class SedRegex(callbacks.PluginRegexp): """History replacer using sed-style regex syntax.""" From 00f26b0a73b43724a33999b432defacf4d0a01c7 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 20 Jul 2016 12:48:15 -0700 Subject: [PATCH 03/32] SedRegex: only operate on messages from the current network Reported by @jztech101. The 'receivedBy' tag is now checked to match the current IRC object, which Works around ProgVal/Limnoria#1211. From: https://github.com/jlu5/SupyPlugins/commit/84b94d589cbce214b8429f73221dbf536dbdd43d --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 153c431c6..9fdf8fdb3 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -116,7 +116,7 @@ class SedRegex(callbacks.PluginRegexp): next(iterable) for m in iterable: if m.command in ('PRIVMSG', 'NOTICE') and \ - m.args[0] == msg.args[0]: + m.args[0] == msg.args[0] and m.tagged('receivedBy') == irc: target = regex.group('nick') if not ircutils.isNick(str(target), strictRfc=True): return From c1e2e2b8a6c4fb8a1fd349957646345d3512e694 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 10 Sep 2016 17:29:47 -0700 Subject: [PATCH 04/32] SedRegex: sanitize against \n\r\t in output From: https://github.com/jlu5/SupyPlugins/commit/48445e256ac002776ce6ac61a858570224cb6b9f --- plugins/SedRegex/plugin.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 9fdf8fdb3..79c9eeb57 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -35,6 +35,7 @@ import supybot.ircmsgs as ircmsgs import supybot.callbacks as callbacks import supybot.ircutils as ircutils import supybot.ircdb as ircdb +import supybot.utils as utils import re @@ -50,6 +51,9 @@ except ImportError: SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" r"(?P.*?)(?:(?P=delim)(?P[gi]{0,2}))?$") +# Replace newlines and friends with things like literal "\n" (backslash and "n") +axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) + class SedRegex(callbacks.PluginRegexp): """History replacer using sed-style regex syntax.""" threaded = True @@ -148,6 +152,9 @@ class SedRegex(callbacks.PluginRegexp): text, count, timeout=0.05) if action: # If the message was an ACTION, prepend the nick back. subst = '* %s %s' % (m.nick, subst) + + subst = axe_spaces(subst) + irc.reply(_("%s meant to say: %s") % (messageprefix, subst), prefixNick=False) return From bd0b1158fcb3a497cc603d55f1d3b8705a7d9f2f Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 17 Dec 2016 20:43:19 -0800 Subject: [PATCH 05/32] SedRegex: allow free form flags and return them in _unpack_sed() From: https://github.com/jlu5/SupyPlugins/commit/423da9f996d7af2f78cee6cc28311a6095f3d390 --- plugins/SedRegex/plugin.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 79c9eeb57..0c4eb7508 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -49,7 +49,7 @@ except ImportError: # lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat # and https://bugs.python.org/issue18647 SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?:(?P=delim)(?P[gi]{0,2}))?$") + r"(?P.*?)(?:(?P=delim)(?P[a-z]+))?$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) @@ -101,7 +101,7 @@ class SedRegex(callbacks.PluginRegexp): pattern = re.compile(pattern, flags) - return (pattern, replacement, count) + return (pattern, replacement, count, raw_flags) def replacer(self, irc, msg, regex): if not self.registryValue('enable', msg.args[0]): @@ -110,7 +110,7 @@ class SedRegex(callbacks.PluginRegexp): msg.tag('Replacer') try: - (pattern, replacement, count) = self._unpack_sed(msg.args[1]) + (pattern, replacement, count, flags) = self._unpack_sed(msg.args[1]) except (ValueError, re.error) as e: self.log.warning(_("SedRegex error: %s"), e) if self.registryValue('displayErrors', msg.args[0]): From 24ae250ac4e94b1b11cfc75b7ea722ca04b6422b Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 17 Dec 2016 20:48:04 -0800 Subject: [PATCH 06/32] SedRegex: implement 's' regex flag to only match the caller's message From: https://github.com/jlu5/SupyPlugins/commit/87c79db3d0ddda75f61c680e8dd9eb20085525e5 --- plugins/SedRegex/plugin.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 0c4eb7508..048f847e8 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -118,18 +118,23 @@ class SedRegex(callbacks.PluginRegexp): return next(iterable) + if 's' in flags: # Special 's' flag lets the bot only look at self messages + target = msg.nick + else: + target = regex.group('nick') + if not ircutils.isNick(str(target), strictRfc=True): + return + for m in iterable: if m.command in ('PRIVMSG', 'NOTICE') and \ m.args[0] == msg.args[0] and m.tagged('receivedBy') == irc: - target = regex.group('nick') - if not ircutils.isNick(str(target), strictRfc=True): - return if target and m.nick != target: continue # Don't snarf ignored users' messages unless specifically # told to. if ircdb.checkIgnored(m.prefix) and not target: continue + # When running substitutions, ignore the "* nick" part of any actions. action = ircmsgs.isAction(m) if action: From c1c423cc0d41013f1b7cab500b04fab8abc26cae Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 17 Dec 2016 20:48:20 -0800 Subject: [PATCH 07/32] SedRegex: add configuration / usage instructions From: https://github.com/jlu5/SupyPlugins/commit/9d0dce6ef5034f1fc923a870f6865b2a7b05e828 --- plugins/SedRegex/README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/plugins/SedRegex/README.md b/plugins/SedRegex/README.md index 5127f0f52..62b423db9 100644 --- a/plugins/SedRegex/README.md +++ b/plugins/SedRegex/README.md @@ -1 +1,25 @@ History replacer using sed-style expressions. + +### Configuration + +Enable SedRegex on the desired channels: `config channel #yourchannel plugins.sedregex.enable True` + +### Usage + +After enabling SedRegex, typing a regex in the form `s/text/replacement/` will make the bot announce replacements. + +``` +20:24 <~GL> helli world +20:24 <~GL> s/i/o/ +20:24 <@Lily> GL meant to say: hello world +``` + +You can also do `othernick: s/text/replacement/` to only replace messages from a certain user. Supybot ignores are respected by the plugin, and messages from ignored users will only be considered if their nick is explicitly given. + +#### Regex flags + +The following regex flags (i.e. the `g` in `s/abc/def/g`, etc.) are supported: + +- `i`: case insensitive replacement +- `g`: replace all occurences of the original text +- `s`: replace only messages from the caller From 889c6a1615e1a83e931bc15bb7fb49cab7ecd7c9 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 17 Dec 2016 21:12:36 -0800 Subject: [PATCH 08/32] SedRegex: fix flag matching From: https://github.com/jlu5/SupyPlugins/commit/db125ee5d18259427b279ba352691155faec6328 --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 048f847e8..961127793 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -49,7 +49,7 @@ except ImportError: # lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat # and https://bugs.python.org/issue18647 SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?:(?P=delim)(?P[a-z]+))?$") + r"(?P.*?)(?:(?P=delim)(?P[a-z]*))?$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) From 656d2172db6271ddae54f698e68e69ae63540c5c Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 22 Dec 2016 12:05:00 -0800 Subject: [PATCH 09/32] SedRegex: work around "nothing to replace" errors on < Python 2.7.6 again From: https://github.com/jlu5/SupyPlugins/commit/244a8c6bee8126e9489b17858e89f02b7432e0ea --- plugins/SedRegex/plugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 961127793..ee8e492f7 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -45,11 +45,11 @@ try: except ImportError: _ = lambda x: x -# Note: {0,2} is used for matching the flags instead of "*" to work around a Python bug in versions +# Note: {0,3} is used for matching the flags instead of "*" to work around a Python bug in versions # lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat # and https://bugs.python.org/issue18647 SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?:(?P=delim)(?P[a-z]*))?$") + r"(?P.*?)(?:(?P=delim)(?P[a-z]{0,3}))?$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) From dee2b6bdb35f239c4d35a07928b53770452ac5ef Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 22 Dec 2016 12:12:21 -0800 Subject: [PATCH 10/32] SedRegex: require ending delimiter to prevent overzealous matching of replacement text Closes #59. From: https://github.com/jlu5/SupyPlugins/commit/414a4a4a16a0d581bd92b9b8d5afde8d63f2355a --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index ee8e492f7..cda81ad19 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -49,7 +49,7 @@ except ImportError: # lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat # and https://bugs.python.org/issue18647 SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?:(?P=delim)(?P[a-z]{0,3}))?$") + r"(?P.*?)(?P=delim)(?P[a-z]{0,3})?$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) From 5370296bbf9c864c3a0cab12afb5dd18a4e1b6ea Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 26 Dec 2016 08:04:20 -0800 Subject: [PATCH 11/32] SedRegex: log the exact error name instead of "SedRegex error" From: https://github.com/jlu5/SupyPlugins/commit/406d7b17902570de9efa57870884bf76f68226a5 --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index cda81ad19..c3e59527b 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -114,7 +114,7 @@ class SedRegex(callbacks.PluginRegexp): except (ValueError, re.error) as e: self.log.warning(_("SedRegex error: %s"), e) if self.registryValue('displayErrors', msg.args[0]): - irc.error(_("SedRegex error: %s" % e), Raise=True) + irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) return next(iterable) From 6445e90ec70fb9b6a3ae8b72b4ec7b9576fe1503 Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 26 Dec 2016 08:05:06 -0800 Subject: [PATCH 12/32] SedRegex: also catch errors in the regexp_wrapper() step E.g. sre_constants.error: invalid group reference From: https://github.com/jlu5/SupyPlugins/commit/e137d29eb94f1d2f39f574bb6757955ff38499c0 --- plugins/SedRegex/plugin.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index c3e59527b..027cf082f 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -149,19 +149,24 @@ class SedRegex(callbacks.PluginRegexp): messageprefix = msg.nick else: messageprefix = '%s thinks %s' % (msg.nick, m.nick) - if regexp_wrapper(text, pattern, timeout=0.05, plugin_name=self.name(), - fcn_name='replacer'): - if self.registryValue('boldReplacementText', msg.args[0]): - replacement = ircutils.bold(replacement) - subst = process(pattern.sub, replacement, - text, count, timeout=0.05) - if action: # If the message was an ACTION, prepend the nick back. - subst = '* %s %s' % (m.nick, subst) + try: + if regexp_wrapper(text, pattern, timeout=0.05, plugin_name=self.name(), + fcn_name='replacer'): + if self.registryValue('boldReplacementText', msg.args[0]): + replacement = ircutils.bold(replacement) + subst = process(pattern.sub, replacement, + text, count, timeout=0.05) + if action: # If the message was an ACTION, prepend the nick back. + subst = '* %s %s' % (m.nick, subst) - subst = axe_spaces(subst) + subst = axe_spaces(subst) - irc.reply(_("%s meant to say: %s") % - (messageprefix, subst), prefixNick=False) + irc.reply(_("%s meant to say: %s") % + (messageprefix, subst), prefixNick=False) + return + except (ValueError, re.error) as e: + if self.registryValue('displayErrors', msg.args[0]): + irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) return self.log.debug(_("SedRegex: Search %r not found in the last %i messages of %s."), From 90ec8a6976c3e9d486f66f12f482c5b943ea508a Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 4 Jan 2017 20:36:45 -0800 Subject: [PATCH 13/32] SedRegex: fix some replacement queries not working From: https://github.com/jlu5/SupyPlugins/commit/926454b01d122c4e1a82381f2f2d7e39576426bd --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 027cf082f..6d95476ed 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -49,7 +49,7 @@ except ImportError: # lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat # and https://bugs.python.org/issue18647 SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?P=delim)(?P[a-z]{0,3})?$") + r"(?P.*?)(?P=delim)(?P[a-z]{0,3})$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) From f40d8c530f3d7911be87c3af82db0c6579643017 Mon Sep 17 00:00:00 2001 From: James Lu Date: Mon, 16 Jan 2017 21:15:58 -0800 Subject: [PATCH 14/32] SedRegex: bump copyright year to 2017 From: https://github.com/jlu5/SupyPlugins/commit/0ef8138ab338c7e226b830a1cc3d7b231a9f0fa2 --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 6d95476ed..8043040fc 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2015, Michael Daniel Telatynski -# Copyright (c) 2015-2016, James Lu +# Copyright (c) 2015-2017, James Lu # All rights reserved. # # Redistribution and use in source and binary forms, with or without From b01c50cd4deadd088f69dc3082baa88db0330731 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 21 Mar 2017 12:57:11 -0700 Subject: [PATCH 15/32] SedRegex: add a bunch of test cases Closes #53. From: https://github.com/jlu5/SupyPlugins/commit/a58785fa4aed5e5b5cd82c5322bb5b46284cb0df --- plugins/SedRegex/test.py | 142 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 3 deletions(-) diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py index 0655e0453..57a2cae46 100644 --- a/plugins/SedRegex/test.py +++ b/plugins/SedRegex/test.py @@ -1,5 +1,5 @@ ### -# Copyright (c) 2015, Michael Daniel Telatynski +# Copyright (c) 2017, James Lu # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -28,9 +28,145 @@ ### +from __future__ import print_function from supybot.test import * -class SedRegexTestCase(PluginTestCase): - plugins = ('SedRegex',) +class SedRegexTestCase(ChannelPluginTestCase): + other = "blah!blah@someone.else" + other2 = "ghost!ghost@spooky" + + plugins = ('SedRegex', 'Utilities') + config = {'plugins.sedregex.enable': True, + 'plugins.sedregex.boldReplacementText': False} + + # getMsg() stalls if no message is ever sent (i.e. if the plugin fails to respond to a request) + # We should limit the timeout to prevent the tests from taking forever. + timeout = 3 + + def testSimpleReplace(self): + self.feedMsg('Abcd abcdefgh') + self.feedMsg('s/abcd/test/') + # Run an empty command so that messages from the previous trigger are caught. + m = self.getMsg(' ') + self.assertIn('Abcd testefgh', str(m)) + + def testCaseInsensitiveReplace(self): + self.feedMsg('Aliens Are Invading, Help!') + self.feedMsg('s/a/e/i') + m = self.getMsg(' ') + self.assertIn('eliens', str(m)) + + def testGlobalReplace(self): + self.feedMsg('AAaa aaAa a b') + self.feedMsg('s/a/e/g') + m = self.getMsg(' ') + self.assertIn('AAee eeAe e b', str(m)) + + def testGlobalCaseInsensitiveReplace(self): + self.feedMsg('Abba') + self.feedMsg('s/a/e/gi') + m = self.getMsg(' ') + self.assertIn('ebbe', str(m)) + + def testOnlySelfReplace(self): + self.feedMsg('evil machines') + self.feedMsg('evil tacocats', frm=__class__.other) + self.feedMsg('s/evil/kind/s') + m = self.getMsg(' ') + self.assertIn('kind machines', str(m)) + + def testAllFlagsReplace(self): + self.feedMsg('Terrible, terrible crimes') + self.feedMsg('Terrible, terrible TV shows', frm=__class__.other) + self.feedMsg('s/terr/horr/sgi') + m = self.getMsg(' ') + self.assertIn('horrible, horrible crimes', str(m)) + + def testOtherPersonReplace(self): + self.feedMsg('yeah, right', frm=__class__.other) + self.feedMsg('s/right/left/', frm=__class__.other2) + m = self.getMsg(' ') + # Note: using the bot prefix for the s/right/left/ part causes the first nick in "X thinks Y" + # to be empty? It works fine in runtime though... + self.assertIn('%s thinks %s meant to say' % (ircutils.nickFromHostmask(__class__.other2), + ircutils.nickFromHostmask(__class__.other)), str(m)) + + def testExplicitOtherReplace(self): + self.feedMsg('ouch', frm=__class__.other2) + self.feedMsg('poof', frm=__class__.other) + self.feedMsg('wow!') + self.feedMsg('%s: s/^/p/' % ircutils.nickFromHostmask(__class__.other2)) + m = self.getMsg(' ') + self.assertIn('pouch', str(m)) + + def testBoldReplacement(self): + with conf.supybot.plugins.sedregex.boldReplacementText.context(True): + self.feedMsg('hahahaha', frm=__class__.other) + + # One replacement + self.feedMsg('s/h/H/', frm=__class__.other2) + m = self.getMsg(' ') + self.assertIn('\x02H\x02aha', str(m)) + + # Replace all instances + self.feedMsg('s/h/H/g', frm=__class__.other2) + m = self.getMsg(' ') + self.assertIn('\x02H\x02a\x02H\x02a', str(m)) + + # One whole word + self.feedMsg('sweet dreams are made of this', frm=__class__.other) + self.feedMsg('s/this/cheese/', frm=__class__.other2) + m = self.getMsg(' ') + self.assertIn('of \x02cheese\x02', str(m)) + + def testNonSlashSeparator(self): + self.feedMsg('we are all decelopers on this blessed day') + self.feedMsg('s.c.v.') + m = self.getMsg(' ') + self.assertIn('developers', str(m)) + + self.feedMsg('4 / 2 = 8') + self.feedMsg('s@/@*@') + m = self.getMsg(' ') + self.assertIn('4 * 2 = 8', str(m)) + + def testWhitespaceSeparatorFails(self): + self.feedMsg('we are all decelopers on this blessed day') + self.feedMsg('s.c.v.') + m = self.getMsg(' ') + self.assertIn('developers', str(m)) + + self.feedMsg('4 / 2 = 8') + self.feedMsg('s@/@*@') + m = self.getMsg(' ') + self.assertIn('4 * 2 = 8', str(m)) + + def testWeirdSeparatorsFail(self): + self.feedMsg("can't touch this", frm=__class__.other) + # Only symbols are allowed as separators + self.feedMsg('blah: s a b ') + self.feedMsg('blah: sdadbd') + + m = self.getMsg('echo dummy message') + # XXX: this is a total hack... + for msg in self.irc.state.history: + print("Message in history: %s" % msg, end='') + self.assertNotIn("cbn't", str(msg)) + + def testActionReplace(self): + self.feedMsg("\x01ACTION sleeps\x01") + self.feedMsg('s/sleeps/wakes/') + + m = self.getMsg(' ') + self.assertIn('meant to say: * %s wakes' % self.nick, str(m)) + + def testOtherPersonActionReplace(self): + self.feedMsg("\x01ACTION sleeps\x01", frm=__class__.other) + self.feedMsg('s/sleeps/wakes/') + m = self.getMsg(' ') + n = ircutils.nickFromHostmask(__class__.other) + self.assertIn('thinks %s meant to say: * %s wakes' % (n, n), str(m)) + + # TODO: test ignores # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From 01e0c12641578da4f61f41d2e4c712e753e495c9 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 21 Mar 2017 13:10:09 -0700 Subject: [PATCH 16/32] SedRegex: use self.__class__ instead of __class__ in tests (Python 2 compat) From: https://github.com/jlu5/SupyPlugins/commit/b53e6e40a0b0e3565c0b7580085164c478abb413 --- plugins/SedRegex/test.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py index 57a2cae46..07a411e08 100644 --- a/plugins/SedRegex/test.py +++ b/plugins/SedRegex/test.py @@ -70,52 +70,52 @@ class SedRegexTestCase(ChannelPluginTestCase): def testOnlySelfReplace(self): self.feedMsg('evil machines') - self.feedMsg('evil tacocats', frm=__class__.other) + self.feedMsg('evil tacocats', frm=self.__class__.other) self.feedMsg('s/evil/kind/s') m = self.getMsg(' ') self.assertIn('kind machines', str(m)) def testAllFlagsReplace(self): self.feedMsg('Terrible, terrible crimes') - self.feedMsg('Terrible, terrible TV shows', frm=__class__.other) + self.feedMsg('Terrible, terrible TV shows', frm=self.__class__.other) self.feedMsg('s/terr/horr/sgi') m = self.getMsg(' ') self.assertIn('horrible, horrible crimes', str(m)) def testOtherPersonReplace(self): - self.feedMsg('yeah, right', frm=__class__.other) - self.feedMsg('s/right/left/', frm=__class__.other2) + self.feedMsg('yeah, right', frm=self.__class__.other) + self.feedMsg('s/right/left/', frm=self.__class__.other2) m = self.getMsg(' ') # Note: using the bot prefix for the s/right/left/ part causes the first nick in "X thinks Y" # to be empty? It works fine in runtime though... - self.assertIn('%s thinks %s meant to say' % (ircutils.nickFromHostmask(__class__.other2), - ircutils.nickFromHostmask(__class__.other)), str(m)) + self.assertIn('%s thinks %s meant to say' % (ircutils.nickFromHostmask(self.__class__.other2), + ircutils.nickFromHostmask(self.__class__.other)), str(m)) def testExplicitOtherReplace(self): - self.feedMsg('ouch', frm=__class__.other2) - self.feedMsg('poof', frm=__class__.other) + self.feedMsg('ouch', frm=self.__class__.other2) + self.feedMsg('poof', frm=self.__class__.other) self.feedMsg('wow!') - self.feedMsg('%s: s/^/p/' % ircutils.nickFromHostmask(__class__.other2)) + self.feedMsg('%s: s/^/p/' % ircutils.nickFromHostmask(self.__class__.other2)) m = self.getMsg(' ') self.assertIn('pouch', str(m)) def testBoldReplacement(self): with conf.supybot.plugins.sedregex.boldReplacementText.context(True): - self.feedMsg('hahahaha', frm=__class__.other) + self.feedMsg('hahahaha', frm=self.__class__.other) # One replacement - self.feedMsg('s/h/H/', frm=__class__.other2) + self.feedMsg('s/h/H/', frm=self.__class__.other2) m = self.getMsg(' ') self.assertIn('\x02H\x02aha', str(m)) # Replace all instances - self.feedMsg('s/h/H/g', frm=__class__.other2) + self.feedMsg('s/h/H/g', frm=self.__class__.other2) m = self.getMsg(' ') self.assertIn('\x02H\x02a\x02H\x02a', str(m)) # One whole word - self.feedMsg('sweet dreams are made of this', frm=__class__.other) - self.feedMsg('s/this/cheese/', frm=__class__.other2) + self.feedMsg('sweet dreams are made of this', frm=self.__class__.other) + self.feedMsg('s/this/cheese/', frm=self.__class__.other2) m = self.getMsg(' ') self.assertIn('of \x02cheese\x02', str(m)) @@ -142,7 +142,7 @@ class SedRegexTestCase(ChannelPluginTestCase): self.assertIn('4 * 2 = 8', str(m)) def testWeirdSeparatorsFail(self): - self.feedMsg("can't touch this", frm=__class__.other) + self.feedMsg("can't touch this", frm=self.__class__.other) # Only symbols are allowed as separators self.feedMsg('blah: s a b ') self.feedMsg('blah: sdadbd') @@ -161,10 +161,10 @@ class SedRegexTestCase(ChannelPluginTestCase): self.assertIn('meant to say: * %s wakes' % self.nick, str(m)) def testOtherPersonActionReplace(self): - self.feedMsg("\x01ACTION sleeps\x01", frm=__class__.other) + self.feedMsg("\x01ACTION sleeps\x01", frm=self.__class__.other) self.feedMsg('s/sleeps/wakes/') m = self.getMsg(' ') - n = ircutils.nickFromHostmask(__class__.other) + n = ircutils.nickFromHostmask(self.__class__.other) self.assertIn('thinks %s meant to say: * %s wakes' % (n, n), str(m)) # TODO: test ignores From dcadb7e73baf5b89bab883522a498610b3a238fd Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 21 Mar 2017 13:14:06 -0700 Subject: [PATCH 17/32] SedRegex: remove a duplicate test From: https://github.com/jlu5/SupyPlugins/commit/5e77c65a5fe5c4a718923bd4b7829f951c445bdf --- plugins/SedRegex/test.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py index 07a411e08..1fee88bfb 100644 --- a/plugins/SedRegex/test.py +++ b/plugins/SedRegex/test.py @@ -130,17 +130,6 @@ class SedRegexTestCase(ChannelPluginTestCase): m = self.getMsg(' ') self.assertIn('4 * 2 = 8', str(m)) - def testWhitespaceSeparatorFails(self): - self.feedMsg('we are all decelopers on this blessed day') - self.feedMsg('s.c.v.') - m = self.getMsg(' ') - self.assertIn('developers', str(m)) - - self.feedMsg('4 / 2 = 8') - self.feedMsg('s@/@*@') - m = self.getMsg(' ') - self.assertIn('4 * 2 = 8', str(m)) - def testWeirdSeparatorsFail(self): self.feedMsg("can't touch this", frm=self.__class__.other) # Only symbols are allowed as separators From 1646ca25c27e580986ded4f3c1165334f4e8c0e5 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 21 Mar 2017 13:21:40 -0700 Subject: [PATCH 18/32] SedRegex: skip testBoldReplacement on Python 2 I don't know why it breaks here and I don't really care. From: https://github.com/jlu5/SupyPlugins/commit/b9481184cbba46fd0064088fa0b9a6d47d16df25 --- plugins/SedRegex/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py index 1fee88bfb..d6b4c1aa6 100644 --- a/plugins/SedRegex/test.py +++ b/plugins/SedRegex/test.py @@ -99,6 +99,7 @@ class SedRegexTestCase(ChannelPluginTestCase): m = self.getMsg(' ') self.assertIn('pouch', str(m)) + @unittest.skipUnless(sys.version_info[0] >= 3, 'Test fails on Python 2.') def testBoldReplacement(self): with conf.supybot.plugins.sedregex.boldReplacementText.context(True): self.feedMsg('hahahaha', frm=self.__class__.other) From fd7aa571e916f9f4e18753cc0946bd9e42287599 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 1 Sep 2017 18:18:56 -0700 Subject: [PATCH 19/32] SedRegex: remove compatibility workaround for Python 2.7.6 and lower From: https://github.com/jlu5/SupyPlugins/commit/c9bcbbb934ed34d51c1f7ee4ee8ed12b26443e5d --- plugins/SedRegex/plugin.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 8043040fc..5a98da9bf 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -38,6 +38,7 @@ import supybot.ircdb as ircdb import supybot.utils as utils import re +import sys try: from supybot.i18n import PluginInternationalization @@ -45,11 +46,13 @@ try: except ImportError: _ = lambda x: x -# Note: {0,3} is used for matching the flags instead of "*" to work around a Python bug in versions -# lower than 2.7.6: see https://stackoverflow.com/questions/3675144/regex-error-nothing-to-repeat -# and https://bugs.python.org/issue18647 +if sys.version_info[0] < 3: + raise ImportError('This plugin requires Python 3. For a legacy version of this plugin that still ' + 'supports Python 2, consult the python2-legacy branch at ' + 'https://github.com/GLolol/SupyPlugins/tree/python2-legacy') + SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" - r"(?P.*?)(?P=delim)(?P[a-z]{0,3})$") + r"(?P.*?)(?P=delim)(?P[a-z]*)$") # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) From e831d0e8ec1a7818329757ac600bb2dcd5d3dea4 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 8 Dec 2017 12:30:50 -0800 Subject: [PATCH 20/32] SedRegex: make the regexp process timeout configurable Although the default has been adequate on my systems, busy or low-powered machines may fail to process regexps quickly enough and cause SedRegex to sporadically error. Reported by @cottongin via IRC. From: https://github.com/jlu5/SupyPlugins/commit/51ff41251b7367f4c056745b18758006c7453ff5 --- plugins/SedRegex/config.py | 8 ++++++++ plugins/SedRegex/plugin.py | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/SedRegex/config.py b/plugins/SedRegex/config.py index 0a64864c7..ff185de04 100644 --- a/plugins/SedRegex/config.py +++ b/plugins/SedRegex/config.py @@ -56,5 +56,13 @@ conf.registerChannelValue(SedRegex, 'enable', conf.registerChannelValue(SedRegex, 'ignoreRegex', registry.Boolean(True, _("""Should Perl/sed regex replacing ignore messages which look like valid regex?"""))) +conf.registerGlobalValue(SedRegex, 'processTimeout', + registry.PositiveFloat(0.05, _("""Sets the timeout when processing a single + regexp. The default should be adequate unless + you have a busy or low-powered system that + cannot process regexps quickly enough. However, + you will not want to set this value too high + as that would make your bot vulnerable to ReDoS + attacks."""))) # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 5a98da9bf..1334bf996 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -153,7 +153,8 @@ class SedRegex(callbacks.PluginRegexp): else: messageprefix = '%s thinks %s' % (msg.nick, m.nick) try: - if regexp_wrapper(text, pattern, timeout=0.05, plugin_name=self.name(), + regex_timeout = self.registryValue('processTimeout') + if regexp_wrapper(text, pattern, timeout=regex_timeout, plugin_name=self.name(), fcn_name='replacer'): if self.registryValue('boldReplacementText', msg.args[0]): replacement = ircutils.bold(replacement) From 363285cfbfafea16bc04bbac1293fbd432098b31 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 19 May 2018 12:54:21 -0700 Subject: [PATCH 21/32] README: specifically mention that /s is specific to this plugin [skip ci] From: https://github.com/jlu5/SupyPlugins/commit/5e6c9349fa850150d6e475a3d34cc4e7e4487ed3 --- plugins/SedRegex/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/README.md b/plugins/SedRegex/README.md index 62b423db9..d0eebd587 100644 --- a/plugins/SedRegex/README.md +++ b/plugins/SedRegex/README.md @@ -22,4 +22,4 @@ The following regex flags (i.e. the `g` in `s/abc/def/g`, etc.) are supported: - `i`: case insensitive replacement - `g`: replace all occurences of the original text -- `s`: replace only messages from the caller +- `s`: *(custom flag specific to this plugin)* replace only messages from the caller From 06f70e59daca9bb778c020b1c2be6706f5beebe2 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 12 Dec 2018 18:56:26 -0800 Subject: [PATCH 22/32] SedRegex: reuse processTimeout in the sub() process too From: https://github.com/jlu5/SupyPlugins/commit/4e08442cdec093609468fd7a35b1b548901bdbc5 --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 1334bf996..a6a2e4ab2 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -159,7 +159,7 @@ class SedRegex(callbacks.PluginRegexp): if self.registryValue('boldReplacementText', msg.args[0]): replacement = ircutils.bold(replacement) subst = process(pattern.sub, replacement, - text, count, timeout=0.05) + text, count, timeout=regex_timeout) if action: # If the message was an ACTION, prepend the nick back. subst = '* %s %s' % (m.nick, subst) From 67a3928e4de61b70ea17ce0f431d400e5076b9d9 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 5 Jun 2019 14:38:03 -0700 Subject: [PATCH 23/32] SedRegex: log all errors, always display the not found mesage From: https://github.com/jlu5/SupyPlugins/commit/fdab0edbc8c82d1442748e6e33a01f3973514fc9 --- plugins/SedRegex/plugin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index a6a2e4ab2..f10f1c863 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -114,8 +114,8 @@ class SedRegex(callbacks.PluginRegexp): try: (pattern, replacement, count, flags) = self._unpack_sed(msg.args[1]) - except (ValueError, re.error) as e: - self.log.warning(_("SedRegex error: %s"), e) + except Exception as e: + self.log.warning(_("SedRegex error: %s"), e, exc_info=True) if self.registryValue('displayErrors', msg.args[0]): irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) return @@ -168,16 +168,16 @@ class SedRegex(callbacks.PluginRegexp): irc.reply(_("%s meant to say: %s") % (messageprefix, subst), prefixNick=False) return - except (ValueError, re.error) as e: + except Exception as e: + self.log.warning(_("SedRegex error: %s"), e, exc_info=True) if self.registryValue('displayErrors', msg.args[0]): irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) return self.log.debug(_("SedRegex: Search %r not found in the last %i messages of %s."), msg.args[1], len(irc.state.history), msg.args[0]) - if self.registryValue("displayErrors", msg.args[0]): - irc.error(_("Search not found in the last %i messages.") % - len(irc.state.history), Raise=True) + irc.error(_("Search not found in the last %i messages.") % + len(irc.state.history), Raise=True) replacer.__doc__ = SED_REGEX.pattern Class = SedRegex From 937930ab6a136696730f85e0bc962954c922e3ee Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 5 Jun 2019 14:58:46 -0700 Subject: [PATCH 24/32] SedRegex: match channel names case insensitively Some IRCds (Unreal 3.2) don't mangle target names for case correctness, leading to a bug where users end up ignored by the plugin. Reported by DOMF via IRC. From: https://github.com/jlu5/SupyPlugins/commit/e19abe049888667c3d0a4eb4a2c3ae88b8bea511 --- plugins/SedRegex/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index f10f1c863..55945f3bc 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -130,7 +130,7 @@ class SedRegex(callbacks.PluginRegexp): for m in iterable: if m.command in ('PRIVMSG', 'NOTICE') and \ - m.args[0] == msg.args[0] and m.tagged('receivedBy') == irc: + ircutils.strEqual(m.args[0], msg.args[0]) and m.tagged('receivedBy') == irc: if target and m.nick != target: continue # Don't snarf ignored users' messages unless specifically From 6c543527134f3f10d828d379b63ee7c4cf1a921b Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 5 Jun 2019 15:21:21 -0700 Subject: [PATCH 25/32] SedRegex: test case normalization of channel names From: https://github.com/jlu5/SupyPlugins/commit/5672008a311ada6eb0b6fda6f632132603d159af --- plugins/SedRegex/test.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py index d6b4c1aa6..c277eb717 100644 --- a/plugins/SedRegex/test.py +++ b/plugins/SedRegex/test.py @@ -1,5 +1,5 @@ ### -# Copyright (c) 2017, James Lu +# Copyright (c) 2017-2019, James Lu # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -157,6 +157,22 @@ class SedRegexTestCase(ChannelPluginTestCase): n = ircutils.nickFromHostmask(self.__class__.other) self.assertIn('thinks %s meant to say: * %s wakes' % (n, n), str(m)) + # https://github.com/jlu5/SupyPlugins/commit/e19abe049888667c3d0a4eb4a2c3ae88b8bea511 + # We want to make sure the bot treats channel names case-insensitively, if some client + # writes to it using a differente case. + def testCaseNormalizationInRead(self): + assert self.channel != self.channel.title() # In case Limnoria's defaults change + self.feedMsg("what a strange bug", to=self.channel.title()) + self.feedMsg('s/strange/hilarious/', to=self.channel) + m = self.getMsg(' ') + self.assertIn('what a hilarious bug', str(m)) + def testCaseNormalizationInReplace(self): + assert self.channel != self.channel.title() # In case Limnoria's defaults change + self.feedMsg("Segmentation fault", to=self.channel) + self.feedMsg('s/$/ (core dumped)/', to=self.channel.title()) + m = self.getMsg(' ') + self.assertIn('Segmentation fault (core dumped)', str(m)) + # TODO: test ignores # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From 43d4861577efa8364b1686e2597568ed5b8507ce Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 11 Oct 2019 09:58:50 -0700 Subject: [PATCH 26/32] Update my email & repo link references From: https://github.com/jlu5/SupyPlugins/commit/2ae51939b3258d7dd28f8f5dc825575e7638b243 --- plugins/SedRegex/__init__.py | 2 +- plugins/SedRegex/plugin.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/SedRegex/__init__.py b/plugins/SedRegex/__init__.py index 25b4daa63..8c15e17f6 100644 --- a/plugins/SedRegex/__init__.py +++ b/plugins/SedRegex/__init__.py @@ -43,7 +43,7 @@ __contributors__ = {supybot.Author("James Lu", "GLolol", "glolol@overdrivenetwor supybot.Author('nyuszika7h', 'nyuszika7h', 'nyuszika7h@openmailbox.org'): ["_unpack_sed method within plugin.py"] } -__url__ = 'https://github.com/GLolol/SupyPlugins' +__url__ = 'https://github.com/jlu5/SupyPlugins' from . import config from . import plugin diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 55945f3bc..cacff350f 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -49,7 +49,7 @@ except ImportError: if sys.version_info[0] < 3: raise ImportError('This plugin requires Python 3. For a legacy version of this plugin that still ' 'supports Python 2, consult the python2-legacy branch at ' - 'https://github.com/GLolol/SupyPlugins/tree/python2-legacy') + 'https://github.com/jlu5/SupyPlugins/tree/python2-legacy') SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P.*?)(?P=delim)" r"(?P.*?)(?P=delim)(?P[a-z]*)$") From 1267d6452e333e5a8546eb0f0afa2e616ecbb994 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 17 Oct 2019 21:55:37 -0700 Subject: [PATCH 27/32] SedRegex: abort when a search times out This requires commit https://github.com/ProgVal/Limnoria/commit/b54d8f8073b4fca1787012b211337dc707cfea45, which separates the timeout and no match cases. Also, raise the default processTimeout as the plugin now aborts on the first message that times out. From: https://github.com/jlu5/SupyPlugins/commit/e5af47993945ce8b0bdd772d144b96ca0be22acd --- plugins/SedRegex/config.py | 4 ++-- plugins/SedRegex/plugin.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/SedRegex/config.py b/plugins/SedRegex/config.py index ff185de04..ce2b8b396 100644 --- a/plugins/SedRegex/config.py +++ b/plugins/SedRegex/config.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2015, Michael Daniel Telatynski -# Copyright (c) 2015, James Lu +# Copyright (c) 2015-2019, James Lu # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ conf.registerChannelValue(SedRegex, 'ignoreRegex', registry.Boolean(True, _("""Should Perl/sed regex replacing ignore messages which look like valid regex?"""))) conf.registerGlobalValue(SedRegex, 'processTimeout', - registry.PositiveFloat(0.05, _("""Sets the timeout when processing a single + registry.PositiveFloat(0.5, _("""Sets the timeout when processing a single regexp. The default should be adequate unless you have a busy or low-powered system that cannot process regexps quickly enough. However, diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index cacff350f..1b20b5eac 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2015, Michael Daniel Telatynski -# Copyright (c) 2015-2017, James Lu +# Copyright (c) 2015-2019, James Lu # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -154,8 +154,9 @@ class SedRegex(callbacks.PluginRegexp): messageprefix = '%s thinks %s' % (msg.nick, m.nick) try: regex_timeout = self.registryValue('processTimeout') - if regexp_wrapper(text, pattern, timeout=regex_timeout, plugin_name=self.name(), - fcn_name='replacer'): + replace_result = regexp_wrapper(text, pattern, timeout=regex_timeout, plugin_name=self.name(), + fcn_name='replacer') + if replace_result is True: if self.registryValue('boldReplacementText', msg.args[0]): replacement = ircutils.bold(replacement) subst = process(pattern.sub, replacement, @@ -168,6 +169,12 @@ class SedRegex(callbacks.PluginRegexp): irc.reply(_("%s meant to say: %s") % (messageprefix, subst), prefixNick=False) return + elif replace_result is None: + # Abort on timeout instead of looking against older messages - this prevents + # replacing the wrong message when we get a one off timeout, which usually leads + # to very confusing results. + # This requires commit https://github.com/ProgVal/Limnoria/commit/b54d8f8073b4fca1787012b211337dc707cfea45 + irc.error(_("Search timed out."), Raise=True) except Exception as e: self.log.warning(_("SedRegex error: %s"), e, exc_info=True) if self.registryValue('displayErrors', msg.args[0]): From 9e0db63b5bfe3aa7dad45f8e937e26dce3ed6006 Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 17 Oct 2019 22:13:51 -0700 Subject: [PATCH 28/32] SedRegex: add test for ReDoS timeout From: https://github.com/jlu5/SupyPlugins/commit/81debc45ecd103f4773e7380ef1be505d86a2909 --- plugins/SedRegex/test.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/SedRegex/test.py b/plugins/SedRegex/test.py index c277eb717..9300ebb16 100644 --- a/plugins/SedRegex/test.py +++ b/plugins/SedRegex/test.py @@ -173,6 +173,14 @@ class SedRegexTestCase(ChannelPluginTestCase): m = self.getMsg(' ') self.assertIn('Segmentation fault (core dumped)', str(m)) + def testReDoSTimeout(self): + # From https://snyk.io/blog/redos-and-catastrophic-backtracking/ + for idx in range(500): + self.feedMsg("ACCCCCCCCCCCCCCCCCCCCCCCCCCCCX") + self.feedMsg(r"s/A(B|C+)+D/this should abort/") + m = self.getMsg(' ', timeout=1) + self.assertIn('timed out', str(m)) + # TODO: test ignores # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: From 08764b85ef5e22949b617119abdee79720c16eab Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Fri, 25 Oct 2019 21:51:37 +0200 Subject: [PATCH 29/32] SedRegex: spawn a single process to handle the whole history. This is more efficient than spawning up to 1000 processes (assuming Limnoria's default config). From: https://github.com/jlu5/SupyPlugins/commit/ede85ca8b087573ddb27cda99123e217b1e2135b --- plugins/SedRegex/plugin.py | 52 +++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/plugins/SedRegex/plugin.py b/plugins/SedRegex/plugin.py index 1b20b5eac..233429b17 100644 --- a/plugins/SedRegex/plugin.py +++ b/plugins/SedRegex/plugin.py @@ -30,6 +30,7 @@ ### from supybot.commands import * +from supybot.commands import ProcessTimeoutError import supybot.plugins as plugins import supybot.ircmsgs as ircmsgs import supybot.callbacks as callbacks @@ -57,6 +58,9 @@ SED_REGEX = re.compile(r"^(?:(?P.+?)[:,] )?s(?P[^\w\s])(?P # Replace newlines and friends with things like literal "\n" (backslash and "n") axe_spaces = utils.str.MultipleReplacer({'\n': '\\n', '\t': '\\t', '\r': '\\r'}) +class SearchNotFound(Exception): + pass + class SedRegex(callbacks.PluginRegexp): """History replacer using sed-style regex syntax.""" threaded = True @@ -128,7 +132,25 @@ class SedRegex(callbacks.PluginRegexp): if not ircutils.isNick(str(target), strictRfc=True): return - for m in iterable: + regex_timeout = self.registryValue('processTimeout') + try: + message = process(self._replacer_process, irc, msg, + target, pattern, replacement, count, iterable, + timeout=regex_timeout, pn=self.name(), cn='replacer') + except ProcessTimeoutError: + irc.error(_("Search timed out.")) + except SearchNotFound: + irc.error(_("Search not found in the last %i messages.") % + len(irc.state.history)) + except Exception as e: + if self.registryValue('displayErrors', msg.args[0]): + irc.error('%s.%s: %s' % (e.__class__.__module__, + e.__class__.__name__, e)) + else: + irc.reply(message, prefixNick=False) + + def _replacer_process(self, irc, msg, target, pattern, replacement, count, messages): + for m in messages: if m.command in ('PRIVMSG', 'NOTICE') and \ ircutils.strEqual(m.args[0], msg.args[0]) and m.tagged('receivedBy') == irc: if target and m.nick != target: @@ -152,39 +174,27 @@ class SedRegex(callbacks.PluginRegexp): messageprefix = msg.nick else: messageprefix = '%s thinks %s' % (msg.nick, m.nick) + try: - regex_timeout = self.registryValue('processTimeout') - replace_result = regexp_wrapper(text, pattern, timeout=regex_timeout, plugin_name=self.name(), - fcn_name='replacer') - if replace_result is True: + replace_result = pattern.search(text) + if replace_result: if self.registryValue('boldReplacementText', msg.args[0]): replacement = ircutils.bold(replacement) - subst = process(pattern.sub, replacement, - text, count, timeout=regex_timeout) + subst = pattern.sub(replacement, text, count) if action: # If the message was an ACTION, prepend the nick back. subst = '* %s %s' % (m.nick, subst) subst = axe_spaces(subst) - irc.reply(_("%s meant to say: %s") % - (messageprefix, subst), prefixNick=False) - return - elif replace_result is None: - # Abort on timeout instead of looking against older messages - this prevents - # replacing the wrong message when we get a one off timeout, which usually leads - # to very confusing results. - # This requires commit https://github.com/ProgVal/Limnoria/commit/b54d8f8073b4fca1787012b211337dc707cfea45 - irc.error(_("Search timed out."), Raise=True) + return _("%s meant to say: %s") % \ + (messageprefix, subst) except Exception as e: self.log.warning(_("SedRegex error: %s"), e, exc_info=True) - if self.registryValue('displayErrors', msg.args[0]): - irc.error('%s.%s: %s' % (e.__class__.__module__, e.__class__.__name__, e)) - return + raise self.log.debug(_("SedRegex: Search %r not found in the last %i messages of %s."), msg.args[1], len(irc.state.history), msg.args[0]) - irc.error(_("Search not found in the last %i messages.") % - len(irc.state.history), Raise=True) + raise SearchNotFound() replacer.__doc__ = SED_REGEX.pattern Class = SedRegex From 35dd3c3d9c63ff763e1d8158398c473001336a55 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 16 Nov 2019 12:26:32 -0800 Subject: [PATCH 30/32] Update plugin author/maintainer data From: https://github.com/jlu5/SupyPlugins/commit/cbd953b32c3da5f5d2bb5b9e711a467050a9dcb9 --- plugins/SedRegex/__init__.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/SedRegex/__init__.py b/plugins/SedRegex/__init__.py index 8c15e17f6..e5ba5725e 100644 --- a/plugins/SedRegex/__init__.py +++ b/plugins/SedRegex/__init__.py @@ -43,6 +43,9 @@ __contributors__ = {supybot.Author("James Lu", "GLolol", "glolol@overdrivenetwor supybot.Author('nyuszika7h', 'nyuszika7h', 'nyuszika7h@openmailbox.org'): ["_unpack_sed method within plugin.py"] } +__maintainer__ = getattr(supybot.authors, 'jlu', + supybot.Author('James Lu', 'GLolol', 'james@overdrivenetworks.com')) + __url__ = 'https://github.com/jlu5/SupyPlugins' from . import config From 51f10dbb608e7b51ca39ad3c9ba4ee253ac5b634 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 1 Jan 2020 21:56:40 -0800 Subject: [PATCH 31/32] SedRegex: update plugin metadata --- plugins/SedRegex/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/SedRegex/__init__.py b/plugins/SedRegex/__init__.py index e5ba5725e..c0c17b85b 100644 --- a/plugins/SedRegex/__init__.py +++ b/plugins/SedRegex/__init__.py @@ -1,6 +1,6 @@ ### # Copyright (c) 2015, Michael Daniel Telatynski -# Copyright (c) 2015-2016, James Lu +# Copyright (c) 2015-2020, James Lu # All rights reserved. # # Redistribution and use in source and binary forms, with or without @@ -36,21 +36,20 @@ History replacer using sed-style expressions. import supybot import supybot.world as world -__version__ = "2016.02.28.1+git" +__version__ = supybot.version.version __author__ = supybot.Author("Michael Daniel Telatynski", "t3chguy", "postmaster@webdevguru.co.uk") -__contributors__ = {supybot.Author("James Lu", "GLolol", "glolol@overdrivenetworks.com"): +__contributors__ = {supybot.authors.jlu: ["options bolding the replacement text", "misc. bug fixes and enhancements"], supybot.Author('nyuszika7h', 'nyuszika7h', 'nyuszika7h@openmailbox.org'): ["_unpack_sed method within plugin.py"] } -__maintainer__ = getattr(supybot.authors, 'jlu', - supybot.Author('James Lu', 'GLolol', 'james@overdrivenetworks.com')) +__maintainer__ = supybot.authors.limnoria_core -__url__ = 'https://github.com/jlu5/SupyPlugins' +__url__ = 'https://github.com/ProgVal/Limnoria/tree/master/plugins/SedRegex' from . import config from . import plugin -from imp import reload +from importlib import reload reload(config) reload(plugin) From 758a3efa44f89d8efb7342ee068bfca9a0c94488 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 1 Jan 2020 22:13:10 -0800 Subject: [PATCH 32/32] .travis.yml: remove --disable-multiprocessing (fix SedRegex tests) --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 984f3995f..2710753dc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,7 +8,7 @@ install: script: - echo $TRAVIS_PYTHON_VERSION - python setup.py install - - supybot-test test -v --plugins-dir=./plugins/ --no-network --disable-multiprocessing + - supybot-test test -v --plugins-dir=./plugins/ --no-network - if [ "$WITH_OPT_DEPS" = "true" ] -a [[ "$TRAVIS_PYTHON_VERSION" =~ ^3\.[4-9] ]] ; then python -m irctest irctest.controllers.limnoria; fi notifications: email: false