diff options
| author | Rapptz <[email protected]> | 2021-05-28 02:41:52 -0400 |
|---|---|---|
| committer | Rapptz <[email protected]> | 2021-05-28 05:34:21 -0400 |
| commit | ff36aedf7bbc04780cbd4efdbfa278a0b7aee404 (patch) | |
| tree | a3ab33c3c2705c210df31c5e65433ca808569c83 /discord/components.py | |
| parent | Move ActionRow to its own separate type split from Component (diff) | |
| download | discord.py-ff36aedf7bbc04780cbd4efdbfa278a0b7aee404.tar.xz discord.py-ff36aedf7bbc04780cbd4efdbfa278a0b7aee404.zip | |
Add support for reading SelectMenu components from messages
Diffstat (limited to 'discord/components.py')
| -rw-r--r-- | discord/components.py | 152 |
1 files changed, 151 insertions, 1 deletions
diff --git a/discord/components.py b/discord/components.py index 0a09a2d5..7e89f8ff 100644 --- a/discord/components.py +++ b/discord/components.py @@ -33,6 +33,8 @@ if TYPE_CHECKING: from .types.components import ( Component as ComponentPayload, ButtonComponent as ButtonComponentPayload, + SelectMenu as SelectMenuPayload, + SelectOption as SelectOptionPayload, ActionRow as ActionRowPayload, ) @@ -41,6 +43,8 @@ __all__ = ( 'Component', 'ActionRow', 'Button', + 'SelectMenu', + 'SelectOption', ) C = TypeVar('C', bound='Component') @@ -53,6 +57,7 @@ class Component: - :class:`ActionRow` - :class:`Button` + - :class:`SelectMenu` This class is abstract and cannot be instantiated. @@ -71,7 +76,7 @@ class Component: 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}>' + return f'<{self.__class__.__name__} {attrs}>' @classmethod def _raw_construct(cls: Type[C], **kwargs) -> C: @@ -94,6 +99,8 @@ class ActionRow(Component): This is a component that holds up to 5 children components in a row. + This inherits from :class:`Component`. + .. versionadded:: 2.0 Attributes @@ -186,12 +193,155 @@ class Button(Component): return payload # type: ignore +class SelectMenu(Component): + """Represents a select menu from the Discord Bot UI Kit. + + A select menu is functionally the same as a dropdown, however + on mobile it renders a bit differently. + + .. versionadded:: 2.0 + + Attributes + ------------ + custom_id: Optional[:class:`str`] + The ID of the select menu that gets received during an interaction. + placeholder: Optional[:class:`str`] + The placeholder text that is shown if nothing is selected, if any. + min_values: :class:`int` + The minimum number of items that must be chosen for this select menu. + Defaults to 1 and must be between 1 and 25. + max_values: :class:`int` + The maximum number of items that must be chosen for this select menu. + Defaults to 1 and must be between 1 and 25. + options: List[:class:`SelectOption`] + A list of options that can be selected in this menu. + """ + + __slots__: Tuple[str, ...] = ( + 'custom_id', + 'placeholder', + 'min_values', + 'max_values', + 'options', + ) + + __repr_info__: ClassVar[Tuple[str, ...]] = __slots__ + + def __init__(self, data: SelectMenuPayload): + self.type = ComponentType.select + self.custom_id: str = data['custom_id'] + self.placeholder: Optional[str] = data.get('placeholder') + self.min_values: int = data.get('min_values', 1) + self.max_values: int = data.get('max_values', 1) + self.options: List[SelectOption] = [SelectOption.from_dict(option) for option in data.get('options', [])] + + def to_dict(self) -> SelectMenuPayload: + payload: SelectMenuPayload = { + 'type': self.type.value, + 'custom_id': self.custom_id, + 'min_values': self.min_values, + 'max_values': self.max_values, + 'options': [op.to_dict() for op in self.options], + } + + if self.placeholder: + payload['placeholder'] = self.placeholder + + return payload + + +class SelectOption: + """Represents a select menu's option. + + These can be created by users. + + .. versionadded:: 2.0 + + Attributes + ----------- + label: :class:`str` + The label of the option. This is displayed to users. + Can only be up to 25 characters. + value: :class:`str` + The value of the option. This is not displayed to users. + Can only be up to 100 characters. + description: Optional[:class:`str`] + An additional description of the option, if any. + Can only be up to 50 characters. + emoji: Optional[:class:`PartialEmoji`] + The emoji of the option, if available. + default: :class:`bool` + Whether this option is selected by default. + """ + + __slots__: Tuple[str, ...] = ( + 'label', + 'value', + 'description', + 'emoji', + 'default', + ) + + def __init__( + self, + *, + label: str, + value: str, + description: Optional[str] = None, + emoji: Optional[PartialEmoji] = None, + default: bool = False, + ) -> None: + self.label = label + self.value = value + self.description = description + self.emoji = emoji + self.default = default + + def __repr__(self) -> str: + return ( + f'<SelectOption label={self.label!r} value={self.value!r} description={self.description!r} ' + f'emoji={self.emoji!r} default={self.default!r}>' + ) + + @classmethod + def from_dict(cls, data: SelectOptionPayload) -> SelectOption: + try: + emoji = PartialEmoji.from_dict(data['emoji']) + except KeyError: + emoji = None + + return cls( + label=data['label'], + value=data['value'], + description=data.get('description'), + emoji=emoji, + default=data.get('default', False), + ) + + def to_dict(self) -> SelectOptionPayload: + payload: SelectOptionPayload = { + 'label': self.label, + 'value': self.value, + 'default': self.default, + } + + if self.emoji: + payload['emoji'] = self.emoji.to_dict() # type: ignore + + if self.description: + payload['description'] = self.description + + return payload + + def _component_factory(data: ComponentPayload) -> Component: component_type = data['type'] if component_type == 1: return ActionRow(data) elif component_type == 2: return Button(data) # type: ignore + elif component_type == 3: + return SelectMenu(data) # type: ignore else: as_enum = try_enum(ComponentType, component_type) return Component._raw_construct(type=as_enum) |