Exemple #1
0
    async def on_message_edit(self, before, after):
        message = after
        if message.guild is None:
            return

        if message.guild.id != self.bot.guild_id:
            return

        if message.author.bot:
            return

        for channel in self.ignored_channels:
            if message.channel.name == channel:
                return

        if before.content == after.content:
            return

        member = message.author
        channel = message.channel

        embed = discord.Embed(colour=discord.Colour.orange())
        embed.set_author(name='Message Edited', icon_url=member.avatar_url)
        embed.description = f'{member.mention} {member}'

        embed.add_field(name='Channel', value=channel.mention, inline=False)
        embed.add_field(name='Before', value=formatting.truncate(before.content, 512) if before.content != '' else 'None', inline=False)
        embed.add_field(name='After', value=formatting.truncate(after.content, 512) if after.content != '' else 'None', inline=False)

        embed.set_footer(text=f'ID: {member.id}')
        embed.timestamp = datetime.datetime.utcnow()

        channel = discord.utils.get(message.guild.channels, name=self.log_channel)
        await channel.send(embed=embed)
Exemple #2
0
    async def on_message_delete(self, message):
        if message.guild is None:
            return

        if message.guild.id != self.bot.guild_id:
            return

        if message.author.bot:
            return

        for channel in self.ignored_channels:
            if message.channel.name == channel:
                return

        member = message.author
        channel = message.channel

        embed = discord.Embed(colour=discord.Colour.red())
        embed.set_author(name='Message Deleted', icon_url=member.avatar_url)
        embed.description = f'{member.mention} {member}'

        embed.add_field(name='Channel', value=channel.mention, inline=False)
        embed.add_field(name='Message', value=formatting.truncate(message.content, 512) if message.content != '' else 'None')

        for attachment in message.attachments:
            embed.add_field(name='Attachment', value=attachment.proxy_url)

        embed.set_footer(text=f'ID: {member.id}')
        embed.timestamp = datetime.datetime.utcnow()

        channel = discord.utils.get(message.guild.channels, name=self.log_channel)
        await channel.send(embed=embed)
Exemple #3
0
    async def on_command_error(self, ctx, error):
        if isinstance(error, commands.CommandNotFound):
            return

        elif isinstance(error, commands.BadArgument):
            await ctx.send(error)

        elif isinstance(error, commands.NoPrivateMessage):
            await ctx.send(error)

        elif isinstance(error, commands.DisabledCommand):
            await ctx.send('This command is disabled and cannot be used.')

        elif isinstance(error, commands.CommandOnCooldown):
            await ctx.send(f'This command is on cooldown. Please retry in {error.retry_after:.0f}s.')

        elif isinstance(error, commands.MissingRequiredArgument):
            await ctx.send(f'Missing parameter: {error.param.name}.')

        elif isinstance(error, commands.MissingPermissions):
            await ctx.send(f'You are missing permissions: {", ".join(error.missing_perms)}.')

        elif isinstance(error, commands.BotMissingPermissions):
            await ctx.send(f'I am missing permissions: {", ".join(error.missing_perms)}.')

        elif isinstance(error, commands.CommandInvokeError):
            trace = ''.join(traceback.format_exception(type(error.original), error.original, error.original.__traceback__))

            log.error(f'Error in command {ctx.command.qualified_name}:')
            log.error(trace)

            embed = discord.Embed(title='Command Error', colour=discord.Colour.red())
            embed.add_field(name='Command', value=ctx.command.qualified_name)
            embed.add_field(name='User', value=str(ctx.author))
            embed.add_field(name='Message', value=formatting.truncate(ctx.message.content, 512) if ctx.message.content != '' else 'None')
            description = formatting.codeblock(trace, lang='py')
            embed.description = formatting.truncate(description, 2000)
            embed.timestamp = datetime.datetime.utcnow()

            owner = await self.get_user_info(self.owner_id)
            await owner.send(embed=embed)

            try:
                await ctx.author.send(f'{ctx.author.mention}, that command just broke me 😮. An error report has been sent to {self.get_user(self.owner_id).name} and a fix will follow shortly 👍.')
            except discord.errors.Forbidden:
                pass
