Add support for fetching the original interaction response message
This commit is contained in:
		@@ -25,19 +25,19 @@ DEALINGS IN THE SOFTWARE.
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
from __future__ import annotations
 | 
			
		||||
from discord.types.interactions import InteractionResponse
 | 
			
		||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
 | 
			
		||||
import asyncio
 | 
			
		||||
 | 
			
		||||
from . import utils
 | 
			
		||||
from .enums import try_enum, InteractionType, InteractionResponseType
 | 
			
		||||
from .errors import InteractionResponded
 | 
			
		||||
from .errors import InteractionResponded, HTTPException, ClientException
 | 
			
		||||
 | 
			
		||||
from .user import User
 | 
			
		||||
from .member import Member
 | 
			
		||||
from .message import Message, Attachment
 | 
			
		||||
from .object import Object
 | 
			
		||||
from .permissions import Permissions
 | 
			
		||||
from .webhook.async_ import async_context, Webhook
 | 
			
		||||
from .webhook.async_ import async_context, Webhook, handle_message_parameters
 | 
			
		||||
 | 
			
		||||
__all__ = (
 | 
			
		||||
    'Interaction',
 | 
			
		||||
@@ -51,6 +51,8 @@ if TYPE_CHECKING:
 | 
			
		||||
    )
 | 
			
		||||
    from .guild import Guild
 | 
			
		||||
    from .state import ConnectionState
 | 
			
		||||
    from .file import File
 | 
			
		||||
    from .mentions import AllowedMentions
 | 
			
		||||
    from aiohttp import ClientSession
 | 
			
		||||
    from .embeds import Embed
 | 
			
		||||
    from .ui.view import View
 | 
			
		||||
@@ -105,6 +107,7 @@ class Interaction:
 | 
			
		||||
        '_permissions',
 | 
			
		||||
        '_state',
 | 
			
		||||
        '_session',
 | 
			
		||||
        '_original_message',
 | 
			
		||||
        '_cs_response',
 | 
			
		||||
        '_cs_followup',
 | 
			
		||||
    )
 | 
			
		||||
@@ -112,6 +115,7 @@ class Interaction:
 | 
			
		||||
    def __init__(self, *, data: InteractionPayload, state: ConnectionState):
 | 
			
		||||
        self._state: ConnectionState = state
 | 
			
		||||
        self._session: ClientSession = state.http._HTTPClient__session
 | 
			
		||||
        self._original_message: Optional[InteractionMessage] = None
 | 
			
		||||
        self._from_data(data)
 | 
			
		||||
 | 
			
		||||
    def _from_data(self, data: InteractionPayload):
 | 
			
		||||
@@ -122,7 +126,7 @@ class Interaction:
 | 
			
		||||
        self.version: int = data['version']
 | 
			
		||||
        self.channel_id: Optional[int] = utils._get_as_snowflake(data, 'channel_id')
 | 
			
		||||
        self.guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id')
 | 
			
		||||
        self.application_id: Optional[int] = utils._get_as_snowflake(data, 'application_id')
 | 
			
		||||
        self.application_id: int = int(data['application_id'])
 | 
			
		||||
 | 
			
		||||
        channel = self.channel or Object(id=self.channel_id)  # type: ignore
 | 
			
		||||
        self.message: Optional[Message]
 | 
			
		||||
@@ -175,7 +179,11 @@ class Interaction:
 | 
			
		||||
 | 
			
		||||
    @utils.cached_slot_property('_cs_response')
 | 
			
		||||
    def response(self) -> InteractionResponse:
 | 
			
		||||
        """:class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction."""
 | 
			
		||||
        """:class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction.
 | 
			
		||||
 | 
			
		||||
        A response can only be done once. If secondary messages need to be sent, consider using :attr:`followup`
 | 
			
		||||
        instead.
 | 
			
		||||
        """
 | 
			
		||||
        return InteractionResponse(self)
 | 
			
		||||
 | 
			
		||||
    @utils.cached_slot_property('_cs_followup')
 | 
			
		||||
