Add support for reading SelectMenu components from messages
This commit is contained in:
		| @@ -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) | ||||
|   | ||||
| @@ -458,6 +458,7 @@ class VideoQualityMode(Enum): | ||||
| class ComponentType(Enum): | ||||
|     action_row = 1 | ||||
|     button = 2 | ||||
|     select = 3 | ||||
|  | ||||
|     def __int__(self): | ||||
|         return self.value | ||||
|   | ||||
| @@ -27,7 +27,7 @@ from __future__ import annotations | ||||
| from typing import List, Literal, TypedDict, Union | ||||
| from .emoji import PartialEmoji | ||||
|  | ||||
| ComponentType = Literal[1, 2] | ||||
| ComponentType = Literal[1, 2, 3] | ||||
| ButtonStyle = Literal[1, 2, 3, 4, 5] | ||||
|  | ||||
|  | ||||
| @@ -43,9 +43,33 @@ class _ButtonComponentOptional(TypedDict, total=False): | ||||
|     emoji: PartialEmoji | ||||
|     label: str | ||||
|  | ||||
|  | ||||
| class ButtonComponent(_ButtonComponentOptional): | ||||
|     type: Literal[2] | ||||
|     style: ButtonStyle | ||||
|  | ||||
|  | ||||
| Component = Union[ActionRow, ButtonComponent] | ||||
| class _SelectMenuOptional(TypedDict, total=False): | ||||
|     placeholder: str | ||||
|     min_values: int | ||||
|     max_values: int | ||||
|  | ||||
|  | ||||
| class _SelectOptionsOptional(TypedDict, total=False): | ||||
|     description: str | ||||
|     emoji: PartialEmoji | ||||
|  | ||||
|  | ||||
| class SelectOption(_SelectOptionsOptional): | ||||
|     label: str | ||||
|     value: str | ||||
|     default: bool | ||||
|  | ||||
|  | ||||
| class SelectMenu(_SelectMenuOptional): | ||||
|     type: Literal[3] | ||||
|     custom_id: str | ||||
|     options: List[SelectOption] | ||||
|  | ||||
|  | ||||
| Component = Union[ActionRow, ButtonComponent, SelectMenu] | ||||
|   | ||||
							
								
								
									
										21
									
								
								docs/api.rst
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								docs/api.rst
									
									
									
									
									
								
							| @@ -1224,6 +1224,10 @@ of :class:`enum.Enum`. | ||||
|     .. attribute:: button | ||||
|  | ||||
|         Represents a button component. | ||||
|     .. attribute:: select | ||||
|  | ||||
|         Represents a select component. | ||||
|  | ||||
|  | ||||
| .. class:: ButtonStyle | ||||
|  | ||||
| @@ -2902,6 +2906,15 @@ Button | ||||
|     :members: | ||||
|     :inherited-members: | ||||
|  | ||||
| SelectMenu | ||||
| ~~~~~~~~~~~ | ||||
|  | ||||
| .. attributetable:: SelectMenu | ||||
|  | ||||
| .. autoclass:: SelectMenu() | ||||
|     :members: | ||||
|     :inherited-members: | ||||
|  | ||||
|  | ||||
| DeletedReferencedMessage | ||||
| ~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||
| @@ -3316,6 +3329,14 @@ PartialMessage | ||||
| .. autoclass:: PartialMessage | ||||
|     :members: | ||||
|  | ||||
| SelectOption | ||||
| ~~~~~~~~~~~~~ | ||||
|  | ||||
| .. attributetable:: SelectOption | ||||
|  | ||||
| .. autoclass:: SelectOption | ||||
|     :members: | ||||
|  | ||||
| Intents | ||||
| ~~~~~~~~~~ | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user