Exemple #4
0
    async def log_action(self,
                         guild: discord.Guild,
                         action: str,
                         member: discord.Member,
                         moderator: discord.Member,
                         issued: datetime.datetime,
                         *,
                         duration: time.ShortTime = None,
                         reason: str = None):
        query = 'INSERT INTO mod_cases (action, user_id, mod_id, issued, duration, reason) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id;'

        duration = duration.delta if duration is not None else duration

        record = await self.bot.pool.fetchrow(query, action, member.id,
                                              moderator.id, issued, duration,
                                              reason)

        embed = discord.Embed()

        if action == 'warn':
            embed.colour = discord.Colour.gold()
        elif action == 'mute' or action == 'tempmute':
            embed.colour = discord.Colour.orange()
        elif action == 'kick':
            embed.colour = discord.Colour.dark_orange()
        elif action == 'ban' or action == 'tempban':
            embed.colour = discord.Colour.red()
        elif action == 'unmute' or action == 'unban':
            embed.colour = discord.Colour.blue()

        embed.set_author(name=action.capitalize(), icon_url=member.avatar_url)
        embed.description = f'{member.mention} {member}'

        embed.add_field(name='Moderator',
                        value=f'{moderator.mention} {moderator}',
                        inline=False)

        if duration is not None:
            embed.add_field(name='Duration',
                            value=time.human_timedelta(duration),
                            inline=False)

        embed.add_field(name='Reason',
                        value=formatting.truncate(reason, 512)
                        if reason is not None else 'None')

        embed.set_footer(text=f'Case #{record["id"]} • ID: {member.id}')
        embed.timestamp = issued

        channel = discord.utils.get(guild.channels, name=self.log_channel)
        await channel.send(embed=embed)
Exemple #5
0
    async def on_error(self, event, *args, **kwargs):
        await self.wait_until_ready()
        if self.owner_id is None:
            app = await self.application_info()
            self.owner_id = app.owner.id

        embed = discord.Embed(title='Error', colour=discord.Colour.red())
        embed.add_field(name='Event', value=event)
        description = traceback.format_exc()
        description = formatting.truncate(description, 2000)
        embed.description = formatting.codeblock(description, lang='py')
        embed.timestamp = datetime.datetime.utcnow()

        owner = await self.get_user_info(self.owner_id)
        await owner.send(embed=embed)
Exemple #6
0
    async def send_async_error(self, loop, context):
        await self.wait_until_ready()
        if self.owner_id is None:
            app = await self.application_info()
            self.owner_id = app.owner.id

        embed = discord.Embed(title='Async Error', colour=discord.Colour.red())
        if 'message' in context:
            embed.add_field(name='Message', value=context['message'])
        error = context['exception']
        description = ''.join(traceback.format_exception(type(error), error, error.__traceback__))
        description = formatting.truncate(description, 2000)
        embed.description = formatting.codeblock(description, lang='py')
        embed.timestamp = datetime.datetime.utcnow()

        owner = await self.get_user_info(self.owner_id)
        await owner.send(embed=embed)
