1
Fork 0
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:
kzndotsh 2024-08-15 02:51:00 +00:00
parent 768df2bb49
commit 03e46c0ab5
10 changed files with 135 additions and 124 deletions

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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.")

View file

@ -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)

View file

@ -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")