NoTrigger: make list of prefixes/suffixes and bell (\x07) blocking configurable, docs+test case updates

This commit is contained in:
James Lu 2015-10-11 22:20:59 -07:00
parent 7f5cea73ad
commit 8e8a3a30a5
4 changed files with 59 additions and 25 deletions

View File

@ -1,12 +1,16 @@
NoTrigger is an anti-abuse script that modifies outFilter to prevent triggering other bots. NoTrigger is an anti-abuse plugin that modifies outFilter to prevent triggering other bots.
## Short description ## Short description
In short, NoTrigger works by: In short, NoTrigger works by:
- Prepending messages that start with a symbol (```!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~```) with a [zero width space](https://en.wikipedia.org/wiki/Zero-width_space) (ZWSP), since these are often used as prefixes for bots. This has the effect of being completely invisible, and tricks most bots into ignoring yours! - Prepending messages that start with a symbol (```!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~```) or any configured prefixes with a [zero width space](https://en.wikipedia.org/wiki/Zero-width_space) (ZWSP), since these are often used as prefixes for bots. This has the effect of being completely invisible, and tricks most bots into ignoring yours!
- Prepending messages with a ZWSP if the channel is set to block colors and a message begins with a formatting code (sneaky attackers can otherwise do something like `\x02!echo hello` to bypass filters). - Prepending messages with a ZWSP if the channel is set to block colors and a message begins with a formatting code (sneaky attackers can otherwise do something like `\x02!echo hello` to bypass filters).
- Optionally, prepending messages with a ZWSP if they match `<something>: ` or `<something>, `, since some bots are taught to respond to their nicks. - Optionally, prepending messages with a ZWSP if they match `<something>: ` or `<something>, `, since some bots are taught to respond to their nicks.
- Optionally, blocking all channel-wide CTCPs (except for ACTION). - Optionally, blocking all channel-wide CTCPs (except for ACTION).
- Optionally, stripping the bell character from any outgoing messages.
- Optionally, appending messages that end with any configured suffixes with a ZWSP.
To enable NoTrigger, set the `plugins.NoTrigger.enable` config option `True` for the channels in question. You can find a list of NoTrigger's options (toggling the things mentioned above) by running `config list plugins.NoTrigger`.
## Longer description/Backstory on why I wrote this ## Longer description/Backstory on why I wrote this
Sometimes when you have a public development channel with many bots residing in it, someone will come along and do something really evil: that is, create a huge message loop by chaining all your innocent bots together! Sometimes when you have a public development channel with many bots residing in it, someone will come along and do something really evil: that is, create a huge message loop by chaining all your innocent bots together!

View File

@ -28,6 +28,8 @@
### ###
import string
import supybot.conf as conf import supybot.conf as conf
import supybot.registry as registry import supybot.registry as registry
try: try:
@ -38,7 +40,6 @@ except:
# without the i18n module # without the i18n module
_ = lambda x: x _ = lambda x: x
def configure(advanced): def configure(advanced):
# This will be called by supybot to configure this module. advanced is # This will be called by supybot to configure this module. advanced is
# a bool that specifies whether the user identified himself as an advanced # a bool that specifies whether the user identified himself as an advanced
@ -54,20 +55,30 @@ conf.registerChannelValue(NoTrigger, 'enable',
conf.registerChannelValue(NoTrigger, 'spaceBeforeNicks', conf.registerChannelValue(NoTrigger, 'spaceBeforeNicks',
registry.Boolean(False, _("""Add a space before messages beginning with registry.Boolean(False, _("""Add a space before messages beginning with
"blah: " or "blah, ", preventing the bot from triggering other bots that "blah: " or "blah, ", preventing the bot from triggering other bots that
respond to nick. This can cause some weird spacings with error messages and respond to nick.""")))
other command replies, so I wouldn't recommend enabling it unless absolutely
necessary.""")))
conf.registerChannelValue(NoTrigger, 'colorAware', conf.registerChannelValue(NoTrigger, 'colorAware',
registry.Boolean(True, _("""Toggles whether the bot should be aware of colour-stripping registry.Boolean(True, _("""Toggles whether the bot should be aware of color-stripping
modes. (+c or +S on most IRCds)"""))) channel modes (+c or +S on most IRCds).""")))
conf.registerGroup(NoTrigger, 'colorAware')
conf.registerChannelValue(NoTrigger.colorAware, 'modes', conf.registerChannelValue(NoTrigger.colorAware, 'modes',
registry.SpaceSeparatedListOfStrings("c S", _("""Determines a list of modes that should registry.SpaceSeparatedListOfStrings("c S", _("""Defines a space-separated list of modes that should
be treated as colour-blocking modes. This is usually +c (block) and +S (stripcolour) on be treated as color-blocking modes. This is usually +c (block) and +S (stripcolour) on
UnrealIRCd/InspIRCd, and just +c (stripcolour) on charybdis-based daemons."""))) UnrealIRCd/InspIRCd, and just +c (stripcolor) on charybdis-based daemons.""")))
conf.registerChannelValue(NoTrigger, 'blockCtcp', conf.registerChannelValue(NoTrigger, 'blockCtcp',
registry.Boolean(False, _("""Determines whether the bot should block all CTCPs (\001 codes) registry.Boolean(False, _("""Determines whether the bot should block all outbound channel CTCPs
except CTCP actions. If you are using the Ctcp plugin, you will want to turn this off."""))) except CTCP actions. If you are using the Ctcp plugin, you will want to turn this off.""")))
conf.registerChannelValue(NoTrigger, 'prefixes',
registry.SpaceSeparatedListOfStrings(' '.join(string.punctuation),
_("""Defines a space-separated list of prefix triggers the bot should ignore.""")))
conf.registerChannelValue(NoTrigger, 'suffixes',
registry.SpaceSeparatedListOfStrings('',
_("""Defines a space-separated list of suffix triggers the bot should ignore.""")))
conf.registerChannelValue(NoTrigger, 'blockBell',
registry.Boolean(True,
_("""Determines whether the bot should strip bell characters (\x07) from any messages it sends.""")))
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79:

View File

@ -46,13 +46,13 @@ except ImportError:
class NoTrigger(callbacks.Plugin): class NoTrigger(callbacks.Plugin):
"""Mods outFilter to prevent the bot from triggering other bots.""" """Modifies outFilter to prevent the bot from triggering other bots."""
def __init__(self, irc): def __init__(self, irc):
self.__parent = super(NoTrigger, self) self.__parent = super(NoTrigger, self)
self.__parent.__init__(irc) self.__parent.__init__(irc)
# This uses Unicode Character 'ZERO WIDTH SPACE' (U+200B) for # This appends the Unicode character 'ZERO WIDTH SPACE' (U+200B) for
# padding, which looks nicer (it's invisible) and does the trick. # which is absolutely invisible and stops bots from being triggered by us.
if version_info[0] >= 3: if version_info[0] >= 3:
self.padchar = "\u200B" self.padchar = "\u200B"
else: else:
@ -60,6 +60,8 @@ class NoTrigger(callbacks.Plugin):
self.padchar = u('\u200B')[0] self.padchar = u('\u200B')[0]
def isChanStripColor(self, irc, channel): def isChanStripColor(self, irc, channel):
"""Returns whether the given channel has a color-stripping mode (usually
+c or +S, but configurable) set."""
try: try:
c = irc.state.channels[channel] c = irc.state.channels[channel]
for item in self.registryValue('colorAware.modes'): for item in self.registryValue('colorAware.modes'):
@ -74,9 +76,8 @@ class NoTrigger(callbacks.Plugin):
ircutils.isChannel(msg.args[0]) and \ ircutils.isChannel(msg.args[0]) and \
self.registryValue('enable', msg.args[0]): self.registryValue('enable', msg.args[0]):
s = msg.args[1] s = msg.args[1]
prefixes = string.punctuation prefixes = self.registryValue('prefixes', msg.args[0])
rpairs = {"\007": ""} suffixes = self.registryValue('suffixes', msg.args[0])
suffixes = ("moo")
if self.registryValue('colorAware') and \ if self.registryValue('colorAware') and \
self.isChanStripColor(irc, msg.args[0]) and \ self.isChanStripColor(irc, msg.args[0]) and \
s.startswith(("\003", "\002", "\017", "\037", "\026")): s.startswith(("\003", "\002", "\017", "\037", "\026")):
@ -105,11 +106,13 @@ class NoTrigger(callbacks.Plugin):
"CTCP due to config " "CTCP due to config "
"plugins.notrigger.blockCtcp.", msg.args[0], "plugins.notrigger.blockCtcp.", msg.args[0],
irc.network) irc.network)
for k, v in rpairs.items(): if self.registryValue('blockBell', msg.args[0]):
s = s.replace(k, v) self.log.debug("NoTrigger (%s/%s): removing bell character"
"from outgoing message.", msg.args[0], irc.network)
s = s.replace('\x07', '')
if s.startswith(tuple(prefixes)): if s.startswith(tuple(prefixes)):
s = self.padchar + s s = self.padchar + s
if s.endswith(suffixes): if s.endswith(tuple(suffixes)):
s += self.padchar s += self.padchar
msg = ircmsgs.privmsg(msg.args[0], s, msg=msg) msg = ircmsgs.privmsg(msg.args[0], s, msg=msg)
return msg return msg

View File

@ -30,7 +30,6 @@
from supybot.test import * from supybot.test import *
class NoTriggerTestCase(ChannelPluginTestCase): class NoTriggerTestCase(ChannelPluginTestCase):
plugins = ('NoTrigger', 'Utilities', 'Reply') plugins = ('NoTrigger', 'Utilities', 'Reply')
config = {'supybot.plugins.notrigger.enable': True, config = {'supybot.plugins.notrigger.enable': True,
@ -39,13 +38,30 @@ class NoTriggerTestCase(ChannelPluginTestCase):
def testSpaceBeforePrefixes(self): def testSpaceBeforePrefixes(self):
self.assertNotRegexp('echo !test', '^!test$') self.assertNotRegexp('echo !test', '^!test$')
self.assertNotRegexp('echo $test', '^\$test$')
def testSpaceBeforeNicks(self): def testSpaceBeforeNicks(self):
self.assertNotRegexp('echo example: hello', '^example: hello$') self.assertNotRegexp('echo example: hello', '^example: hello$')
self.assertNotRegexp('echo user1, hello', '^user1, hello$') self.assertNotRegexp('echo user1, hello', '^user1, hello$')
def testCTCPBlocking(self): def testBlockCTCP(self):
self.assertResponse('echo \x01PING abcd\x01', 'PING abcd') self.assertResponse('echo \x01PING abcd\x01', 'PING abcd')
self.assertAction('reply action jumps around', 'jumps around') self.assertAction('reply action jumps around', 'jumps around')
def testBlockBell(self):
self.assertResponse('echo \x07', '')
self.assertResponse('echo evil bell char\x07', 'evil bell char')
def testConfigurablePrefixes(self):
with conf.supybot.plugins.notrigger.prefixes.context("moo !"):
self.assertNotRegexp('echo moo test', '^moo test$')
self.assertNotRegexp('echo !test', '^!test$')
self.assertResponse('echo .test', '.test')
def testConfigurableSuffixes(self):
with conf.supybot.plugins.notrigger.suffixes.context("abcd +"):
self.assertNotRegexp('echo moo test+', '^moo test+$')
self.assertNotRegexp('echo 1234 abcd', '^!1234 abcd$')
self.assertResponse('echo test-', 'test-')
# vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: # vim:set shiftwidth=4 tabstop=4 expandtab textwidth=79: