from datetime import timedelta from math import ceil from time import time from typing import Tuple from discord import AllowedMentions, File, Interaction, Member, Object, Role, TextChannel, User from discord.abc import Messageable from discord.errors import Forbidden, HTTPException, NotFound from redbot.core import app_commands, commands from redbot.core.bot import Red from redbot.core.commands.converter import parse_relativedelta, parse_timedelta from redbot.core.utils.chat_formatting import bold, error, humanize_timedelta, inline from ..utilities.config import config from ..utilities.factory import message_factory, resolve_factory from ..utilities.logger import logger from ..utilities.utils import get_footer_image, log, send_evidenceformat, timedelta_from_relativedelta from .moderation import Moderation from .type import Type, type_registry def get_icon(bot: Red) -> File: cog = bot.get_cog("Aurora") if cog: return get_footer_image(cog) raise ValueError("Aurora cog not found. How was this managed?") class Note(Type): key="note" string="note" verb="noted" embed_desc="received a " @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str) -> 'Note': response = await ctx.send( content=f"{target.mention} has {cls.embed_desc}{cls.string}!\n**Reason** - `{reason}`" ) if silent is False: try: embed = await message_factory( bot=ctx.bot, color=await ctx.embed_color, guild=ctx.guild, moderator=ctx.author, reason=reason, moderation_type=cls(), response=response, ) await target.send(embed=embed, file=get_icon(ctx.bot)) except HTTPException: pass moderation = await Moderation.log( bot=ctx.bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type="user", target_id=target.id, role_id=None, duration=None, reason=reason, ) await response.edit( content=f"{target.mention} has {cls.embed_desc}{cls.string}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls() @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: if await config.guild(moderation.guild).dm_users() is True: try: target = await moderation.bot.fetch_user(moderation.target_id) embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except (Forbidden, HTTPException, NotFound): pass return True, "" class Warn(Type): key="warn" string="warn" verb="warned" @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str) -> 'Warn': response = await ctx.send( content=f"{target.mention} has {cls.embed_desc}{cls.verb}!\n**Reason** - `{reason}`" ) if silent is False: try: embed = await message_factory( bot=ctx.bot, color=await ctx.embed_color, guild=ctx.guild, moderator=ctx.author, reason=reason, moderation_type=cls(), response=response, ) await target.send(embed=embed, file=get_icon(ctx.bot)) except HTTPException: pass moderation = await Moderation.log( bot=ctx.bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type="user", target_id=target.id, role_id=None, duration=None, reason=reason, ) await response.edit( content=f"{target.mention} has {cls.embed_desc}{cls.verb}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls() @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: if await config.guild(moderation.guild).dm_users() is True: try: target = await moderation.bot.fetch_user(moderation.target_id) embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except (Forbidden, HTTPException, NotFound): pass return True, "" class AddRole(Type): key="addrole" string="addrole" verb="added a role to" embed_desc="been given the " @classmethod async def handler(cls, ctx: commands.Context, target: Member, role: Role, silent: bool, duration: str = None, reason: str = None): addrole_whitelist = await config.guild(ctx.guild).addrole_whitelist() if not addrole_whitelist: await ctx.send( content=error("There are no whitelisted roles set for this server!"), ephemeral=True, ) return if duration is not None: parsed_time = parse_timedelta(duration) if parsed_time is None: await ctx.send( content=error("Please provide a valid duration!"), ephemeral=True ) return else: parsed_time = None if role.id not in addrole_whitelist: await ctx.send( content=error("That role isn't whitelisted!"), ephemeral=True ) return if role.id in [user_role.id for user_role in target.roles]: await ctx.send( content=error(f"{target.mention} already has this role!"), ephemeral=True, ) return response = await ctx.send( content=f"{target.mention} has {cls.embed_desc}{role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`" ) if silent is False: try: embed = await message_factory( bot=ctx.bot, color=await ctx.embed_color(), guild=ctx.guild, moderator=ctx.author, reason=reason, moderation_type=cls(), response=response, duration=parsed_time, role=role, ) await target.send(embed=embed, file=get_icon(ctx.bot)) except HTTPException: pass await target.add_roles( role, reason=f"Role added by {ctx.author.id}{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''} for: {reason}", ) moderation = await Moderation.log( bot=ctx.bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type="user", target_id=target.id, role_id=role.id, duration=parsed_time, reason=reason, ) await response.edit( content=f"{target.mention} has {cls.embed_desc}{role.mention} role{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`", ) await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls() @classmethod async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument return True @classmethod async def expiry_handler(cls, moderation: Moderation) -> int: try: target = await moderation.get_target() await target.remove_roles( Object(moderation.role_id), reason=f"Automatic role removal from case #{moderation.id}" ) if await config.guild(moderation.guild).dm_users() is True: try: embed = await message_factory( bot=moderation.bot, color=await moderation.bot.get_embed_color(moderation.guild.channels[0]), guild=moderation.guild, reason=f"Automatic role removal from case #{moderation.id}", moderation_type=type_registry["removerole"], moderator=None, duration=None, response=None, case=False, ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except HTTPException: pass logger.trace( "Removed role %s from %s (%s)", moderation.role_id, target.name, target.id, ) return 1 except ( NotFound, Forbidden, HTTPException, ) as e: logger.error( "Removing the role %s from user %s failed due to: \n%s", moderation.role_id, moderation.target_id, e, ) return 0 class RemoveRole(Type): key="removerole" string="removerole" verb="removed a role from" embed_desc="had the " @classmethod async def handler(cls, ctx: commands.Context, target: Member, role: Role, silent: bool, duration: str = None, reason: str = None): addrole_whitelist = await config.guild(ctx.guild).addrole_whitelist() if not addrole_whitelist: await ctx.send( content=error("There are no whitelisted roles set for this server!"), ephemeral=True, ) return if duration is not None: parsed_time = parse_timedelta(duration) if parsed_time is None: await ctx.send( content=error("Please provide a valid duration!"), ephemeral=True ) return else: parsed_time = None if role.id not in addrole_whitelist: await ctx.send( content=error("That role isn't whitelisted!"), ephemeral=True ) return if role.id not in [user_role.id for user_role in target.roles]: await ctx.send( content=error(f"{target.mention} does not have this role!"), ephemeral=True, ) return response = await ctx.send( content=f"{target.mention} has {cls.embed_desc}{role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}!\n**Reason** - `{reason}`" ) if silent is False: try: embed = await message_factory( bot=ctx.bot, color=await ctx.embed_color(), guild=ctx.guild, moderator=ctx.author, reason=reason, moderation_type="removerole", response=response, duration=parsed_time, role=role, ) await target.send(embed=embed, file=get_icon(ctx.bot)) except HTTPException: pass await target.remove_roles( role, reason=f"Role removed by {ctx.author.id}{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''} for: {reason}", ) moderation = await Moderation.log( bot=ctx.bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type="user", target_id=target.id, role_id=role.id, duration=parsed_time, reason=reason, ) await response.edit( content=f"{target.mention} has {cls.embed_desc}{role.mention} role removed{' for ' + humanize_timedelta(timedelta=parsed_time) if parsed_time != 'NULL' else ''}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`", ) await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls() @classmethod async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument return True @classmethod async def expiry_handler(cls, moderation: Moderation) -> int: try: target = await moderation.get_target() await target.add_roles( Object(moderation.role_id), reason=f"Automatic role addition from case #{moderation.id}" ) if await config.guild(moderation.guild).dm_users() is True: try: embed = await message_factory( bot=moderation.bot, color=await moderation.bot.get_embed_color(moderation.guild.channels[0]), guild=moderation.guild, reason=f"Automatic role addition from case #{moderation.id}", moderation_type=type_registry["addrole"], moderator=None, duration=None, response=None, case=False, ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except HTTPException: pass logger.trace( "Added role %s to %s (%s)", moderation.role_id, target.name, target.id, ) return 1 except ( NotFound, Forbidden, HTTPException, ) as e: logger.error( "Adding the role %s to user %s failed due to: \n%s", moderation.role_id, moderation.target_id, e, ) return 0 @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: try: target = await moderation.get_target() await target.add_roles( Object(moderation.role_id), reason=reason ) if await config.guild(moderation.guild).dm_users() is True: try: embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except HTTPException: pass logger.trace( "Added role %s to %s (%s)", moderation.role_id, target.name, target.id, ) return True, "" except (NotFound, Forbidden, HTTPException) as e: logger.error( "Failed to add role %s to user %s (%s)\n%s", moderation.role_id, target.name, target.id, e, ) return False, "Failed to add role to user." class Mute(Type): key="mute" string="mute" verb="muted" @classmethod async def handler(cls, ctx: commands.Context, target: Member, silent: bool, duration: str, reason: str = None): if target.is_timed_out() is True: await ctx.send( error(f"{target.mention} is already muted!"), allowed_mentions=AllowedMentions(users=False), ephemeral=True, ) return try: parsed_time = parse_timedelta(duration, maximum=timedelta(days=28)) if parsed_time is None: await ctx.send( error("Please provide a valid duration!"), ephemeral=True ) return except commands.BadArgument: await ctx.send( error("Please provide a duration that is less than 28 days."), ephemeral=True ) return await target.timeout( parsed_time, reason=f"Muted by {ctx.author.id} for: {reason}" ) response = await ctx.send( content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}!\n**Reason** - `{reason}`" ) if silent is False: try: embed = await message_factory( bot=ctx.bot, color=await ctx.embed_color(), guild=ctx.guild, moderator=ctx.author, reason=reason, moderation_type=cls(), response=response, duration=parsed_time, ) await target.send(embed=embed, file=get_icon(ctx.bot)) except HTTPException: pass moderation = await Moderation.log( bot=ctx.bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type="user", target_id=target.id, role_id=None, duration=parsed_time, reason=reason, ) await response.edit( content=f"{target.mention} has been muted for {humanize_timedelta(timedelta=parsed_time)}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls() @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: try: target = await moderation.guild.fetch_member(moderation.target_id) except (Forbidden, HTTPException, NotFound): return False, "User is not in the server, so I cannot unmute them." if target.is_timed_out() is False: return True, "" await target.timeout(None, reason=reason) if await config.guild(moderation.guild).dm_users() is True: try: embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except (Forbidden, HTTPException): pass return True, "" @classmethod async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument if ( time() - new_moderation.unix_timestamp ) + new_moderation.duration.total_seconds() > 2419200: await interaction.response.send_message( content=error( "Please provide a duration that is less than 28 days from the initial moderation." ), ephemeral=True ) return False try: member = await interaction.guild.fetch_member( new_moderation.target_id ) await member.timeout( new_moderation.duration, reason=f"Case #{new_moderation.id:,} edited by {interaction.user.id}", ) except NotFound: pass return True class Unmute(Type): key="unmute" string="unmute" verb="unmuted" @classmethod async def handler(cls, ctx: commands.Context, target: Member, silent: bool, reason: str = None): if target.is_timed_out() is False: await ctx.send( content=error(f"{target.mention} is not muted!"), allowed_mentions=AllowedMentions(users=False), ephemeral=True, ) return if reason: await target.timeout( None, reason=f"{cls.verb.title()} by {ctx.author.id} for: {reason}" ) else: await target.timeout(None, reason=f"{cls.verb.title()} by {ctx.author.id}") reason = "No reason given." response_message = await ctx.send( content=f"{target.mention} has been {cls.verb}!\n**Reason** - `{reason}`" ) if silent is False: try: embed = await message_factory( bot=ctx.bot, color=await ctx.embed_color(), guild=ctx.guild, moderator=ctx.author, reason=reason, moderation_type="unmuted", response=response_message, ) await target.send(embed=embed, file=get_icon(ctx.bot)) except HTTPException: pass moderation = await Moderation.log( bot=ctx.bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type="user", target_id=target.id, role_id=None, duration=None, reason=reason, ) await response_message.edit( content=f"{target.mention} has been {cls.verb}! (Case `#{moderation.id:,}`)\n**Reason** - `{reason}`" ) await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) class Kick(Type): key="kick" string="kick" verb="kicked" @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None) -> 'Kick': """Kick a user.""" bot = ctx.bot response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}") if silent is False: try: embed = await message_factory( bot=bot, color=await ctx.embed_color(), guild=ctx.guild, reason=reason, moderation_type=cls(), moderator=ctx.author, duration=None, response=response_message ) await target.send(embed=embed, file=get_icon(bot)) except HTTPException: pass await target.kick(reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}") moderation = await Moderation.log( bot=bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type='user', target_id=target.id, role_id=None, duration=None, reason=reason ) await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}") await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: if await config.guild(moderation.guild).dm_users() is True: try: target = await moderation.bot.fetch_user(moderation.target_id) embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except (Forbidden, HTTPException, NotFound): pass return True, "" class Ban(Type): key="ban" string="ban" verb="banned" @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None, delete_messages: app_commands.Choice | None = None) -> 'Ban': """Ban a user.""" bot = ctx.bot try: await ctx.guild.fetch_ban(target) await ctx.send(content=error(f"{target.mention} is already {cls.verb}!"), ephemeral=True) except NotFound: pass if delete_messages is None: delete_messages_seconds = 0 else: delete_messages_seconds = delete_messages.value response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}") if silent is False: try: embed = await message_factory( bot=bot, color=await ctx.embed_color(), guild=ctx.guild, reason=reason, moderation_type=cls(), moderator=ctx.author, duration=None, response=response_message ) await target.send(embed=embed, file=get_icon(bot)) except HTTPException: pass await ctx.guild.ban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}", delete_message_seconds=delete_messages_seconds) moderation = await Moderation.log( bot=bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type='user', target_id=target.id, role_id=None, duration=None, reason=reason ) await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}") await log(ctx=ctx, moderation_id=moderation.id) await send_evidenceformat(ctx=ctx, moderation_id=moderation.id) return cls @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: try: target = await moderation.bot.fetch_user(moderation.target_id) except (HTTPException, NotFound): return False, "Fetching the target failed, so I cannot unban them." try: await moderation.guild.unban(user=target, reason=reason) except (NotFound, Forbidden, HTTPException) as e: if e == NotFound: return True, "" return False, "I do not have permission to unban this user." if await config.guild(moderation.guild).dm_users() is True: try: embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except (Forbidden, HTTPException): pass return True, "" class Tempban(Ban): key="tempban" string="tempban" verb="tempbanned" @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, duration: str, reason: str = None, delete_messages: app_commands.Choice | None = None) -> 'Ban': """Ban a user.""" bot = ctx.bot try: await ctx.guild.fetch_ban(target) await ctx.send(content=error(f"{target.mention} is already {Ban.verb}!"), ephemeral=True) except NotFound: pass if delete_messages is None: delete_messages_seconds = 0 else: delete_messages_seconds = delete_messages.value parsed_time = parse_relativedelta(duration) if not parsed_time: await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True) try: parsed_time = timedelta_from_relativedelta(parsed_time) except ValueError: await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True) response_message = await ctx.send(f"{target.mention} has been {cls.verb} for {humanize_timedelta(parsed_time)}!\n{bold('Reason:')} {inline(reason)}") if silent is False: try: embed = await message_factory( bot=bot, color=await ctx.embed_color(), guild=ctx.guild, reason=reason, moderation_type=cls(), moderator=ctx.author, duration=parsed_time, response=response_message ) await target.send(embed=embed, file=get_icon(bot)) except HTTPException: pass await ctx.guild.ban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason} (Duration: {parsed_time})", delete_message_seconds=delete_messages_seconds) moderation = await Moderation.log( bot=bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type='user', target_id=target.id, role_id=None, duration=parsed_time, reason=reason ) await response_message.edit(content=f"{target.mention} has been {cls.verb} for {humanize_timedelta(parsed_time)}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}") await log(ctx, moderation.id) await send_evidenceformat(ctx, moderation.id) return cls @classmethod async def expiry_handler(cls, moderation: Moderation) -> int: reason = f"Automatic {Unban.string} from case #{moderation.id}" try: target = await moderation.get_target() await moderation.guild.unban(user=target, reason=reason) if await config.guild(moderation.guild).dm_users() is True: try: embed = await message_factory( bot=moderation.bot, color=await moderation.bot.get_embed_color(moderation.guild.channels[0]), guild=moderation.guild, reason=reason, moderation_type=type_registry["unban"], moderator=None, duration=None, response=None, case=False, ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except HTTPException: pass logger.trace( "%s %s (%s) from %s (%s)", Unban.verb.title(), target.name, target.id, moderation.guild.name, moderation.guild.id, ) return 1 except (NotFound, Forbidden, HTTPException) as e: logger.error( "Failed to %s %s (%s) from %s (%s)\n%s", Unban.string, target.name, target.id, moderation.guild.name, moderation.guild.id, e, ) return 0 @classmethod async def duration_edit_handler(cls, interaction: Interaction, old_moderation: Moderation, new_moderation: Moderation) -> bool: # pylint: disable=unused-argument return True class Softban(Type): key="softban" string="softban" verb="softbanned" @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None, delete_messages: app_commands.Choice | None = None) -> 'Softban': """Softban a user.""" bot = ctx.bot try: await ctx.guild.fetch_ban(target) await ctx.send(content=error(f"{target.mention} is already {Ban.verb}!"), ephemeral=True) except NotFound: pass if delete_messages is None: delete_messages_seconds = 0 else: delete_messages_seconds = delete_messages.value response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}") if silent is False: try: embed = await message_factory( bot=bot, color=await ctx.embed_color(), guild=ctx.guild, reason=reason, moderation_type=cls(), moderator=ctx.author, duration=None, response=response_message ) await target.send(embed=embed, file=get_icon(bot)) except HTTPException: pass await ctx.guild.ban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}", delete_message_seconds=delete_messages_seconds) await ctx.guild.unban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}") moderation = await Moderation.log( bot=bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type='user', target_id=target.id, role_id=None, duration=None, reason=reason ) await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}") await log(ctx, moderation.id) await send_evidenceformat(ctx, moderation.id) return cls @classmethod async def resolve_handler(cls, moderation: Moderation, reason: str) -> Tuple[bool, str]: if await config.guild(moderation.guild).dm_users() is True: try: target = await moderation.bot.fetch_user(moderation.target_id) embed = await resolve_factory( moderation=moderation, reason=reason ) await target.send(embed=embed, file=get_icon(bot=moderation.bot)) except (Forbidden, HTTPException, NotFound): pass return True, "" class Unban(Type): key="unban" string="unban" verb="unbanned" @classmethod async def handler(cls, ctx: commands.Context, target: Member | User, silent: bool, reason: str = None) -> 'Unban': """Unban a user.""" bot = ctx.bot try: await ctx.guild.fetch_ban(target) except NotFound: await ctx.send(content=error(f"{target.mention} is not {Ban.verb}!"), ephemeral=True) return response_message = await ctx.send(f"{target.mention} has been {cls.verb}!\n{bold('Reason:')} {inline(reason)}") if silent is False: try: embed = await message_factory( bot=bot, color=await ctx.embed_color(), guild=ctx.guild, reason=reason, moderation_type=cls(), moderator=ctx.author, duration=None, response=response_message ) await target.send(embed=embed, file=get_icon(bot)) except HTTPException: pass await ctx.guild.unban(target, reason=f"{str.title(cls.verb)} by {ctx.author.id} for: {reason}") moderation = await Moderation.log( bot=bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type='user', target_id=target.id, role_id=None, duration=None, reason=reason ) await response_message.edit(content=f"{target.mention} has been {cls.verb}! (Case {inline(f'#{moderation.id}')})\n{bold('Reason:')} {inline(reason)}") await log(ctx, moderation.id) await send_evidenceformat(ctx, moderation.id) return cls class Slowmode(Type): key="slowmode" string="slowmode" verb="set the slowmode in" channel=True @classmethod async def handler(cls, ctx: commands.Context, target: Messageable, silent: bool, duration: str, reason: str) -> 'Slowmode': # pylint: disable=unused-argument """Set the slowmode in a channel.""" bot = ctx.bot parsed_time = parse_relativedelta(duration) if not parsed_time: await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True) try: parsed_time = timedelta_from_relativedelta(parsed_time) except ValueError: await ctx.send(content=error("Please provide a valid duration!"), ephemeral=True) if ceil(parsed_time.total_seconds()) > 21600: await ctx.send(content=error("The slowmode duration cannot exceed 6 hours!"), ephemeral=True) return if isinstance(target, TextChannel): await target.edit(slowmode_delay=ceil(parsed_time.total_seconds())) moderation = await Moderation.log( bot=bot, guild_id=ctx.guild.id, moderator_id=ctx.author.id, moderation_type=cls(), target_type='channel', target_id=target.id, role_id=None, duration=parsed_time, reason=None ) await ctx.send(f"{ctx.author.mention} has {cls.verb} {target.mention} to {humanize_timedelta(parsed_time)}!\n{bold('Reason:')} {inline(reason)}") await log(ctx, moderation.id) return cls class Lockdown(Type): key="lockdown" string="lockdown" verb="locked down" channel=True