Repository: python-discord/bot

Issue:
Pban will use duration for tempbanning, not purge_days. Also tpban should be a thing.
# Proposal
- Currently `!pban` can be provided a duration to specify how many days of messages to purge (ranging from 1 to 7) with the default being 1 day. I don't think we have every actually used a duration other than 1 day, which is the default.
- In the spirit of consistency, we would like the option to pass in a duration for allow tempbanning of the person in addition to purging messages.
- So we want to remove the purge_days argument (always make it purge 1 days work of messages) and if a duration is specified, it tempbans the person for that duration
- The above should also exist as its own standalone command: `!tpban`

# Reasoning
- Consistency! We're altering how `!ban` and `!vban` work to allow for optional arguments for duration that would turn them into temporary bans. It would be nice if all of our ban commands operated similarly to reduce mod command mishaps.
- While we do strive to Delete + Report the first message of something that breaks discord's rules, it isn't really feasible to manually delete a large quantity of messages. After we delete + report the first message, this command will essentially allow us to clean + tempban / clean + ban in one go which is nice.


Discussion:

Resolving patch:
diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py
--- a/bot/exts/moderation/infraction/infractions.py
+++ b/bot/exts/moderation/infraction/infractions.py
@@ -76,31 +76,52 @@ async def kick(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[st
         await self.apply_kick(ctx, user, reason)
 
     @command()
-    async def ban(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[str] = None) -> None:
-        """Permanently ban a user for the given reason and stop watching them with Big Brother."""
-        await self.apply_ban(ctx, user, reason)
+    async def ban(
+        self,
+        ctx: Context,
+        user: FetchedMember,
+        duration: t.Optional[Expiry] = None,
+        *,
+        reason: t.Optional[str] = None
+    ) -> None:
+        """
+        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.
+        """
+        await self.apply_ban(ctx, user, reason, expires_at=duration)
 
     @command(aliases=('pban',))
     async def purgeban(
         self,
         ctx: Context,
         user: FetchedMember,
-        purge_days: t.Optional[int] = 1,
+        duration: t.Optional[Expiry] = None,
         *,
         reason: t.Optional[str] = None
     ) -> None:
         """
-        Same as ban but removes all their messages for the given number of days, default being 1.
+        Same as ban but removes all their messages of the last 24 hours.
 
-        `purge_days` can only be values between 0 and 7.
-        Anything outside these bounds are automatically adjusted to their respective limits.
+        If duration is specified, it temporarily bans that user for the given duration.
         """
-        await self.apply_ban(ctx, user, reason, max(min(purge_days, 7), 0))
+        await self.apply_ban(ctx, user, reason, 1, expires_at=duration)
 
     @command(aliases=('vban',))
-    async def voiceban(self, ctx: Context, user: FetchedMember, *, reason: t.Optional[str]) -> None:
-        """Permanently ban user from using voice channels."""
-        await self.apply_voice_ban(ctx, user, reason)
+    async def voiceban(
+        self,
+        ctx: Context,
+        user: FetchedMember,
+        duration: t.Optional[Expiry] = None,
+        *,
+        reason: t.Optional[str]
+    ) -> None:
+        """
+        Permanently ban user from using voice channels.
+
+        If duration is specified, it temporarily voice bans that user for the given duration.
+        """
+        await self.apply_voice_ban(ctx, user, reason, expires_at=duration)
 
     # endregion
     # region: Temporary infractions


Test patch:
diff --git a/tests/bot/exts/moderation/infraction/test_infractions.py b/tests/bot/exts/moderation/infraction/test_infractions.py
--- a/tests/bot/exts/moderation/infraction/test_infractions.py
+++ b/tests/bot/exts/moderation/infraction/test_infractions.py
@@ -74,7 +74,7 @@ async def test_permanent_voice_ban(self):
         """Should call voice ban applying function without expiry."""
         self.cog.apply_voice_ban = AsyncMock()
         self.assertIsNone(await self.cog.voiceban(self.cog, self.ctx, self.user, reason="foobar"))
-        self.cog.apply_voice_ban.assert_awaited_once_with(self.ctx, self.user, "foobar")
+        self.cog.apply_voice_ban.assert_awaited_once_with(self.ctx, self.user, "foobar", expires_at=None)
 
     async def test_temporary_voice_ban(self):
         """Should call voice ban applying function with expiry."""
@@ -184,7 +184,7 @@ async def test_voice_ban_user_left_guild(self, apply_infraction_mock, post_infra
 
         user = MockUser()
         await self.cog.voiceban(self.cog, self.ctx, user, reason=None)
-        post_infraction_mock.assert_called_once_with(self.ctx, user, "voice_ban", None, active=True)
+        post_infraction_mock.assert_called_once_with(self.ctx, user, "voice_ban", None, active=True, expires_at=None)
         apply_infraction_mock.assert_called_once_with(self.cog, self.ctx, infraction, user, ANY)
 
         # Test action