Exemple #7
0
class Moderation:
    """Server moderation commands."""
    def __init__(self, bot):
        self.bot = bot
        self.log_channel = 'mod-logs'
        self.mute_role = 'Muted'
        self.protected_roles = ['Queen', 'Inquiline', 'Alate']

    async def on_member_join(self, member):
        if member.guild is None:
            return

        if member.guild.id != self.bot.guild_id:
            return

        query = 'SELECT * FROM mod_tempactions WHERE user_id = $1;'
        record = await self.bot.pool.fetchrow(query, member.id)

        if record is not None:
            role = discord.utils.get(member.guild.roles, name=self.mute_role)
            await member.add_roles(role, reason='Tempmute Reapplication')

    async def log_action(self,
                         guild: discord.Guild,
                         action: str,
                         member: discord.Member,
                         moderator: discord.Member,
                         issued: datetime.datetime,
                         *,
                         duration: time.ShortTime = None,
                         reason: str = None):
        query = 'INSERT INTO mod_cases (action, user_id, mod_id, issued, duration, reason) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id;'

        duration = duration.delta if duration is not None else duration

        record = await self.bot.pool.fetchrow(query, action, member.id,
                                              moderator.id, issued, duration,
                                              reason)

        embed = discord.Embed()

        if action == 'warn':
            embed.colour = discord.Colour.gold()
        elif action == 'mute' or action == 'tempmute':
            embed.colour = discord.Colour.orange()
        elif action == 'kick':
            embed.colour = discord.Colour.dark_orange()
        elif action == 'ban' or action == 'tempban':
            embed.colour = discord.Colour.red()
        elif action == 'unmute' or action == 'unban':
            embed.colour = discord.Colour.blue()

        embed.set_author(name=action.capitalize(), icon_url=member.avatar_url)
        embed.description = f'{member.mention} {member}'

        embed.add_field(name='Moderator',
                        value=f'{moderator.mention} {moderator}',
                        inline=False)

        if duration is not None:
            embed.add_field(name='Duration',
                            value=time.human_timedelta(duration),
                            inline=False)

        embed.add_field(name='Reason',
                        value=formatting.truncate(reason, 512)
                        if reason is not None else 'None')

        embed.set_footer(text=f'Case #{record["id"]} • ID: {member.id}')
        embed.timestamp = issued

        channel = discord.utils.get(guild.channels, name=self.log_channel)
        await channel.send(embed=embed)

    async def temp_action(self, action: str, member: discord.Member,
                          duration: time.ShortTime):
        timers = self.bot.get_cog('Timers')
        if timers is None:
            return

        query = 'SELECT * FROM mod_tempactions WHERE user_id = $1 AND action = $2'
        record = await self.bot.pool.fetchrow(query, member.id, action)

        if record is None:
            timer = await timers.create_timer(action, duration.datetime)

            query = 'INSERT INTO mod_tempactions (user_id, action, timer_id) VALUES ($1, $2, $3);'
            await self.bot.pool.execute(query, member.id, action, timer.id)
        else:
            await timers.update_timer(record['timer_id'], duration.datetime)

    async def on_tempmute_timer_complete(self, timer):
        query = 'SELECT * FROM mod_tempactions WHERE timer_id = ($1);'
        record = await self.bot.pool.fetchrow(query, timer.id)

        timers = self.bot.get_cog('Timers')
        if timers is None:
            return
        await timers.remove_timer(timer.id)

        if record is not None:
            guild = self.bot.get_guild(self.bot.guild_id)
            if guild is None:
                return

            member = guild.get_member(record['user_id'])
            if member is None:
                return

            reason = 'Tempmute Expiration'
            await self.log_action(guild,
                                  'unmute',
                                  member,
                                  self.bot.user,
                                  datetime.datetime.utcnow(),
                                  reason=reason)

            try:
                await member.send(
                    f'You have been unmuted in **{guild.name}**.\n**Reason:** {reason}'
                )
            except discord.errors.Forbidden:
                pass

            role = discord.utils.get(guild.roles, name=self.mute_role)
            await member.remove_roles(role, reason=reason)

    async def on_tempban_timer_complete(self, timer):
        query = 'SELECT * FROM mod_tempactions WHERE timer_id = ($1);'
        record = await self.bot.pool.fetchrow(query, timer.id)

        timers = self.bot.get_cog('Timers')
        if timers is None:
            return
        await timers.remove_timer(timer.id)

        if record is not None:
            guild = self.bot.get_guild(self.bot.guild_id)
            if guild is None:
                return

            user = await self.bot.get_user_info(record['user_id'])
            if user is None:
                return

            reason = 'Tempban Expiration'
            await self.log_action(guild,
                                  'unban',
                                  user,
                                  self.bot.user,
                                  datetime.datetime.utcnow(),
                                  reason=reason)

            await guild.unban(user, reason=reason)

    @commands.command()
    @commands.has_any_role('Queen', 'Inquiline', 'Alate')
    @commands.guild_only()
    async def warn(self, ctx, member: discord.Member, *, reason: str = None):
        """Warn a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        if checks.has_any_role(member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        await self.log_action(ctx.guild,
                              'warn',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              reason=reason)

        try:
            await member.send(
                f'You have been warned in **{ctx.guild.name}**.\n**Reason:** {reason}'
            )
        except discord.errors.Forbidden:
            pass

        await ctx.send(
            f'{member.mention} has been warned.\n**Reason:** {reason}')

    @commands.command()
    @commands.bot_has_permissions(manage_roles=True)
    @commands.has_any_role('Queen', 'Inquiline', 'Alate')
    @commands.guild_only()
    async def tempmute(self,
                       ctx,
                       member: discord.Member,
                       duration: time.ShortTime,
                       *,
                       reason: str = None):
        """Temporarily mute a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        if checks.has_any_role(member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        await self.temp_action('tempmute', member, duration)

        await self.log_action(ctx.guild,
                              'tempmute',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              duration=duration,
                              reason=reason)

        try:
            await member.send(
                f'You have been temporarily muted in **{ctx.guild.name}** for {time.human_timedelta(duration.delta)}.\n**Reason:** {reason}'
            )
        except discord.errors.Forbidden:
            pass

        role = discord.utils.get(ctx.guild.roles, name=self.mute_role)
        await member.add_roles(role, reason=reason)

        await ctx.send(
            f'{member.mention} has been temporarily muted for {time.human_timedelta(duration.delta)}.\n**Reason:** {reason}'
        )

    @commands.command()
    @commands.bot_has_permissions(manage_roles=True)
    @commands.has_any_role('Queen', 'Inquiline', 'Alate')
    @commands.guild_only()
    async def mute(self, ctx, member: discord.Member, *, reason: str = None):
        """Mute a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        if checks.has_any_role(member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        query = 'SELECT timer_id FROM mod_tempactions WHERE user_id = $1 AND action = $2;'
        record = await self.bot.pool.fetchrow(query, member.id, 'tempmute')

        if record is not None:
            timer_id = record['timer_id']

            timers = self.bot.get_cog('Timers')
            if timers is None:
                return await ctx.send('Timers module is not loaded.')
            else:
                await timers.remove_timer(timer_id)

        await self.log_action(ctx.guild,
                              'mute',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              reason=reason)

        try:
            await member.send(
                f'You have been muted in **{ctx.guild.name}**.\n**Reason:** {reason}'
            )
        except discord.errors.Forbidden:
            pass

        role = discord.utils.get(ctx.guild.roles, name=self.mute_role)
        await member.add_roles(role, reason=reason)

        await ctx.send(
            f'{member.mention} has been muted.\n**Reason:** {reason}')

    @commands.command()
    @commands.bot_has_permissions(manage_roles=True)
    @commands.has_any_role('Queen', 'Inquiline', 'Alate')
    @commands.guild_only()
    async def unmute(self, ctx, member: discord.Member, *, reason: str = None):
        """Unmute a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        if checks.has_any_role(member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        query = 'SELECT timer_id FROM mod_tempactions WHERE user_id = $1 AND action = $2;'
        record = await self.bot.pool.fetchrow(query, member.id, 'tempmute')

        if record is not None:
            timer_id = record['timer_id']

            timers = self.bot.get_cog('Timers')
            if timers is None:
                return await ctx.send('Timers module is not loaded.')
            else:
                await timers.remove_timer(timer_id)

        await self.log_action(ctx.guild,
                              'unmute',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              reason=reason)

        try:
            await member.send(
                f'You have been unmuted in **{ctx.guild.name}**.\n**Reason:** {reason}'
            )
        except discord.errors.Forbidden:
            pass

        role = discord.utils.get(ctx.guild.roles, name=self.mute_role)
        await member.remove_roles(role, reason=reason)

        await ctx.send(
            f'{member.mention} has been unmuted.\n**Reason:** {reason}')

    @commands.command(description='Kick a user')
    @commands.bot_has_permissions(kick_members=True)
    @commands.has_any_role('Queen', 'Inquiline')
    @commands.guild_only()
    async def kick(self, ctx, member: discord.Member, *, reason: str = None):
        """Kick a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        if checks.has_any_role(member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        await self.log_action(ctx.guild,
                              'kick',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              reason=reason)

        try:
            await member.send(
                f'You have been kicked from **{ctx.guild.name}**.\n**Reason:** {reason}'
            )
        except discord.errors.Forbidden:
            pass

        await ctx.send(
            f'{member.mention} has been kicked.\n**Reason:** {reason}')

        await member.kick(reason=reason)

    @commands.command(description='Temporarily ban a user')
    @commands.bot_has_permissions(kick_members=True)
    @commands.has_any_role('Queen', 'Inquiline')
    @commands.guild_only()
    async def tempban(self,
                      ctx,
                      member: converters.UserConverter,
                      duration: time.ShortTime,
                      *,
                      reason: str = None):
        """Temporarily ban a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        guild_member = ctx.guild.get_member(member.id)

        if guild_member is not None and checks.has_any_role(
                guild_member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        await self.temp_action('tempban', member, duration)

        await self.log_action(ctx.guild,
                              'tempban',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              duration=duration,
                              reason=reason)

        if guild_member is not None:
            try:
                await member.send(
                    f'You have been temporarily banned from **{ctx.guild.name}** for {time.human_timedelta(duration.delta)}.\n**Reason:** {reason}'
                )
            except discord.errors.Forbidden:
                pass

        await ctx.send(
            f'{member.mention} has been temporarily banned for {time.human_timedelta(duration.delta)}.\n**Reason:** {reason}'
        )

        await ctx.guild.ban(member, reason=reason, delete_message_days=0)

    @commands.command(description='Ban a user')
    @commands.bot_has_permissions(ban_members=True)
    @commands.has_any_role('Queen', 'Inquiline')
    @commands.guild_only()
    async def ban(self,
                  ctx,
                  member: converters.UserConverter,
                  *,
                  reason: str = None):
        """Ban a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        guild_member = ctx.guild.get_member(member.id)

        if guild_member is not None and checks.has_any_role(
                guild_member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        query = 'SELECT timer_id FROM mod_tempactions WHERE user_id = $1 AND action = $2;'
        record = await self.bot.pool.fetchrow(query, member.id, 'tempban')

        if record is not None:
            timer_id = record['timer_id']

            timers = self.bot.get_cog('Timers')
            if timers is None:
                return await ctx.send('Timers module is not loaded.')
            else:
                await timers.remove_timer(timer_id)

        await self.log_action(ctx.guild,
                              'ban',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              reason=reason)

        if guild_member is not None:
            try:
                await member.send(
                    f'You have been banned from **{ctx.guild.name}**.\n**Reason:** {reason}'
                )
            except discord.errors.Forbidden:
                pass

        await ctx.send(
            f'{member.mention} has been banned.\n**Reason:** {reason}')

        await ctx.guild.ban(member, reason=reason, delete_message_days=0)

    @commands.command(description='Unban a user')
    @commands.bot_has_permissions(ban_members=True)
    @commands.has_any_role('Queen', 'Inquiline')
    @commands.guild_only()
    async def unban(self,
                    ctx,
                    member: converters.UserConverter,
                    *,
                    reason: str = None):
        """Unban a member."""

        try:
            await ctx.message.delete()
        except discord.errors.NotFound:
            pass

        guild_member = ctx.guild.get_member(member.id)

        if guild_member is not None and checks.has_any_role(
                guild_member, *self.protected_roles):
            return await ctx.send(f'That member has a protected role.')

        if await self.bot.is_owner(member):
            return

        if await ctx.guild.get_ban(member) is None:
            return await ctx.send(f'That member is not banned.')

        query = "SELECT timer_id FROM mod_tempactions WHERE user_id = $1 AND action = $2;"
        record = await self.bot.pool.fetchrow(query, member.id, 'tempban')

        if record is not None:
            timer_id = record['timer_id']

            timers = self.bot.get_cog('Timers')
            if timers is None:
                return await ctx.send('Timers module is not loaded.')
            else:
                await timers.remove_timer(timer_id)

        await self.log_action(ctx.guild,
                              'unban',
                              member,
                              ctx.author,
                              datetime.datetime.utcnow(),
                              reason=reason)

        await ctx.guild.unban(member, reason=reason)

    @commands.group(name='case',
                    description='View a mod case',
                    invoke_without_command=True)
    @commands.bot_has_permissions(embed_links=True)
    @commands.has_any_role('Queen', 'Inquiline', 'Alate')
    @commands.guild_only()
    async def case(self, ctx, case: int):
        """View a mod case."""

        query = 'SELECT * FROM mod_cases WHERE id = $1;'
        record = await self.bot.pool.fetchrow(query, case)

        if record is None:
            return await ctx.send(f'Case #{case} not found.')

        action = record['action']
        user_id = record['user_id']
        mod_id = record['mod_id']
        issued = record['issued']
        duration = record['duration']
        reason = record['reason']

        member = ctx.guild.get_member(user_id) or await self.bot.get_user_info(
            user_id)

        moderator = ctx.guild.get_member(mod_id)

        embed = discord.Embed()

        if action == 'warn':
            embed.colour = discord.Colour.gold()
        elif action == 'mute' or action == 'tempmute':
            embed.colour = discord.Colour.orange()
        elif action == 'kick':
            embed.colour = discord.Colour.dark_orange()
        elif action == 'ban' or action == 'tempban':
            embed.colour = discord.Colour.red()
        elif action == 'unmute' or action == 'unban':
            embed.colour = discord.Colour.blue()

        embed.set_author(name=f'{action.capitalize()}',
                         icon_url=member.avatar_url)
        embed.description = f'{member.mention} {member}'

        embed.add_field(name='Moderator',
                        value=f'{moderator.mention} {moderator}',
                        inline=False)

        if duration is not None:
            embed.add_field(name='Duration',
                            value=time.human_timedelta(duration),
                            inline=False)

        embed.add_field(name='Reason',
                        value=formatting.truncate(reason, 512)
                        if reason is not None else 'None')

        embed.set_footer(text=f'Case #{record["id"]} • ID: {member.id}')
        embed.timestamp = issued

        await ctx.send(embed=embed)
Exemple #8
0
        embed.set_author(name=f'{action.capitalize()} • Update',
                         icon_url=member.avatar_url)
        embed.description = f'{member.mention} {member}'

        embed.add_field(name='Moderator',
                        value=f'{moderator.mention} {moderator}',
                        inline=False)

        if duration is not None:
            embed.add_field(name='Duration',
                            value=time.human_timedelta(duration),
                            inline=False)

        embed.add_field(name='Reason',
                        value=formatting.truncate(reason, 512)
                        if reason is not None else 'None')

        embed.set_footer(text=f'Case #{record["id"]} • ID: {member.id}')
        embed.timestamp = issued

        channel = discord.utils.get(ctx.guild.channels, name=self.log_channel)
        await channel.send(embed=embed)

    @case.command(name='pardon', description='Pardon a mod case')
    # @commands.bot_has_permissions(ban_members=True, manage_roles=True, embed_links=True)
    @commands.has_any_role('Queen', 'Inquiline')
    @commands.guild_only()
    async def case_pardon(self, ctx, case: int):
        """Pardon a mod case."""