From 774b934f7432ea7274befb3580fd51cdc8cf366e Mon Sep 17 00:00:00 2001 From: Soheab <33902984+Soheab@users.noreply.github.com> Date: Thu, 3 Jul 2025 00:59:40 +0200 Subject: [PATCH] Add support for guest invites --- discord/abc.py | 12 +++++++++ discord/audit_logs.py | 11 ++++++-- discord/flags.py | 57 +++++++++++++++++++++++++++++++++++++++++ discord/http.py | 4 +++ discord/invite.py | 11 ++++++++ discord/member.py | 3 ++- discord/types/invite.py | 2 ++ discord/types/member.py | 2 +- docs/api.rst | 16 +++++++++--- 9 files changed, 111 insertions(+), 7 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 692472f8f..713398a7d 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -60,6 +60,7 @@ from .http import handle_message_parameters from .voice_client import VoiceClient, VoiceProtocol from .sticker import GuildSticker, StickerItem from . import utils +from .flags import InviteFlags __all__ = ( 'Snowflake', @@ -1257,6 +1258,7 @@ class GuildChannel: target_type: Optional[InviteTarget] = None, target_user: Optional[User] = None, target_application_id: Optional[int] = None, + guest: bool = False, ) -> Invite: """|coro| @@ -1295,6 +1297,10 @@ class GuildChannel: The id of the embedded application for the invite, required if ``target_type`` is :attr:`.InviteTarget.embedded_application`. .. versionadded:: 2.0 + guest: :class:`bool` + Whether the invite is a guest invite. + + .. versionadded:: 2.6 Raises ------- @@ -1312,6 +1318,11 @@ class GuildChannel: if target_type is InviteTarget.unknown: raise ValueError('Cannot create invite with an unknown target type') + flags: Optional[InviteFlags] = None + if guest: + flags = InviteFlags._from_value(0) + flags.guest = True + data = await self._state.http.create_invite( self.id, reason=reason, @@ -1322,6 +1333,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, + flags=flags.value if flags else None, ) return Invite.from_incomplete(data=data, state=self._state) diff --git a/discord/audit_logs.py b/discord/audit_logs.py index af67855d4..89577769f 100644 --- a/discord/audit_logs.py +++ b/discord/audit_logs.py @@ -145,8 +145,8 @@ def _transform_applied_forum_tags(entry: AuditLogEntry, data: List[Snowflake]) - return [Object(id=tag_id, type=ForumTag) for tag_id in data] -def _transform_overloaded_flags(entry: AuditLogEntry, data: int) -> Union[int, flags.ChannelFlags]: - # The `flags` key is definitely overloaded. Right now it's for channels and threads but +def _transform_overloaded_flags(entry: AuditLogEntry, data: int) -> Union[int, flags.ChannelFlags, flags.InviteFlags]: + # The `flags` key is definitely overloaded. Right now it's for channels, threads and invites but # I am aware of `member.flags` and `user.flags` existing. However, this does not impact audit logs # at the moment but better safe than sorry. channel_audit_log_types = ( @@ -157,9 +157,16 @@ def _transform_overloaded_flags(entry: AuditLogEntry, data: int) -> Union[int, f enums.AuditLogAction.thread_update, enums.AuditLogAction.thread_delete, ) + invite_audit_log_types = ( + enums.AuditLogAction.invite_create, + enums.AuditLogAction.invite_update, + enums.AuditLogAction.invite_delete, + ) if entry.action in channel_audit_log_types: return flags.ChannelFlags._from_value(data) + elif entry.action in invite_audit_log_types: + return flags.InviteFlags._from_value(data) return data diff --git a/discord/flags.py b/discord/flags.py index 20f8c5470..59a4909b8 100644 --- a/discord/flags.py +++ b/discord/flags.py @@ -64,6 +64,7 @@ __all__ = ( 'AppInstallationType', 'SKUFlags', 'EmbedFlags', + 'InviteFlags', ) BF = TypeVar('BF', bound='BaseFlags') @@ -2397,3 +2398,59 @@ class EmbedFlags(BaseFlags): longer displayed. """ return 1 << 5 + + +class InviteFlags(BaseFlags): + r"""Wraps up the Discord Invite flags + + .. versionadded:: 2.6 + + .. container:: operations + + .. describe:: x == y + + Checks if two InviteFlags are equal. + + .. describe:: x != y + + Checks if two InviteFlags are not equal. + + .. describe:: x | y, x |= y + + Returns a InviteFlags instance with all enabled flags from + both x and y. + + .. describe:: x ^ y, x ^= y + + Returns a InviteFlags instance with only flags enabled on + only one of x or y, not on both. + + .. describe:: ~x + + Returns a InviteFlags instance with all flags inverted from x. + + .. 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. + + .. describe:: bool(b) + + Returns whether any flag is set to ``True``. + + Attributes + ---------- + value: :class:`int` + The raw value. You should query flags via the properties + rather than using this raw value. + """ + + @flag_value + def guest(self): + """:class:`bool`: Returns ``True`` if this is a guest invite for a voice channel.""" + return 1 << 0 diff --git a/discord/http.py b/discord/http.py index f1f4ec58b..71912f71b 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1834,6 +1834,7 @@ class HTTPClient: target_type: Optional[invite.InviteTargetType] = None, target_user_id: Optional[Snowflake] = None, target_application_id: Optional[Snowflake] = None, + flags: Optional[int] = None, ) -> Response[invite.Invite]: r = Route('POST', '/channels/{channel_id}/invites', channel_id=channel_id) payload = { @@ -1852,6 +1853,9 @@ class HTTPClient: if target_application_id: payload['target_application_id'] = str(target_application_id) + if flags: + payload['flags'] = flags + return self.request(r, reason=reason, json=payload) def get_invite( diff --git a/discord/invite.py b/discord/invite.py index 8c37bd232..362f97693 100644 --- a/discord/invite.py +++ b/discord/invite.py @@ -32,6 +32,7 @@ from .mixins import Hashable from .enums import ChannelType, NSFWLevel, VerificationLevel, InviteTarget, InviteType, try_enum from .appinfo import PartialAppInfo from .scheduled_event import ScheduledEvent +from .flags import InviteFlags __all__ = ( 'PartialInviteChannel', @@ -379,6 +380,7 @@ class Invite(Hashable): 'scheduled_event', 'scheduled_event_id', 'type', + '_flags', ) BASE = 'https://discord.gg' @@ -432,6 +434,7 @@ class Invite(Hashable): else None ) self.scheduled_event_id: Optional[int] = self.scheduled_event.id if self.scheduled_event else None + self._flags: int = data.get('flags', 0) @classmethod def from_incomplete(cls, *, state: ConnectionState, data: InvitePayload) -> Self: @@ -523,6 +526,14 @@ class Invite(Hashable): url += '?event=' + str(self.scheduled_event_id) return url + @property + def flags(self) -> InviteFlags: + """:class:`InviteFlags`: Returns the flags for this invite. + + .. versionadded:: 2.6 + """ + return InviteFlags._from_value(self._flags) + def set_scheduled_event(self, scheduled_event: Snowflake, /) -> Self: """Sets the scheduled event for this invite. diff --git a/discord/member.py b/discord/member.py index 6af1571f4..ed52600dd 100644 --- a/discord/member.py +++ b/discord/member.py @@ -238,7 +238,8 @@ class Member(discord.abc.Messageable, _UserTag): ---------- joined_at: Optional[:class:`datetime.datetime`] An aware datetime object that specifies the date and time in UTC that the member joined the guild. - If the member left and rejoined the guild, this will be the latest date. In certain cases, this can be ``None``. + If the member left and rejoined the guild, this will be the latest date. + This can be ``None``, such as when the member is a guest. activities: Tuple[Union[:class:`BaseActivity`, :class:`Spotify`]] The activities that the user is currently doing. diff --git a/discord/types/invite.py b/discord/types/invite.py index f5f00078e..47c972994 100644 --- a/discord/types/invite.py +++ b/discord/types/invite.py @@ -65,6 +65,7 @@ class Invite(IncompleteInvite, total=False): target_application: PartialAppInfo guild_scheduled_event: GuildScheduledEvent type: InviteType + flags: NotRequired[int] class InviteWithCounts(Invite, _GuildPreviewUnique): @@ -84,6 +85,7 @@ class GatewayInviteCreate(TypedDict): target_type: NotRequired[InviteTargetType] target_user: NotRequired[PartialUser] target_application: NotRequired[PartialAppInfo] + flags: NotRequired[int] class GatewayInviteDelete(TypedDict): diff --git a/discord/types/member.py b/discord/types/member.py index 88fb619fd..576ef421d 100644 --- a/discord/types/member.py +++ b/discord/types/member.py @@ -34,7 +34,7 @@ class Nickname(TypedDict): class PartialMember(TypedDict): roles: SnowflakeList - joined_at: str + joined_at: Optional[str] # null if guest deaf: bool mute: bool flags: int diff --git a/docs/api.rst b/docs/api.rst index dda5553b7..dc6775ec6 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2517,6 +2517,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.channel` - :attr:`~AuditLogDiff.uses` - :attr:`~AuditLogDiff.max_uses` + - :attr:`~AuditLogDiff.flags` .. attribute:: invite_update @@ -2541,6 +2542,7 @@ of :class:`enum.Enum`. - :attr:`~AuditLogDiff.channel` - :attr:`~AuditLogDiff.uses` - :attr:`~AuditLogDiff.max_uses` + - :attr:`~AuditLogDiff.flags` .. attribute:: webhook_create @@ -4552,11 +4554,11 @@ AuditLogDiff .. attribute:: flags - The channel flags associated with this thread or forum post. + The flags associated with this thread, forum post or invite. - See also :attr:`ForumChannel.flags` and :attr:`Thread.flags` + See also :attr:`ForumChannel.flags`, :attr:`Thread.flags` and :attr:`Invite.flags` - :type: :class:`ChannelFlags` + :type: Union[:class:`ChannelFlags`, :class:`InviteFlags`] .. attribute:: default_thread_slowmode_delay @@ -5734,6 +5736,14 @@ EmbedFlags .. autoclass:: EmbedFlags() :members: +InviteFlags +~~~~~~~~~~~~~~~~ + +.. attributetable:: InviteFlags + +.. autoclass:: InviteFlags() + :members: + ForumTag ~~~~~~~~~