diff --git a/discord/components.py b/discord/components.py index 924c21526..0a09a2d56 100644 --- a/discord/components.py +++ b/discord/components.py @@ -24,29 +24,75 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar +from typing import Any, ClassVar, Dict, List, Optional, TYPE_CHECKING, Tuple, Type, TypeVar from .enums import try_enum, ComponentType, ButtonStyle +from .utils import get_slots from .partial_emoji import PartialEmoji if TYPE_CHECKING: from .types.components import ( Component as ComponentPayload, ButtonComponent as ButtonComponentPayload, - ComponentContainer as ComponentContainerPayload, + ActionRow as ActionRowPayload, ) __all__ = ( 'Component', + 'ActionRow', 'Button', ) C = TypeVar('C', bound='Component') + class Component: """Represents a Discord Bot UI Kit Component. - Currently, the only components supported by Discord are buttons and button groups. + Currently, the only components supported by Discord are: + + - :class:`ActionRow` + - :class:`Button` + + This class is abstract and cannot be instantiated. + + .. versionadded:: 2.0 + + Attributes + ------------ + type: :class:`ComponentType` + The type of component. + """ + + __slots__: Tuple[str, ...] = ('type',) + + __repr_info__: ClassVar[Tuple[str, ...]] + type: ComponentType + + def __repr__(self) -> str: + attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__repr_info__) + return f'<{self.__class__.__name__} type={self.type!r} {attrs}>' + + @classmethod + def _raw_construct(cls: Type[C], **kwargs) -> C: + self: C = cls.__new__(cls) + for slot in get_slots(cls): + try: + value = kwargs[slot] + except KeyError: + pass + else: + setattr(self, slot, value) + return self + + def to_dict(self) -> Dict[str, Any]: + raise NotImplementedError + + +class ActionRow(Component): + """Represents a Discord Bot UI Kit Action Row. + + This is a component that holds up to 5 children components in a row. .. versionadded:: 2.0 @@ -58,36 +104,21 @@ class Component: The children components that this holds, if any. """ - __slots__: Tuple[str, ...] = ( - 'type', - 'children', - ) + __slots__: Tuple[str, ...] = ('children',) + + __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ def __init__(self, data: ComponentPayload): self.type: ComponentType = try_enum(ComponentType, data['type']) self.children: List[Component] = [_component_factory(d) for d in data.get('components', [])] - def __repr__(self) -> str: - attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__slots__) - return f'<{self.__class__.__name__} type={self.type!r} {attrs}>' - - def to_dict(self) -> ComponentContainerPayload: + def to_dict(self) -> ActionRowPayload: return { 'type': int(self.type), 'components': [child.to_dict() for child in self.children], } # type: ignore - @classmethod - def _raw_construct(cls: Type[C], **kwargs) -> C: - self: C = cls.__new__(cls) - slots = cls.__slots__ - for attr, value in kwargs.items(): - if attr in slots: - setattr(self, attr, value) - return self - - class Button(Component): """Represents a button from the Discord Bot UI Kit. @@ -112,7 +143,7 @@ class Button(Component): The emoji of the button, if available. """ - __slots__: Tuple[str, ...] = Component.__slots__ + ( + __slots__: Tuple[str, ...] = ( 'style', 'custom_id', 'url', @@ -121,6 +152,8 @@ class Button(Component): 'emoji', ) + __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + def __init__(self, data: ButtonComponentPayload): self.type: ComponentType = try_enum(ComponentType, data['type']) self.style: ButtonStyle = try_enum(ButtonStyle, data['style']) @@ -152,11 +185,13 @@ class Button(Component): return payload # type: ignore + def _component_factory(data: ComponentPayload) -> Component: component_type = data['type'] if component_type == 1: - return Component(data) + return ActionRow(data) elif component_type == 2: return Button(data) # type: ignore else: - return Component(data) + as_enum = try_enum(ComponentType, component_type) + return Component._raw_construct(type=as_enum) diff --git a/discord/types/components.py b/discord/types/components.py index 2f14f9033..8f1838b87 100644 --- a/discord/types/components.py +++ b/discord/types/components.py @@ -24,16 +24,16 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations -from typing import Literal, TypedDict, Union +from typing import List, Literal, TypedDict, Union from .emoji import PartialEmoji ComponentType = Literal[1, 2] ButtonStyle = Literal[1, 2, 3, 4, 5] -class ComponentContainer(TypedDict): +class ActionRow(TypedDict): type: Literal[1] - components: Component + components: List[Component] class _ButtonComponentOptional(TypedDict, total=False): @@ -48,4 +48,4 @@ class ButtonComponent(_ButtonComponentOptional): style: ButtonStyle -Component = Union[ComponentContainer, ButtonComponent] +Component = Union[ActionRow, ButtonComponent] diff --git a/docs/api.rst b/docs/api.rst index 72faae7be..5639b85e5 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -2885,6 +2885,14 @@ Component .. autoclass:: Component() :members: +ActionRow +~~~~~~~~~~ + +.. attributetable:: ActionRow + +.. autoclass:: ActionRow() + :members: + Button ~~~~~~~