mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-07-21 10:26:47 +00:00
Refactor interaction response handling to support files
This adds support for file sending and allowed_mentions
This commit is contained in:
parent
770a5fee9c
commit
92d1b4cd2b
@ -31,6 +31,7 @@ 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, HTTPException, ClientException
|
from .errors import InteractionResponded, HTTPException, ClientException
|
||||||
|
from .flags import MessageFlags
|
||||||
from .channel import PartialMessageable, ChannelType
|
from .channel import PartialMessageable, ChannelType
|
||||||
|
|
||||||
from .user import User
|
from .user import User
|
||||||
@ -39,7 +40,7 @@ from .message import Message, Attachment
|
|||||||
from .object import Object
|
from .object import Object
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .http import handle_message_parameters
|
from .http import handle_message_parameters
|
||||||
from .webhook.async_ import async_context, Webhook
|
from .webhook.async_ import async_context, Webhook, interaction_response_params, interaction_message_response_params
|
||||||
|
|
||||||
__all__ = (
|
__all__ = (
|
||||||
'Interaction',
|
'Interaction',
|
||||||
@ -421,9 +422,8 @@ class InteractionResponse:
|
|||||||
|
|
||||||
if defer_type:
|
if defer_type:
|
||||||
adapter = async_context.get()
|
adapter = async_context.get()
|
||||||
await adapter.create_interaction_response(
|
params = interaction_response_params(type=defer_type, data=data)
|
||||||
parent.id, parent.token, session=parent._session, type=defer_type, data=data
|
await adapter.create_interaction_response(parent.id, parent.token, session=parent._session, params=params)
|
||||||
)
|
|
||||||
self._responded = True
|
self._responded = True
|
||||||
|
|
||||||
async def pong(self) -> None:
|
async def pong(self) -> None:
|
||||||
@ -446,9 +446,8 @@ class InteractionResponse:
|
|||||||
parent = self._parent
|
parent = self._parent
|
||||||
if parent.type is InteractionType.ping:
|
if parent.type is InteractionType.ping:
|
||||||
adapter = async_context.get()
|
adapter = async_context.get()
|
||||||
await adapter.create_interaction_response(
|
params = interaction_response_params(InteractionResponseType.pong.value)
|
||||||
parent.id, parent.token, session=parent._session, type=InteractionResponseType.pong.value
|
await adapter.create_interaction_response(parent.id, parent.token, session=parent._session, params=params)
|
||||||
)
|
|
||||||
self._responded = True
|
self._responded = True
|
||||||
|
|
||||||
async def send_message(
|
async def send_message(
|
||||||
@ -457,9 +456,12 @@ class InteractionResponse:
|
|||||||
*,
|
*,
|
||||||
embed: Embed = MISSING,
|
embed: Embed = MISSING,
|
||||||
embeds: List[Embed] = MISSING,
|
embeds: List[Embed] = MISSING,
|
||||||
|
file: File = MISSING,
|
||||||
|
files: List[File] = MISSING,
|
||||||
view: View = MISSING,
|
view: View = MISSING,
|
||||||
tts: bool = False,
|
tts: bool = False,
|
||||||
ephemeral: bool = False,
|
ephemeral: bool = False,
|
||||||
|
allowed_mentions: AllowedMentions = MISSING,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -475,6 +477,10 @@ class InteractionResponse:
|
|||||||
embed: :class:`Embed`
|
embed: :class:`Embed`
|
||||||
The rich embed for the content to send. This cannot be mixed with
|
The rich embed for the content to send. This cannot be mixed with
|
||||||
``embeds`` parameter.
|
``embeds`` parameter.
|
||||||
|
file: :class:`~discord.File`
|
||||||
|
The file to upload.
|
||||||
|
files: List[:class:`~discord.File`]
|
||||||
|
A list of files to upload. Must be a maximum of 10.
|
||||||
tts: :class:`bool`
|
tts: :class:`bool`
|
||||||
Indicates if the message should be sent using text-to-speech.
|
Indicates if the message should be sent using text-to-speech.
|
||||||
view: :class:`discord.ui.View`
|
view: :class:`discord.ui.View`
|
||||||
@ -483,13 +489,16 @@ class InteractionResponse:
|
|||||||
Indicates if the message should only be visible to the user who started the interaction.
|
Indicates if the message should only be visible to the user who started the interaction.
|
||||||
If a view is sent with an ephemeral message and it has no timeout set then the timeout
|
If a view is sent with an ephemeral message and it has no timeout set then the timeout
|
||||||
is set to 15 minutes.
|
is set to 15 minutes.
|
||||||
|
allowed_mentions: :class:`~discord.AllowedMentions`
|
||||||
|
Controls the mentions being processed in this message. See :meth:`.abc.Messageable.send` for
|
||||||
|
more information.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
HTTPException
|
HTTPException
|
||||||
Sending the message failed.
|
Sending the message failed.
|
||||||
TypeError
|
TypeError
|
||||||
You specified both ``embed`` and ``embeds``.
|
You specified both ``embed`` and ``embeds`` or ``file`` and ``files``.
|
||||||
ValueError
|
ValueError
|
||||||
The length of ``embeds`` was invalid.
|
The length of ``embeds`` was invalid.
|
||||||
InteractionResponded
|
InteractionResponded
|
||||||
@ -498,38 +507,32 @@ class InteractionResponse:
|
|||||||
if self._responded:
|
if self._responded:
|
||||||
raise InteractionResponded(self._parent)
|
raise InteractionResponded(self._parent)
|
||||||
|
|
||||||
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:
|
if ephemeral:
|
||||||
payload['flags'] = 64
|
flags = MessageFlags._from_value(64)
|
||||||
|
else:
|
||||||
if view is not MISSING:
|
flags = MISSING
|
||||||
payload['components'] = view.to_components()
|
|
||||||
|
|
||||||
parent = self._parent
|
parent = self._parent
|
||||||
adapter = async_context.get()
|
adapter = async_context.get()
|
||||||
|
params = interaction_message_response_params(
|
||||||
|
type=InteractionResponseType.channel_message.value,
|
||||||
|
content=content,
|
||||||
|
tts=tts,
|
||||||
|
embeds=embeds,
|
||||||
|
embed=embed,
|
||||||
|
file=file,
|
||||||
|
files=files,
|
||||||
|
previous_allowed_mentions=parent._state.allowed_mentions,
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
flags=flags,
|
||||||
|
view=view,
|
||||||
|
)
|
||||||
|
|
||||||
await adapter.create_interaction_response(
|
await adapter.create_interaction_response(
|
||||||
parent.id,
|
parent.id,
|
||||||
parent.token,
|
parent.token,
|
||||||
session=parent._session,
|
session=parent._session,
|
||||||
type=InteractionResponseType.channel_message.value,
|
params=params,
|
||||||
data=payload,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if view is not MISSING:
|
if view is not MISSING:
|
||||||
@ -548,6 +551,7 @@ class InteractionResponse:
|
|||||||
embeds: List[Embed] = MISSING,
|
embeds: List[Embed] = MISSING,
|
||||||
attachments: List[Attachment] = MISSING,
|
attachments: List[Attachment] = MISSING,
|
||||||
view: Optional[View] = MISSING,
|
view: Optional[View] = MISSING,
|
||||||
|
allowed_mentions: Optional[AllowedMentions] = MISSING,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""|coro|
|
"""|coro|
|
||||||
|
|
||||||
@ -569,6 +573,9 @@ class InteractionResponse:
|
|||||||
view: Optional[:class:`~discord.ui.View`]
|
view: Optional[:class:`~discord.ui.View`]
|
||||||
The updated view to update this message with. If ``None`` is passed then
|
The updated view to update this message with. If ``None`` is passed then
|
||||||
the view is removed.
|
the view is removed.
|
||||||
|
allowed_mentions: Optional[:class:`~discord.AllowedMentions`]
|
||||||
|
Controls the mentions being processed in this message. See :meth:`.Message.edit`
|
||||||
|
for more information.
|
||||||
|
|
||||||
Raises
|
Raises
|
||||||
-------
|
-------
|
||||||
@ -589,42 +596,25 @@ class InteractionResponse:
|
|||||||
if parent.type is not InteractionType.component:
|
if parent.type is not InteractionType.component:
|
||||||
return
|
return
|
||||||
|
|
||||||
payload = {}
|
if view is not MISSING and message_id is not None:
|
||||||
if content is not MISSING:
|
|
||||||
if content is None:
|
|
||||||
payload['content'] = None
|
|
||||||
else:
|
|
||||||
payload['content'] = str(content)
|
|
||||||
|
|
||||||
if embed is not MISSING and embeds is not MISSING:
|
|
||||||
raise TypeError('cannot mix both embed and embeds keyword arguments')
|
|
||||||
|
|
||||||
if embed is not MISSING:
|
|
||||||
if embed is None:
|
|
||||||
embeds = []
|
|
||||||
else:
|
|
||||||
embeds = [embed]
|
|
||||||
|
|
||||||
if embeds is not MISSING:
|
|
||||||
payload['embeds'] = [e.to_dict() for e in embeds]
|
|
||||||
|
|
||||||
if attachments is not MISSING:
|
|
||||||
payload['attachments'] = [a.to_dict() for a in attachments]
|
|
||||||
|
|
||||||
if view is not MISSING:
|
|
||||||
state.prevent_view_updates_for(message_id)
|
state.prevent_view_updates_for(message_id)
|
||||||
if view is None:
|
|
||||||
payload['components'] = []
|
|
||||||
else:
|
|
||||||
payload['components'] = view.to_components()
|
|
||||||
|
|
||||||
adapter = async_context.get()
|
adapter = async_context.get()
|
||||||
|
params = interaction_message_response_params(
|
||||||
|
type=InteractionResponseType.message_update.value,
|
||||||
|
content=content,
|
||||||
|
embed=embed,
|
||||||
|
embeds=embeds,
|
||||||
|
attachments=attachments,
|
||||||
|
previous_allowed_mentions=parent._state.allowed_mentions,
|
||||||
|
allowed_mentions=allowed_mentions,
|
||||||
|
)
|
||||||
|
|
||||||
await adapter.create_interaction_response(
|
await adapter.create_interaction_response(
|
||||||
parent.id,
|
parent.id,
|
||||||
parent.token,
|
parent.token,
|
||||||
session=parent._session,
|
session=parent._session,
|
||||||
type=InteractionResponseType.message_update.value,
|
params=params,
|
||||||
data=payload,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if view and not view.is_finished():
|
if view and not view.is_finished():
|
||||||
|
@ -43,7 +43,7 @@ from ..enums import try_enum, WebhookType
|
|||||||
from ..user import BaseUser, User
|
from ..user import BaseUser, User
|
||||||
from ..flags import MessageFlags
|
from ..flags import MessageFlags
|
||||||
from ..asset import Asset
|
from ..asset import Asset
|
||||||
from ..http import Route, handle_message_parameters
|
from ..http import Route, handle_message_parameters, MultipartParameters
|
||||||
from ..mixins import Hashable
|
from ..mixins import Hashable
|
||||||
from ..channel import PartialMessageable
|
from ..channel import PartialMessageable
|
||||||
|
|
||||||
@ -60,6 +60,7 @@ if TYPE_CHECKING:
|
|||||||
from ..file import File
|
from ..file import File
|
||||||
from ..embeds import Embed
|
from ..embeds import Embed
|
||||||
from ..mentions import AllowedMentions
|
from ..mentions import AllowedMentions
|
||||||
|
from ..message import Attachment
|
||||||
from ..state import ConnectionState
|
from ..state import ConnectionState
|
||||||
from ..http import Response
|
from ..http import Response
|
||||||
from ..types.webhook import (
|
from ..types.webhook import (
|
||||||
@ -349,16 +350,8 @@ class AsyncWebhookAdapter:
|
|||||||
token: str,
|
token: str,
|
||||||
*,
|
*,
|
||||||
session: aiohttp.ClientSession,
|
session: aiohttp.ClientSession,
|
||||||
type: int,
|
params: MultipartParameters,
|
||||||
data: Optional[Dict[str, Any]] = None,
|
|
||||||
) -> Response[None]:
|
) -> Response[None]:
|
||||||
payload: Dict[str, Any] = {
|
|
||||||
'type': type,
|
|
||||||
}
|
|
||||||
|
|
||||||
if data is not None:
|
|
||||||
payload['data'] = data
|
|
||||||
|
|
||||||
route = Route(
|
route = Route(
|
||||||
'POST',
|
'POST',
|
||||||
'/interactions/{webhook_id}/{webhook_token}/callback',
|
'/interactions/{webhook_id}/{webhook_token}/callback',
|
||||||
@ -366,7 +359,10 @@ class AsyncWebhookAdapter:
|
|||||||
webhook_token=token,
|
webhook_token=token,
|
||||||
)
|
)
|
||||||
|
|
||||||
return self.request(route, session=session, payload=payload)
|
if params.files:
|
||||||
|
return self.request(route, session=session, files=params.files, multipart=params.multipart)
|
||||||
|
else:
|
||||||
|
return self.request(route, session=session, payload=params.payload)
|
||||||
|
|
||||||
def get_original_interaction_response(
|
def get_original_interaction_response(
|
||||||
self,
|
self,
|
||||||
@ -417,6 +413,118 @@ class AsyncWebhookAdapter:
|
|||||||
return self.request(r, session=session)
|
return self.request(r, session=session)
|
||||||
|
|
||||||
|
|
||||||
|
def interaction_response_params(type: int, data: Optional[Dict[str, Any]] = None) -> MultipartParameters:
|
||||||
|
payload: Dict[str, Any] = {
|
||||||
|
'type': type,
|
||||||
|
}
|
||||||
|
if data is not None:
|
||||||
|
payload['data'] = data
|
||||||
|
|
||||||
|
return MultipartParameters(payload=payload, multipart=None, files=None)
|
||||||
|
|
||||||
|
|
||||||
|
# This is a subset of handle_message_parameters
|
||||||
|
def interaction_message_response_params(
|
||||||
|
*,
|
||||||
|
type: int,
|
||||||
|
content: Optional[str] = MISSING,
|
||||||
|
tts: bool = False,
|
||||||
|
flags: MessageFlags = MISSING,
|
||||||
|
file: File = MISSING,
|
||||||
|
files: List[File] = MISSING,
|
||||||
|
embed: Optional[Embed] = MISSING,
|
||||||
|
embeds: List[Embed] = MISSING,
|
||||||
|
attachments: List[Attachment] = MISSING,
|
||||||
|
view: Optional[View] = MISSING,
|
||||||
|
allowed_mentions: Optional[AllowedMentions] = MISSING,
|
||||||
|
previous_allowed_mentions: Optional[AllowedMentions] = None,
|
||||||
|
) -> MultipartParameters:
|
||||||
|
if files is not MISSING and file is not MISSING:
|
||||||
|
raise TypeError('Cannot mix file and files keyword arguments.')
|
||||||
|
if embeds is not MISSING and embed is not MISSING:
|
||||||
|
raise TypeError('Cannot mix embed and embeds keyword arguments.')
|
||||||
|
|
||||||
|
data: Optional[Dict[str, Any]] = {
|
||||||
|
'tts': tts,
|
||||||
|
}
|
||||||
|
|
||||||
|
if embeds is not MISSING:
|
||||||
|
if len(embeds) > 10:
|
||||||
|
raise InvalidArgument('embeds has a maximum of 10 elements.')
|
||||||
|
data['embeds'] = [e.to_dict() for e in embeds]
|
||||||
|
|
||||||
|
if embed is not MISSING:
|
||||||
|
if embed is None:
|
||||||
|
data['embeds'] = []
|
||||||
|
else:
|
||||||
|
data['embeds'] = [embed.to_dict()]
|
||||||
|
|
||||||
|
if content is not MISSING:
|
||||||
|
if content is not None:
|
||||||
|
data['content'] = str(content)
|
||||||
|
else:
|
||||||
|
data['content'] = None
|
||||||
|
|
||||||
|
if view is not MISSING:
|
||||||
|
if view is not None:
|
||||||
|
data['components'] = view.to_components()
|
||||||
|
else:
|
||||||
|
data['components'] = []
|
||||||
|
|
||||||
|
if attachments is not MISSING:
|
||||||
|
# Note: This will be overwritten if file or files is provided
|
||||||
|
# However, right now this is only passed via edit not send
|
||||||
|
data['attachments'] = [a.to_dict() for a in attachments]
|
||||||
|
|
||||||
|
if flags is not MISSING:
|
||||||
|
data['flags'] = flags.value
|
||||||
|
|
||||||
|
if allowed_mentions:
|
||||||
|
if previous_allowed_mentions is not None:
|
||||||
|
data['allowed_mentions'] = previous_allowed_mentions.merge(allowed_mentions).to_dict()
|
||||||
|
else:
|
||||||
|
data['allowed_mentions'] = allowed_mentions.to_dict()
|
||||||
|
elif previous_allowed_mentions is not None:
|
||||||
|
data['allowed_mentions'] = previous_allowed_mentions.to_dict()
|
||||||
|
|
||||||
|
multipart = []
|
||||||
|
if file is not MISSING:
|
||||||
|
files = [file]
|
||||||
|
|
||||||
|
if files:
|
||||||
|
for index, file in enumerate(files):
|
||||||
|
attachments_payload = []
|
||||||
|
for index, file in enumerate(files):
|
||||||
|
attachment = {
|
||||||
|
'id': index,
|
||||||
|
'filename': file.filename,
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.description is not None:
|
||||||
|
attachment['description'] = file.description
|
||||||
|
|
||||||
|
attachments_payload.append(attachment)
|
||||||
|
|
||||||
|
data['attachments'] = attachments_payload
|
||||||
|
|
||||||
|
data = {'type': type, 'data': data}
|
||||||
|
multipart.append({'name': 'payload_json', 'value': utils._to_json(data)})
|
||||||
|
data = None
|
||||||
|
for index, file in enumerate(files):
|
||||||
|
multipart.append(
|
||||||
|
{
|
||||||
|
'name': f'files[{index}]',
|
||||||
|
'value': file.fp,
|
||||||
|
'filename': file.filename,
|
||||||
|
'content_type': 'application/octet-stream',
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = {'type': type, 'data': data}
|
||||||
|
|
||||||
|
return MultipartParameters(payload=data, multipart=multipart, files=files)
|
||||||
|
|
||||||
|
|
||||||
async_context: ContextVar[AsyncWebhookAdapter] = ContextVar('async_webhook_context', default=AsyncWebhookAdapter())
|
async_context: ContextVar[AsyncWebhookAdapter] = ContextVar('async_webhook_context', default=AsyncWebhookAdapter())
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user