Automated mod action immunity settings (#2129)

Refactors, and fixes some logic in filter which was encountered while
applying the settings to core
This commit is contained in:
Michael H 2018-09-24 21:30:28 -04:00 committed by Toby Harradine
parent 84ac5f3952
commit f8558b98c1
4 changed files with 142 additions and 55 deletions

View File

@ -249,70 +249,41 @@ class Filter:
mod_or_superior = await is_mod_or_superior(self.bot, obj=author)
if mod_or_superior:
return
await self.check_filter(message)
async def on_message_edit(self, _, message):
author = message.author
if message.guild is None or self.bot.user == author:
return
valid_user = isinstance(author, discord.Member) and not author.bot
if not valid_user:
return
# Bots and mods or superior are ignored from the filter
mod_or_superior = await is_mod_or_superior(self.bot, obj=author)
if mod_or_superior:
# As is anyone configured to be
if await self.bot.is_automod_immune(message):
return
await self.check_filter(message)
async def on_message_edit(self, _prior, message):
# message content has to change for non-bot's currently.
# if this changes, we should compare before passing it.
await self.on_message(message)
async def on_member_update(self, before: discord.Member, after: discord.Member):
if not after.guild.me.guild_permissions.manage_nicknames:
return # No permissions to manage nicknames, so can't do anything
word_list = await self.settings.guild(after.guild).filter()
filter_names = await self.settings.guild(after.guild).filter_names()
name_to_use = await self.settings.guild(after.guild).filter_default_name()
if not filter_names:
return
name_filtered = False
nick_filtered = False
for w in word_list:
if w in after.name:
name_filtered = True
if after.nick and w in after.nick: # since Member.nick can be None
nick_filtered = True
if name_filtered and nick_filtered: # Both true, so break from loop
break
if name_filtered and after.nick is None:
try:
await after.edit(nick=name_to_use, reason="Filtered name")
except:
pass
elif nick_filtered:
try:
await after.edit(nick=None, reason="Filtered nickname")
except:
pass
if before.display_name != after.display_name:
await self.maybe_filter_name(after)
async def on_member_join(self, member: discord.Member):
guild = member.guild
if not guild.me.guild_permissions.manage_nicknames:
return
word_list = await self.settings.guild(guild).filter()
filter_names = await self.settings.guild(guild).filter_names()
name_to_use = await self.settings.guild(guild).filter_default_name()
await self.maybe_filter_name(member)
if not filter_names:
async def maybe_filter_name(self, member: discord.Member):
if not member.guild.me.guild_permissions.manage_nicknames:
return # No permissions to manage nicknames, so can't do anything
if member.top_role >= member.guild.me.top_role:
return # Discord Hierarchy applies to nicks
if await self.bot.is_automod_immune(member):
return
word_list = await self.settings.guild(member.guild).filter()
if not await self.settings.guild(member.guild).filter_names():
return
for w in word_list:
if w in member.name:
if w in member.display_name.lower():
name_to_use = await self.settings.guild(member.guild).filter_default_name()
reason = "Filtered nick" if member.nick else "Filtered name"
try:
await member.edit(nick=name_to_use, reason="Filtered name")
except:
await member.edit(nick=name_to_use, reason=reason)
except discord.HTTPException:
pass
break
return

View File

@ -1501,6 +1501,9 @@ class Mod:
mod_or_superior = await is_mod_or_superior(self.bot, obj=author)
if mod_or_superior:
return
# As are anyone configured to be
if await self.bot.is_automod_immune(message):
return
deleted = await self.check_duplicates(message)
if not deleted:
deleted = await self.check_mention_spam(message)

View File

@ -5,6 +5,7 @@ from collections import Counter
from enum import Enum
from importlib.machinery import ModuleSpec
from pathlib import Path
from typing import Union
import discord
import sys
@ -72,6 +73,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
use_bot_color=False,
fuzzy=False,
disabled_commands=[],
autoimmune_ids=[],
)
self.db.register_user(embeds=None)
@ -294,6 +296,41 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
if pkg_name.startswith("redbot.cogs."):
del sys.modules["redbot.cogs"].__dict__[name]
async def is_automod_immune(
self, to_check: Union[discord.Message, commands.Context, discord.abc.User, discord.Role]
) -> bool:
"""
Checks if the user, message, context, or role should be considered immune from automated
moderation actions.
This will return ``False`` in direct messages.
Parameters
----------
to_check : `discord.Message` or `commands.Context` or `discord.abc.User` or `discord.Role`
Something to check if it would be immune
Returns
-------
bool
``True`` if immune
"""
guild = to_check.guild
if not guild:
return False
if isinstance(to_check, discord.Role):
ids_to_check = [to_check.id]
else:
author = getattr(to_check, "author", to_check)
ids_to_check = [r.id for r in author.roles]
ids_to_check.append(author.id)
immune_ids = await self.db.guild(guild).autoimmune_ids()
return any(i in immune_ids for i in ids_to_check)
@staticmethod
async def send_filtered(
destination: discord.abc.Messageable,

View File

@ -14,7 +14,7 @@ from pathlib import Path
from random import SystemRandom
from string import ascii_letters, digits
from distutils.version import StrictVersion
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union
import aiohttp
import discord
@ -1694,6 +1694,82 @@ class Core(CoreLogic):
await ctx.bot.db.disabled_command_msg.set(message)
await ctx.tick()
@commands.guild_only()
@checks.guildowner_or_permissions(manage_server=True)
@commands.group(name="autoimmune")
async def autoimmune_group(self, ctx: commands.Context):
"""
Server settings for immunity from automated actions
"""
pass
@autoimmune_group.command(name="list")
async def autoimmune_list(self, ctx: commands.Context):
"""
Get's the current members and roles
configured for automatic moderation action immunity
"""
ai_ids = await ctx.bot.db.guild(ctx.guild).autoimmune_ids()
roles = {r.name for r in ctx.guild.roles if r.id in ai_ids}
members = {str(m) for m in ctx.guild.members if m.id in ai_ids}
output = ""
if roles:
output += _("Roles immune from automated moderation actions:\n")
output += ", ".join(roles)
if members:
if roles:
output += "\n"
output += _("Members immune from automated moderation actions:\n")
output += ", ".join(members)
if not output:
output = _("No immunty settings here.")
for page in pagify(output):
await ctx.send(page)
@autoimmune_group.command(name="add")
async def autoimmune_add(
self, ctx: commands.Context, user_or_role: Union[discord.Member, discord.Role]
):
"""
Makes a user or roles immune from automated moderation actions
"""
async with ctx.bot.db.guild(ctx.guild).autoimmune_ids() as ai_ids:
if user_or_role.id in ai_ids:
return await ctx.send(_("Already added."))
ai_ids.append(user_or_role.id)
await ctx.tick()
@autoimmune_group.command(name="remove")
async def autoimmune_remove(
self, ctx: commands.Context, user_or_role: Union[discord.Member, discord.Role]
):
"""
Makes a user or roles immune from automated moderation actions
"""
async with ctx.bot.db.guild(ctx.guild).autoimmune_ids() as ai_ids:
if user_or_role.id not in ai_ids:
return await ctx.send(_("Not in list."))
ai_ids.remove(user_or_role.id)
await ctx.tick()
@autoimmune_group.command(name="isimmune")
async def autoimmune_checkimmune(
self, ctx: commands.Context, user_or_role: Union[discord.Member, discord.Role]
):
"""
Checks if a user or role would be considered immune from automated actions
"""
if await ctx.bot.is_automod_immune(user_or_role):
await ctx.send(_("They are immune"))
else:
await ctx.send(_("They are not Immune"))
# RPC handlers
async def rpc_load(self, request):
cog_name = request.params[0]