feat(aurora): starting on the updated moderation type system
Some checks failed
Actions / Build Documentation (MkDocs) (pull_request) Failing after 26s
Actions / Lint Code (Ruff & Pylint) (pull_request) Failing after 40s

This commit is contained in:
Seaswimmer 2024-07-06 11:30:37 -04:00
parent fcecfe8e88
commit 9f068bba6f
Signed by: cswimr
GPG key ID: 3813315477F26F82
10 changed files with 321 additions and 172 deletions

View file

@ -2,13 +2,14 @@
from datetime import datetime, timedelta
from typing import Union
from discord import Color, Embed, Guild, Interaction, InteractionMessage, Member, Role, User
from discord import Color, Embed, Guild, Interaction, Member, Message, Role, User
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, warning
from ..models.moderation import Moderation
from ..models.partials import PartialUser
from ..models.type import Type
from .config import config
from .utils import get_bool_emoji, get_pagesize_str
@ -18,10 +19,10 @@ async def message_factory(
color: Color,
guild: Guild,
reason: str,
moderation_type: str,
moderation_type: Type,
moderator: Union[Member, User] | None = None,
duration: timedelta | None = None,
response: InteractionMessage | None = None,
response: Message | None = None,
role: Role | None = None,
) -> Embed:
"""This function creates a message from set parameters, meant for contacting the moderated user.
@ -31,49 +32,47 @@ async def message_factory(
color (Color): The color of the embed.
guild (Guild): The guild the moderation occurred in.
reason (str): The reason for the moderation.
moderation_type (str): The type of moderation.
moderation_type (Type): The type of moderation.
moderator (Union[Member, User], optional): The moderator who performed the moderation. Defaults to None.
duration (timedelta, optional): The duration of the moderation. Defaults to None.
response (InteractionMessage, optional): The response message. Defaults to None.
response (Message, optional): The response message. Defaults to None.
role (Role, optional): The role that was added or removed. Defaults to None.
Returns:
embed: The message embed.
"""
if response is not None and moderation_type not in [
"kicked",
"banned",
"tempbanned",
"unbanned",
if response is not None and moderation_type.type not in [
"kick",
"ban",
"tempban",
"unban",
]:
guild_name = f"[{guild.name}]({response.jump_url})"
else:
guild_name = guild.name
title = moderation_type
if moderation_type in ["tempbanned", "muted"] and duration:
if duration:
embed_duration = f" for {humanize_timedelta(timedelta=duration)}"
else:
embed_duration = ""
if moderation_type == "note":
embed_desc = "received a"
elif moderation_type == "addrole":
embed_desc = f"received the {role.name} role"
title = "Role Added"
moderation_type = ""
elif moderation_type == "removerole":
embed_desc = f"lost the {role.name} role"
title = "Role Removed"
moderation_type = ""
else:
embed_desc = "been"
# if moderation_type.type == "note":
# embed_desc = "received a"
# elif moderation_type.type == "addrole":
# embed_desc = f"received the {role.name} role"
# title = "Role Added"
# verb = ""
# elif moderation_type.type == "removerole":
# embed_desc = f"lost the {role.name} role"
# title = "Role Removed"
# verb = ""
# else:
# embed_desc = "been"
embed = Embed(
title=str.title(title),
description=f"You have {embed_desc} {moderation_type}{embed_duration} in {guild_name}.",
title=str.title(moderation_type.type),
description=f"You have {moderation_type.embed_desc} {moderation_type.verb}{embed_duration} in {guild_name}.",
color=color,
timestamp=datetime.now(),
)
@ -99,12 +98,12 @@ async def message_factory(
async def log_factory(
interaction: Interaction, moderation: Moderation, resolved: bool = False
ctx: commands.Context, moderation: Moderation, resolved: bool = False
) -> Embed:
"""This function creates a log embed from set parameters, meant for moderation logging.
Args:
interaction (discord.Interaction): The interaction object.
ctx (commands.Context): The ctx object.
moderation (aurora.models.Moderation): The moderation object.
resolved (bool, optional): Whether the case is resolved or not. Defaults to False.
"""
@ -113,7 +112,7 @@ async def log_factory(
if resolved:
embed = Embed(
title=f"📕 Case #{moderation.id:,} Resolved",
color=await interaction.client.get_embed_color(interaction.channel),
color=await ctx.bot.get_embed_color(ctx.channel),
)
resolved_by = await moderation.get_resolved_by()
@ -145,7 +144,7 @@ async def log_factory(
else:
embed = Embed(
title=f"📕 Case #{moderation.id:,}",
color=await interaction.client.get_embed_color(interaction.channel),
color=await ctx.bot.get_embed_color(ctx.channel),
)
embed.description = f"**Type:** {str.title(moderation.type)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"

View file

@ -0,0 +1,41 @@
from typing import List, Union
import discord
from redbot.core import app_commands, commands
from ..models.moderation_types import Type
from .config import config
from .registry import type_registry
from .utils import check_moddable
async def moderate(ctx: Union[commands.Context, discord.Interaction], target: discord.Member, silent: bool | None, permissions: List[str], moderation_type: Type | str, **kwargs) -> None | Type:
"""This function is used to moderate users.
It checks if the target can be moderated, then calls the handler method of the moderation type specified.
Args:
bot (Red): The bot instance.
ctx (Union[commands.Context, discord.Interaction]): The context of the command. If this is a `discord.Interaction` object, it will be converted to a `commands.Context` object. Additionally, if the interaction orignated from a context menu, the `ctx.author` attribute will be overriden to `interaction.user`.
target (discord.Member): The target user to moderate.
silent (bool | None): Whether to send the moderation action to the target.
permissions (List[str]): The permissions required to moderate the target.
moderation_type (Type): The moderation type (handler) to use. See `aurora.models.moderation_types` for some examples.
**kwargs: The keyword arguments to pass to the handler method.
"""
if not await check_moddable(target, ctx, permissions):
return
if silent is None:
silent = not await config.guild(ctx.guild).dm_users()
if isinstance(moderation_type, str):
moderation_type = type_registry[str.lower(moderation_type)]
if isinstance(ctx, discord.Interaction):
interaction = ctx
ctx = await commands.Context.from_interaction(interaction)
if isinstance(interaction.command, app_commands.ContextMenu):
ctx.author = interaction.user
return await moderation_type.handler(
ctx,
target,
silent,
**kwargs
)

View file

@ -0,0 +1,3 @@
from class_registry import ClassRegistry
type_registry = ClassRegistry()

View file

@ -17,7 +17,7 @@ from ..utilities.logger import logger
def check_permissions(
user: User,
permissions: Tuple[str],
ctx: Union[commands.Context, Interaction] | None = None,
ctx: commands.Context | Interaction | None = None,
guild: Guild | None = None,
) -> Union[bool, str]:
"""Checks if a user has a specific permission (or a list of permissions) in a channel."""
@ -43,12 +43,12 @@ def check_permissions(
async def check_moddable(
target: Union[User, Member, TextChannel], interaction: Interaction, permissions: Tuple[str]
target: Union[User, Member, TextChannel], ctx: commands.Context, permissions: Tuple[str]
) -> bool:
"""Checks if a moderator can moderate a target."""
is_channel = isinstance(target, TextChannel)
if check_permissions(interaction.client.user, permissions, guild=interaction.guild):
await interaction.response.send_message(
if check_permissions(ctx.bot.user, permissions, guild=ctx.guild):
await ctx.send(
error(
f"I do not have the `{permissions}` permission, required for this action."
),
@ -56,9 +56,9 @@ async def check_moddable(
)
return False
if await config.guild(interaction.guild).use_discord_permissions() is True:
if check_permissions(interaction.user, permissions, guild=interaction.guild):
await interaction.response.send_message(
if await config.guild(ctx.guild).use_discord_permissions() is True:
if check_permissions(ctx.author, permissions, guild=ctx.guild):
await ctx.send(
error(
f"You do not have the `{permissions}` permission, required for this action."
),
@ -66,21 +66,21 @@ async def check_moddable(
)
return False
if interaction.user.id == target.id:
await interaction.response.send_message(
if ctx.author.id == target.id:
await ctx.send(
content="You cannot moderate yourself!", ephemeral=True
)
return False
if not is_channel and target.bot:
await interaction.response.send_message(
await ctx.send(
content="You cannot moderate bots!", ephemeral=True
)
return False
if isinstance(target, Member):
if interaction.user.top_role <= target.top_role and await config.guild(interaction.guild).respect_hierarchy() is True:
await interaction.response.send_message(
if ctx.author.top_role <= target.top_role and await config.guild(ctx.guild).respect_hierarchy() is True:
await ctx.send(
content=error(
"You cannot moderate members with a higher role than you!"
),
@ -89,10 +89,10 @@ async def check_moddable(
return False
if (
interaction.guild.get_member(interaction.client.user.id).top_role
ctx.guild.get_member(ctx.bot.user.id).top_role
<= target.top_role
):
await interaction.response.send_message(
await ctx.send(
content=error(
"You cannot moderate members with a role higher than the bot!"
),
@ -104,7 +104,7 @@ async def check_moddable(
for role in target.roles:
if role.id in immune_roles:
await interaction.response.send_message(
await ctx.send(
content=error("You cannot moderate members with an immune role!"),
ephemeral=True,
)
@ -113,19 +113,19 @@ async def check_moddable(
return True
async def log(interaction: Interaction, moderation_id: int, resolved: bool = False) -> None:
async def log(ctx: commands.Context, moderation_id: int, resolved: bool = False) -> None:
"""This function sends a message to the guild's configured logging channel when an infraction takes place."""
from ..models.moderation import Moderation
from .factory import log_factory
logging_channel_id = await config.guild(interaction.guild).log_channel()
logging_channel_id = await config.guild(ctx.guild).log_channel()
if logging_channel_id != " ":
logging_channel = interaction.guild.get_channel(logging_channel_id)
logging_channel = ctx.guild.get_channel(logging_channel_id)
try:
moderation = await Moderation.find_by_id(interaction.client, moderation_id, interaction.guild_id)
moderation = await Moderation.find_by_id(ctx.bot, moderation_id, ctx.guild_id)
embed = await log_factory(
interaction=interaction, moderation=moderation, resolved=resolved
ctx=ctx, moderation=moderation, resolved=resolved
)
try:
await logging_channel.send(embed=embed)
@ -135,22 +135,22 @@ async def log(interaction: Interaction, moderation_id: int, resolved: bool = Fal
return
async def send_evidenceformat(interaction: Interaction, moderation_id: int) -> None:
async def send_evidenceformat(ctx: commands.Context, moderation_id: int) -> None:
"""This function sends an ephemeral message to the moderator who took the moderation action, with a pre-made codeblock for use in the mod-evidence channel."""
from ..models.moderation import Moderation
from .factory import evidenceformat_factory
send_evidence_bool = (
await config.user(interaction.user).auto_evidenceformat()
or await config.guild(interaction.guild).auto_evidenceformat()
await config.user(ctx.author).auto_evidenceformat()
or await config.guild(guild=ctx.guild).auto_evidenceformat()
or False
)
if send_evidence_bool is False:
return
moderation = await Moderation.find_by_id(interaction.client, moderation_id, interaction.guild.id)
moderation = await Moderation.find_by_id(ctx.bot, moderation_id, ctx.guild.id)
content = await evidenceformat_factory(moderation=moderation)
await interaction.followup.send(content=content, ephemeral=True)
await ctx.send(content=content, ephemeral=True)
def get_bool_emoji(value: Optional[bool]) -> str: