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) mod_or_superior = await is_mod_or_superior(self.bot, obj=author)
if mod_or_superior: if mod_or_superior:
return return
# As is anyone configured to be
await self.check_filter(message) if await self.bot.is_automod_immune(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:
return return
await self.check_filter(message) 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): async def on_member_update(self, before: discord.Member, after: discord.Member):
if not after.guild.me.guild_permissions.manage_nicknames: if before.display_name != after.display_name:
return # No permissions to manage nicknames, so can't do anything await self.maybe_filter_name(after)
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
async def on_member_join(self, member: discord.Member): async def on_member_join(self, member: discord.Member):
guild = member.guild await self.maybe_filter_name(member)
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()
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 return
for w in word_list: 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: try:
await member.edit(nick=name_to_use, reason="Filtered name") await member.edit(nick=name_to_use, reason=reason)
except: except discord.HTTPException:
pass pass
break return

View File

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

View File

@ -5,6 +5,7 @@ from collections import Counter
from enum import Enum from enum import Enum
from importlib.machinery import ModuleSpec from importlib.machinery import ModuleSpec
from pathlib import Path from pathlib import Path
from typing import Union
import discord import discord
import sys import sys
@ -72,6 +73,7 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
use_bot_color=False, use_bot_color=False,
fuzzy=False, fuzzy=False,
disabled_commands=[], disabled_commands=[],
autoimmune_ids=[],
) )
self.db.register_user(embeds=None) self.db.register_user(embeds=None)
@ -294,6 +296,41 @@ class RedBase(commands.GroupMixin, commands.bot.BotBase, RPCMixin):
if pkg_name.startswith("redbot.cogs."): if pkg_name.startswith("redbot.cogs."):
del sys.modules["redbot.cogs"].__dict__[name] 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 @staticmethod
async def send_filtered( async def send_filtered(
destination: discord.abc.Messageable, destination: discord.abc.Messageable,

View File

@ -14,7 +14,7 @@ from pathlib import Path
from random import SystemRandom from random import SystemRandom
from string import ascii_letters, digits from string import ascii_letters, digits
from distutils.version import StrictVersion from distutils.version import StrictVersion
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Union
import aiohttp import aiohttp
import discord import discord
@ -1694,6 +1694,82 @@ class Core(CoreLogic):
await ctx.bot.db.disabled_command_msg.set(message) await ctx.bot.db.disabled_command_msg.set(message)
await ctx.tick() 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 # RPC handlers
async def rpc_load(self, request): async def rpc_load(self, request):
cog_name = request.params[0] cog_name = request.params[0]