Compare commits

...

4 Commits

Author SHA1 Message Date
Valentin Lorentz
df190e6aca Channel: Add @redact command 2023-05-26 21:36:45 +02:00
Valentin Lorentz
7b97f7fe2a Remove timeout override 2023-05-26 21:30:56 +02:00
Valentin Lorentz
f00ecdeaef BadWords: Add the option to redact messages containing bad words
instead of or in addition to kicking
2023-05-26 21:15:12 +02:00
Valentin Lorentz
10152d79ee Request the draft/message-redaction capability 2023-05-26 21:14:28 +02:00
6 changed files with 210 additions and 24 deletions

View File

@ -119,6 +119,11 @@ conf.registerChannelValue(BadWords, 'selfCensor',
conf.registerChannelValue(BadWords, 'kick',
registry.Boolean(False, _("""Determines whether the bot will kick people with
a warning when they use bad words.""")))
conf.registerChannelValue(BadWords, 'redact',
registry.Boolean(False, _("""Determines whether the bot will redact
messages containing bad words (only on servers supporting IRCv3
draft/message-redaction; requires supybot.protocols.irc.experimentalExtensions).
""")))
conf.registerChannelValue(BadWords.kick, 'message',
registry.NormalizedString(_("""You have been kicked for using a word
prohibited in the presence of this bot. Please use more appropriate

View File

@ -1,7 +1,7 @@
###
# Copyright (c) 2002-2004, Jeremiah Fincher
# Copyright (c) 2009, James McCoy
# Copyright (c) 2010-2021, Valentin Lorentz
# Copyright (c) 2010-2023, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -77,24 +77,49 @@ class BadWords(callbacks.Privmsg):
channel = msg.channel
self.updateRegexp(channel, irc.network)
s = ircutils.stripFormatting(msg.args[1])
if irc.isChannel(channel) \
and self.registryValue('kick', channel, irc.network):
if self.regexp.search(s):
c = irc.state.channels[channel]
cap = ircdb.makeChannelCapability(channel, 'op')
if c.isHalfopPlus(irc.nick):
if c.isHalfopPlus(msg.nick) or \
ircdb.checkCapability(msg.prefix, cap):
self.log.debug("Not kicking %s from %s, because "
"they are halfop+ or can't be "
"kicked.", msg.nick, channel)
else:
message = self.registryValue('kick.message',
channel, irc.network)
irc.queueMsg(ircmsgs.kick(channel, msg.nick, message))
else:
self.log.warning('Should kick %s from %s, but not opped.',
msg.nick, channel)
if not irc.isChannel(channel):
# not a channel, don't bother checking
return msg
may_kick = self.registryValue('kick', channel, irc.network)
msgid = msg.server_tags.get("msgid")
may_redact = (
"draft/message-redaction" in irc.state.capabilities_ack
and msgid
and conf.supybot.protocols.irc.experimentalExtensions()
and self.registryValue('redact', channel, irc.network)
)
if not may_kick and not may_redact:
# no configured action, don't bother checking
return msg
if not self.regexp.search(s):
# message does not contain a bad word
return msg
c = irc.state.channels[channel]
cap = ircdb.makeChannelCapability(channel, 'op')
if not c.isHalfopPlus(irc.nick):
self.log.warning(
'Should kick and/or redact %s from %s, but not opped.',
msg.nick, channel)
return msg
if may_redact:
irc.queueMsg(ircmsgs.IrcMsg(
command="REDACT", args=(channel, msgid)))
if may_kick:
if c.isHalfopPlus(msg.nick) or \
ircdb.checkCapability(msg.prefix, cap):
self.log.debug("Not kicking %s from %s, because "
"they are halfop+ or can't be "
"kicked.", msg.nick, channel)
else:
message = self.registryValue('kick.message',
channel, irc.network)
irc.queueMsg(ircmsgs.kick(channel, msg.nick, message))
return msg
def updateRegexp(self, channel, network):

View File

@ -1,6 +1,6 @@
###
# Copyright (c) 2002-2004, Jeremiah Fincher
# Copyright (c) 2010-2021, Valentin Lorentz
# Copyright (c) 2010-2023, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -31,7 +31,7 @@
import supybot.conf as conf
from supybot.test import *
class BadWordsTestCase(PluginTestCase):
class BadWordsOutfilterTestCase(PluginTestCase):
plugins = ('BadWords', 'Utilities', 'Format', 'Filter')
badwords = ('shit', 'ass', 'fuck')
def tearDown(self):
@ -80,5 +80,48 @@ class BadWordsTestCase(PluginTestCase):
self.assertNotError('badwords add "fuck you"')
self.assertResponse('badwords list', 'ass, fuck you, and shit')
class BadWordsInfilterTestCase(ChannelPluginTestCase):
plugins = ('BadWords',)
badwords = ('shit', 'ass', 'fuck')
def tearDown(self):
# .default() doesn't seem to be working for BadWords.words
#default = conf.supybot.plugins.BadWords.words.default()
#conf.supybot.plugins.BadWords.words.setValue(default)
conf.supybot.plugins.BadWords.words.setValue([])
def testKick(self):
self.irc.feedMsg(ircmsgs.op(self.channel, self.nick))
self.assertNotError('badwords add shit')
with conf.supybot.plugins.BadWords.kick \
.getSpecific(self.irc.network, self.channel).context(True):
self.irc.feedMsg(ircmsgs.privmsg(self.channel,
'oh shit',
prefix='foobar!user@__no_testcap__'))
m = self.getMsg(' ')
self.assertIsNotNone(m)
self.assertEqual(m.command, 'KICK', m)
self.assertEqual(m.args[0], self.channel, m)
self.assertEqual(m.args[1], 'foobar', m)
def testRedact(self):
self.irc.feedMsg(ircmsgs.op(self.channel, self.nick))
self.irc.state.capabilities_ack.add('draft/message-redaction')
self.assertNotError('badwords add shit')
with conf.supybot.plugins.BadWords.redact \
.getSpecific(self.irc.network, self.channel).context(True):
with conf.supybot.protocols.irc.experimentalExtensions.context(True):
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, 'oh shit'),
prefix='foobar!user@__no_testcap__',
server_tags={'msgid': 'abcde'}))
m = self.getMsg(' ')
self.assertIsNotNone(m)
self.assertEqual(m.command, 'REDACT', m)
self.assertEqual(m.args, (self.channel, 'abcde'), m)
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -1,7 +1,7 @@
###
# Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2009-2012, James McCoy
# Copyright (c) 2010-2021, Valentin Lorentz
# Copyright (c) 2010-2023, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -464,6 +464,53 @@ class Channel(callbacks.Plugin):
first('hostmask',
('literal', '--all')))])
@internationalizeDocstring
def redact(self, irc, msg, args, channel, msgid, reason):
"""[<channel>] [--msgid <msgid>] [<reason>]
Redacts the message identified by the given <msgid> from the <channel>
for <reason>. <msgid> may be omitted if this command is given as a
reply to another message (on servers supporting IRCv3 message-tags
and +draft/reply).
This command requires supybot.protocols.irc.experimentalExtensions and
is only available on networks which support IRCv3
draft/message-redaction.
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
if not conf.supybot.protocols.irc.experimentalExtensions():
irc.error(
_('Experimental IRC extensions are not enabled for this bot.'),
Raise=True)
msgid = msgid or msg.server_tags.get('+draft/reply')
if not msgid:
irc.error(_('--msgid is required when this command is not a '
'reply to a message'), Raise=True)
for target_message in reversed(irc.state.history):
if target_message.server_tags.get('msgid') == msgid:
break
else:
irc.error(_('Could not find the message in my history'),
Raise=True)
if target_message.command not in ('PRIVMSG', 'NOTICE', 'TAGMSG'):
irc.error(_('This is not a valid message.'),
Raise=True)
if ircutils.strEqual(target_message.nick, irc.nick):
irc.error(_('I cowardly refuse to redact my own messages.'),
Raise=True)
args = [channel, msgid]
if reason:
args.append(reason)
self._sendMsg(irc, ircmsgs.IrcMsg(command="REDACT", args=args))
redact = wrap(redact, ['op', ('haveHalfop+', _('kick someone')),
getopts({'msgid': 'somethingWithoutSpaces'}),
additional('text')])
@internationalizeDocstring
def listbans(self, irc, msg, args, channel):
"""[<channel>]

View File

@ -1,7 +1,7 @@
###
# Copyright (c) 2002-2005, Jeremiah Fincher
# Copyright (c) 2009, James McCoy
# Copyright (c) 2010-2021, Valentin Lorentz
# Copyright (c) 2010-2023, Valentin Lorentz
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
@ -289,5 +289,71 @@ class ChannelTestCase(ChannelPluginTestCase):
self.assertEqual(m.args[0], '#foo')
self.assertEqual(m.args[1], 'reason')
def assertRedactNoMsgid(self, query, hostmask, **kwargs):
self.irc.feedMsg(ircmsgs.op(self.channel, self.nick))
self.irc.state.capabilities_ack.add('draft/message-redaction')
with conf.supybot.protocols.irc.experimentalExtensions.context(True):
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, '@redact')))
self.assertRegexp(' ', 'Error: --msgid is required when')
def assertRedactMsgid(self, query, hostmask, **kwargs):
self.irc.feedMsg(ircmsgs.op(self.channel, self.nick))
self.irc.state.capabilities_ack.add('draft/message-redaction')
with conf.supybot.protocols.irc.experimentalExtensions.context(True):
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, '@redact --msgid foobar')))
self.assertRegexp(' ', 'Error: Cound not find')
m = self.getMsg(' ', timeout=0.5)
self.assertIsNone(m)
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, 'oh shit'),
server_tags={'msgid': 'foobar'}))
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, '@redact --msgid foobar')))
m = self.getMsg(' ')
self.assertEqual(m.command, 'REDACT')
self.assertEqual(m.args, (self.channel, 'foobar'))
def assertRedactReply(self, query, hostmask, **kwargs):
self.irc.feedMsg(ircmsgs.op(self.channel, self.nick))
self.irc.state.capabilities_ack.add('draft/message-redaction')
with conf.supybot.protocols.irc.experimentalExtensions.context(True):
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, '@redact'),
server_tags={'+draft/reply=foobar'}))
self.assertRegexp(' ', 'Error: Cound not find')
m = self.getMsg(' ', timeout=0.5)
self.assertIsNone(m)
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, 'oh shit'),
server_tags={'msgid': 'foobar'}))
self.irc.feedMsg(ircmsgs.IrcMsg(
command='PRIVMSG',
args=(self.channel, '@redact'),
server_tags={'+draft/reply=foobar'}))
m = self.getMsg(' ')
self.assertEqual(m.command, 'REDACT')
self.assertEqual(m.args, (self.channel, 'foobar'))
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -1786,7 +1786,7 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
"""
REQUEST_EXPERIMENTAL_CAPABILITIES = set(['draft/account-registration',
'draft/multiline'])
'draft/message-redaction', 'draft/multiline'])
"""Like REQUEST_CAPABILITIES, but these capabilities are only requested
if supybot.protocols.irc.experimentalExtensions is enabled."""