From 173f27cdfe2e265bdd98f3b739ae5f2a7e868b23 Mon Sep 17 00:00:00 2001 From: Developer Anonymous Date: Fri, 4 Oct 2024 16:34:06 +0200 Subject: [PATCH] Added more InteractionCallback things --- discord/interactions.py | 205 ++++++++++++++++++++++++++++++++-- discord/types/interactions.py | 25 +++++ discord/webhook/async_.py | 19 +++- 3 files changed, 239 insertions(+), 10 deletions(-) diff --git a/discord/interactions.py b/discord/interactions.py index e0fb7ed86..589f3ea76 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -61,6 +61,8 @@ if TYPE_CHECKING: Interaction as InteractionPayload, InteractionData, ApplicationCommandInteractionData, + InteractionCallbackResponse as InteractionCallbackResponsePayload, + InteractionCallbackActivity as InteractionCallbackActivityPayload, ) from .types.webhook import ( Webhook as WebhookPayload, @@ -466,6 +468,7 @@ class Interaction(Generic[ClientT]): attachments: Sequence[Union[Attachment, File]] = MISSING, view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = None, + poll: Poll = MISSING, ) -> InteractionMessage: """|coro| @@ -500,6 +503,14 @@ class Interaction(Generic[ClientT]): view: Optional[:class:`~discord.ui.View`] The updated view to update this message with. If ``None`` is passed then the view is removed. + poll: :class:`Poll` + The poll to create when editing the message. + + .. versionadded:: 2.5 + + .. note:: + + This is only accepted when the response type is :attr:`InteractionResponseType.deferred_channel_message`. Raises ------- @@ -529,6 +540,7 @@ class Interaction(Generic[ClientT]): view=view, allowed_mentions=allowed_mentions, previous_allowed_mentions=previous_mentions, + poll=poll, ) as params: adapter = async_context.get() http = self._state.http @@ -621,6 +633,98 @@ class Interaction(Generic[ClientT]): return await translator.translate(string, locale=locale, context=context) +class InteractionCallbackActivity: + """Represents an activity instance returned by an interaction callback. + + .. versionadded:: 2.5 + + Attributes + ---------- + id: :class:`str` + The activity instance ID. + """ + + __slots__ = ('id',) + + def __init__(self, *, data: InteractionCallbackActivityPayload) -> None: + self.id: str = data['id'] + + def __repr__(self) -> str: + return f'' + + +class InteractionCallback(Generic[ClientT]): + """Represents a Discord response to an interaction. + + .. versionadded:: 2.5 + """ + + __slots__ = ( + 'id', + '_parent', + '_state', + 'interaction_type', + 'activity_instance_id', + 'activity_instance', + 'response_message_id', + 'response_message_loading', + 'response_message_ephemeral', + 'response_message', + 'response_channel', + 'callback_type', + ) + + def __init__( + self, + *, + parent: Interaction[ClientT], + channel: InteractionChannel, + data: InteractionCallbackResponsePayload, + ) -> None: + self._parent: Interaction[ClientT] = parent + self._state: ConnectionState = parent._state + self.response_channel: InteractionChannel = channel + self._update(data) + + def _update(self, data: InteractionCallbackResponsePayload) -> None: + interaction = data['interaction'] + self.id: int = int(interaction['id']) + self.interaction_type: InteractionType = try_enum(InteractionType, interaction['type']) + self.activity_instance_id: Optional[str] = interaction.get('activity_instance_id') + response_id = interaction.get('response_message_id') + self.response_message_id: Optional[int] = int( + response_id, + ) if response_id is not None else None + self.response_message_loading: Optional[bool] = interaction.get('response_message_loading') + self.response_message_ephemeral: Optional[bool] = interaction.get('response_message_ephemeral') + + resource = data.get('resource', {}) + self.callback_type: Optional[InteractionResponseType] = None + self.activity_instance: Optional[InteractionCallbackActivity] = None + self.response_message: Optional[InteractionMessage] = None + + try: + self.callback_type = try_enum(InteractionResponseType, resource['type']) + except KeyError: + pass + + try: + self.activity_instance = InteractionCallbackActivity( + data=resource['activity_instance'], + ) + except KeyError: + pass + + try: + self.response_message = InteractionMessage( + state=self._state, + channel=self.response_channel, # type: ignore + data=resource['message'], + ) + except KeyError: + pass + + class InteractionResponse(Generic[ClientT]): """Represents a Discord interaction response. @@ -650,7 +754,13 @@ class InteractionResponse(Generic[ClientT]): """:class:`InteractionResponseType`: The type of response that was sent, ``None`` if response is not done.""" return self._response_type - async def defer(self, *, ephemeral: bool = False, thinking: bool = False) -> None: + async def defer( + self, + *, + ephemeral: bool = False, + thinking: bool = False, + with_response: bool = True, + ) -> Optional[InteractionCallback]: """|coro| Defers the interaction response. @@ -675,6 +785,10 @@ class InteractionResponse(Generic[ClientT]): In UI terms, this is represented as if the bot is thinking of a response. It is your responsibility to eventually send a followup message via :attr:`Interaction.followup` to make this thinking state go away. Application commands (AKA Slash commands) cannot use :attr:`InteractionResponseType.deferred_message_update`. + with_response: :class:`bool` + Whether to return the interaction response callback resource. + + .. versionadded:: 2.5 Raises ------- @@ -682,6 +796,11 @@ class InteractionResponse(Generic[ClientT]): Deferring the interaction failed. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + Optional[:class:`InteractionCallback`] + The interaction callback resource, or ``None``. """ if self._response_type: raise InteractionResponded(self._parent) @@ -706,15 +825,22 @@ class InteractionResponse(Generic[ClientT]): adapter = async_context.get() params = interaction_response_params(type=defer_type, data=data) http = parent._state.http - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, proxy=http.proxy, proxy_auth=http.proxy_auth, params=params, + with_response=with_response, ) self._response_type = InteractionResponseType(defer_type) + if response: + return InteractionCallback( + parent=parent, + channel=parent.channel, # type: ignore + data=response, + ) async def pong(self) -> None: """|coro| @@ -764,7 +890,8 @@ class InteractionResponse(Generic[ClientT]): silent: bool = False, delete_after: Optional[float] = None, poll: Poll = MISSING, - ) -> None: + with_response: bool = True, + ) -> Optional[InteractionCallback]: """|coro| Responds to this interaction by sending a message. @@ -811,6 +938,10 @@ class InteractionResponse(Generic[ClientT]): The poll to send with this message. .. versionadded:: 2.4 + with_response: :class:`bool` + Whether to return the interaction response callback resource. + + .. versionadded:: 2.5 Raises ------- @@ -822,6 +953,11 @@ class InteractionResponse(Generic[ClientT]): The length of ``embeds`` was invalid. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + Optional[:class:`InteractionCallback`] + The interaction callback data, or ``None``. """ if self._response_type: raise InteractionResponded(self._parent) @@ -852,13 +988,14 @@ class InteractionResponse(Generic[ClientT]): ) http = parent._state.http - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, proxy=http.proxy, proxy_auth=http.proxy_auth, params=params, + with_response=with_response, ) if view is not MISSING and not view.is_finished(): @@ -882,6 +1019,12 @@ class InteractionResponse(Generic[ClientT]): pass asyncio.create_task(inner_call()) + if response: + return InteractionCallback( + parent=parent, + channel=parent.channel, # type: ignore + data=response, + ) async def edit_message( self, @@ -894,7 +1037,8 @@ class InteractionResponse(Generic[ClientT]): allowed_mentions: Optional[AllowedMentions] = MISSING, delete_after: Optional[float] = None, suppress_embeds: bool = MISSING, - ) -> None: + with_response: bool = True, + ) -> Optional[InteractionCallback]: """|coro| Responds to this interaction by editing the original message of @@ -936,6 +1080,10 @@ class InteractionResponse(Generic[ClientT]): Using this parameter requires :attr:`~.Permissions.manage_messages`. .. versionadded:: 2.4 + with_response: :class:`bool` + Whether to return the interaction response callback resource. + + .. versionadded:: 2.5 Raises ------- @@ -945,6 +1093,11 @@ class InteractionResponse(Generic[ClientT]): You specified both ``embed`` and ``embeds``. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + Optional[:class:`InteractionCallback`] + The interaction callback data, or ``None``. """ if self._response_type: raise InteractionResponded(self._parent) @@ -987,13 +1140,14 @@ class InteractionResponse(Generic[ClientT]): ) http = parent._state.http - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, proxy=http.proxy, proxy_auth=http.proxy_auth, params=params, + with_response=with_response, ) if view and not view.is_finished(): @@ -1012,7 +1166,14 @@ class InteractionResponse(Generic[ClientT]): asyncio.create_task(inner_call()) - async def send_modal(self, modal: Modal, /) -> None: + if response: + return InteractionCallback( + parent=parent, + channel=parent.channel, # type: ignore + data=response, + ) + + async def send_modal(self, modal: Modal, /, *, with_response: bool = True) -> Optional[InteractionCallback]: """|coro| Responds to this interaction by sending a modal. @@ -1021,6 +1182,10 @@ class InteractionResponse(Generic[ClientT]): ----------- modal: :class:`~discord.ui.Modal` The modal to send. + with_response: :class:`bool` + Whether to return the interaction response callback resource. + + .. versionadded:: 2.5 Raises ------- @@ -1028,6 +1193,11 @@ class InteractionResponse(Generic[ClientT]): Sending the modal failed. InteractionResponded This interaction has already been responded to before. + + Returns + ------- + Optional[:class:`InteractionCallback`] + The interaction callback data, or ``None``. """ if self._response_type: raise InteractionResponded(self._parent) @@ -1038,18 +1208,26 @@ class InteractionResponse(Generic[ClientT]): http = parent._state.http params = interaction_response_params(InteractionResponseType.modal.value, modal.to_dict()) - await adapter.create_interaction_response( + response = await adapter.create_interaction_response( parent.id, parent.token, session=parent._session, proxy=http.proxy, proxy_auth=http.proxy_auth, params=params, + with_response=with_response, ) if not modal.is_finished(): self._parent._state.store_view(modal) self._response_type = InteractionResponseType.modal + if response: + return InteractionCallback( + parent=parent, + channel=parent.channel, # type: ignore + data=response, + ) + async def autocomplete(self, choices: Sequence[Choice[ChoiceT]]) -> None: """|coro| @@ -1151,6 +1329,7 @@ class InteractionMessage(Message): view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = None, delete_after: Optional[float] = None, + poll: Poll = MISSING, ) -> InteractionMessage: """|coro| @@ -1185,6 +1364,15 @@ class InteractionMessage(Message): then it is silently ignored. .. versionadded:: 2.2 + poll: :class:`~discord.Poll` + The poll to create when editing the message. + + .. versionadded:: 2.5 + + .. note:: + + This is only accepted if the interaction response's :attr:`InteractionResponse.type` + attribute is :attr:`InteractionResponseType.deferred_channel_message`. Raises ------- @@ -1209,6 +1397,7 @@ class InteractionMessage(Message): attachments=attachments, view=view, allowed_mentions=allowed_mentions, + poll=poll, ) if delete_after is not None: await self.delete(delay=delete_after) diff --git a/discord/types/interactions.py b/discord/types/interactions.py index 7aac5df7d..d6e1c289e 100644 --- a/discord/types/interactions.py +++ b/discord/types/interactions.py @@ -42,6 +42,7 @@ if TYPE_CHECKING: InteractionType = Literal[1, 2, 3, 4, 5] +InteractionResponseType = Literal[1, 4, 5, 6, 7, 8, 9, 10,] InteractionContextType = Literal[0, 1, 2] InteractionInstallationType = Literal[0, 1] @@ -263,3 +264,27 @@ class MessageInteractionMetadata(TypedDict): original_response_message_id: NotRequired[Snowflake] interacted_message_id: NotRequired[Snowflake] triggering_interaction_metadata: NotRequired[MessageInteractionMetadata] + + +class InteractionCallback(TypedDict): + id: Snowflake + type: InteractionType + activity_instance_id: NotRequired[str] + response_message_id: NotRequired[Snowflake] + response_message_loading: NotRequired[bool] + response_message_ephemeral: NotRequired[bool] + + +class InteractionCallbackActivity(TypedDict): + id: str + + +class InteractionCallbackResource(TypedDict): + type: InteractionResponseType + activity_instance: NotRequired[InteractionCallbackActivity] + message: NotRequired[Message] + + +class InteractionCallbackResponse(TypedDict): + interaction: InteractionCallback + resource: NotRequired[InteractionCallbackResource] diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 3b0416f9a..26510b585 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -90,6 +90,9 @@ if TYPE_CHECKING: ) from ..types.emoji import PartialEmoji as PartialEmojiPayload from ..types.snowflake import SnowflakeList + from ..types.interactions import ( + InteractionCallbackResponse as InteractionCallbackResponsePayload, + ) BE = TypeVar('BE', bound=BaseException) _State = Union[ConnectionState, '_WebhookState'] @@ -434,7 +437,8 @@ class AsyncWebhookAdapter: proxy: Optional[str] = None, proxy_auth: Optional[aiohttp.BasicAuth] = None, params: MultipartParameters, - ) -> Response[None]: + with_response: bool = MISSING, + ) -> Response[Optional[InteractionCallbackResponsePayload]]: route = Route( 'POST', '/interactions/{webhook_id}/{webhook_token}/callback', @@ -442,6 +446,9 @@ class AsyncWebhookAdapter: webhook_token=token, ) + if with_response is not MISSING: + request_params = {'with_response': with_response} + if params.files: return self.request( route, @@ -450,9 +457,17 @@ class AsyncWebhookAdapter: proxy_auth=proxy_auth, files=params.files, multipart=params.multipart, + params=request_params, ) else: - return self.request(route, session=session, proxy=proxy, proxy_auth=proxy_auth, payload=params.payload) + return self.request( + route, + session=session, + proxy=proxy, + proxy_auth=proxy_auth, + payload=params.payload, + params=request_params, + ) def get_original_interaction_response( self,