mirror of
https://github.com/allthingslinux/tux.git
synced 2024-10-02 16:43:12 +00:00
refactor(moderation): centralize condition checks for moderation actions
docs(moderation): add docstrings to moderation methods for better understanding fix(moderation): remove redundant condition checks in individual moderation commands feat(moderation): add check_conditions method to handle common condition checks refactor(moderation): remove redundant permission checks in unban, unjail, untimeout, warn commands feat(moderation): implement check_conditions method to centralize permission checks for moderation commands
This commit is contained in:
parent
768df2bb49
commit
03e46c0ab5
10 changed files with 135 additions and 124 deletions
|
@ -7,8 +7,6 @@ from loguru import logger
|
|||
from tux.database.controllers import DatabaseController
|
||||
from tux.utils.embeds import create_embed_footer
|
||||
|
||||
# TODO: Move command permission checks to the base class
|
||||
|
||||
|
||||
class ModerationCogBase(commands.Cog):
|
||||
def __init__(self, bot: commands.Bot) -> None:
|
||||
|
@ -25,12 +23,39 @@ class ModerationCogBase(commands.Cog):
|
|||
icon_url: str,
|
||||
timestamp: datetime | None = None,
|
||||
) -> discord.Embed:
|
||||
"""
|
||||
Create an embed for moderation actions.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : commands.Context[commands.Bot]
|
||||
The context of the command.
|
||||
title : str
|
||||
The title of the embed.
|
||||
fields : list[tuple[str, str, bool]]
|
||||
The fields to add to the embed.
|
||||
color : int
|
||||
The color of the embed.
|
||||
icon_url : str
|
||||
The icon URL for the embed.
|
||||
timestamp : datetime | None, optional
|
||||
The timestamp for the embed, by default None (ctx.message.created_at).
|
||||
|
||||
Returns
|
||||
-------
|
||||
discord.Embed
|
||||
The embed for the moderation action.
|
||||
"""
|
||||
|
||||
embed = discord.Embed(color=color, timestamp=timestamp or ctx.message.created_at)
|
||||
embed.set_author(name=title, icon_url=icon_url)
|
||||
|
||||
footer_text, footer_icon_url = create_embed_footer(ctx)
|
||||
embed.set_footer(text=footer_text, icon_url=footer_icon_url)
|
||||
|
||||
for name, value, inline in fields:
|
||||
embed.add_field(name=name, value=value, inline=inline)
|
||||
|
||||
return embed
|
||||
|
||||
async def send_embed(
|
||||
|
@ -39,6 +64,19 @@ class ModerationCogBase(commands.Cog):
|
|||
embed: discord.Embed,
|
||||
log_type: str,
|
||||
) -> None:
|
||||
"""
|
||||
Send an embed to the log channel.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : commands.Context[commands.Bot]
|
||||
The context of the command.
|
||||
embed : discord.Embed
|
||||
The embed to send.
|
||||
log_type : str
|
||||
The type of log to send the embed to.
|
||||
"""
|
||||
|
||||
if ctx.guild:
|
||||
log_channel_id = await self.config.get_log_channel(ctx.guild.id, log_type)
|
||||
if log_channel_id:
|
||||
|
@ -54,11 +92,74 @@ class ModerationCogBase(commands.Cog):
|
|||
reason: str,
|
||||
action: str,
|
||||
) -> None:
|
||||
"""
|
||||
Send a DM to the target user.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : commands.Context[commands.Bot]
|
||||
The context of the command.
|
||||
silent : bool
|
||||
Whether the command is silent.
|
||||
target : discord.Member
|
||||
The target of the moderation action.
|
||||
reason : str
|
||||
The reason for the moderation action.
|
||||
action : str
|
||||
The action being performed.
|
||||
"""
|
||||
|
||||
if not silent:
|
||||
try:
|
||||
await target.send(
|
||||
f"You have been {action} from {ctx.guild} for the following reason:\n> {reason}",
|
||||
)
|
||||
|
||||
except (discord.Forbidden, discord.HTTPException) as e:
|
||||
logger.warning(f"Failed to send DM to {target}. {e}")
|
||||
await ctx.send(f"Failed to send DM to {target}. {e}", delete_after=30, ephemeral=True)
|
||||
|
||||
async def check_conditions(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
moderator: discord.Member | discord.User,
|
||||
action: str,
|
||||
) -> bool:
|
||||
"""
|
||||
Check if the conditions for the moderation action are met.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
ctx : commands.Context[commands.Bot]
|
||||
The context of the command.
|
||||
target : discord.Member
|
||||
The target of the moderation action.
|
||||
moderator : discord.Member | discord.User
|
||||
The moderator of the moderation action.
|
||||
action : str
|
||||
The action being performed.
|
||||
|
||||
Returns
|
||||
-------
|
||||
bool
|
||||
Whether the conditions are met.
|
||||
"""
|
||||
|
||||
if ctx.guild is None:
|
||||
logger.warning(f"{action.capitalize()} command used outside of a guild context.")
|
||||
return False
|
||||
|
||||
if target == ctx.author:
|
||||
await ctx.send(f"You cannot {action} yourself.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send(f"You cannot {action} a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send(f"You cannot {action} the server owner.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -15,30 +15,6 @@ class Ban(ModerationCogBase):
|
|||
def __init__(self, bot: commands.Bot) -> None:
|
||||
super().__init__(bot)
|
||||
|
||||
async def check_ban_conditions(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
moderator: discord.Member | discord.User,
|
||||
) -> bool:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Ban command used outside of a guild context.")
|
||||
return False
|
||||
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot ban yourself.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot ban a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot ban the server owner.", delete_after=30, ephemeral=True)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@commands.hybrid_command(
|
||||
name="ban",
|
||||
aliases=["b"],
|
||||
|
@ -78,7 +54,7 @@ class Ban(ModerationCogBase):
|
|||
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_ban_conditions(ctx, target, moderator):
|
||||
if not await self.check_conditions(ctx, target, moderator, "ban"):
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
|
@ -110,13 +110,14 @@ class Jail(ModerationCogBase):
|
|||
flags : JailFlags
|
||||
The flags for the command. (reason: str, silent: bool)
|
||||
"""
|
||||
moderator = ctx.author
|
||||
|
||||
if not ctx.guild:
|
||||
logger.warning("Jail command used outside of a guild context.")
|
||||
return
|
||||
|
||||
if await self._cannot_jail(ctx, target, moderator):
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_conditions(ctx, target, moderator, "jail"):
|
||||
return
|
||||
|
||||
jail_role_id = await self.config.get_jail_role(ctx.guild.id)
|
||||
|
@ -130,7 +131,6 @@ class Jail(ModerationCogBase):
|
|||
return
|
||||
|
||||
target_roles: list[discord.Role] = self._get_manageable_roles(target, jail_role)
|
||||
|
||||
if jail_role in target.roles:
|
||||
await ctx.send("The user is already jailed.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
@ -138,7 +138,9 @@ class Jail(ModerationCogBase):
|
|||
case_target_roles = [role.id for role in target_roles]
|
||||
|
||||
await self._jail_user(ctx, target, flags, jail_role, target_roles)
|
||||
|
||||
case = await self._insert_jail_case(ctx, target, flags.reason, case_target_roles)
|
||||
|
||||
await self.handle_case_response(ctx, case, "created", flags.reason, target)
|
||||
|
||||
async def _insert_jail_case(
|
||||
|
@ -166,30 +168,6 @@ class Jail(ModerationCogBase):
|
|||
await ctx.send(f"Failed to insert jail case for {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return None
|
||||
|
||||
async def _cannot_jail(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
moderator: discord.Member | discord.User,
|
||||
) -> bool:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Jail command used outside of a guild context.")
|
||||
return True
|
||||
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot jail yourself.", delete_after=30, ephemeral=True)
|
||||
return True
|
||||
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot jail a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return True
|
||||
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot jail the server owner.", delete_after=30, ephemeral=True)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _get_manageable_roles(self, target: discord.Member, jail_role: discord.Role) -> list[discord.Role]:
|
||||
return [
|
||||
role
|
||||
|
|
|
@ -49,19 +49,13 @@ class Kick(ModerationCogBase):
|
|||
If an error occurs while kicking the user.
|
||||
"""
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if ctx.guild is None:
|
||||
logger.warning("Kick command used outside of a guild context.")
|
||||
return
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot kick yourself.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot kick a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot kick the server owner.", delete_after=30, ephemeral=True)
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_conditions(ctx, target, moderator, "kick"):
|
||||
return
|
||||
|
||||
try:
|
||||
|
|
|
@ -118,11 +118,13 @@ class Purge(commands.Cog):
|
|||
if not isinstance(ctx.channel, discord.TextChannel | discord.Thread):
|
||||
await ctx.send("Invalid channel type, must be a text channel.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
||||
channel = ctx.channel
|
||||
|
||||
# Purge the specified number of messages
|
||||
try:
|
||||
await channel.purge(limit=limit + 1)
|
||||
|
||||
except Exception as error:
|
||||
logger.error(f"An error occurred while purging messages: {error}")
|
||||
await ctx.send(f"An error occurred while purging messages: {error}", delete_after=30, ephemeral=True)
|
||||
|
|
|
@ -89,21 +89,15 @@ class Timeout(ModerationCogBase):
|
|||
discord.DiscordException
|
||||
If an error occurs while timing out the user.
|
||||
"""
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if ctx.guild is None:
|
||||
logger.warning("Timeout command used outside of a guild context.")
|
||||
return
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot timeout yourself.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot timeout a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot timeout the server owner.", delete_after=30, ephemeral=True)
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_conditions(ctx, target, moderator, "timeout"):
|
||||
return
|
||||
|
||||
if target.is_timed_out():
|
||||
await ctx.send(f"{target} is already timed out.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
@ -113,6 +107,7 @@ class Timeout(ModerationCogBase):
|
|||
try:
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, f"timed out for {flags.duration}")
|
||||
await target.timeout(duration, reason=flags.reason)
|
||||
|
||||
except discord.DiscordException as e:
|
||||
await ctx.send(f"Failed to timeout {target}. {e}", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
|
|
@ -48,7 +48,6 @@ class Unban(ModerationCogBase):
|
|||
If an error occurs while unbanning the user.
|
||||
"""
|
||||
|
||||
# Check for necessary permissions
|
||||
if ctx.guild is None:
|
||||
logger.warning("Unban command used outside of a guild context.")
|
||||
return
|
||||
|
@ -63,6 +62,7 @@ class Unban(ModerationCogBase):
|
|||
|
||||
try:
|
||||
await ctx.guild.unban(user, reason=flags.reason)
|
||||
|
||||
except (discord.Forbidden, discord.HTTPException, discord.NotFound) as e:
|
||||
logger.error(f"Failed to unban {user}. {e}")
|
||||
await ctx.send(f"Failed to unban {user}. {e}", delete_after=30, ephemeral=True)
|
||||
|
|
|
@ -41,18 +41,19 @@ class Unjail(ModerationCogBase):
|
|||
flags : UnjailFlags
|
||||
The flags for the command. (reason: str, silent: bool)
|
||||
"""
|
||||
moderator = ctx.author
|
||||
|
||||
if not ctx.guild:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
return
|
||||
|
||||
if await self._cannot_unjail(ctx, target, moderator):
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_conditions(ctx, target, moderator, "unjail"):
|
||||
return
|
||||
|
||||
jail_role = await self._get_jail_role(ctx)
|
||||
if not jail_role:
|
||||
return
|
||||
|
||||
if jail_role not in target.roles:
|
||||
await ctx.send("The user is not jailed.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
|
@ -71,30 +72,6 @@ class Unjail(ModerationCogBase):
|
|||
|
||||
await self.handle_case_response(ctx, unjail_case, "created", flags.reason, target)
|
||||
|
||||
async def _cannot_unjail(
|
||||
self,
|
||||
ctx: commands.Context[commands.Bot],
|
||||
target: discord.Member,
|
||||
moderator: discord.Member | discord.User,
|
||||
) -> bool:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
return True
|
||||
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot unjail yourself.", delete_after=30, ephemeral=True)
|
||||
return True
|
||||
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot unjail a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return True
|
||||
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot unjail the server owner.", delete_after=30, ephemeral=True)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def _get_jail_role(self, ctx: commands.Context[commands.Bot]) -> discord.Role | None:
|
||||
if ctx.guild is None:
|
||||
logger.warning("Unjail command used outside of a guild context.")
|
||||
|
|
|
@ -46,21 +46,15 @@ class Untimeout(ModerationCogBase):
|
|||
discord.DiscordException
|
||||
If an error occurs while timing out the user.
|
||||
"""
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if ctx.guild is None:
|
||||
logger.warning("Timeout command used outside of a guild context.")
|
||||
return
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot untimeout yourself.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot untimeout a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot untimeout the server owner.", delete_after=30, ephemeral=True)
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_conditions(ctx, target, moderator, "untimeout"):
|
||||
return
|
||||
|
||||
if not target.is_timed_out():
|
||||
await ctx.send(f"{target} is not currently timed out.", delete_after=30, ephemeral=True)
|
||||
|
||||
|
|
|
@ -42,19 +42,13 @@ class Warn(ModerationCogBase):
|
|||
The flags for the command. (reason: str, silent: bool)
|
||||
"""
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if ctx.guild is None:
|
||||
logger.warning("Warn command used outside of a guild context.")
|
||||
return
|
||||
if target == ctx.author:
|
||||
await ctx.send("You cannot warn yourself.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if isinstance(moderator, discord.Member) and target.top_role >= moderator.top_role:
|
||||
await ctx.send("You cannot warn a user with a higher or equal role.", delete_after=30, ephemeral=True)
|
||||
return
|
||||
if target == ctx.guild.owner:
|
||||
await ctx.send("You cannot warn the server owner.", delete_after=30, ephemeral=True)
|
||||
|
||||
moderator = ctx.author
|
||||
|
||||
if not await self.check_conditions(ctx, target, moderator, "warn"):
|
||||
return
|
||||
|
||||
await self.send_dm(ctx, flags.silent, target, flags.reason, "warned")
|
||||
|
|
Loading…
Reference in a new issue