diff options
Diffstat (limited to 'bot/exts/moderation/infraction/infractions.py')
-rw-r--r-- | bot/exts/moderation/infraction/infractions.py | 108 |
1 files changed, 75 insertions, 33 deletions
diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 46fd3381c..999f9ba7f 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -1,6 +1,8 @@ import textwrap import typing as t +from datetime import timedelta +import arrow import discord from discord import Member from discord.ext import commands @@ -9,7 +11,7 @@ from discord.ext.commands import Context, command from bot import constants from bot.bot import Bot from bot.constants import Event -from bot.converters import Age, Duration, Expiry, MemberOrUser, UnambiguousMemberOrUser +from bot.converters import Age, Duration, DurationOrExpiry, MemberOrUser, UnambiguousMemberOrUser from bot.decorators import ensure_future_timestamp, respect_role_hierarchy from bot.exts.moderation.infraction import _utils from bot.exts.moderation.infraction._scheduler import InfractionScheduler @@ -25,6 +27,24 @@ if t.TYPE_CHECKING: from bot.exts.moderation.watchchannels.bigbrother import BigBrother +# Comp ban +LINK_PASSWORD = "https://support.discord.com/hc/en-us/articles/218410947-I-forgot-my-Password-Where-can-I-set-a-new-one" +LINK_2FA = "https://support.discord.com/hc/en-us/articles/219576828-Setting-up-Two-Factor-Authentication" +COMP_BAN_REASON = ( + "Your account has been used to send links to a phishing website. You have been automatically banned. " + "If you are not aware of sending them, that means your account has been compromised.\n\n" + + f"Here is a guide from Discord on [how to change your password]({LINK_PASSWORD}).\n\n" + + f"We also highly recommend that you [enable 2 factor authentication on your account]({LINK_2FA}), " + "for heightened security.\n\n" + + "Once you have changed your password, feel free to follow the instructions at the bottom of " + "this message to appeal your ban." +) +COMP_BAN_DURATION = timedelta(days=4) + + class Infractions(InfractionScheduler, commands.Cog): """Apply and pardon infractions on users for moderation purposes.""" @@ -52,8 +72,9 @@ class Infractions(InfractionScheduler, commands.Cog): if active_mutes: reason = f"Re-applying active mute: {active_mutes[0]['id']}" - action = member.add_roles(self._muted_role, reason=reason) + async def action() -> None: + await member.add_roles(self._muted_role, reason=reason) await self.reapply_infraction(active_mutes[0], action) # region: Permanent infractions @@ -86,16 +107,18 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: UnambiguousMemberOrUser, - duration: t.Optional[Expiry] = None, + duration_or_expiry: t.Optional[DurationOrExpiry] = None, *, reason: t.Optional[str] = None ) -> None: """ - Permanently ban a user for the given reason and stop watching them with Big Brother. + Permanently ban a `user` for the given `reason` and stop watching them with Big Brother. - If duration is specified, it temporarily bans that user for the given duration. + If a duration is specified, it temporarily bans the `user` for the given duration. + Alternatively, an ISO 8601 timestamp representing the expiry time can be provided + for `duration_or_expiry`. """ - await self.apply_ban(ctx, user, reason, expires_at=duration) + await self.apply_ban(ctx, user, reason, duration_or_expiry=duration_or_expiry) @command(aliases=("cban", "purgeban", "pban")) @ensure_future_timestamp(timestamp_arg=3) @@ -103,7 +126,7 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: UnambiguousMemberOrUser, - duration: t.Optional[Expiry] = None, + duration: t.Optional[DurationOrExpiry] = None, *, reason: t.Optional[str] = None ) -> None: @@ -115,14 +138,12 @@ class Infractions(InfractionScheduler, commands.Cog): clean_cog: t.Optional[Clean] = self.bot.get_cog("Clean") if clean_cog is None: # If we can't get the clean cog, fall back to native purgeban. - await self.apply_ban(ctx, user, reason, purge_days=1, expires_at=duration) + await self.apply_ban(ctx, user, reason, purge_days=1, duration_or_expiry=duration) return - infraction = await self.apply_ban(ctx, user, reason, expires_at=duration) + infraction = await self.apply_ban(ctx, user, reason, duration_or_expiry=duration) if not infraction or not infraction.get("id"): # Ban was unsuccessful, quit early. - await ctx.send(":x: Failed to apply ban.") - log.error("Failed to apply ban to user %d", user.id) return # Calling commands directly skips discord.py's convertors, so we need to convert args manually. @@ -151,6 +172,11 @@ class Infractions(InfractionScheduler, commands.Cog): ctx.send = send await infr_manage_cog.infraction_append(ctx, infraction, None, reason=f"[Clean log]({log_url})") + @command() + async def compban(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: + """Same as cleanban, but specifically with the ban reason and duration used for compromised accounts.""" + await self.cleanban(ctx, user, duration=(arrow.utcnow() + COMP_BAN_DURATION).datetime, reason=COMP_BAN_REASON) + @command(aliases=("vban",)) async def voiceban(self, ctx: Context) -> None: """ @@ -168,7 +194,7 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: UnambiguousMemberOrUser, - duration: t.Optional[Expiry] = None, + duration: t.Optional[DurationOrExpiry] = None, *, reason: t.Optional[str] ) -> None: @@ -177,7 +203,7 @@ class Infractions(InfractionScheduler, commands.Cog): If duration is specified, it temporarily voice mutes that user for the given duration. """ - await self.apply_voice_mute(ctx, user, reason, expires_at=duration) + await self.apply_voice_mute(ctx, user, reason, duration_or_expiry=duration) # endregion # region: Temporary infractions @@ -187,7 +213,7 @@ class Infractions(InfractionScheduler, commands.Cog): async def tempmute( self, ctx: Context, user: UnambiguousMemberOrUser, - duration: t.Optional[Expiry] = None, + duration: t.Optional[DurationOrExpiry] = None, *, reason: t.Optional[str] = None ) -> None: @@ -214,7 +240,7 @@ class Infractions(InfractionScheduler, commands.Cog): if duration is None: duration = await Duration().convert(ctx, "1h") - await self.apply_mute(ctx, user, reason, expires_at=duration) + await self.apply_mute(ctx, user, reason, duration_or_expiry=duration) @command(aliases=("tban",)) @ensure_future_timestamp(timestamp_arg=3) @@ -222,7 +248,7 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: UnambiguousMemberOrUser, - duration: Expiry, + duration_or_expiry: DurationOrExpiry, *, reason: t.Optional[str] = None ) -> None: @@ -241,7 +267,7 @@ class Infractions(InfractionScheduler, commands.Cog): Alternatively, an ISO 8601 timestamp can be provided for the duration. """ - await self.apply_ban(ctx, user, reason, expires_at=duration) + await self.apply_ban(ctx, user, reason, duration_or_expiry=duration_or_expiry) @command(aliases=("tempvban", "tvban")) async def tempvoiceban(self, ctx: Context) -> None: @@ -258,7 +284,7 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: UnambiguousMemberOrUser, - duration: Expiry, + duration: DurationOrExpiry, *, reason: t.Optional[str] ) -> None: @@ -277,7 +303,7 @@ class Infractions(InfractionScheduler, commands.Cog): Alternatively, an ISO 8601 timestamp can be provided for the duration. """ - await self.apply_voice_mute(ctx, user, reason, expires_at=duration) + await self.apply_voice_mute(ctx, user, reason, duration_or_expiry=duration) # endregion # region: Permanent shadow infractions @@ -305,7 +331,7 @@ class Infractions(InfractionScheduler, commands.Cog): self, ctx: Context, user: UnambiguousMemberOrUser, - duration: Expiry, + duration: DurationOrExpiry, *, reason: t.Optional[str] = None ) -> None: @@ -324,20 +350,26 @@ class Infractions(InfractionScheduler, commands.Cog): Alternatively, an ISO 8601 timestamp can be provided for the duration. """ - await self.apply_ban(ctx, user, reason, expires_at=duration, hidden=True) + await self.apply_ban(ctx, user, reason, duration_or_expiry=duration, hidden=True) # endregion # region: Remove infractions (un- commands) @command() - async def unmute(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: + async def unmute( + self, + ctx: Context, + user: UnambiguousMemberOrUser, + *, + pardon_reason: t.Optional[str] = None + ) -> None: """Prematurely end the active mute infraction for the user.""" - await self.pardon_infraction(ctx, "mute", user) + await self.pardon_infraction(ctx, "mute", user, pardon_reason) @command() - async def unban(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: + async def unban(self, ctx: Context, user: UnambiguousMemberOrUser, *, pardon_reason: str) -> None: """Prematurely end the active ban infraction for the user.""" - await self.pardon_infraction(ctx, "ban", user) + await self.pardon_infraction(ctx, "ban", user, pardon_reason) @command(aliases=("uvban",)) async def unvoiceban(self, ctx: Context) -> None: @@ -349,9 +381,15 @@ class Infractions(InfractionScheduler, commands.Cog): await ctx.send(":x: This command is not yet implemented. Maybe you meant to use `unvoicemute`?") @command(aliases=("uvmute",)) - async def unvoicemute(self, ctx: Context, user: UnambiguousMemberOrUser) -> None: + async def unvoicemute( + self, + ctx: Context, + user: UnambiguousMemberOrUser, + *, + pardon_reason: t.Optional[str] = None + ) -> None: """Prematurely end the active voice mute infraction for the user.""" - await self.pardon_infraction(ctx, "voice_mute", user) + await self.pardon_infraction(ctx, "voice_mute", user, pardon_reason) # endregion # region: Base apply functions @@ -388,7 +426,7 @@ class Infractions(InfractionScheduler, commands.Cog): log.trace(f"Attempting to kick {user} from voice because they've been muted.") await user.move_to(None, reason=reason) - await self.apply_infraction(ctx, infraction, user, action()) + await self.apply_infraction(ctx, infraction, user, action) @respect_role_hierarchy(member_arg=2) async def apply_kick(self, ctx: Context, user: Member, reason: t.Optional[str], **kwargs) -> None: @@ -406,7 +444,9 @@ class Infractions(InfractionScheduler, commands.Cog): if reason: reason = textwrap.shorten(reason, width=512, placeholder="...") - action = user.kick(reason=reason) + async def action() -> None: + await user.kick(reason=reason) + await self.apply_infraction(ctx, infraction, user, action) @respect_role_hierarchy(member_arg=2) @@ -428,7 +468,7 @@ class Infractions(InfractionScheduler, commands.Cog): return None # In the case of a permanent ban, we don't need get_active_infractions to tell us if one is active - is_temporary = kwargs.get("expires_at") is not None + is_temporary = kwargs.get("duration_or_expiry") is not None active_infraction = await _utils.get_active_infraction(ctx, user, "ban", is_temporary) if active_infraction: @@ -436,7 +476,7 @@ class Infractions(InfractionScheduler, commands.Cog): log.trace("Tempban ignored as it cannot overwrite an active ban.") return None - if active_infraction.get('expires_at') is None: + if active_infraction.get("duration_or_expiry") is None: log.trace("Permaban already exists, notify.") await ctx.send(f":x: User is already permanently banned (#{active_infraction['id']}).") return None @@ -455,7 +495,9 @@ class Infractions(InfractionScheduler, commands.Cog): if reason: reason = textwrap.shorten(reason, width=512, placeholder="...") - action = ctx.guild.ban(user, reason=reason, delete_message_days=purge_days) + async def action() -> None: + await ctx.guild.ban(user, reason=reason, delete_message_days=purge_days) + await self.apply_infraction(ctx, infraction, user, action) bb_cog: t.Optional[BigBrother] = self.bot.get_cog("Big Brother") @@ -493,7 +535,7 @@ class Infractions(InfractionScheduler, commands.Cog): await user.move_to(None, reason="Disconnected from voice to apply voice mute.") await user.remove_roles(self._voice_verified_role, reason=reason) - await self.apply_infraction(ctx, infraction, user, action()) + await self.apply_infraction(ctx, infraction, user, action) # endregion # region: Base pardon functions |