aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--discord/interactions.py286
-rw-r--r--docs/api.rst8
2 files changed, 288 insertions, 6 deletions
diff --git a/discord/interactions.py b/discord/interactions.py
index 3e9bbce6..b81dfa08 100644
--- a/discord/interactions.py
+++ b/discord/interactions.py
@@ -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()
diff --git a/docs/api.rst b/docs/api.rst
index 83dbca4e..ee40fa1b 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -3271,6 +3271,14 @@ InteractionResponse
.. autoclass:: InteractionResponse()
:members:
+InteractionMessage
+~~~~~~~~~~~~~~~~~~~
+
+.. attributetable:: InteractionMessage
+
+.. autoclass:: InteractionMessage()
+ :members:
+
Member
~~~~~~