@@ -188,6 +196,146 @@ class Interaction:
 | 
			
		||||
        }
 | 
			
		||||
        return Webhook.from_state(data=payload, state=self._state)
 | 
			
		||||
 | 
			
		||||
    async def original_message(self) -> InteractionMessage:
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Fetches the original interaction response message associated with the interaction.
 | 
			
		||||
 | 
			
		||||
        If the interaction response was :meth:`InteractionResponse.send_message` then this would
 | 
			
		||||
        return the message that was sent using that response. Otherwise, this would return
 | 
			
		||||
        the message that triggered the interaction.
 | 
			
		||||
 | 
			
		||||
        Repeated calls to this will return a cached value.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Fetching the original response message failed.
 | 
			
		||||
        ClientException
 | 
			
		||||
            The channel for the message could not be resolved.
 | 
			
		||||
 | 
			
		||||
        Returns
 | 
			
		||||
        --------
 | 
			
		||||
        InteractionMessage
 | 
			
		||||
            The original interaction response message.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if self._original_message is not None:
 | 
			
		||||
            return self._original_message
 | 
			
		||||
 | 
			
		||||
        # TODO: fix later to not raise?
 | 
			
		||||
        channel = self.channel
 | 
			
		||||
        if channel is None:
 | 
			
		||||
            raise ClientException('Channel for message could not be resolved')
 | 
			
		||||
 | 
			
		||||
        adapter = async_context.get()
 | 
			
		||||
        data = await adapter.get_original_interaction_response(
 | 
			
		||||
            application_id=self.application_id,
 | 
			
		||||
            token=self.token,
 | 
			
		||||
            session=self._session,
 | 
			
		||||
        )
 | 
			
		||||
        state = _InteractionMessageState(self, self._state)
 | 
			
		||||
        message = InteractionMessage(state=state, channel=channel, data=data)  # type: ignore
 | 
			
		||||
        self._original_message = message
 | 
			
		||||
        return message
 | 
			
		||||
 | 
			
		||||
    async def edit_original_message(
 | 
			
		||||
        self,
 | 
			
		||||
        *,
 | 
			
		||||
        content: Optional[str] = MISSING,
 | 
			
		||||
        embeds: List[Embed] = MISSING,
 | 
			
		||||
        embed: Optional[Embed] = MISSING,
 | 
			
		||||
        file: File = MISSING,
 | 
			
		||||
        files: List[File] = MISSING,
 | 
			
		||||
        view: Optional[View] = MISSING,
 | 
			
		||||
        allowed_mentions: Optional[AllowedMentions] = None,
 | 
			
		||||
    ):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Edits the original interaction response message.
 | 
			
		||||
 | 
			
		||||
        This is a lower level interface to :meth:`InteractionMessage.edit` in case
 | 
			
		||||
        you do not want to fetch the message and save an HTTP request.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ------------
 | 
			
		||||
        content: Optional[:class:`str`]
 | 
			
		||||
            The content to edit the message with or ``None`` to clear it.
 | 
			
		||||
        embeds: List[:class:`Embed`]
 | 
			
		||||
            A list of embeds to edit the message with.
 | 
			
		||||
        embed: Optional[:class:`Embed`]
 | 
			
		||||
            The embed to edit the message with. ``None`` suppresses the embeds.
 | 
			
		||||
            This should not be mixed with the ``embeds`` parameter.
 | 
			
		||||
        file: :class:`File`
 | 
			
		||||
            The file to upload. This cannot be mixed with ``files`` parameter.
 | 
			
		||||
        files: List[:class:`File`]
 | 
			
		||||
            A list of files to send with the content. This cannot be mixed with the
 | 
			
		||||
            ``file`` parameter.
 | 
			
		||||
        allowed_mentions: :class:`AllowedMentions`
 | 
			
		||||
            Controls the mentions being processed in this message.
 | 
			
		||||
            See :meth:`.abc.Messageable.send` for more information.
 | 
			
		||||
        view: Optional[:class:`~discord.ui.View`]
 | 
			
		||||
            The updated view to update this message with. If ``None`` is passed then
 | 
			
		||||
            the view is removed.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Editing the message failed.
 | 
			
		||||
        Forbidden
 | 
			
		||||
            Edited a message that is not yours.
 | 
			
		||||
        TypeError
 | 
			
		||||
            You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
 | 
			
		||||
        ValueError
 | 
			
		||||
            The length of ``embeds`` was invalid
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        previous_mentions: Optional[AllowedMentions] = self._state.allowed_mentions
 | 
			
		||||
        params = handle_message_parameters(
 | 
			
		||||
            content=content,
 | 
			
		||||
            file=file,
 | 
			
		||||
            files=files,
 | 
			
		||||
            embed=embed,
 | 
			
		||||
            embeds=embeds,
 | 
			
		||||
            view=view,
 | 
			
		||||
            allowed_mentions=allowed_mentions,
 | 
			
		||||
            previous_allowed_mentions=previous_mentions,
 | 
			
		||||
        )
 | 
			
		||||
        adapter = async_context.get()
 | 
			
		||||
        data = await adapter.edit_original_interaction_response(
 | 
			
		||||
            self.application_id,
 | 
			
		||||
            self.token,
 | 
			
		||||
            session=self._session,
 | 
			
		||||
            payload=params.payload,
 | 
			
		||||
            multipart=params.multipart,
 | 
			
		||||
            files=params.files,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if view and not view.is_finished():
 | 
			
		||||
            self._state.store_view(view, int(data['id']))
 | 
			
		||||
 | 
			
		||||
    async def delete_original_message(self) -> None:
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Deletes the original interaction response message.
 | 
			
		||||
 | 
			
		||||
        This is a lower level interface to :meth:`InteractionMessage.delete` in case
 | 
			
		||||
        you do not want to fetch the message and save an HTTP request.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Deleting the message failed.
 | 
			
		||||
        Forbidden
 | 
			
		||||
            Deleted a message that is not yours.
 | 
			
		||||
        """
 | 
			
		||||
        adapter = async_context.get()
 | 
			
		||||
        await adapter.delete_original_interaction_response(
 | 
			
		||||
            self.application_id,
 | 
			
		||||
            self.token,
 | 
			
		||||
            session=self._session,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InteractionResponse:
 | 
			
		||||
    """Represents a Discord interaction response.
 | 
			
		||||
@@ -417,7 +565,6 @@ class InteractionResponse:
 | 
			
		||||
        if parent.type is not InteractionType.component:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        # TODO: embeds: List[Embed]?
 | 
			
		||||
        payload = {}
 | 
			
		||||
        if content is not MISSING:
 | 
			
		||||
            if content is None:
 | 
			
		||||
@@ -460,3 +607,130 @@ class InteractionResponse:
 | 
			
		||||
            state.store_view(view, message_id)
 | 
			
		||||
 | 
			
		||||
        self._responded = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _InteractionMessageState:
 | 
			
		||||
    __slots__ = ('_parent', '_interaction')
 | 
			
		||||
 | 
			
		||||
    def __init__(self, interaction: Interaction, parent: ConnectionState):
 | 
			
		||||
        self._interaction: Interaction = interaction
 | 
			
		||||
        self._parent: ConnectionState = parent
 | 
			
		||||
 | 
			
		||||
    def _get_guild(self, guild_id):
 | 
			
		||||
        return self._parent._get_guild(guild_id)
 | 
			
		||||
 | 
			
		||||
    def store_user(self, data):
 | 
			
		||||
        return self._parent.store_user(data)
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
    def http(self):
 | 
			
		||||
        return self._parent.http
 | 
			
		||||
 | 
			
		||||
    def __getattr__(self, attr):
 | 
			
		||||
        return getattr(self._parent, attr)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InteractionMessage(Message):
 | 
			
		||||
    """Represents the original interaction response message.
 | 
			
		||||
 | 
			
		||||
    This allows you to edit or delete the message associated with
 | 
			
		||||
    the interaction response. To retrieve this object see :meth:`Interaction.original_message`.
 | 
			
		||||
 | 
			
		||||
    This inherits from :class:`discord.Message` with changes to
 | 
			
		||||
    :meth:`edit` and :meth:`delete` to work.
 | 
			
		||||
 | 
			
		||||
    .. versionadded:: 2.0
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    __slots__ = ()
 | 
			
		||||
    _state: _InteractionMessageState
 | 
			
		||||
 | 
			
		||||
    async def edit(
 | 
			
		||||
        self,
 | 
			
		||||
        content: Optional[str] = MISSING,
 | 
			
		||||
        embeds: List[Embed] = MISSING,
 | 
			
		||||
        embed: Optional[Embed] = MISSING,
 | 
			
		||||
        file: File = MISSING,
 | 
			
		||||
        files: List[File] = MISSING,
 | 
			
		||||
        view: Optional[View] = MISSING,
 | 
			
		||||
        allowed_mentions: Optional[AllowedMentions] = None,
 | 
			
		||||
    ):
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Edits the message.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        ------------
 | 
			
		||||
        content: Optional[:class:`str`]
 | 
			
		||||
            The content to edit the message with or ``None`` to clear it.
 | 
			
		||||
        embeds: List[:class:`Embed`]
 | 
			
		||||
            A list of embeds to edit the message with.
 | 
			
		||||
        embed: Optional[:class:`Embed`]
 | 
			
		||||
            The embed to edit the message with. ``None`` suppresses the embeds.
 | 
			
		||||
            This should not be mixed with the ``embeds`` parameter.
 | 
			
		||||
        file: :class:`File`
 | 
			
		||||
            The file to upload. This cannot be mixed with ``files`` parameter.
 | 
			
		||||
        files: List[:class:`File`]
 | 
			
		||||
            A list of files to send with the content. This cannot be mixed with the
 | 
			
		||||
            ``file`` parameter.
 | 
			
		||||
        allowed_mentions: :class:`AllowedMentions`
 | 
			
		||||
            Controls the mentions being processed in this message.
 | 
			
		||||
            See :meth:`.abc.Messageable.send` for more information.
 | 
			
		||||
        view: Optional[:class:`~discord.ui.View`]
 | 
			
		||||
            The updated view to update this message with. If ``None`` is passed then
 | 
			
		||||
            the view is removed.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        -------
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Editing the message failed.
 | 
			
		||||
        Forbidden
 | 
			
		||||
            Edited a message that is not yours.
 | 
			
		||||
        TypeError
 | 
			
		||||
            You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
 | 
			
		||||
        ValueError
 | 
			
		||||
            The length of ``embeds`` was invalid
 | 
			
		||||
        """
 | 
			
		||||
        await self._state._interaction.edit_original_message(
 | 
			
		||||
            content=content,
 | 
			
		||||
            embeds=embeds,
 | 
			
		||||
            embed=embed,
 | 
			
		||||
            file=file,
 | 
			
		||||
            files=files,
 | 
			
		||||
            view=view,
 | 
			
		||||
            allowed_mentions=allowed_mentions,
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    async def delete(self, *, delay: Optional[float] = None) -> None:
 | 
			
		||||
        """|coro|
 | 
			
		||||
 | 
			
		||||
        Deletes the message.
 | 
			
		||||
 | 
			
		||||
        Parameters
 | 
			
		||||
        -----------
 | 
			
		||||
        delay: Optional[:class:`float`]
 | 
			
		||||
            If provided, the number of seconds to wait before deleting the message.
 | 
			
		||||
            The waiting is done in the background and deletion failures are ignored.
 | 
			
		||||
 | 
			
		||||
        Raises
 | 
			
		||||
        ------
 | 
			
		||||
        Forbidden
 | 
			
		||||
            You do not have proper permissions to delete the message.
 | 
			
		||||
        NotFound
 | 
			
		||||
            The message was deleted already.
 | 
			
		||||
        HTTPException
 | 
			
		||||
            Deleting the message failed.
 | 
			
		||||
        """
 | 
			
		||||
 | 
			
		||||
        if delay is not None:
 | 
			
		||||
 | 
			
		||||
            async def inner_call(delay: float = delay):
 | 
			
		||||
                await asyncio.sleep(delay)
 | 
			
		||||
                try:
 | 
			
		||||
                    await self._state._interaction.delete_original_message()
 | 
			
		||||
                except HTTPException:
 | 
			
		||||
                    pass
 | 
			
		||||
 | 
			
		||||
            asyncio.create_task(inner_call())
 | 
			
		||||
        else:
 | 
			
		||||
            await self._state._interaction.delete_original_message()
 | 
			
		||||
 
 | 
			
		||||
@@ -3271,6 +3271,14 @@ InteractionResponse
 | 
			
		||||
.. autoclass:: InteractionResponse()
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
InteractionMessage
 | 
			
		||||
~~~~~~~~~~~~~~~~~~~
 | 
			
		||||
 | 
			
		||||
.. attributetable:: InteractionMessage
 | 
			
		||||
 | 
			
		||||
.. autoclass:: InteractionMessage()
 | 
			
		||||
    :members:
 | 
			
		||||
 | 
			
		||||
Member
 | 
			
		||||
~~~~~~
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user