From 3697a9b96ca3e1f512242fbce06164a674e1a244 Mon Sep 17 00:00:00 2001 From: DA-344 <108473820+DA-344@users.noreply.github.com> Date: Sat, 14 Jun 2025 12:15:30 +0200 Subject: [PATCH] chore: prevent storing non-interactable views and various fixes --- discord/abc.py | 2 +- discord/channel.py | 2 +- discord/interactions.py | 4 ++-- discord/message.py | 6 +++--- discord/ui/view.py | 23 ++++++++++++----------- discord/webhook/async_.py | 4 ++-- 6 files changed, 21 insertions(+), 20 deletions(-) diff --git a/discord/abc.py b/discord/abc.py index 5ea20b558..b8a7c90c5 100644 --- a/discord/abc.py +++ b/discord/abc.py @@ -1678,7 +1678,7 @@ class Messageable: data = await state.http.send_message(channel.id, params=params) ret = state.create_message(channel=channel, data=data) - if view and not view.is_finished(): + if view and not view.is_finished() and view.is_dispatchable(): state.store_view(view, ret.id) if poll: diff --git a/discord/channel.py b/discord/channel.py index 9a1218f31..58a9943d4 100644 --- a/discord/channel.py +++ b/discord/channel.py @@ -3027,7 +3027,7 @@ class ForumChannel(discord.abc.GuildChannel, Hashable): data = await state.http.start_thread_in_forum(self.id, params=params, reason=reason) thread = Thread(guild=self.guild, state=self._state, data=data) message = Message(state=self._state, channel=thread, data=data['message']) - if view and not view.is_finished(): + if view and not view.is_finished() and view.is_dispatchable(): self._state.store_view(view, message.id) return ThreadWithMessage(thread=thread, message=message) diff --git a/discord/interactions.py b/discord/interactions.py index 7b0b9c493..05e627e84 100644 --- a/discord/interactions.py +++ b/discord/interactions.py @@ -592,7 +592,7 @@ class Interaction(Generic[ClientT]): # The message channel types should always match state = _InteractionMessageState(self, self._state) message = InteractionMessage(state=state, channel=self.channel, data=data) # type: ignore - if view and not view.is_finished(): + if view and not view.is_finished() and view.is_dispatchable(): self._state.store_view(view, message.id, interaction_id=self.id) return message @@ -1252,7 +1252,7 @@ class InteractionResponse(Generic[ClientT]): params=params, ) - if view and not view.is_finished(): + if view and not view.is_finished() and view.is_dispatchable(): state.store_view(view, message_id, interaction_id=original_interaction_id) self._response_type = InteractionResponseType.message_update diff --git a/discord/message.py b/discord/message.py index 0057b06f8..6a27be910 100644 --- a/discord/message.py +++ b/discord/message.py @@ -1443,8 +1443,8 @@ class PartialMessage(Hashable): data = await self._state.http.edit_message(self.channel.id, self.id, params=params) message = Message(state=self._state, channel=self.channel, data=data) - if view and not view.is_finished(): - interaction: Optional[MessageInteraction] = getattr(self, 'interaction', None) + if view and not view.is_finished() and view.is_dispatchable(): + interaction: Optional[MessageInteractionMetadata] = getattr(self, 'interaction_metadata', None) if interaction is not None: self._state.store_view(view, self.id, interaction_id=interaction.id) else: @@ -3033,7 +3033,7 @@ class Message(PartialMessage, Hashable): data = await self._state.http.edit_message(self.channel.id, self.id, params=params) message = Message(state=self._state, channel=self.channel, data=data) - if view and not view.is_finished(): + if view and not view.is_finished() and view.is_dispatchable(): self._state.store_view(view, self.id) if delete_after is not None: diff --git a/discord/ui/view.py b/discord/ui/view.py index 1499b709b..5f7899da2 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -70,7 +70,7 @@ from ..components import ( SelectOption, Container as ContainerComponent, ) -from ..utils import get as _utils_get, _get_as_snowflake +from ..utils import get as _utils_get, _get_as_snowflake, find as _utils_find from ..enums import SeparatorSpacing, TextStyle, try_enum, ButtonStyle from ..emoji import PartialEmoji @@ -412,8 +412,9 @@ class BaseView: 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 + # checks whether any interactable items (buttons or selects) are present + # in this view, and check whether this requires a state attached in case + # of webhooks and if the view should be stored in the view store return any(item.is_dispatchable() for item in self.children) def has_components_v2(self) -> bool: @@ -1102,13 +1103,13 @@ class ViewStore: view_cls = View if not interaction.message.flags.components_v2 else LayoutView view = view_cls.from_message(interaction.message, timeout=None) - try: - base_item = next( - child - for child in view.walk_children() - if child.type.value == component_type and getattr(child, 'custom_id', None) == custom_id - ) - except StopIteration: + base_item = _utils_find( + lambda i: i.type.value == component_type and getattr(i, 'custom_id', None) == custom_id, + view.walk_children(), + ) + + # if the item is not found then return + if not base_item: return try: @@ -1124,7 +1125,7 @@ class ViewStore: try: child_index = parent._children.index(base_item) # type: ignore except ValueError: - # handle cases in which the item is a section accesory + # handle cases in which the item is a section accessory if getattr(base_item._parent, '__discord_ui_section__', False): if ( base_item._parent.accessory.type.value == component_type # type: ignore diff --git a/discord/webhook/async_.py b/discord/webhook/async_.py index 104da78ca..322e60465 100644 --- a/discord/webhook/async_.py +++ b/discord/webhook/async_.py @@ -1921,7 +1921,7 @@ class Webhook(BaseWebhook): if wait: msg = self._create_message(data, thread=thread) - if view is not MISSING and not view.is_finished(): + if view is not MISSING and not view.is_finished() and view.is_dispatchable(): message_id = None if msg is None else msg.id self._state.store_view(view, message_id) @@ -2124,7 +2124,7 @@ class Webhook(BaseWebhook): ) message = self._create_message(data, thread=thread) - if view and not view.is_finished(): + if view and not view.is_finished() and view.is_dispatchable(): self._state.store_view(view, message_id) return message