Browse Source

Add support for setting interaction responses

pull/6961/head
Rapptz 4 years ago
parent
commit
3b83f60b35
  1. 1
      discord/enums.py
  2. 249
      discord/interactions.py
  3. 25
      docs/api.rst

1
discord/enums.py

@ -446,6 +446,7 @@ class InteractionResponseType(Enum):
channel_message = 4 # (with source) channel_message = 4 # (with source)
deferred_channel_message = 5 # (with source) deferred_channel_message = 5 # (with source)
deferred_message_update = 6 # for components deferred_message_update = 6 # for components
message_update = 7 # for components
class VideoQualityMode(Enum): class VideoQualityMode(Enum):
auto = 1 auto = 1

249
discord/interactions.py

@ -25,18 +25,21 @@ DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations from __future__ import annotations
from typing import Optional, TYPE_CHECKING, Tuple, Union from discord.types.interactions import InteractionResponse
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Tuple, Union
from . import utils from . import utils
from .enums import try_enum, InteractionType from .enums import try_enum, InteractionType, InteractionResponseType
from .user import User from .user import User
from .member import Member from .member import Member
from .message import Message from .message import Message, Attachment
from .object import Object from .object import Object
from .webhook.async_ import async_context
__all__ = ( __all__ = (
'Interaction', 'Interaction',
'InteractionResponse',
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -45,6 +48,12 @@ if TYPE_CHECKING:
) )
from .guild import Guild from .guild import Guild
from .abc import GuildChannel from .abc import GuildChannel
from .state import ConnectionState
from aiohttp import ClientSession
from .embeds import Embed
from .ui.view import View
MISSING: Any = utils.MISSING
class Interaction: class Interaction:
@ -89,10 +98,13 @@ class Interaction:
'token', 'token',
'version', 'version',
'_state', '_state',
'_session',
'_cs_response',
) )
def __init__(self, *, data: InteractionPayload, state=None): def __init__(self, *, data: InteractionPayload, state: ConnectionState):
self._state = state self._state = state
self._session: ClientSession = state.http._HTTPClient__session
self._from_data(data) self._from_data(data)
def _from_data(self, data: InteractionPayload): def _from_data(self, data: InteractionPayload):
@ -126,7 +138,6 @@ class Interaction:
except KeyError: except KeyError:
pass pass
@property @property
def guild(self) -> Optional[Guild]: def guild(self) -> Optional[Guild]:
"""Optional[:class:`Guild`]: The guild the interaction was sent from.""" """Optional[:class:`Guild`]: The guild the interaction was sent from."""
@ -141,3 +152,231 @@ class Interaction:
""" """
guild = self.guild guild = self.guild
return guild and guild.get_channel(self.channel_id) return guild and guild.get_channel(self.channel_id)
@utils.cached_slot_property('_cs_response')
def response(self) -> InteractionResponse:
""":class:`InteractionResponse`: Returns an object responsible for handling responding to the interaction."""
return InteractionResponse(self)
class InteractionResponse:
"""Represents a Discord interaction response.
This type can be accessed through :attr:`Interaction.response`.
.. versionadded:: 2.0
"""
__slots__: Tuple[str, ...] = (
'_responded',
'_parent',
)
def __init__(self, parent: Interaction):
self._parent: Interaction = parent
self._responded: bool = False
async def defer(self, *, ephemeral: bool = False) -> None:
"""|coro|
Defers the interaction response.
This is typically used when the interaction is acknowledged
and a secondary action will be done later.
Parameters
-----------
ephemeral: :class:`bool`
Indicates whether the deferred message will eventually be ephemeral.
This only applies for interactions of type :attr:`InteractionType.application_command`.
Raises
-------
HTTPException
Deferring the interaction failed.
"""
if self._responded:
return
defer_type: int = 0
data: Optional[Dict[str, Any]] = None
parent = self._parent
if parent.type is InteractionType.component:
defer_type = InteractionResponseType.deferred_message_update.value
elif parent.type is InteractionType.application_command:
defer_type = InteractionResponseType.deferred_channel_message.value
if ephemeral:
data = {'flags': 64}
if defer_type:
adapter = async_context.get()
await adapter.create_interaction_response(
parent.id, parent.token, session=parent._session, type=defer_type, data=data
)
self._responded = True
async def pong(self) -> None:
"""|coro|
Pongs the ping interaction.
This should rarely be used.
Raises
-------
HTTPException
Ponging the interaction failed.
"""
if self._responded:
return
parent = self._parent
if parent.type is InteractionType.ping:
adapter = async_context.get()
await adapter.create_interaction_response(
parent.id, parent.token, session=parent._session, type=InteractionResponseType.pong.value
)
self._responded = True
async def send_message(
self,
content: Optional[Any] = None,
*,
embed: Embed = MISSING,
embeds: List[Embed] = MISSING,
tts: bool = False,
ephemeral: bool = False,
) -> None:
"""|coro|
Responds to this interaction by sending a message.
Parameters
-----------
content: Optional[:class:`str`]
The content of the message to send.
embeds: List[:class:`Embed`]
A list of embeds to send with the content. Maximum of 10. This cannot
be mixed with the ``embed`` parameter.
embed: :class:`Embed`
The rich embed for the content to send. This cannot be mixed with
``embeds`` parameter.
tts: :class:`bool`
Indicates if the message should be sent using text-to-speech.
ephemeral: :class:`bool`
Indicates if the message should only be visible to the user who started the interaction.
Raises
-------
HTTPException
Sending the message failed.
TypeError
You specified both ``embed`` and ``embeds``.
ValueError
The length of ``embeds`` was invalid.
"""
if self._responded:
return
payload: Dict[str, Any] = {
'tts': tts,
}
if embed is not MISSING and embeds is not MISSING:
raise TypeError('cannot mix embed and embeds keyword arguments')
if embed is not MISSING:
embeds = [embed]
if embeds:
if len(embeds) > 10:
raise ValueError('embeds cannot exceed maximum of 10 elements')
payload['embeds'] = [e.to_dict() for e in embeds]
if content is not None:
payload['content'] = str(content)
if ephemeral:
payload['flags'] = 64
parent = self._parent
adapter = async_context.get()
await adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
type=InteractionResponseType.channel_message.value,
data=payload,
)
self._responded = True
async def edit_message(
self,
*,
content: Optional[Any] = MISSING,
embed: Optional[Embed] = MISSING,
attachments: List[Attachment] = MISSING,
view: Optional[View] = MISSING,
) -> None:
"""|coro|
Responds to this interaction by editing the original message of
a component interaction.
Parameters
-----------
content: Optional[:class:`str`]
The new content to replace the message with. ``None`` removes the content.
embed: Optional[:class:`Embed`]
The new embed to replace the embed with. ``None`` removes the embed.
attachments: List[:class:`Attachment`]
A list of attachments to keep in the message. If ``[]`` is passed
then all attachments are removed.
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.
"""
if self._responded:
return
parent = self._parent
if parent.type is not InteractionType.component:
return
# TODO: embeds: List[Embed]?
payload = {}
if content is not MISSING:
if content is None:
payload['content'] = None
else:
payload['content'] = str(content)
if embed is not MISSING:
if embed is None:
payload['embed'] = None
else:
payload['embed'] = embed.to_dict()
if attachments is not MISSING:
payload['attachments'] = [a.to_dict() for a in attachments]
if view is not MISSING:
if view is None:
payload['components'] = []
else:
payload['components'] = view.to_components()
adapter = async_context.get()
await adapter.create_interaction_response(
parent.id,
parent.token,
session=parent._session,
type=InteractionResponseType.message_update.value,
data=payload,
)
self._responded = True

