Skip to content

Commit 6ab747f

Browse files
authored
Add support for sending views in stateless webhooks
1 parent 8edf433 commit 6ab747f

File tree

3 files changed

+42
-13
lines changed

3 files changed

+42
-13
lines changed

discord/ui/view.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ async def __timeout_task_impl(self) -> None:
214214
# Wait N seconds to see if timeout data has been refreshed
215215
await asyncio.sleep(self.__timeout_expiry - now)
216216

217+
def is_dispatchable(self) -> bool:
218+
# this is used by webhooks to check whether a view requires a state attached
219+
# or not, this simply is, whether a view has a component other than a url button
220+
return any(item.is_dispatchable() for item in self.children)
221+
217222
def to_components(self) -> List[Dict[str, Any]]:
218223
def key(item: Item) -> int:
219224
return item._rendered_row or 0

discord/webhook/async_.py

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -310,8 +310,9 @@ def execute_webhook(
310310
files: Optional[Sequence[File]] = None,
311311
thread_id: Optional[int] = None,
312312
wait: bool = False,
313+
with_components: bool = False,
313314
) -> Response[Optional[MessagePayload]]:
314-
params = {'wait': int(wait)}
315+
params = {'wait': int(wait), 'with_components': int(with_components)}
315316
if thread_id:
316317
params['thread_id'] = thread_id
317318
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
@@ -1715,10 +1716,9 @@ async def send(
17151716
17161717
.. versionadded:: 1.4
17171718
view: :class:`discord.ui.View`
1718-
The view to send with the message. You can only send a view
1719-
if this webhook is not partial and has state attached. A
1720-
webhook has state attached if the webhook is managed by the
1721-
library.
1719+
The view to send with the message. If the webhook is partial or
1720+
is not managed by the library, then you can only send URL buttons.
1721+
Otherwise, you can send views with any type of components.
17221722
17231723
.. versionadded:: 2.0
17241724
thread: :class:`~discord.abc.Snowflake`
@@ -1770,7 +1770,8 @@ async def send(
17701770
The length of ``embeds`` was invalid, there was no token
17711771
associated with this webhook or ``ephemeral`` was passed
17721772
with the improper webhook type or there was no state
1773-
attached with this webhook when giving it a view.
1773+
attached with this webhook when giving it a view that had
1774+
components other than URL buttons.
17741775
17751776
Returns
17761777
---------
@@ -1800,13 +1801,15 @@ async def send(
18001801
wait = True
18011802

18021803
if view is not MISSING:
1803-
if isinstance(self._state, _WebhookState):
1804-
raise ValueError('Webhook views require an associated state with the webhook')
1805-
18061804
if not hasattr(view, '__discord_ui_view__'):
18071805
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}')
18081806

1809-
if ephemeral is True and view.timeout is None:
1807+
if isinstance(self._state, _WebhookState) and view.is_dispatchable():
1808+
raise ValueError(
1809+
'Webhook views with any component other than URL buttons require an associated state with the webhook'
1810+
)
1811+
1812+
if ephemeral is True and view.timeout is None and view.is_dispatchable():
18101813
view.timeout = 15 * 60.0
18111814

18121815
if thread_name is not MISSING and thread is not MISSING:
@@ -1850,6 +1853,7 @@ async def send(
18501853
files=params.files,
18511854
thread_id=thread_id,
18521855
wait=wait,
1856+
with_components=view is not MISSING,
18531857
)
18541858

18551859
msg = None

discord/webhook/sync.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
from ..message import Attachment
6767
from ..abc import Snowflake
6868
from ..state import ConnectionState
69+
from ..ui import View
6970
from ..types.webhook import (
7071
Webhook as WebhookPayload,
7172
)
@@ -290,8 +291,9 @@ def execute_webhook(
290291
files: Optional[Sequence[File]] = None,
291292
thread_id: Optional[int] = None,
292293
wait: bool = False,
294+
with_components: bool = False,
293295
) -> MessagePayload:
294-
params = {'wait': int(wait)}
296+
params = {'wait': int(wait), 'with_components': int(with_components)}
295297
if thread_id:
296298
params['thread_id'] = thread_id
297299
route = Route('POST', '/webhooks/{webhook_id}/{webhook_token}', webhook_id=webhook_id, webhook_token=token)
@@ -919,6 +921,7 @@ def send(
919921
silent: bool = False,
920922
applied_tags: List[ForumTag] = MISSING,
921923
poll: Poll = MISSING,
924+
view: View = MISSING,
922925
) -> Optional[SyncWebhookMessage]:
923926
"""Sends a message using the webhook.
924927
@@ -991,6 +994,13 @@ def send(
991994
When sending a Poll via webhook, you cannot manually end it.
992995
993996
.. versionadded:: 2.4
997+
view: :class:`~discord.ui.View`
998+
The view to send with the message. This can only have URL buttons, which donnot
999+
require a state to be attached to it.
1000+
1001+
If you want to send a view with any component attached to it, check :meth:`Webhook.send`.
1002+
1003+
.. versionadded:: 2.5
9941004
9951005
Raises
9961006
--------
@@ -1004,8 +1014,9 @@ def send(
10041014
You specified both ``embed`` and ``embeds`` or ``file`` and ``files``
10051015
or ``thread`` and ``thread_name``.
10061016
ValueError
1007-
The length of ``embeds`` was invalid or
1008-
there was no token associated with this webhook.
1017+
The length of ``embeds`` was invalid, there was no token
1018+
associated with this webhook or you tried to send a view
1019+
with components other than URL buttons.
10091020
10101021
Returns
10111022
---------
@@ -1027,6 +1038,13 @@ def send(
10271038
else:
10281039
flags = MISSING
10291040

1041+
if view is not MISSING:
1042+
if not hasattr(view, '__discord_ui_view__'):
1043+
raise TypeError(f'expected view parameter to be of type View not {view.__class__.__name__}')
1044+
1045+
if view.is_dispatchable():
1046+
raise ValueError('SyncWebhook views can only contain URL buttons')
1047+
10301048
if thread_name is not MISSING and thread is not MISSING:
10311049
raise TypeError('Cannot mix thread_name and thread keyword arguments.')
10321050

@@ -1050,6 +1068,7 @@ def send(
10501068
flags=flags,
10511069
applied_tags=applied_tag_ids,
10521070
poll=poll,
1071+
view=view,
10531072
) as params:
10541073
adapter: WebhookAdapter = _get_webhook_adapter()
10551074
thread_id: Optional[int] = None
@@ -1065,6 +1084,7 @@ def send(
10651084
files=params.files,
10661085
thread_id=thread_id,
10671086
wait=wait,
1087+
with_components=view is not MISSING,
10681088
)
10691089

10701090
msg = None

0 commit comments

Comments
 (0)