aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2021-05-31 23:08:08 -0400
committerRapptz <[email protected]>2021-05-31 23:08:08 -0400
commit7c40e83d10668b94c30f5d1f3bd502327f027e9e (patch)
tree2575e7c5969f58113667e4492388f3a37d8b7696
parentDon't mark URL buttons as dispatchable (diff)
downloaddiscord.py-7c40e83d10668b94c30f5d1f3bd502327f027e9e.tar.xz
discord.py-7c40e83d10668b94c30f5d1f3bd502327f027e9e.zip
Ensure views added to Client.add_view are persistent views
-rw-r--r--discord/client.py6
-rw-r--r--discord/ui/button.py1
-rw-r--r--discord/ui/item.py10
-rw-r--r--discord/ui/select.py1
-rw-r--r--discord/ui/view.py8
5 files changed, 26 insertions, 0 deletions
diff --git a/discord/client.py b/discord/client.py
index 48bae688..cc793c0b 100644
--- a/discord/client.py
+++ b/discord/client.py
@@ -1451,9 +1451,15 @@ class Client:
-------
TypeError
A view was not passed.
+ ValueError
+ The view is not persistent. A persistent view has no timeout
+ and all their components have an explicitly provided custom_id.
"""
if not isinstance(view, View):
raise TypeError(f'expected an instance of View not {view.__class__!r}')
+ if not view.is_persistent():
+ raise ValueError('View is not persistent. Items need to have a custom_id set and View must have no timeout')
+
self._connection.store_view(view, message_id)
diff --git a/discord/ui/button.py b/discord/ui/button.py
index 2e4e54fd..7ac2a8af 100644
--- a/discord/ui/button.py
+++ b/discord/ui/button.py
@@ -98,6 +98,7 @@ class Button(Item[V]):
if custom_id is not None and url is not None:
raise TypeError('cannot mix both url and custom_id with Button')
+ self._provided_custom_id = custom_id is not None
if url is None and custom_id is None:
custom_id = os.urandom(16).hex()
diff --git a/discord/ui/item.py b/discord/ui/item.py
index 6744f12d..3a7c76f3 100644
--- a/discord/ui/item.py
+++ b/discord/ui/item.py
@@ -56,6 +56,13 @@ class Item(Generic[V]):
self._view: Optional[V] = None
self._row: Optional[int] = None
self._rendered_row: Optional[int] = None
+ # This works mostly well but there is a gotcha with
+ # the interaction with from_component, since that technically provides
+ # a custom_id most dispatchable items would get this set to True even though
+ # it might not be provided by the library user. However, this edge case doesn't
+ # actually affect the intended purpose of this check because from_component is
+ # only called upon edit and we're mainly interested during initial creation time.
+ self._provided_custom_id: bool = False
def to_component_dict(self) -> Dict[str, Any]:
raise NotImplementedError
@@ -77,6 +84,9 @@ class Item(Generic[V]):
def is_dispatchable(self) -> bool:
return False
+ def is_persistent(self) -> bool:
+ return self._provided_custom_id
+
def __repr__(self) -> str:
attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__item_repr_attributes__)
return f'<{self.__class__.__name__} {attrs}>'
diff --git a/discord/ui/select.py b/discord/ui/select.py
index 22d869fb..22143aa7 100644
--- a/discord/ui/select.py
+++ b/discord/ui/select.py
@@ -101,6 +101,7 @@ class Select(Item[V]):
row: Optional[int] = None,
) -> None:
self._selected_values: List[str] = []
+ self._provided_custom_id = custom_id is not MISSING
custom_id = os.urandom(16).hex() if custom_id is MISSING else custom_id
options = [] if options is MISSING else options
self._underlying = SelectMenu._raw_construct(
diff --git a/discord/ui/view.py b/discord/ui/view.py
index 4df899ed..b6ddf4e6 100644
--- a/discord/ui/view.py
+++ b/discord/ui/view.py
@@ -339,6 +339,14 @@ class View:
""":class:`bool`: Whether the view has finished interacting."""
return self._stopped.done()
+ def is_persistent(self) -> bool:
+ """:class:`bool`: Whether the view is set up as persistent.
+
+ A persistent view has all their components with a set ``custom_id`` and
+ a :attr:`timeout` set to ``None``.
+ """
+ return self.timeout is None and all(item.is_persistent() for item in self.children)
+
async def wait(self) -> bool:
"""Waits until the view has finished interacting.