Add support for fetching the original interaction response message
This commit is contained in:
parent
be5603141e
commit
0b577fa209
@ -25,19 +25,19 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from discord.types.interactions import InteractionResponse
|
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
|
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
|
||||||
|
import asyncio
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .enums import try_enum, InteractionType, InteractionResponseType
|
from .enums import try_enum, InteractionType, InteractionResponseType
|
||||||
from .errors import InteractionResponded
|
from .errors import InteractionResponded, HTTPException, ClientException
|
||||||
|
|
||||||
from .user import User
|
from .user import User
|
||||||
from .member import Member
|
from .member import Member
|
||||||
from .message import Message, Attachment
|
from .message import Message, Attachment
|
||||||
from .object import Object
|
from .object import Object
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .webhook.async_ import async_context, Webhook
|
from .webhook.async_ import async_context, Webhook, handle_message_parameters
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Interaction',
|
'Interaction',
|
||||||
@ -51,6 +51,8 @@ if TYPE_CHECKING:
|
|||||||
)
|
)
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
from .state import ConnectionState
|
from .state import ConnectionState
|
||||||
|
from .file import File
|
||||||
|
from .mentions import AllowedMentions
|
||||||
from aiohttp import ClientSession
|
from aiohttp import ClientSession
|
||||||
from .embeds import Embed
|
from .embeds import Embed
|
||||||
from .ui.view import View
|
from .ui.view import View
|
||||||
@ -105,6 +107,7 @@ class Interaction:
|
|||||||
'_permissions',
|
'_permissions',
|
||||||
'_state',
|
'_state',
|
||||||
'_session',
|
'_session',
|
||||||
|
'_original_message',
|
||||||
'_cs_response',
|
'_cs_response',
|
||||||
'_cs_followup',
|
'_cs_followup',
|
||||||
)
|
)
|
||||||
@ -112,6 +115,7 @@ class Interaction:
|
|||||||
def __init__(self, *, data: InteractionPayload, state: ConnectionState):
|
def __init__(self, *, data: InteractionPayload, state: ConnectionState):
|
||||||
self._state: ConnectionState = state
|
self._state: ConnectionState = state
|
||||||
self._session: ClientSession = state.http._HTTPClient__session
|
self._session: ClientSession = state.http._HTTPClient__session
|
||||||
|
self._original_message: Optional[InteractionMessage] = None
|
||||||
self._from_data(data)
|
self._from_data(data)
|
||||||
|
|
||||||
def _from_data(self, data: InteractionPayload):
|
def _from_data(self, data: InteractionPayload):
|
||||||
@ -122,7 +126,7 @@ class Interaction:
|
|||||||
self.version: int = data['version']
|
self.version: int = data['version']
|
||||||
self.channel_id: Optional[int] = utils._get_as_snowflake(data, 'channel_id')
|
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.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
|
channel = self.channel or Object(id=self.channel_id) # type: ignore
|
||||||
self.message: Optional[Message]
|
self.message: Optional[Message]
|
||||||
@ -175,7 +179,11 @@ class Interaction:
|
|||||||
|
|
||||||
@utils.cached_slot_property('_cs_response')
|
@utils.cached_slot_property('_cs_response')
|
||||||
def response(self) -> InteractionResponse:
|
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)
|
return InteractionResponse(self)
|
||||||
|
|
||||||
@utils.cached_slot_property('_cs_followup')
|
@utils.cached_slot_property('_cs_followup')
|
||||||
@ -188,6 +196,146 @@ class Interaction:
|
|||||||
}
|
}
|
||||||
return Webhook.from_state(data=payload, state=self._state)
|
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:
|
class InteractionResponse:
|
||||||
"""Represents a Discord interaction response.
|
"""Represents a Discord interaction response.
|
||||||
@ -417,7 +565,6 @@ class InteractionResponse:
|
|||||||
if parent.type is not InteractionType.component:
|
if parent.type is not InteractionType.component:
|
||||||
return
|
return
|
||||||
|
|
||||||
# TODO: embeds: List[Embed]?
|
|
||||||
payload = {}
|
payload = {}
|
||||||
if content is not MISSING:
|
if content is not MISSING:
|
||||||
if content is None:
|
if content is None:
|
||||||
@ -460,3 +607,130 @@ class InteractionResponse:
|
|||||||
state.store_view(view, message_id)
|
state.store_view(view, message_id)
|
||||||
|
|
||||||
self._responded = True
|
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()
|
.. autoclass:: InteractionResponse()
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
InteractionMessage
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: InteractionMessage
|
||||||
|
|
||||||
|
.. autoclass:: InteractionMessage()
|
||||||
|
:members:
|
||||||
|
|
||||||
Member
|
Member
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user