diff --git a/jarvis/cogs/admin/modcase.py b/jarvis/cogs/admin/modcase.py new file mode 100644 index 0000000..cc1bb05 --- /dev/null +++ b/jarvis/cogs/admin/modcase.py @@ -0,0 +1,172 @@ +"""JARVIS Moderation Case management.""" +from typing import TYPE_CHECKING, Optional + +from jarvis_core.db import q +from jarvis_core.db.models import Modlog, actions +from naff import Client, Cog, InteractionContext, Permissions +from naff.ext.paginators import Paginator +from naff.models.discord.embed import Embed, EmbedField +from naff.models.discord.user import Member +from naff.models.naff.application_commands import ( + OptionTypes, + SlashCommand, + slash_option, +) +from naff.models.naff.command import check +from rich.console import Console +from rich.table import Table + +from jarvis.utils import build_embed +from jarvis.utils.permissions import admin_or_permissions + +if TYPE_CHECKING: + from naff.models.discord.guild import Guild + +ACTIONS_LOOKUP = { + "ban": actions.Ban, + "kick": actions.Kick, + "mute": actions.Mute, + "unban": actions.Unban, + "warning": actions.Warning, +} + + +class CaseCog(Cog): + """JARVIS CaseCog.""" + + async def get_embed(self, mod_case: Modlog, guild: "Guild") -> Embed: + """ + Get Moderation case embed. + + Args: + mod_case: Moderation case + guild: Originating guild + """ + action_table = Table(title="Actions", show_header=False) + action_table.add_column(header="Type", justify="left", style="orange4", no_wrap=True) + action_table.add_column( + header="Admin", justify="left", style="light_sky_blue1", no_wrap=True + ) + action_table.add_column(header="Reason", justify="left", style="white") + + note_table = Table(title="Notes") + note_table.add_column(header="Admin", justify="left", style="light_sky_blue1", no_wrap=True) + note_table.add_column(header="Content", justify="left", style="white") + + console = Console() + + action_output = "" + for action in mod_case.actions: + parent_action = await ACTIONS_LOOKUP[action.action_type].find_one(q(id=action.parent)) + if not parent_action: + action.orphaned = True + action_table.add_row(action.action_type.title(), "N/A", "N/A") + else: + admin = await self.bot.fetch_user(parent_action.admin) + admin_text = "Deleted User" + if admin: + admin_text = f"{admin.username}#{admin.discriminator}" + action_table.add_row(action.action_type.title(), admin_text, parent_action.reason) + with console.capture() as cap: + console.print(action_table) + + tmp_output = cap.get() + if len(tmp_output) >= 1000: + break + + action_output = tmp_output + + note_output = "" + for note in sorted(mod_case.notes, lambda x: x.created_at): + admin = await self.bot.fetch_user(note.admin) + admin_text = "N/A" + if not admin: + admin_text = f"{admin.username}#{admin.discriminator}" + note_table.add_row(admin_text, note.content) + + with console.capture() as cap: + console.print(note_table) + + tmp_output = cap.get() + if len(tmp_output) >= 1000: + break + + note_output = tmp_output + + status = "Open" if mod_case.open else "Closed" + + user = await self.bot.fetch_user(mod_case.user) + username = "Deleted User" + user_text = "Deleted User" + if user: + username = f"{user.username}#{user.discriminator}" + user_text = user.mention + + admin = await self.bot.fetch_user(mod_case.admin) + admin_text = "[N/A]" + if admin: + admin_text = admin.mention + + ts = int(mod_case.created_at.timestamp()) + + action_output = f"```ansi\n{action_output}\n```" + note_output = f"```ansi\n{note_output}\n```" + + fields = ( + EmbedField(name="Opened By", value=admin_text), + EmbedField(name="Opened At", value=f""), + EmbedField(name="Actions", value=action_output), + EmbedField(name="Notes", value=note_output), + ) + + embed = build_embed( + title="Moderation Case", description=f"{status} case against {user_text}", fields=fields + ) + icon_url = None + if user: + icon_url = user.avatar.url + + embed.set_author(name=username, icon_url=icon_url) + embed.set_footer(text=str(mod_case.user)) + + await mod_case.commit() + return embed + + cases = SlashCommand(name="cases", description="Manage moderation cases") + + @cases.subcommand(sub_cmd_name="list", sub_cmd_description="List moderation cases") + @slash_option( + name="user", + description="User to filter cases to", + opt_type=OptionTypes.USER, + required=False, + ) + @slash_option( + name="closed", + description="Include closed cases", + opt_type=OptionTypes.BOOLEAN, + required=False, + ) + @check(admin_or_permissions(Permissions.BAN_MEMBERS)) + async def _cases_list( + self, ctx: InteractionContext, user: Optional[Member] = None, closed: bool = False + ) -> None: + query = q(guild=ctx.guild.id) + if not closed: + query.update(q(open=True)) + if user: + query.update(q(user=user.id)) + cases = await Modlog.find(query).sort("created_at", -1).to_list(None) + + if len(cases) == 0: + await ctx.send("No cases to view", ephemeral=True) + return + + pages = [await self.get_embed(c, ctx.guild) for c in cases] + paginator = Paginator.create_from_embeds(self.bot, *pages, timeout=300) + await paginator.send(ctx) + + +def setup(bot: Client) -> None: + """Add CaseCog to JARVIS""" + CaseCog(bot)