From a1439bab393c3014a1c815bd9b76292cf0a96abb Mon Sep 17 00:00:00 2001 From: UK <41271523+NeloBlivion@users.noreply.github.com> Date: Fri, 26 Apr 2024 16:04:23 +0100 Subject: [PATCH] feat: Support bulk banning in guilds (#2421) Signed-off-by: UK <41271523+NeloBlivion@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: plun1331 Co-authored-by: Lala Sabathil --- CHANGELOG.md | 8 ++++ discord/guild.py | 80 ++++++++++++++++++++++++++++++---- discord/http.py | 29 ++++++++---- discord/member.py | 4 +- discord/types/guild.py | 5 +++ docs/ext/commands/commands.rst | 9 ++-- 6 files changed, 109 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6e30c8a5..39df9290f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2417](https://github.com/Pycord-Development/pycord/pull/2417)) - Added `Guild.search_members`. ([#2418](https://github.com/Pycord-Development/pycord/pull/2418)) +- Added bulk banning up to 200 users through `Guild.bulk_ban`. + ([#2421](https://github.com/Pycord-Development/pycord/pull/2421)) - Added `member` data to the `raw_reaction_remove` event. ([#2412](https://github.com/Pycord-Development/pycord/pull/2412)) @@ -56,6 +58,12 @@ These changes are available on the `master` branch, but have not yet been releas - `Guild.query_members` now accepts `limit=None` to retrieve all members. ([#2419](https://github.com/Pycord-Development/pycord/pull/2419)) +### Removed + +- Removed the `delete_message_days` parameter from ban methods. Please use + `delete_message_seconds` instead. + ([#2421](https://github.com/Pycord-Development/pycord/pull/2421)) + ## [2.5.0] - 2024-03-02 ### Added diff --git a/discord/guild.py b/discord/guild.py index d94eb2df83..f015048f08 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -32,7 +32,6 @@ Any, ClassVar, List, - Literal, NamedTuple, Optional, Sequence, @@ -3076,7 +3075,6 @@ async def ban( user: Snowflake, *, delete_message_seconds: int | None = None, - delete_message_days: Literal[0, 1, 2, 3, 4, 5, 6, 7] | None = None, reason: str | None = None, ) -> None: """|coro| @@ -3096,9 +3094,6 @@ async def ban( The number of seconds worth of messages to delete from the user in the guild. The minimum is 0 and the maximum is 604800 (i.e. 7 days). The default is 0. - delete_message_days: Optional[:class:`int`] - ***Deprecated parameter***, same as ``delete_message_seconds`` but - is used for days instead. reason: Optional[:class:`str`] The reason the user got banned. @@ -3109,11 +3104,67 @@ async def ban( HTTPException Banning failed. """ - if delete_message_seconds and delete_message_days: + + if delete_message_seconds is not None and not ( + 0 <= delete_message_seconds <= 604800 + ): raise TypeError( - "delete_message_seconds and delete_message_days are mutually exclusive." + "delete_message_seconds must be between 0 and 604800 seconds." ) + await self._state.http.ban( + user.id, self.id, delete_message_seconds, reason=reason + ) + + async def bulk_ban( + self, + *users: Snowflake, + delete_message_seconds: int | None = None, + reason: str | None = None, + ) -> list[list[Snowflake], list[Snowflake]]: + r"""|coro| + + Bulk ban users from the guild. + + The users must meet the :class:`abc.Snowflake` abc. + + You must have the :attr:`~Permissions.ban_members` permission to + do this. + + Example Usage: :: + + # Ban multiple users + successes, failures = await guild.ban(user1, user2, user3, ..., reason="Raid") + + # Ban a list of users + successes, failures = await guild.ban(*users) + + Parameters + ---------- + \*users: :class:`abc.Snowflake` + An argument list of users to ban from the guild, up to 200. + delete_message_seconds: Optional[:class:`int`] + The number of seconds worth of messages to delete from + the user in the guild. The minimum is 0 and the maximum + is 604800 (i.e. 7 days). The default is 0. + reason: Optional[:class:`str`] + The reason the users were banned. + + Returns + ------- + List[List[:class:`abc.Snowflake`], List[:class:`abc.Snowflake`]] + Returns two lists: the first contains members that were successfully banned, while the second is members that could not be banned. + + Raises + ------ + ValueError + You tried to ban more than 200 users. + Forbidden + You do not have the proper permissions to ban. + HTTPException + No users were banned. + """ + if delete_message_seconds is not None and not ( 0 <= delete_message_seconds <= 604800 ): @@ -3121,9 +3172,20 @@ async def ban( "delete_message_seconds must be between 0 and 604800 seconds." ) - await self._state.http.ban( - user.id, self.id, delete_message_seconds, delete_message_days, reason=reason + if len(users) > 200 or len(users) < 1: + raise ValueError( + "The number of users to be banned must be between 1 and 200." + ) + + data = await self._state.http.bulk_ban( + [u.id for u in users], + self.id, + delete_message_seconds, + reason=reason, ) + banned = [u for u in users if str(u.id) in data["banned_users"]] + failed = [u for u in users if str(u.id) in data["failed_users"]] + return banned, failed async def unban(self, user: Snowflake, *, reason: str | None = None) -> None: """|coro| diff --git a/discord/http.py b/discord/http.py index e79d6120b7..3622afc2bd 100644 --- a/discord/http.py +++ b/discord/http.py @@ -907,7 +907,6 @@ def ban( user_id: Snowflake, guild_id: Snowflake, delete_message_seconds: int = None, - delete_message_days: int = None, reason: str | None = None, ) -> Response[None]: r = Route( @@ -920,17 +919,29 @@ def ban( if delete_message_seconds: params["delete_message_seconds"] = delete_message_seconds - elif delete_message_days: - warn_deprecated( - "delete_message_days", - "delete_message_seconds", - "2.2", - reference="https://github.com/discord/discord-api-docs/pull/5219", - ) - params["delete_message_days"] = delete_message_days return self.request(r, params=params, reason=reason) + def bulk_ban( + self, + user_ids: list[Snowflake], + guild_id: Snowflake, + delete_message_seconds: int = None, + reason: str | None = None, + ) -> Response[guild.GuildBulkBan]: + r = Route( + "POST", + "/guilds/{guild_id}/bulk-ban", + guild_id=guild_id, + ) + payload = { + "user_ids": user_ids, + } + if delete_message_seconds: + payload["delete_message_seconds"] = delete_message_seconds + + return self.request(r, json=payload, reason=reason) + def unban( self, user_id: Snowflake, guild_id: Snowflake, *, reason: str | None = None ) -> Response[None]: diff --git a/discord/member.py b/discord/member.py index fecb9b9dfa..c854e12979 100644 --- a/discord/member.py +++ b/discord/member.py @@ -30,7 +30,7 @@ import itertools import sys from operator import attrgetter -from typing import TYPE_CHECKING, Any, Literal, TypeVar, Union +from typing import TYPE_CHECKING, Any, TypeVar, Union import discord.abc @@ -684,7 +684,6 @@ async def ban( self, *, delete_message_seconds: int | None = None, - delete_message_days: Literal[0, 1, 2, 3, 4, 5, 6, 7] | None = None, reason: str | None = None, ) -> None: """|coro| @@ -695,7 +694,6 @@ async def ban( self, reason=reason, delete_message_seconds=delete_message_seconds, - delete_message_days=delete_message_days, ) async def unban(self, *, reason: str | None = None) -> None: diff --git a/discord/types/guild.py b/discord/types/guild.py index be417a726d..cac645b272 100644 --- a/discord/types/guild.py +++ b/discord/types/guild.py @@ -185,3 +185,8 @@ class RolePositionUpdate(TypedDict, total=False): class GuildMFAModify(TypedDict): level: Literal[0, 1] + + +class GuildBulkBan(TypedDict): + banned_users: list[Snowflake] + failed_users: list[Snowflake] diff --git a/docs/ext/commands/commands.rst b/docs/ext/commands/commands.rst index 793c3a64e4..c907e2a1b4 100644 --- a/docs/ext/commands/commands.rst +++ b/docs/ext/commands/commands.rst @@ -573,11 +573,10 @@ When mixed with the :data:`typing.Optional` converter you can provide simple and @bot.command() async def ban(ctx, members: commands.Greedy[discord.Member], - delete_days: typing.Optional[int] = 0, *, + delete_seconds: typing.Optional[int] = 0, *, reason: str): - """Mass bans members with an optional delete_days parameter""" - for member in members: - await member.ban(delete_message_days=delete_days, reason=reason) + """Bulk bans members with an optional delete_seconds parameter""" + await ctx.guild.bulk_ban(*members, delete_message_seconds=delete_seconds, reason=reason) This command can be invoked any of the following ways: @@ -707,7 +706,7 @@ For example, augmenting the example above: @commands.command() async def ban(ctx, *, flags: BanFlags): for member in flags.members: - await member.ban(reason=flags.reason, delete_message_days=flags.days) + await member.ban(reason=flags.reason, delete_message_seconds=flags.days * 60 * 24) members = ', '.join(str(member) for member in flags.members) plural = f'{flags.days} days' if flags.days != 1 else f'{flags.days} day'