diff --git a/discord/abc.py b/discord/abc.py index 95ccfd67b..d60abc796 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1282,6 +1282,7 @@ class GuildChannel: target_type: Optional[InviteTarget] = None, target_user: Optional[User] = None, target_application_id: Optional[int] = None, + target_role_ids: Optional[Sequence[Snowflake]] = None, guest: bool = False, ) -> Invite: """|coro| @@ -1321,6 +1322,9 @@ class GuildChannel: The id of the embedded application for the invite, required if ``target_type`` is :attr:`.InviteTarget.embedded_application`. .. versionadded:: 2.0 + target_role_ids: Optional[Sequence[:class:`abc.Snowflake`]] + A list of role IDs to assign to the user upon accepting this invite. + guest: :class:`bool` Whether the invite is a guest invite. @@ -1357,6 +1361,7 @@ class GuildChannel: target_type=target_type.value if target_type else None, target_user_id=target_user.id if target_user else None, target_application_id=target_application_id, + target_role_ids=[r.id for r in target_role_ids] if target_role_ids is not None else None, flags=flags.value if flags else None, ) return Invite.from_incomplete(data=data, state=self._state) diff --git a/discord/http.py b/discord/http.py index 05c1e69fc..d08f8b9f0 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1854,6 +1854,7 @@ class HTTPClient: target_type: Optional[invite.InviteTargetType] = None, target_user_id: Optional[Snowflake] = None, target_application_id: Optional[Snowflake] = None, + target_role_ids: Optional[SnowflakeList] = None, flags: Optional[int] = None, ) -> Response[invite.Invite]: r = Route('POST', '/channels/{channel_id}/invites', channel_id=channel_id) @@ -1873,6 +1874,9 @@ class HTTPClient: if target_application_id: payload['target_application_id'] = str(target_application_id) + if target_role_ids is not None: + payload['target_role_ids'] = [str(r) for r in target_role_ids] + if flags: payload['flags'] = flags diff --git a/discord/invite.py b/discord/invite.py index 8b5088a89..390ce84d0 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -37,6 +37,7 @@ from .flags import InviteFlags __all__ = ( 'PartialInviteChannel', 'PartialInviteGuild', + 'PartialInviteRole', 'Invite', ) @@ -48,6 +49,7 @@ if TYPE_CHECKING: InviteGuild as InviteGuildPayload, GatewayInvite as GatewayInvitePayload, ) + from .types.role import PartialInviteRole as PartialInviteRolePayload from .types.guild import GuildFeature from .types.channel import ( PartialChannel as InviteChannelPayload, @@ -64,6 +66,47 @@ if TYPE_CHECKING: import datetime +class PartialInviteRole: + """Represents a partial role that is assigned to a user upon accepting an invite. + + .. versionadded:: 2.5 + + Attributes + ----------- + id: :class:`int` + The role's ID. + name: :class:`str` + The role's name. + position: :class:`int` + The role's position in the role hierarchy. + color: :class:`int` + The role's color as an integer. + icon: Optional[:class:`str`] + The role's icon hash, if any. + unicode_emoji: Optional[:class:`str`] + The role's unicode emoji, if any. + """ + + __slots__ = ('id', 'name', 'position', 'color', 'icon', 'unicode_emoji') + + def __init__(self, data: PartialInviteRolePayload) -> None: + self.id: int = int(data['id']) + self.name: str = data['name'] + self.position: int = data['position'] + self.color: int = data['color'] + self.icon: Optional[str] = data.get('icon') + self.unicode_emoji: Optional[str] = data.get('unicode_emoji') + + def __repr__(self) -> str: + return f'' + + def __eq__(self, other: object) -> bool: + return isinstance(other, PartialInviteRole) and other.id == self.id + + def __hash__(self) -> int: + return hash(self.id) + + class PartialInviteChannel: """Represents a "partial" invite channel. @@ -358,6 +401,11 @@ class Invite(Hashable): The ID of the scheduled event associated with this invite, if any. .. versionadded:: 2.0 + roles: List[:class:`PartialInviteRole`] + The roles that will be assigned to the user upon accepting this invite. + Empty list if no roles are assigned. + + .. versionadded:: 2.5 """ __slots__ = ( @@ -382,6 +430,7 @@ class Invite(Hashable): 'scheduled_event_id', 'type', '_flags', + 'roles', ) BASE = 'https://discord.gg' @@ -436,6 +485,9 @@ class Invite(Hashable): ) self.scheduled_event_id: Optional[int] = self.scheduled_event.id if self.scheduled_event else None self._flags: int = data.get('flags', 0) + self.roles: List[PartialInviteRole] = [ + PartialInviteRole(r) for r in data.get('roles', []) + ] @classmethod def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self: diff --git a/discord/types/invite.py b/discord/types/invite.py index 38e28f959..64eec8433 100644 --- a/discord/types/invite.py +++ b/discord/types/invite.py @@ -24,7 +24,7 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import Literal, Optional, TypedDict, Union +from typing import List, Literal, Optional, TypedDict, Union from typing_extensions import NotRequired from .scheduled_event import GuildScheduledEvent @@ -33,6 +33,7 @@ from .guild import InviteGuild, _GuildPreviewUnique from .channel import PartialChannel from .user import PartialUser from .appinfo import PartialAppInfo +from .role import PartialInviteRole InviteTargetType = Literal[1, 2] InviteType = Literal[0, 1, 2] @@ -66,6 +67,7 @@ class Invite(IncompleteInvite, total=False): type: InviteType flags: NotRequired[int] expires_at: Optional[str] + roles: List[PartialInviteRole] class InviteWithCounts(Invite, _GuildPreviewUnique): ... diff --git a/discord/types/role.py b/discord/types/role.py index dabd1c1cf..f811aa021 100644 --- a/discord/types/role.py +++ b/discord/types/role.py @@ -59,3 +59,13 @@ class RoleTags(TypedDict, total=False): premium_subscriber: None available_for_purchase: None guild_connections: None + + +class PartialInviteRole(TypedDict): + id: Snowflake + name: str + position: int + color: int + colors: RoleColours + icon: Optional[str] + unicode_emoji: Optional[str]