From 909d1f6cb888bbadb31fe716e0bffdaa15c54612 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 11:43:39 +0100 Subject: [PATCH 01/15] implement community invites --- changelog/1510.feature.rst | 1 + disnake/abc.py | 24 +++++- disnake/flags.py | 75 +++++++++++++++++++ disnake/http.py | 53 +++++++++++--- disnake/invite.py | 146 ++++++++++++++++++++++++++++++++++++- disnake/role.py | 12 ++- disnake/types/invite.py | 25 ++++++- disnake/types/role.py | 12 ++- docs/api/invites.rst | 8 ++ 9 files changed, 337 insertions(+), 19 deletions(-) create mode 100644 changelog/1510.feature.rst diff --git a/changelog/1510.feature.rst b/changelog/1510.feature.rst new file mode 100644 index 0000000000..f02dad85a5 --- /dev/null +++ b/changelog/1510.feature.rst @@ -0,0 +1 @@ +Add the new :meth:`Invite.fetch_target_users`, :meth:`Invite.update_target_users`, :meth:`Invite.fetch_target_users_job_status` methods as well as :attr:`Invite.roles`, :attr:`Invite.flags`, :class:`GuildInviteFlags`. Update :meth:`abc.GuildChannel.create_invite` to be able to specify ``roles`` and the ``target_users_file``. diff --git a/disnake/abc.py b/disnake/abc.py index cc30167ce4..022171b34d 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -1300,10 +1300,12 @@ async def create_invite( unique: bool = True, target_type: InviteTarget | None = None, target_user: User | None = None, + target_users_file: File | None = None, target_application: Snowflake | PartyType | None = None, guild_scheduled_event: GuildScheduledEvent | None = None, + roles: list[Role] | None = None, ) -> Invite: - """|coro| + r"""|coro| Creates an instant invite from a text or voice channel. @@ -1336,6 +1338,17 @@ async def create_invite( .. versionadded:: 2.0 + target_users_file: :class:`~disnake.File` | :data:`None` + A csv file with a list of users able to accept the invite. + This file must only have valid user ids separated by ``/n``. + A valid file content would look like this: :: + + 710570210159099984 + 1081815963990761542 + ... other user ids + + .. versionadded:: 2.13 + target_application: :class:`.Snowflake` | :data:`None` The ID of the embedded application for the invite, required if ``target_type`` is :attr:`.InviteTarget.embedded_application`. @@ -1349,6 +1362,13 @@ async def create_invite( .. versionadded:: 2.3 + roles: :class:`list`\[:class:`.Role`] | :data:`None` + A list of roles added to the user upon accepting the invite. + You must have the :attr:`.Permissions.manage_roles` permission and cannot assign roles with + higher permissions than you to do this. + + .. versionadded:: 2.13 + reason: :class:`str` | :data:`None` The reason for creating this invite. Shows up on the audit log. @@ -1379,7 +1399,9 @@ async def create_invite( unique=unique, target_type=try_enum_to_int(target_type), target_user_id=target_user.id if target_user else None, + target_users_file=target_users_file, target_application_id=target_application.id if target_application else None, + role_ids=[r.id for r in roles] if roles else None, ) invite = Invite.from_incomplete(data=data, state=self._state) invite.guild_scheduled_event = guild_scheduled_event diff --git a/disnake/flags.py b/disnake/flags.py index d5cdd9fe29..3ee8596ed4 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -41,6 +41,7 @@ "SKUFlags", "ApplicationInstallTypes", "InteractionContextTypes", + "GuildInviteFlags", ) BF = TypeVar("BF", bound="BaseFlags") @@ -2922,3 +2923,77 @@ def bot_dm(self) -> int: def private_channel(self) -> int: """:class:`bool`: Returns ``True`` if the command is usable in DMs and group DMs with other users.""" return 1 << 2 + + +class GuildInviteFlags(BaseFlags): + """Wraps up Discord Invite flags. + + .. collapse:: operations + + .. describe:: x == y + + Checks if two GuildInviteFlags instances are equal. + .. describe:: x != y + + Checks if two GuildInviteFlags instances are not equal. + .. describe:: x <= y + + Checks if a GuildInviteFlags instance is a subset of another GuildInviteFlags instance. + .. describe:: x >= y + + Checks if a GuildInviteFlags instance is a superset of another GuildInviteFlags instance. + .. describe:: x < y + + Checks if a GuildInviteFlags instance is a strict subset of another GuildInviteFlags instance. + .. describe:: x > y + + Checks if a GuildInviteFlags instance is a strict superset of another GuildInviteFlags instance. + .. describe:: x | y, x |= y + + Returns a new GuildInviteFlags instance with all enabled flags from both x and y. + (Using ``|=`` will update in place). + .. describe:: x & y, x &= y + + Returns a new GuildInviteFlags instance with only flags enabled on both x and y. + (Using ``&=`` will update in place). + .. describe:: x ^ y, x ^= y + + Returns a new GuildInviteFlags instance with only flags enabled on one of x or y, but not both. + (Using ``^=`` will update in place). + .. describe:: ~x + + Returns a new GuildInviteFlags instance with all flags from x inverted. + .. describe:: hash(x) + + Returns the flag's hash. + .. describe:: iter(x) + + Returns an iterator of ``(name, value)`` pairs. This allows it + to be, for example, constructed as a dict or a list of pairs. + Note that aliases are not shown. + + Additionally supported are a few operations on class attributes. + + .. describe:: GuildInviteFlags.y | GuildInviteFlags.z, GuildInviteFlags(y=True) | GuildInviteFlags.z + + Returns a GuildInviteFlags instance with all provided flags enabled. + + .. describe:: ~GuildInviteFlags.y + + Returns a GuildInviteFlags instance with all flags except ``y`` inverted from their default value. + + .. versionadded:: 2.13 + + Attributes + ---------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + __slots__ = () + + @flag_value + def is_guest_invite(self) -> int: + """:class:`bool`: Returns ``True`` if this invite is a guest invite for a voice channel.""" + return 1 << 0 diff --git a/disnake/http.py b/disnake/http.py index 0b941b593a..a803ea6974 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -132,20 +132,27 @@ def set_attachments(payload: dict[str, Any], files: Sequence[File]) -> None: payload["attachments"] = attachments -def to_multipart(payload: dict[str, Any], files: Sequence[File]) -> list[dict[str, Any]]: +def to_multipart( + payload: dict[str, Any], files: Sequence[File], *, is_csv: bool = False +) -> list[dict[str, Any]]: """Converts the payload and list of files to a multipart payload, as specified by https://docs.discord.com/developers/reference#uploading-files """ multipart: list[dict[str, Any]] = [] for index, file in enumerate(files): - multipart.append( - { - "name": f"files[{index}]", - "value": file.fp, - "filename": file.filename, - "content_type": "application/octet-stream", - } - ) + if is_csv: + multipart.append( + {"name": "target_users_file", "value": file.fp, "content_type": "text/csv"} + ) + else: + multipart.append( + { + "name": f"files[{index}]", + "value": file.fp, + "filename": file.filename, + "content_type": "application/octet-stream", + } + ) multipart.append({"name": "payload_json", "value": utils._to_json(payload)}) return multipart @@ -1950,7 +1957,9 @@ def create_invite( unique: bool = True, target_type: invite.InviteTargetType | None = None, target_user_id: Snowflake | None = None, + target_users_file: File | None = None, target_application_id: Snowflake | None = None, + role_ids: list[Snowflake] | None = None, ) -> Response[invite.Invite]: r = Route("POST", "/channels/{channel_id}/invites", channel_id=channel_id) payload: dict[str, Any] = { @@ -1969,6 +1978,16 @@ def create_invite( if target_application_id: payload["target_application_id"] = str(target_application_id) + if role_ids: + payload["role_ids"] = role_ids + + if target_users_file: + return self.request( + r, + reason=reason, + form=to_multipart(payload=payload, files=[target_users_file], is_csv=True), + ) + return self.request(r, reason=reason, json=payload) def get_invite( @@ -1988,6 +2007,22 @@ def get_invite( Route("GET", "/invites/{invite_id}", invite_id=invite_id), params=params ) + def get_invite_target_users(self, invite_id: str) -> Response[str]: + return self.request(Route("GET", "/invites/{invite_id}/target-users", invite_id=invite_id)) + + def update_invite_target_users(self, invite_id: str, *, file: File) -> Response[None]: + return self.request( + Route("PUT", "/invites/{invite_id}/target-users", invite_id=invite_id), + form=to_multipart(payload={}, files=[file], is_csv=True), + ) + + def get_invite_target_users_job_status( + self, invite_id: str + ) -> Response[invite.TargetUsersJobPayload]: + return self.request( + Route("GET", "/invites/{invite_id}/target-users/job-status", invite_id=invite_id) + ) + def invites_from(self, guild_id: Snowflake) -> Response[list[invite.Invite]]: return self.request(Route("GET", "/guilds/{guild_id}/invites", guild_id=guild_id)) diff --git a/disnake/invite.py b/disnake/invite.py index 17a636e5c9..c5449c12f6 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -7,9 +7,12 @@ from .appinfo import PartialAppInfo from .asset import Asset from .enums import ChannelType, InviteTarget, InviteType, NSFWLevel, VerificationLevel, try_enum +from .file import File +from .flags import GuildInviteFlags from .guild_scheduled_event import GuildScheduledEvent from .mixins import Hashable from .object import Object +from .role import Role from .utils import _get_as_snowflake, parse_time, snowflake_time from .welcome_screen import WelcomeScreen @@ -33,7 +36,12 @@ ) from .types.gateway import InviteCreateEvent, InviteDeleteEvent from .types.guild import GuildFeature - from .types.invite import Invite as InvitePayload, InviteGuild as InviteGuildPayload + from .types.invite import ( + Invite as InvitePayload, + InviteGuild as InviteGuildPayload, + TargetUserJob, + TargetUsersJobPayload, + ) from .user import User GatewayInvitePayload: TypeAlias = InviteCreateEvent | InviteDeleteEvent @@ -251,7 +259,7 @@ def splash(self) -> Asset | None: class Invite(Hashable): - """Represents a Discord :class:`Guild` or :class:`abc.GuildChannel` invite. + r"""Represents a Discord :class:`Guild` or :class:`abc.GuildChannel` invite. Depending on the way this object was created, some of the attributes can have a value of :data:`None` (see table below). @@ -384,6 +392,16 @@ class Invite(Hashable): The partial guild's welcome screen, if any. .. versionadded:: 2.5 + + flags: :class:`GuildInviteFlags` + The flags of this invite. + + .. versionadded:: 2.13 + + roles: :class:`list`\[:class:`Role`] + A list of roles that will be assigned to the users when joining, if any. + + .. versionadded:: 2.13 """ __slots__ = ( @@ -405,6 +423,8 @@ class Invite(Hashable): "expires_at", "guild_scheduled_event", "guild_welcome_screen", + "flags", + "roles", "_state", ) @@ -475,6 +495,16 @@ def __init__( else: self.guild_scheduled_event: GuildScheduledEvent | None = None + self.flags = GuildInviteFlags._from_value(data.get("flags", 0)) + self.roles = [ + Role( + guild=self.guild, + state=self._state, + data=d, + ) + for d in data.get("roles", []) + ] + @classmethod def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self: guild: Guild | PartialInviteGuild | None @@ -594,3 +624,115 @@ async def delete(self, *, reason: str | None = None) -> None: Revoking the invite failed. """ await self._state.http.delete_invite(self.code, reason=reason) + + async def fetch_target_users(self) -> str: + """|coro| + + Fetch the csv file with the target users for this invite. + You must have the :attr:`~Permissions.manage_guild` or :attr:`~Permissions.view_audit_log` + permissions or to be the inviter to do this. + + .. versionadded:: 2.13 + + Raises + ------ + Forbidden + You do not have permissions to see the target users. + HTTPException + Getting the target users failed. + + Returns + ------- + :class:`str` + The target users for this invite. + """ + return await self._state.http.get_invite_target_users(self.code) + + async def update_target_users(self, *, file: File) -> None: + """|coro| + + Update the target users for this invite. + You must have the :attr:`~Permissions.manage_guild` permission or to be the inviter to do this. + + .. versionadded:: 2.13 + + Parameters + ---------- + file: :class:`File` + The csv file containing the new user ids to target. + This file must only have valid user ids separated by ``/n``. + A valid file content would look like this: :: + + 710570210159099984 + 1081815963990761542 + ... other user ids + + Raises + ------ + Forbidden + You do not have permissions to update the target users. + HTTPException + Updating the target users failed. + """ + return await self._state.http.update_invite_target_users(self.code, file=file) + + async def fetch_target_users_job_status(self) -> TargetUserJob: + r"""|coro| + + Get the target users job status. + You must have the :attr:`~Permissions.manage_guild` or :attr:`~Permissions.view_audit_log` + permissions or to be the inviter to do this. + + .. versionadded:: 2.13 + + Raises + ------ + Forbidden + You do not have permissions to get the target users job status. + HTTPException + Getting the target users job status failed. + + Returns + ------- + :class:`dict`\[:class:`str`, :class:`str` | :class:`int` | :class:`datetime.datetime` | :data:`None`] + A :class:`dict` containing the job status. + + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + | Key | Type | Description | + +=================+===========================================+=========================================================================+ + | status | :class:`int` | The status of the job | + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + | total_users | :class:`int` | The total number of targeted users | + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + | processed_users | :class:`int` | The total number of processed users so far | + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + | created_at | :class:`datetime.datetime` | The date when the job started | + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + | completed_at | :class:`datetime.datetime` | :data:`None` | The date when the job was completed, :data:`None` if it's still running | + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + | error_message | :class:`str` | :data:`None` | The error message of the job, if any | + +-----------------+-------------------------------------------+-------------------------------------------------------------------------+ + + +-------------------+---------------------+------------------------------------------------------------------+ + | Status Value | Name | Description | + +===================+=====================+==================================================================+ + | ``0`` | ``UNSPECIFIED`` | The default value | + +-------------------+---------------------+------------------------------------------------------------------+ + | ``1`` | ``PROCESSING`` | The job is currently being processed | + +-------------------+---------------------+------------------------------------------------------------------+ + | ``2`` | ``COMPLETED`` | The job has been completed successfully | + +-------------------+---------------------+------------------------------------------------------------------+ + | ``3`` | ``FAILED`` | The job has failed, see ``error_message`` field for more details | + +-------------------+---------------------+------------------------------------------------------------------+ + """ + data: TargetUsersJobPayload = await self._state.http.get_invite_target_users_job_status( + self.code + ) + return { + "status": data["status"], + "total_users": data["total_users"], + "processed_users": data["processed_users"], + "created_at": parse_time(data["created_at"]), + "completed_at": parse_time(data["completed_at"]), + "error_message": data["error_message"], + } diff --git a/disnake/role.py b/disnake/role.py index 800affff01..1084a67d50 100644 --- a/disnake/role.py +++ b/disnake/role.py @@ -27,7 +27,11 @@ from .member import Member from .state import ConnectionState from .types.guild import RolePositionUpdate - from .types.role import Role as RolePayload, RoleTags as RoleTagPayload + from .types.role import ( + PartialRole as PartialRolePayload, + Role as RolePayload, + RoleTags as RoleTagPayload, + ) class RoleTags: @@ -223,7 +227,9 @@ class Role(Hashable): "_tertiary_color", ) - def __init__(self, *, guild: Guild, state: ConnectionState, data: RolePayload) -> None: + def __init__( + self, *, guild: Guild, state: ConnectionState, data: RolePayload | PartialRolePayload + ) -> None: self.guild: Guild = guild self._state: ConnectionState = state self.id: int = int(data["id"]) @@ -272,7 +278,7 @@ def __ge__(self, other: Self) -> bool: return NotImplemented return not r - def _update(self, data: RolePayload) -> None: + def _update(self, data: RolePayload | PartialRolePayload) -> None: self.name: str = data["name"] self._permissions: int = int(data.get("permissions", 0)) self.position: int = data.get("position", 0) diff --git a/disnake/types/invite.py b/disnake/types/invite.py index 55d79e9540..4a6e964a81 100644 --- a/disnake/types/invite.py +++ b/disnake/types/invite.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import Literal, TypedDict +from typing import TYPE_CHECKING, Literal, TypedDict from typing_extensions import NotRequired @@ -10,8 +10,12 @@ from .channel import InviteChannel from .guild import InviteGuild from .guild_scheduled_event import GuildScheduledEvent +from .role import PartialRole from .user import PartialUser +if TYPE_CHECKING: + from datetime import datetime + InviteType = Literal[0, 1, 2] InviteTargetType = Literal[1, 2] @@ -42,3 +46,22 @@ class Invite(_InviteMetadata): approximate_member_count: NotRequired[int] expires_at: str | None guild_scheduled_event: NotRequired[GuildScheduledEvent] + flags: NotRequired[int] + roles: NotRequired[list[PartialRole]] + + +class TargetUsersJobBase(TypedDict): + status: Literal[0, 1, 2, 3] + total_users: int + processed_users: int + error_message: str | None + + +class TargetUsersJobPayload(TargetUsersJobBase): + created_at: str + completed_at: str | None + + +class TargetUserJob(TargetUsersJobBase): + created_at: datetime + completed_at: datetime | None diff --git a/disnake/types/role.py b/disnake/types/role.py index 6422b65741..72f71be8df 100644 --- a/disnake/types/role.py +++ b/disnake/types/role.py @@ -9,15 +9,18 @@ from .snowflake import Snowflake -class Role(TypedDict): +class RoleBase(TypedDict): id: Snowflake name: str + position: int color: int colors: RoleColors - hoist: bool icon: NotRequired[str | None] unicode_emoji: NotRequired[str | None] - position: int + + +class Role(RoleBase): + hoist: bool permissions: str managed: bool mentionable: bool @@ -25,6 +28,9 @@ class Role(TypedDict): flags: int +class PartialRole(RoleBase): ... + + class RoleTags(TypedDict, total=False): bot_id: Snowflake integration_id: Snowflake diff --git a/docs/api/invites.rst b/docs/api/invites.rst index 94d4cf9ad4..5a5df05883 100644 --- a/docs/api/invites.rst +++ b/docs/api/invites.rst @@ -34,6 +34,14 @@ PartialInviteChannel .. autoclass:: PartialInviteChannel() :members: +GuildInviteFlags +~~~~~~~~~~~~~~~~ + +.. attributetable:: GuildInviteFlags + +.. autoclass:: GuildInviteFlags() + :members: + Enumerations ------------ From b6a288f7d9d24bc4c3fd5d9133009b98807e5fc9 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:57:34 +0100 Subject: [PATCH 02/15] Update disnake/abc.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/abc.py b/disnake/abc.py index 022171b34d..b3759a30b4 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -1340,7 +1340,7 @@ async def create_invite( target_users_file: :class:`~disnake.File` | :data:`None` A csv file with a list of users able to accept the invite. - This file must only have valid user ids separated by ``/n``. + This file must only have valid user ids separated by ``\n``. A valid file content would look like this: :: 710570210159099984 From 4eecb98c0f560f05babaa6b9a4494ef7b0c04282 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:57:45 +0100 Subject: [PATCH 03/15] Update disnake/abc.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/abc.py b/disnake/abc.py index b3759a30b4..2e1c7b3591 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -1347,7 +1347,7 @@ async def create_invite( 1081815963990761542 ... other user ids - .. versionadded:: 2.13 + .. versionadded:: |vnext| target_application: :class:`.Snowflake` | :data:`None` The ID of the embedded application for the invite, required if ``target_type`` is :attr:`.InviteTarget.embedded_application`. From 79da010d6b6bfa95aa19033a1331f0f184351aa3 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:58:00 +0100 Subject: [PATCH 04/15] Update disnake/abc.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/abc.py b/disnake/abc.py index 2e1c7b3591..e3e0d5d7e6 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -1367,7 +1367,7 @@ async def create_invite( You must have the :attr:`.Permissions.manage_roles` permission and cannot assign roles with higher permissions than you to do this. - .. versionadded:: 2.13 + .. versionadded:: |vnext| reason: :class:`str` | :data:`None` The reason for creating this invite. Shows up on the audit log. From e4f527174ed0b53f7974646909791edab2ed1a4a Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:58:09 +0100 Subject: [PATCH 05/15] Update disnake/flags.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/flags.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/flags.py b/disnake/flags.py index 3ee8596ed4..6979034f14 100644 --- a/disnake/flags.py +++ b/disnake/flags.py @@ -2982,7 +2982,7 @@ class GuildInviteFlags(BaseFlags): Returns a GuildInviteFlags instance with all flags except ``y`` inverted from their default value. - .. versionadded:: 2.13 + .. versionadded:: |vnext| Attributes ---------- From bd7dd08adc9f287443e6636e7049d354593f7664 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:58:22 +0100 Subject: [PATCH 06/15] Update disnake/invite.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/invite.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/invite.py b/disnake/invite.py index c5449c12f6..329057b865 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -396,12 +396,12 @@ class Invite(Hashable): flags: :class:`GuildInviteFlags` The flags of this invite. - .. versionadded:: 2.13 + .. versionadded:: |vnext| roles: :class:`list`\[:class:`Role`] A list of roles that will be assigned to the users when joining, if any. - .. versionadded:: 2.13 + .. versionadded:: |vnext| """ __slots__ = ( From 891a66b2f205731880fb2ef2d49966487c43718c Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:58:48 +0100 Subject: [PATCH 07/15] Update disnake/invite.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/invite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/invite.py b/disnake/invite.py index 329057b865..feb5550d61 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -632,7 +632,7 @@ async def fetch_target_users(self) -> str: You must have the :attr:`~Permissions.manage_guild` or :attr:`~Permissions.view_audit_log` permissions or to be the inviter to do this. - .. versionadded:: 2.13 + .. versionadded:: |vnext| Raises ------ From c9e0f3e95c73a50f043021288293c9566f158239 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:58:59 +0100 Subject: [PATCH 08/15] Update disnake/invite.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/invite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/invite.py b/disnake/invite.py index feb5550d61..d58cc93a48 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -654,7 +654,7 @@ async def update_target_users(self, *, file: File) -> None: Update the target users for this invite. You must have the :attr:`~Permissions.manage_guild` permission or to be the inviter to do this. - .. versionadded:: 2.13 + .. versionadded:: |vnext| Parameters ---------- From 58573ff5fb0c3268b3f8e73388ea4746d1539860 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:59:12 +0100 Subject: [PATCH 09/15] Update disnake/invite.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/invite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/invite.py b/disnake/invite.py index d58cc93a48..07e5a132ee 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -660,7 +660,7 @@ async def update_target_users(self, *, file: File) -> None: ---------- file: :class:`File` The csv file containing the new user ids to target. - This file must only have valid user ids separated by ``/n``. + This file must only have valid user ids separated by ``\n``. A valid file content would look like this: :: 710570210159099984 From 155d94aabdb4611813dbc19fe09accbb50344ae4 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 16:59:26 +0100 Subject: [PATCH 10/15] Update disnake/invite.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/invite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/invite.py b/disnake/invite.py index 07e5a132ee..2e970c695a 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -683,7 +683,7 @@ async def fetch_target_users_job_status(self) -> TargetUserJob: You must have the :attr:`~Permissions.manage_guild` or :attr:`~Permissions.view_audit_log` permissions or to be the inviter to do this. - .. versionadded:: 2.13 + .. versionadded:: |vnext| Raises ------ From caf03f61accb9e2a23e6baa40eb15f57d3d0dea7 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:02:04 +0100 Subject: [PATCH 11/15] Update disnake/abc.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/abc.py b/disnake/abc.py index e3e0d5d7e6..f77c4779c5 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -1364,8 +1364,8 @@ async def create_invite( roles: :class:`list`\[:class:`.Role`] | :data:`None` A list of roles added to the user upon accepting the invite. - You must have the :attr:`.Permissions.manage_roles` permission and cannot assign roles with - higher permissions than you to do this. + You must have the :attr:`.Permissions.manage_roles` permission, and the roles must be + below the bot's top-most role. .. versionadded:: |vnext| From 5d4d920135b51e632ffe6f0182b3f786a9ee6517 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:05:36 +0100 Subject: [PATCH 12/15] Update disnake/invite.py Co-authored-by: Eneg <42005170+Enegg@users.noreply.github.com> Signed-off-by: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> --- disnake/invite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disnake/invite.py b/disnake/invite.py index 2e970c695a..028c8e168f 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -681,7 +681,7 @@ async def fetch_target_users_job_status(self) -> TargetUserJob: Get the target users job status. You must have the :attr:`~Permissions.manage_guild` or :attr:`~Permissions.view_audit_log` - permissions or to be the inviter to do this. + permissions or be the inviter to do this. .. versionadded:: |vnext| From 2970aa469b4aa05569e8964e826b0044fb06799e Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:10:10 +0100 Subject: [PATCH 13/15] apply changes to to_multipart --- disnake/http.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/disnake/http.py b/disnake/http.py index a803ea6974..ef3ba39fbc 100644 --- a/disnake/http.py +++ b/disnake/http.py @@ -138,21 +138,22 @@ def to_multipart( """Converts the payload and list of files to a multipart payload, as specified by https://docs.discord.com/developers/reference#uploading-files """ - multipart: list[dict[str, Any]] = [] - for index, file in enumerate(files): - if is_csv: - multipart.append( - {"name": "target_users_file", "value": file.fp, "content_type": "text/csv"} - ) - else: - multipart.append( - { - "name": f"files[{index}]", - "value": file.fp, - "filename": file.filename, - "content_type": "application/octet-stream", - } - ) + multipart: list[dict[str, Any]] + if is_csv: + multipart = [ + {"name": "target_users_file", "value": file.fp, "content_type": "text/csv"} + for file in files + ] + else: + multipart = [ + { + "name": f"files[{index}]", + "value": file.fp, + "filename": file.filename, + "content_type": "application/octet-stream", + } + for index, file in enumerate(files) + ] multipart.append({"name": "payload_json", "value": utils._to_json(payload)}) return multipart From 4a635be61db962a1d1e5b28aff702bc3f0c25a7d Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:14:10 +0100 Subject: [PATCH 14/15] make roles a Collection --- disnake/abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/abc.py b/disnake/abc.py index f77c4779c5..dddb056108 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -5,7 +5,7 @@ import asyncio import copy from abc import ABC -from collections.abc import Callable, Mapping, Sequence +from collections.abc import Callable, Mapping, Sequence, Collection from typing import ( TYPE_CHECKING, Any, @@ -1303,7 +1303,7 @@ async def create_invite( target_users_file: File | None = None, target_application: Snowflake | PartyType | None = None, guild_scheduled_event: GuildScheduledEvent | None = None, - roles: list[Role] | None = None, + roles: Collection[Role] | None = None, ) -> Invite: r"""|coro| From dffc5e97b5008a13e33a6e88fb7a4dbffa5dabe8 Mon Sep 17 00:00:00 2001 From: Snipy7374 <100313469+Snipy7374@users.noreply.github.com> Date: Sun, 8 Mar 2026 17:15:27 +0100 Subject: [PATCH 15/15] change codeblocks --- disnake/abc.py | 2 +- disnake/invite.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/disnake/abc.py b/disnake/abc.py index dddb056108..ae072370a0 100644 --- a/disnake/abc.py +++ b/disnake/abc.py @@ -1341,7 +1341,7 @@ async def create_invite( target_users_file: :class:`~disnake.File` | :data:`None` A csv file with a list of users able to accept the invite. This file must only have valid user ids separated by ``\n``. - A valid file content would look like this: :: + A valid file content would look like this:: 710570210159099984 1081815963990761542 diff --git a/disnake/invite.py b/disnake/invite.py index 028c8e168f..ec72f6704b 100644 --- a/disnake/invite.py +++ b/disnake/invite.py @@ -661,7 +661,7 @@ async def update_target_users(self, *, file: File) -> None: file: :class:`File` The csv file containing the new user ids to target. This file must only have valid user ids separated by ``\n``. - A valid file content would look like this: :: + A valid file content would look like this:: 710570210159099984 1081815963990761542