diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c92fe5937..d35a50bf40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2883](https://github.com/Pycord-Development/pycord/pull/2883)) - Added `discord.User.primary_guild` and the `PrimaryGuild` class. ([#2876](https://github.com/Pycord-Development/pycord/pull/2876)) +- Added `get_component` to `Message`, `Section`, `Container` and `ActionRow`. + ([#2849](https://github.com/Pycord-Development/pycord/pull/2849)) ### Fixed diff --git a/discord/components.py b/discord/components.py index 6fc769f345..3de19a7fc0 100644 --- a/discord/components.py +++ b/discord/components.py @@ -39,7 +39,7 @@ ) from .flags import AttachmentFlags from .partial_emoji import PartialEmoji, _EmojiTag -from .utils import MISSING, get_slots +from .utils import MISSING, find, get_slots if TYPE_CHECKING: from .emoji import AppEmoji, GuildEmoji @@ -189,6 +189,25 @@ def to_dict(self) -> ActionRowPayload: def walk_components(self) -> Iterator[Component]: yield from self.children + def get_component(self, id: str | int) -> Component | None: + """Get a component from this action row. Roughly equivalent to `utils.get(row.children, ...)`. + If an ``int`` is provided, the component will be retrieved by ``id``, otherwise by ``custom_id``. + + Parameters + ---------- + id: Union[:class:`str`, :class:`int`] + The custom_id or id of the component to get. + + Returns + ------- + Optional[:class:`Component`] + The component with the matching ``id`` or ``custom_id`` if it exists. + """ + if not id: + return None + attr = "id" if isinstance(id, int) else "custom_id" + return find(lambda i: getattr(i, attr, None) == id, self.children) + @classmethod def with_components(cls, *components, id=None): return cls._raw_construct( @@ -632,6 +651,28 @@ def walk_components(self) -> Iterator[Component]: yield from r + [self.accessory] yield from r + def get_component(self, id: str | int) -> Component | None: + """Get a component from this section. Roughly equivalent to `utils.get(section.walk_components(), ...)`. + If an ``int`` is provided, the component will be retrieved by ``id``, otherwise by ``custom_id``. + + Parameters + ---------- + id: Union[:class:`str`, :class:`int`] + The custom_id or id of the component to get. + + Returns + ------- + Optional[:class:`Component`] + The component with the matching ``id`` or ``custom_id`` if it exists. + """ + if not id: + return None + attr = "id" if isinstance(id, int) else "custom_id" + if self.accessory and id == getattr(self.accessory, attr, None): + return self.accessory + component = find(lambda i: getattr(i, attr, None) == id, self.components) + return component + class TextDisplay(Component): """Represents a Text Display from Components V2. @@ -1048,6 +1089,32 @@ def walk_components(self) -> Iterator[Component]: else: yield c + def get_component(self, id: str | int) -> Component | None: + """Get a component from this container. Roughly equivalent to `utils.get(container.components, ...)`. + If an ``int`` is provided, the component will be retrieved by ``id``, otherwise by ``custom_id``. + This method will also search for nested components. + + Parameters + ---------- + id: Union[:class:`str`, :class:`int`] + The custom_id or id of the component to get. + + Returns + ------- + Optional[:class:`Component`] + The component with the matching ``id`` or ``custom_id`` if it exists. + """ + if not id: + return None + attr = "id" if isinstance(id, int) else "custom_id" + for i in self.components: + if getattr(i, attr, None) == id: + return i + elif hasattr(i, "get_component"): + if component := i.get_component(id): + return component + return None + class Label(Component): """Represents a Label used in modals as the top-level component. diff --git a/discord/message.py b/discord/message.py index cb38323bc6..6c2ef1c7d4 100644 --- a/discord/message.py +++ b/discord/message.py @@ -59,7 +59,7 @@ from .reaction import Reaction from .sticker import StickerItem from .threads import Thread -from .utils import MISSING, escape_mentions +from .utils import MISSING, escape_mentions, find if TYPE_CHECKING: from .abc import ( @@ -2244,6 +2244,32 @@ def to_message_reference_dict( return data + def get_component(self, id: str | int) -> Component | None: + """Gets a component from this message. Roughly equal to `utils.get(message.components, ...)`. + If an :class:`int` is provided, the component will be retrieved by ``id``, otherwise by ``custom_id``. + This method will also search nested components. + + Parameters + ---------- + id: Union[:class:`str`, :class:`int`] + The id or custom_id the item to get + + Returns + ------- + Optional[:class:`Component`] + The component with the matching ``custom_id`` or ``id`` if it exists. + """ + if not id: + return None + attr = "id" if isinstance(id, int) else "custom_id" + for i in self.components: + if getattr(i, attr, None) == id: + return i + elif hasattr(i, "get_component"): + if component := i.get_component(id): + return component + return None + class PartialMessage(Hashable): """Represents a partial message to aid with working messages when only diff --git a/discord/ui/view.py b/discord/ui/view.py index 43dd9cccba..020e8fd6c4 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -445,7 +445,7 @@ def get_item(self, custom_id: str | int) -> Item[V] | None: Parameters ---------- - custom_id: :class:`str` + custom_id: Union[:class:`str`, :class:`int`] The custom_id of the item to get Returns