diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 523fec745..9d10f9e8c 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -64,6 +64,7 @@ if TYPE_CHECKING: from ..guild import Guild from ..channel import TextChannel from ..abc import Snowflake + from ..ui.view import View import datetime MISSING = utils.MISSING @@ -423,6 +424,7 @@ def handle_message_parameters( files: List[File] = MISSING, embed: Optional[Embed] = MISSING, embeds: List[Embed] = MISSING, + view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = MISSING, previous_allowed_mentions: Optional[AllowedMentions] = None, ) -> ExecuteWebhookParameters: @@ -449,6 +451,12 @@ def handle_message_parameters( else: payload['content'] = None + if view is not MISSING: + if view is not None: + payload['components'] = view.to_components() + else: + payload['components'] = [] + payload['tts'] = tts if avatar_url: payload['avatar_url'] = str(avatar_url) @@ -621,6 +629,7 @@ class WebhookMessage(Message): embed: Optional[Embed] = MISSING, file: File = MISSING, files: List[File] = MISSING, + view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = None, ): """|coro| @@ -646,6 +655,9 @@ class WebhookMessage(Message): 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 ------- @@ -667,6 +679,7 @@ class WebhookMessage(Message): embed=embed, file=file, files=files, + view=view, allowed_mentions=allowed_mentions, ) @@ -1148,6 +1161,7 @@ class Webhook(BaseWebhook): embed: Embed = MISSING, embeds: List[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, + view: View = MISSING, wait: Literal[True], ) -> WebhookMessage: ... @@ -1166,6 +1180,7 @@ class Webhook(BaseWebhook): embed: Embed = MISSING, embeds: List[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, + view: View = MISSING, wait: Literal[False] = ..., ) -> None: ... @@ -1183,6 +1198,7 @@ class Webhook(BaseWebhook): embed: Embed = MISSING, embeds: List[Embed] = MISSING, allowed_mentions: AllowedMentions = MISSING, + view: View = MISSING, wait: bool = False, ) -> Optional[WebhookMessage]: """|coro| @@ -1218,6 +1234,8 @@ class Webhook(BaseWebhook): ephemeral: :class:`bool` Indicates if the message should only be visible to the user. This is only available to :attr:`WebhookType.application` webhooks. + If a view is sent with an ephemeral message and it has no timeout set + then the timeout is set to 15 minutes. file: :class:`File` The file to upload. This cannot be mixed with ``files`` parameter. files: List[:class:`File`] @@ -1231,8 +1249,11 @@ class Webhook(BaseWebhook): be mixed with the ``embed`` parameter. allowed_mentions: :class:`AllowedMentions` Controls the mentions being processed in this message. - - .. versionadded:: 1.4 + view: :class:`discord.ui.View` + The view to send with the message. You can only send a view + if this webhook is not partial and has state attached. A + webhook has state attached if the webhook is managed by the + library. Raises -------- @@ -1248,7 +1269,8 @@ class Webhook(BaseWebhook): The length of ``embeds`` was invalid. InvalidArgument There was no token associated with this webhook or ``ephemeral`` - was passed with the improper webhook type. + was passed with the improper webhook type or there was no state + attached with this webhook when giving it a view. Returns --------- @@ -1270,6 +1292,12 @@ class Webhook(BaseWebhook): if application_webhook: wait = True + if view is not MISSING: + if isinstance(self._state, _WebhookState): + raise InvalidArgument('Webhook views require an associated state with the webhook') + if ephemeral is True and view.timeout is None: + view.timeout = 15 * 60.0 + params = handle_message_parameters( content=content, username=username, @@ -1280,6 +1308,7 @@ class Webhook(BaseWebhook): embed=embed, embeds=embeds, ephemeral=ephemeral, + view=view, allowed_mentions=allowed_mentions, previous_allowed_mentions=previous_mentions, ) @@ -1293,8 +1322,16 @@ class Webhook(BaseWebhook): files=params.files, wait=wait, ) + + msg = None if wait: - return self._create_message(data) + msg = self._create_message(data) + + if view is not MISSING: + message_id = None if msg is None else msg.id + self._state.store_view(view, message_id) + + return msg async def fetch_message(self, id: int) -> WebhookMessage: """|coro| @@ -1346,6 +1383,7 @@ class Webhook(BaseWebhook): embed: Optional[Embed] = MISSING, file: File = MISSING, files: List[File] = MISSING, + view: Optional[View] = MISSING, allowed_mentions: Optional[AllowedMentions] = None, ): """|coro| @@ -1376,6 +1414,10 @@ class Webhook(BaseWebhook): 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. The webhook must have state attached, similar to + :meth:`send`. Raises ------- @@ -1388,12 +1430,16 @@ class Webhook(BaseWebhook): ValueError The length of ``embeds`` was invalid InvalidArgument - There was no token associated with this webhook. + There was no token associated with this webhook or the webhook had + no state. """ if self.token is None: raise InvalidArgument('This webhook does not have a token associated with it') + if view is not MISSING and isinstance(self._state, _WebhookState): + raise InvalidArgument('This webhook does not have state associated with it') + previous_mentions: Optional[AllowedMentions] = getattr(self._state, 'allowed_mentions', None) params = handle_message_parameters( content=content, @@ -1401,6 +1447,7 @@ class Webhook(BaseWebhook): files=files, embed=embed, embeds=embeds, + view=view, allowed_mentions=allowed_mentions, previous_allowed_mentions=previous_mentions, ) @@ -1415,6 +1462,9 @@ class Webhook(BaseWebhook): files=params.files, ) + if view: + self._state.store_view(view, message_id) + async def delete_message(self, message_id: int): """|coro|