diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d76398448..6c8ddba498 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,8 @@ These changes are available on the `master` branch, but have not yet been releas ([#2818](https://github.com/Pycord-Development/pycord/pull/2818)) - Added `Interaction.attachment_size_limit`. ([#2854](https://github.com/Pycord-Development/pycord/pull/2854)) +- 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 39576c9eea..16203da73b 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 @@ -187,6 +187,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( @@ -620,6 +639,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. @@ -1036,6 +1077,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 + COMPONENT_MAPPINGS = { 1: ActionRow, diff --git a/discord/message.py b/discord/message.py index 909453c1ad..6af61b9e12 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 ( @@ -2211,6 +2211,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 64b0520172..0d8ca13a6f 100644 --- a/discord/ui/view.py +++ b/discord/ui/view.py @@ -434,7 +434,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