Skip to content

Commit

Permalink
Add support for sending views in stateless webhooks
Browse files Browse the repository at this point in the history
  • Loading branch information
DA-344 authored Feb 17, 2025
1 parent 8edf433 commit 6ab747f
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 13 deletions.
5 changes: 5 additions & 0 deletions discord/ui/view.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,11 @@ async def __timeout_task_impl(self) -> None:
# Wait N seconds to see if timeout data has been refreshed
await asyncio.sleep(self.__timeout_expiry - now)

def is_dispatchable(self) -> bool:
# this is used by webhooks to check whether a view requires a state attached
# or not, this simply is, whether a view has a component other than a url button
return any(item.is_dispatchable() for item in self.children)

def to_components(self) -> List[Dict[str, Any]]:
def key(item: Item) -> int:
return item._rendered_row or 0
Expand Down
24 changes: 14 additions & 10 deletions discord/webhook/async_.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,9 @@ def execute_webhook(
files: Optional[Sequence[File]] = None,
thread_id: Optional[int] = None,
wait: bool = False,
with_components: bool = False,
) -> Response[Optional[MessagePayload]]:
params = {'wait': int(wait)}
params = {'wait': int(wait), 'with_components': int(with_components)}
if thread_id:
params['thread_id'] = thread_id
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
Expand Down Expand Up @@ -1715,10 +1716,9 @@ async def send(
.. 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.
The view to send with the message. If the webhook is partial or
is not managed by the library, then you can only send URL buttons.
Otherwise, you can send views with any type of components.
.. versionadded:: 2.0
thread: :class:`~discord.abc.Snowflake`
Expand Down Expand Up @@ -1770,7 +1770,8 @@ async def send(
The length of ``embeds`` was invalid, there was no token
associated with this webhook or ``ephemeral`` was passed
with the improper webhook type or there was no state
attached with this webhook when giving it a view.
attached with this webhook when giving it a view that had
components other than URL buttons.
Returns
---------
Expand Down Expand Up @@ -1800,13 +1801,15 @@ async def send(
wait = True

if view is not MISSING:
if isinstance(self._state, _WebhookState):
raise ValueError('Webhook views require an associated state with the webhook')

if not hasattr(view, '__discord_ui_view__'):
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}')

if ephemeral is True and view.timeout is None:
if isinstance(self._state, _WebhookState) and view.is_dispatchable():
raise ValueError(
'Webhook views with any component other than URL buttons require an associated state with the webhook'
)

if ephemeral is True and view.timeout is None and view.is_dispatchable():
view.timeout = 15 * 60.0

if thread_name is not MISSING and thread is not MISSING:
Expand Down Expand Up @@ -1850,6 +1853,7 @@ async def send(
files=params.files,
thread_id=thread_id,
wait=wait,
with_components=view is not MISSING,
)

msg = None
Expand Down
26 changes: 23 additions & 3 deletions discord/webhook/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
from ..message import Attachment
from ..abc import Snowflake
from ..state import ConnectionState
from ..ui import View
from ..types.webhook import (
Webhook as WebhookPayload,
)
Expand Down Expand Up @@ -290,8 +291,9 @@ def execute_webhook(
files: Optional[Sequence[File]] = None,
thread_id: Optional[int] = None,
wait: bool = False,
with_components: bool = False,
) -> MessagePayload:
params = {'wait': int(wait)}
params = {'wait': int(wait), 'with_components': int(with_components)}
if thread_id:
params['thread_id'] = thread_id
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
Expand Down Expand Up @@ -919,6 +921,7 @@ def send(
silent: bool = False,
applied_tags: List[ForumTag] = MISSING,
poll: Poll = MISSING,
view: View = MISSING,
) -> Optional[SyncWebhookMessage]:
"""Sends a message using the webhook.
Expand Down Expand Up @@ -991,6 +994,13 @@ def send(
When sending a Poll via webhook, you cannot manually end it.
.. versionadded:: 2.4
view: :class:`~discord.ui.View`
The view to send with the message. This can only have URL buttons, which donnot
require a state to be attached to it.
If you want to send a view with any component attached to it, check :meth:`Webhook.send`.
.. versionadded:: 2.5
Raises
--------
Expand All @@ -1004,8 +1014,9 @@ def send(
You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
or ``thread`` and ``thread_name``.
ValueError
The length of ``embeds`` was invalid or
there was no token associated with this webhook.
The length of ``embeds`` was invalid, there was no token
associated with this webhook or you tried to send a view
with components other than URL buttons.
Returns
---------
Expand All @@ -1027,6 +1038,13 @@ def send(
else:
flags = MISSING

if view is not MISSING:
if not hasattr(view, '__discord_ui_view__'):
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}')

if view.is_dispatchable():
raise ValueError('SyncWebhook views can only contain URL buttons')

if thread_name is not MISSING and thread is not MISSING:
raise TypeError('Cannot mix thread_name and thread keyword arguments.')

Expand All @@ -1050,6 +1068,7 @@ def send(
flags=flags,
applied_tags=applied_tag_ids,
poll=poll,
view=view,
) as params:
adapter: WebhookAdapter = _get_webhook_adapter()
thread_id: Optional[int] = None
Expand All @@ -1065,6 +1084,7 @@ def send(
files=params.files,
thread_id=thread_id,
wait=wait,
with_components=view is not MISSING,
)

msg = None
Expand Down

0 comments on commit 6ab747f

Please sign in to comment.