From 8e3d8cdc660f964c55330ef0ae73f315faa56ddb Mon Sep 17 00:00:00 2001 From: kzndotsh Date: Sun, 25 Aug 2024 08:53:50 +0000 Subject: [PATCH] feat(emojis): add new snippetban and snippetunban emojis feat(setup.py): add new Setup class for server setup refactor(moderation): move handle_case_response method to ModerationCogBase class for code reusability fix(moderation): replace async create_embed method with synchronous one feat(moderation): add support for case handling in ban command feat(cases.py): update snippetban and snippetunban emoji ids refactor(cases.py): replace async create_embed method with synchronous one refactor(jail.py, kick.py): remove redundant code and simplify jail and kick commands feat(jail.py, kick.py): improve error handling and logging for jail and kick commands style(jail.py, kick.py): improve code readability and maintainability by removing unnecessary imports and functions refactor(snippetban.py, snippetunban.py, timeout.py): simplify case handling logic and error handling feat(snippetban.py, snippetunban.py): add reason parameter to usage and make error messages ephemeral fix(timeout.py): move send_dm call after successful timeout to ensure user is notified only when action is successful refactor(unban.py, unjail.py): simplify unban and unjail commands by removing redundant code feat(unjail.py): add error handling for missing jail role or jail channel fix(unjail.py): ensure atomicity when removing jail role and adding previous roles to prevent partial role assignment refactor(untimeout.py, warn.py): remove redundant code and simplify case response handling fix(untimeout.py, warn.py): move send_dm call to after timeout/warn action to ensure it's sent after action is successful --- assets/emojis/snippetban.png | Bin 0 -> 10297 bytes assets/emojis/snippetunban.png | Bin 0 -> 8172 bytes tux/cogs/guild/setup.py | 85 ++++++++++++ tux/cogs/moderation/__init__.py | 45 ++++++- tux/cogs/moderation/ban.py | 46 +------ tux/cogs/moderation/cases.py | 6 +- tux/cogs/moderation/jail.py | 202 ++++++---------------------- tux/cogs/moderation/kick.py | 45 +------ tux/cogs/moderation/snippetban.py | 69 +++------- tux/cogs/moderation/snippetunban.py | 70 +++------- tux/cogs/moderation/timeout.py | 48 +------ tux/cogs/moderation/unban.py | 47 +------ tux/cogs/moderation/unjail.py | 152 ++++----------------- tux/cogs/moderation/untimeout.py | 48 +------ tux/cogs/moderation/warn.py | 45 +------ 15 files changed, 242 insertions(+), 666 deletions(-) create mode 100644 assets/emojis/snippetban.png create mode 100644 assets/emojis/snippetunban.png create mode 100644 tux/cogs/guild/setup.py diff --git a/assets/emojis/snippetban.png b/assets/emojis/snippetban.png new file mode 100644 index 0000000000000000000000000000000000000000..39f0b127652f12753b806832e88861a2df9f475b GIT binary patch literal 10297 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 None: + self.bot = bot + self.db = DatabaseController() + self.config = DatabaseController().guild_config + + setup = app_commands.Group(name="setup", description="Set up Tux for your server.") + + @setup.command(name="jail") + @commands.guild_only() + @checks.ac_has_pl(7) + async def setup_jail(self, interaction: discord.Interaction) -> None: + """ + Set up the jail role channel permissions for the server. + + Parameters + ---------- + interaction : discord.Interaction + The discord interaction object. + """ + + if interaction.guild is None: + return + + jail_role_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_role_id") + if not jail_role_id: + await interaction.response.send_message("No jail role has been set up for this server.", ephemeral=True) + return + + jail_role = interaction.guild.get_role(jail_role_id) + if not jail_role: + await interaction.response.send_message("The jail role has been deleted.", ephemeral=True) + return + + jail_channel_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_channel_id") + if not jail_channel_id: + await interaction.response.send_message("No jail channel has been set up for this server.", ephemeral=True) + return + + await interaction.response.defer(ephemeral=True) + + await self._set_permissions_for_channels(interaction, jail_role, jail_channel_id) + + await interaction.edit_original_response( + content="Permissions have been set up for the jail role.", + ) + + async def _set_permissions_for_channels( + self, + interaction: discord.Interaction, + jail_role: discord.Role, + jail_channel_id: int, + ) -> None: + if interaction.guild is None: + return + + for channel in interaction.guild.channels: + if not isinstance(channel, discord.TextChannel | discord.VoiceChannel | discord.ForumChannel): + continue + + if ( + jail_role in channel.overwrites + and channel.overwrites[jail_role].send_messages is False + and channel.overwrites[jail_role].read_messages is False + and channel.id != jail_channel_id + ): + continue + + await channel.set_permissions(jail_role, send_messages=False, read_messages=False) + if channel.id == jail_channel_id: + await channel.set_permissions(jail_role, send_messages=True, read_messages=True) + + await interaction.edit_original_response(content=f"Setting up permissions for {channel.name}.") + + +async def setup(bot: commands.Bot) -> None: + await bot.add_cog(Setup(bot)) diff --git a/tux/cogs/moderation/__init__.py b/tux/cogs/moderation/__init__.py index dff5908..e19932c 100644 --- a/tux/cogs/moderation/__init__.py +++ b/tux/cogs/moderation/__init__.py @@ -4,7 +4,9 @@ import discord from discord.ext import commands from loguru import logger +from prisma.enums import CaseType from tux.database.controllers import DatabaseController +from tux.utils.constants import Constants as CONST from tux.utils.embeds import create_embed_footer, create_error_embed @@ -14,7 +16,7 @@ class ModerationCogBase(commands.Cog): self.db = DatabaseController() self.config = DatabaseController().guild_config - async def create_embed( + def create_embed( self, ctx: commands.Context[commands.Bot], title: str, @@ -22,6 +24,7 @@ class ModerationCogBase(commands.Cog): color: int, icon_url: str, timestamp: datetime | None = None, + thumbnail_url: str | None = None, ) -> discord.Embed: """ Create an embed for moderation actions. @@ -49,6 +52,7 @@ class ModerationCogBase(commands.Cog): embed = discord.Embed(color=color, timestamp=timestamp or ctx.message.created_at) embed.set_author(name=title, icon_url=icon_url) + embed.set_thumbnail(url=thumbnail_url) footer_text, footer_icon_url = create_embed_footer(ctx) embed.set_footer(text=footer_text, icon_url=footer_icon_url) @@ -166,3 +170,42 @@ class ModerationCogBase(commands.Cog): return False return True + + async def handle_case_response( + self, + ctx: commands.Context[commands.Bot], + case_type: CaseType, + case_id: int | None, + reason: str, + target: discord.Member | discord.User, + duration: str | None = None, + ): + moderator = ctx.author + + fields = [ + ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), + ("Target", f"__{target}__\n`{target.id}`", True), + ("Reason", f"> {reason}", False), + ] + + if case_id is not None: + embed = self.create_embed( + ctx, + title=f"Case #{case_id} ({duration} {case_type})" if duration else f"Case #{case_id} ({case_type})", + fields=fields, + color=CONST.EMBED_COLORS["CASE"], + icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + ) + embed.set_thumbnail(url=target.avatar) + + else: + embed = self.create_embed( + ctx, + title=f"Case #0 ({duration} {case_type})" if duration else f"Case #0 ({case_type})", + fields=fields, + color=CONST.EMBED_COLORS["CASE"], + icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + ) + + await self.send_embed(ctx, embed, log_type="mod") + await ctx.send(embed=embed, delete_after=30, ephemeral=True) diff --git a/tux/cogs/moderation/ban.py b/tux/cogs/moderation/ban.py index 747bba9..f1d0c5e 100644 --- a/tux/cogs/moderation/ban.py +++ b/tux/cogs/moderation/ban.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import BanFlags from . import ModerationCogBase @@ -48,6 +46,7 @@ class Ban(ModerationCogBase): discord.HTTPException If an error occurs while banning the user. """ + if ctx.guild is None: logger.warning("Ban command used outside of a guild context.") return @@ -74,48 +73,7 @@ class Ban(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.BAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.BAN, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/cases.py b/tux/cogs/moderation/cases.py index a53e89a..2f513a5 100644 --- a/tux/cogs/moderation/cases.py +++ b/tux/cogs/moderation/cases.py @@ -23,8 +23,8 @@ emojis: dict[str, int] = { "timeout": 1268115809083981886, "warn": 1268115764498399264, "jail": 1268115750392954880, - "snippetban": 1275782294363312172, # Placeholder - "snippetunban": 1275782294363312172, # Placeholder + "snippetban": 1277174953950576681, + "snippetunban": 1277174953292337222, } @@ -269,7 +269,7 @@ class Cases(ModerationCogBase): fields = self._create_case_fields(moderator, target, reason) - embed = await self.create_embed( + embed = self.create_embed( ctx, title=f"Case #{case.case_number} ({case.case_type}) {action}", fields=fields, diff --git a/tux/cogs/moderation/jail.py b/tux/cogs/moderation/jail.py index 6a4222a..ee48982 100644 --- a/tux/cogs/moderation/jail.py +++ b/tux/cogs/moderation/jail.py @@ -1,12 +1,9 @@ import discord -from discord import app_commands from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import JailFlags from . import ModerationCogBase @@ -16,74 +13,6 @@ class Jail(ModerationCogBase): def __init__(self, bot: commands.Bot) -> None: super().__init__(bot) - @app_commands.command( - name="setup_jail", - ) - @commands.guild_only() - @checks.ac_has_pl(7) - async def setup_jail(self, interaction: discord.Interaction) -> None: - """ - Set up the jail role channel permissions for the server. - - Parameters - ---------- - interaction : discord.Interaction - The discord interaction object. - """ - - if interaction.guild is None: - return - - jail_role_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_role_id") - if not jail_role_id: - await interaction.response.send_message("No jail role has been set up for this server.", ephemeral=True) - return - - jail_role = interaction.guild.get_role(jail_role_id) - if not jail_role: - await interaction.response.send_message("The jail role has been deleted.", ephemeral=True) - return - - jail_channel_id = await self.config.get_guild_config_field_value(interaction.guild.id, "jail_channel_id") - if not jail_channel_id: - await interaction.response.send_message("No jail channel has been set up for this server.", ephemeral=True) - return - - await interaction.response.defer(ephemeral=True) - - await self._set_permissions_for_channels(interaction, jail_role, jail_channel_id) - - await interaction.edit_original_response( - content="Permissions have been set up for the jail role.", - ) - - async def _set_permissions_for_channels( - self, - interaction: discord.Interaction, - jail_role: discord.Role, - jail_channel_id: int, - ) -> None: - if interaction.guild is None: - return - - for channel in interaction.guild.channels: - if not isinstance(channel, discord.TextChannel | discord.VoiceChannel | discord.ForumChannel): - continue - - if ( - jail_role in channel.overwrites - and channel.overwrites[jail_role].send_messages is False - and channel.overwrites[jail_role].read_messages is False - and channel.id != jail_channel_id - ): - continue - - await channel.set_permissions(jail_role, send_messages=False, read_messages=False) - if channel.id == jail_channel_id: - await channel.set_permissions(jail_role, send_messages=True, read_messages=True) - - await interaction.edit_original_response(content=f"Setting up permissions for {channel.name}.") - @commands.hybrid_command( name="jail", aliases=["j"], @@ -91,7 +20,7 @@ class Jail(ModerationCogBase): ) @commands.guild_only() @checks.has_pl(2) - async def jail( + async def jail( # noqa: PLR0911 self, ctx: commands.Context[commands.Bot], target: discord.Member, @@ -137,38 +66,55 @@ 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( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - reason: str, - case_target_roles: list[int] | None = None, - ) -> Case | None: - if not ctx.guild: - logger.warning("Jail command used outside of a guild context.") - return None - try: - return await self.db.case.insert_case( + case = await self.db.case.insert_case( case_target_id=target.id, case_moderator_id=ctx.author.id, case_type=CaseType.JAIL, - case_reason=reason, + case_reason=flags.reason, guild_id=ctx.guild.id, case_target_roles=case_target_roles, ) - except Exception as e: - logger.error(f"Failed to insert jail case for {target}. {e}") - await ctx.send(f"Failed to insert jail case for {target}. {e}", delete_after=30, ephemeral=True) - return None - def _get_manageable_roles(self, target: discord.Member, jail_role: discord.Role) -> list[discord.Role]: + except Exception as e: + logger.error(f"Failed to jail {target}. {e}") + await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True) + return + + try: + if target_roles: + await target.remove_roles(*target_roles, reason=flags.reason, atomic=False) + await target.add_roles(jail_role, reason=flags.reason) + + except (discord.Forbidden, discord.HTTPException) as e: + logger.error(f"Failed to jail {target}. {e}") + await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True) + return + + await self.send_dm(ctx, flags.silent, target, flags.reason, "jailed") + await self.handle_case_response(ctx, CaseType.JAIL, case.case_id, flags.reason, target) + + def _get_manageable_roles( + self, + target: discord.Member, + jail_role: discord.Role, + ) -> list[discord.Role]: + """ + Get the roles that can be managed by the bot. + + Parameters + ---------- + target : discord.Member + The member to jail. + jail_role : discord.Role + The jail role. + + Returns + ------- + list[discord.Role] + The roles that can be managed by the bot. + """ + return [ role for role in target.roles @@ -182,70 +128,6 @@ class Jail(ModerationCogBase): and role.is_assignable() ] - async def _jail_user( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - flags: JailFlags, - jail_role: discord.Role, - target_roles: list[discord.Role], - ) -> None: - try: - await self.send_dm(ctx, flags.silent, target, flags.reason, "jailed") - - if target_roles: - await target.remove_roles(*target_roles, reason=flags.reason, atomic=False) - await target.add_roles(jail_role, reason=flags.reason) - - except (discord.Forbidden, discord.HTTPException) as e: - logger.error(f"Failed to jail {target}. {e}") - await ctx.send(f"Failed to jail {target}. {e}", delete_after=30, ephemeral=True) - return - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - fields = [ - ("Moderator", f"__{ctx.author}__\n`{ctx.author.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - embed = await self._create_case_embed(ctx, case, action, fields, target) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) - - async def _create_case_embed( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - fields: list[tuple[str, str, bool]], - target: discord.Member | discord.User, - ) -> discord.Embed: - title = f"Case #{case.case_number} ({case.case_type}) {action}" if case else f"Case {action} ({CaseType.JAIL})" - - embed = await self.create_embed( - ctx, - title=title, - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - embed.set_thumbnail(url=target.avatar) - return embed - async def setup(bot: commands.Bot) -> None: await bot.add_cog(Jail(bot)) diff --git a/tux/cogs/moderation/kick.py b/tux/cogs/moderation/kick.py index 697a51f..935b0b4 100644 --- a/tux/cogs/moderation/kick.py +++ b/tux/cogs/moderation/kick.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import KickFlags from . import ModerationCogBase @@ -75,48 +73,7 @@ class Kick(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} {action} ({case.case_type})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.KICK})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.KICK, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/snippetban.py b/tux/cogs/moderation/snippetban.py index 2555f4f..98f6c17 100644 --- a/tux/cogs/moderation/snippetban.py +++ b/tux/cogs/moderation/snippetban.py @@ -3,10 +3,8 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.database.controllers.case import CaseController from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import SnippetBanFlags from . import ModerationCogBase @@ -20,7 +18,7 @@ class SnippetBan(ModerationCogBase): @commands.hybrid_command( name="snippetban", aliases=["sb"], - usage="snippetban [target]", + usage="snippetban [target] [reason] ", ) @commands.guild_only() @checks.has_pl(3) @@ -49,60 +47,25 @@ class SnippetBan(ModerationCogBase): return if await self.is_snippetbanned(ctx.guild.id, target.id): - await ctx.send("User is already snippet banned.", delete_after=30) + await ctx.send("User is already snippet banned.", delete_after=30, ephemeral=True) return - case = await self.db.case.insert_case( - case_target_id=target.id, - case_moderator_id=ctx.author.id, - case_type=CaseType.SNIPPETBAN, - case_reason=flags.reason, - guild_id=ctx.guild.id, - ) - - await self.send_dm(ctx, flags.silent, target, flags.reason, "Snippet banned") - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.SNIPPETBAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + try: + case = await self.db.case.insert_case( + case_target_id=target.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.SNIPPETBAN, + case_reason=flags.reason, + guild_id=ctx.guild.id, ) - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + except Exception as e: + logger.error(f"Failed to ban {target}. {e}") + await ctx.send(f"Failed to ban {target}. {e}", delete_after=30) + return + + await self.send_dm(ctx, flags.silent, target, flags.reason, "snippet banned") + await self.handle_case_response(ctx, CaseType.SNIPPETBAN, case.case_id, flags.reason, target) async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: """ diff --git a/tux/cogs/moderation/snippetunban.py b/tux/cogs/moderation/snippetunban.py index d44ee50..eac5dd5 100644 --- a/tux/cogs/moderation/snippetunban.py +++ b/tux/cogs/moderation/snippetunban.py @@ -3,10 +3,8 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.database.controllers.case import CaseController from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import SnippetUnbanFlags from . import ModerationCogBase @@ -20,7 +18,7 @@ class SnippetUnban(ModerationCogBase): @commands.hybrid_command( name="snippetunban", aliases=["sub"], - usage="snippetunban [target]", + usage="snippetunban [target] [reason] ", ) @commands.guild_only() @checks.has_pl(3) @@ -48,62 +46,26 @@ class SnippetUnban(ModerationCogBase): logger.warning("Snippet ban command used outside of a guild context.") return - # Check if the user is already snippet banned if not await self.is_snippetbanned(ctx.guild.id, target.id): - await ctx.send("User is not snippet banned.", delete_after=30) + await ctx.send("User is not snippet banned.", delete_after=30, ephemeral=True) return - case = await self.db.case.insert_case( - case_target_id=target.id, - case_moderator_id=ctx.author.id, - case_type=CaseType.SNIPPETUNBAN, - case_reason=flags.reason, - guild_id=ctx.guild.id, - ) - - await self.send_dm(ctx, flags.silent, target, flags.reason, "Snippet unbanned") - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.SNIPPETUNBAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], + try: + case = await self.db.case.insert_case( + case_target_id=target.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.SNIPPETUNBAN, + case_reason=flags.reason, + guild_id=ctx.guild.id, ) - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + except Exception as e: + logger.error(f"Failed to snippet unban {target}. {e}") + await ctx.send(f"Failed to snippet unban {target}. {e}", delete_after=30, ephemeral=True) + return + + await self.send_dm(ctx, flags.silent, target, flags.reason, "snippet unbanned") + await self.handle_case_response(ctx, CaseType.SNIPPETUNBAN, case.case_id, flags.reason, target) async def is_snippetbanned(self, guild_id: int, user_id: int) -> bool: """ diff --git a/tux/cogs/moderation/timeout.py b/tux/cogs/moderation/timeout.py index fd4620f..c96d67f 100644 --- a/tux/cogs/moderation/timeout.py +++ b/tux/cogs/moderation/timeout.py @@ -6,9 +6,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import TimeoutFlags from . import ModerationCogBase @@ -106,7 +104,6 @@ class Timeout(ModerationCogBase): duration = parse_time_string(flags.duration) 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: @@ -122,49 +119,8 @@ class Timeout(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, flags, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - flags: TimeoutFlags, - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} {action} ({flags.duration} {case.case_type})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case #0 {action} ({flags.duration} {CaseType.TIMEOUT})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.send_dm(ctx, flags.silent, target, flags.reason, f"timed out for {flags.duration}") + await self.handle_case_response(ctx, CaseType.TIMEOUT, case.case_id, flags.reason, target, flags.duration) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/unban.py b/tux/cogs/moderation/unban.py index 0eadebb..52ccf94 100644 --- a/tux/cogs/moderation/unban.py +++ b/tux/cogs/moderation/unban.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import UnbanFlags from . import ModerationCogBase @@ -35,8 +33,6 @@ class Unban(ModerationCogBase): ---------- ctx : commands.Context[commands.Bot] The context object for the command. - target : discord.Member - The member to unban. flags : UnbanFlags The flags for the command (username_or_id: str, reason: str). @@ -76,48 +72,7 @@ class Unban(ModerationCogBase): case_reason=flags.reason, ) - await self.handle_case_response(ctx, case, "created", flags.reason, user) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.UNBAN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.UNBAN, case.case_id, flags.reason, user) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/unjail.py b/tux/cogs/moderation/unjail.py index ff4b191..5c10b5c 100644 --- a/tux/cogs/moderation/unjail.py +++ b/tux/cogs/moderation/unjail.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import UnjailFlags from . import ModerationCogBase @@ -22,7 +20,7 @@ class Unjail(ModerationCogBase): ) @commands.guild_only() @checks.has_pl(2) - async def unjail( + async def unjail( # noqa: PLR0911 self, ctx: commands.Context[commands.Bot], target: discord.Member, @@ -51,151 +49,55 @@ class Unjail(ModerationCogBase): if not await self.check_conditions(ctx, target, moderator, "unjail"): return - jail_role = await self._get_jail_role(ctx) - if not jail_role: + jail_role_id = await self.config.get_jail_role_id(ctx.guild.id) + jail_role = ctx.guild.get_role(jail_role_id) if jail_role_id else None + + jail_channel_id = await self.config.get_jail_channel_id(ctx.guild.id) + + if not all([jail_role_id, jail_role, jail_channel_id]): + error_msgs = { + not jail_role_id: "No jail role has been set up for this server.", + not jail_role: "The jail role has been deleted.", + not jail_channel_id: "No jail channel has been set up for this server.", + } + + for condition, msg in error_msgs.items(): + if condition: + await ctx.send(msg, delete_after=30, ephemeral=True) return if jail_role not in target.roles: await ctx.send("The member is not jailed.", delete_after=30, ephemeral=True) return - if not await self._check_jail_channel(ctx): - return - case = await self.db.case.get_last_jail_case_by_target_id(ctx.guild.id, target.id) if not case: await ctx.send("No jail case found for this member.", delete_after=30, ephemeral=True) return - await self._unjail_user(ctx, target, jail_role, case, flags.reason) - - unjail_case = await self._insert_unjail_case(ctx, target, flags.reason) - - await self.handle_case_response(ctx, unjail_case, "created", flags.reason, target) - - 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.") - return None - - jail_role_id = await self.config.get_jail_role_id(ctx.guild.id) - if not jail_role_id: - await ctx.send("No jail role has been set up for this server.", delete_after=30, ephemeral=True) - return None - - jail_role = ctx.guild.get_role(jail_role_id) - if not jail_role: - await ctx.send("The jail role has been deleted.", delete_after=30, ephemeral=True) - return None - - return jail_role - - async def _check_jail_channel(self, ctx: commands.Context[commands.Bot]) -> bool: - if ctx.guild is None: - logger.warning("Unjail command used outside of a guild context.") - return False - - jail_channel_id = await self.config.get_jail_channel_id(ctx.guild.id) - if not jail_channel_id: - await ctx.send("No jail channel has been set up for this server.", delete_after=30, ephemeral=True) - return False - - return True - - async def _unjail_user( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - jail_role: discord.Role, - case: Case, - reason: str, - ) -> None: - if ctx.guild is None: - logger.warning("Unjail command used outside of a guild context.") - return - try: - await target.remove_roles(jail_role, reason=reason) - previous_roles = [await commands.RoleConverter().convert(ctx, str(role)) for role in case.case_target_roles] - if previous_roles: - await target.add_roles(*previous_roles, reason=reason, atomic=False) + await target.remove_roles(jail_role, reason=flags.reason, atomic=True) + await target.add_roles(*previous_roles, reason=flags.reason, atomic=True) else: await ctx.send("No previous roles found for the member.", delete_after=30, ephemeral=True) + return except (discord.Forbidden, discord.HTTPException) as e: logger.error(f"Failed to unjail member {target}. {e}") await ctx.send(f"Failed to unjail member {target}. {e}", delete_after=30, ephemeral=True) + return - async def _insert_unjail_case( - self, - ctx: commands.Context[commands.Bot], - target: discord.Member, - reason: str, - ) -> Case | None: - if not ctx.guild: - logger.warning("Unjail command used outside of a guild context.") - return None - - try: - return await self.db.case.insert_case( - guild_id=ctx.guild.id, - case_target_id=target.id, - case_moderator_id=ctx.author.id, - case_type=CaseType.UNJAIL, - case_reason=reason, - ) - - except Exception as e: - logger.error(f"Failed to insert unjail case for {target}. {e}") - await ctx.send(f"Failed to insert unjail case for {target}. {e}", delete_after=30, ephemeral=True) - return None - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - fields = [ - ("Moderator", f"__{ctx.author}__\n`{ctx.author.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - embed = await self._create_case_embed(ctx, case, action, fields, target) - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) - - async def _create_case_embed( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - fields: list[tuple[str, str, bool]], - target: discord.Member | discord.User, - ) -> discord.Embed: - title = ( - f"Case #{case.case_number} ({case.case_type}) {action}" if case else f"Case {action} ({CaseType.UNJAIL})" + unjail_case = await self.db.case.insert_case( + guild_id=ctx.guild.id, + case_target_id=target.id, + case_moderator_id=ctx.author.id, + case_type=CaseType.UNJAIL, + case_reason=flags.reason, ) - embed = await self.create_embed( - ctx, - title=title, - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - embed.set_thumbnail(url=target.avatar) - return embed + await self.handle_case_response(ctx, CaseType.UNJAIL, unjail_case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/untimeout.py b/tux/cogs/moderation/untimeout.py index 60587cf..9972528 100644 --- a/tux/cogs/moderation/untimeout.py +++ b/tux/cogs/moderation/untimeout.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import UntimeoutFlags from . import ModerationCogBase @@ -59,7 +57,6 @@ class Untimeout(ModerationCogBase): await ctx.send(f"{target} is not currently timed out.", delete_after=30, ephemeral=True) try: - await self.send_dm(ctx, flags.silent, target, flags.reason, "untimed out") await target.timeout(None, reason=flags.reason) except discord.DiscordException as e: await ctx.send(f"Failed to untimeout {target}. {e}", delete_after=30, ephemeral=True) @@ -74,49 +71,8 @@ class Untimeout(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, flags, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - flags: UntimeoutFlags, - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} {action} ({case.case_type})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case #0 {action} ({CaseType.UNTIMEOUT})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.send_dm(ctx, flags.silent, target, flags.reason, "untimed out") + await self.handle_case_response(ctx, CaseType.UNTIMEOUT, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: diff --git a/tux/cogs/moderation/warn.py b/tux/cogs/moderation/warn.py index 975d86c..537a179 100644 --- a/tux/cogs/moderation/warn.py +++ b/tux/cogs/moderation/warn.py @@ -3,9 +3,7 @@ from discord.ext import commands from loguru import logger from prisma.enums import CaseType -from prisma.models import Case from tux.utils import checks -from tux.utils.constants import Constants as CONST from tux.utils.flags import WarnFlags from . import ModerationCogBase @@ -61,48 +59,7 @@ class Warn(ModerationCogBase): guild_id=ctx.guild.id, ) - await self.handle_case_response(ctx, case, "created", flags.reason, target) - - async def handle_case_response( - self, - ctx: commands.Context[commands.Bot], - case: Case | None, - action: str, - reason: str, - target: discord.Member | discord.User, - previous_reason: str | None = None, - ) -> None: - moderator = ctx.author - - fields = [ - ("Moderator", f"__{moderator}__\n`{moderator.id}`", True), - ("Target", f"__{target}__\n`{target.id}`", True), - ("Reason", f"> {reason}", False), - ] - - if previous_reason: - fields.append(("Previous Reason", f"> {previous_reason}", False)) - - if case is not None: - embed = await self.create_embed( - ctx, - title=f"Case #{case.case_number} ({case.case_type}) {action}", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - embed.set_thumbnail(url=target.avatar) - else: - embed = await self.create_embed( - ctx, - title=f"Case {action} ({CaseType.WARN})", - fields=fields, - color=CONST.EMBED_COLORS["CASE"], - icon_url=CONST.EMBED_ICONS["ACTIVE_CASE"], - ) - - await self.send_embed(ctx, embed, log_type="mod") - await ctx.send(embed=embed, delete_after=30, ephemeral=True) + await self.handle_case_response(ctx, CaseType.WARN, case.case_id, flags.reason, target) async def setup(bot: commands.Bot) -> None: