"""JARVIS MuteCog.""" import asyncio from datetime import datetime, timedelta, timezone from dateparser import parse from dateparser_data.settings import default_parsers from jarvis_core.db.models import Mute from naff import InteractionContext, Permissions from naff.client.errors import Forbidden from naff.models.discord.embed import EmbedField from naff.models.discord.modal import InputText, Modal, TextStyles from naff.models.discord.user import Member from naff.models.naff.application_commands import ( CommandTypes, OptionTypes, SlashCommandChoice, context_menu, slash_command, slash_option, ) from naff.models.naff.command import check from jarvis.branding import get_command_color from jarvis.utils import build_embed from jarvis.utils.cogs import ModcaseCog from jarvis.utils.permissions import admin_or_permissions class MuteCog(ModcaseCog): """JARVIS MuteCog.""" async def _apply_timeout( self, ctx: InteractionContext, user: Member, reason: str, until: datetime ) -> None: await user.timeout(communication_disabled_until=until, reason=reason) duration = int((until - datetime.now(tz=timezone.utc)).seconds / 60) await Mute( user=user.id, reason=reason, admin=ctx.author.id, guild=ctx.guild.id, duration=duration, active=True, ).commit() ts = int(until.timestamp()) embed = build_embed( title="User Muted", description=f"{user.mention} has been muted", fields=[ EmbedField(name="Reason", value=reason), EmbedField(name="Until", value=f" "), ], color=get_command_color("mute"), ) embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") return embed @context_menu(name="Mute User", context_type=CommandTypes.USER) @check( admin_or_permissions( Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS ) ) async def _timeout_cm(self, ctx: InteractionContext) -> None: modal = Modal( title=f"Muting {ctx.target.mention}", components=[ InputText( label="Reason?", placeholder="Spamming, harrassment, etc", style=TextStyles.SHORT, custom_id="reason", max_length=100, ), InputText( label="Duration", placeholder="1h 30m | in 5 minutes | in 4 weeks", style=TextStyles.SHORT, custom_id="until", max_length=100, ), ], ) await ctx.send_modal(modal) try: response = await self.bot.wait_for_modal(modal, author=ctx.author.id, timeout=60 * 5) reason = response.responses.get("reason") until = response.responses.get("until") except asyncio.TimeoutError: return base_settings = { "PREFER_DATES_FROM": "future", "TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True, } rt_settings = base_settings.copy() rt_settings["PARSERS"] = [ x for x in default_parsers if x not in ["absolute-time", "timestamp"] ] rt_until = parse(until, settings=rt_settings) at_settings = base_settings.copy() at_settings["PARSERS"] = [x for x in default_parsers if x != "relative-time"] at_until = parse(until, settings=at_settings) old_until = until if rt_until: until = rt_until elif at_until: until = at_until else: self.logger.debug(f"Failed to parse delay: {until}") await response.send( f"`{until}` is not a parsable date, please try again", ephemeral=True ) return if until < datetime.now(tz=timezone.utc): await response.send( f"`{old_until}` is in the past, which isn't allowed", ephemeral=True ) return try: embed = await self._apply_timeout(ctx, ctx.target, reason, until) await response.send(embeds=embed) except Forbidden: await response.send("Unable to mute this user", ephemeral=True) @slash_command(name="mute", description="Mute a user") @slash_option(name="user", description="User to mute", opt_type=OptionTypes.USER, required=True) @slash_option( name="reason", description="Reason for mute", opt_type=OptionTypes.STRING, required=True, ) @slash_option( name="time", description="Duration of mute, default 1", opt_type=OptionTypes.INTEGER, required=False, ) @slash_option( name="scale", description="Time scale, default Hour(s)", opt_type=OptionTypes.INTEGER, required=False, choices=[ SlashCommandChoice(name="Minute(s)", value=1), SlashCommandChoice(name="Hour(s)", value=60), SlashCommandChoice(name="Day(s)", value=1440), SlashCommandChoice(name="Week(s)", value=10080), ], ) @check( admin_or_permissions( Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS ) ) async def _timeout( self, ctx: InteractionContext, user: Member, reason: str, time: int = 1, scale: int = 60 ) -> None: if user == ctx.author: await ctx.send("You cannot mute yourself.", ephemeral=True) return if user == self.bot.user: await ctx.send("I'm afraid I can't let you do that", ephemeral=True) return if len(reason) > 100: await ctx.send("Reason must be < 100 characters", ephemeral=True) return if not await ctx.guild.fetch_member(user.id): await ctx.send("User must be in guild", ephemeral=True) return # Max 4 weeks (2419200 seconds) per API duration = time * scale if duration > 40320: await ctx.send("Mute must be less than 4 weeks (40,320 minutes)", ephemeral=True) return until = datetime.now(tz=timezone.utc) + timedelta(minutes=duration) try: embed = await self._apply_timeout(ctx, user, reason, until) await ctx.send(embeds=embed) except Forbidden: await ctx.send("Unable to mute this user", ephemeral=True) @slash_command(name="unmute", description="Unmute a user") @slash_option( name="user", description="User to unmute", opt_type=OptionTypes.USER, required=True ) @check( admin_or_permissions( Permissions.MUTE_MEMBERS, Permissions.BAN_MEMBERS, Permissions.KICK_MEMBERS ) ) async def _unmute(self, ctx: InteractionContext, user: Member) -> None: if ( not user.communication_disabled_until or user.communication_disabled_until.timestamp() < datetime.now(tz=timezone.utc).timestamp() # noqa: W503 ): await ctx.send("User is not muted", ephemeral=True) return if not await ctx.guild.fetch_member(user.id): await ctx.send("User must be in guild", ephemeral=True) return await user.timeout(communication_disabled_until=datetime.now(tz=timezone.utc)) embed = build_embed( title="User Unmuted", description=f"{user.mention} has been unmuted", fields=[], color=get_command_color("unmute"), ) embed.set_author(name=user.display_name, icon_url=user.display_avatar.url) embed.set_thumbnail(url=user.display_avatar.url) embed.set_footer(text=f"{user.username}#{user.discriminator} | {user.id}") await ctx.send(embeds=embed)