25
docs/api.rst

@ -1188,17 +1188,30 @@ of :class:`enum.Enum`.
.. attribute:: pong .. attribute:: pong
Pongs the interaction when given a ping. Pongs the interaction when given a ping.
See also :meth:`InteractionResponse.pong`
.. attribute:: channel_message .. attribute:: channel_message
Respond to a slash command with a message. Respond to the interaction with a message.
See also :meth:`InteractionResponse.send_message`
.. attribute:: deferred_channel_message .. attribute:: deferred_channel_message
Responds to a slash command with a message at a later time. Responds to the interaction with a message at a later time.
See also :meth:`InteractionResponse.defer`
.. attribute:: deferred_message_update .. attribute:: deferred_message_update
Acknowledges the component interaction with a promise that Acknowledges the component interaction with a promise that
the message will update later (though there is no need to actually update the message). the message will update later (though there is no need to actually update the message).
See also :meth:`InteractionResponse.defer`
.. attribute:: message_update
Responds to the interaction by editing the message.
See also :meth:`InteractionResponse.edit_message`
.. class:: ComponentType .. class:: ComponentType
Represents the component type of a component. Represents the component type of a component.
@ -2951,6 +2964,14 @@ Interaction
.. autoclass:: Interaction() .. autoclass:: Interaction()
:members: :members:
InteractionResponse
~~~~~~~~~~~~~~~~~~~~
.. attributetable:: InteractionResponse
.. autoclass:: InteractionResponse()
:members:
Member Member
~~~~~~ ~~~~~~

Loading…
Cancel
Save