Add support for reading SelectMenu components from messages
This commit is contained in:
parent
8bd17ede47
commit
ff36aedf7b
@ -33,6 +33,8 @@ if TYPE_CHECKING:
|
|||||||
from .types.components import (
|
from .types.components import (
|
||||||
Component as ComponentPayload,
|
Component as ComponentPayload,
|
||||||
ButtonComponent as ButtonComponentPayload,
|
ButtonComponent as ButtonComponentPayload,
|
||||||
|
SelectMenu as SelectMenuPayload,
|
||||||
|
SelectOption as SelectOptionPayload,
|
||||||
ActionRow as ActionRowPayload,
|
ActionRow as ActionRowPayload,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -41,6 +43,8 @@ __all__ = (
|
|||||||
'Component',
|
'Component',
|
||||||
'ActionRow',
|
'ActionRow',
|
||||||
'Button',
|
'Button',
|
||||||
|
'SelectMenu',
|
||||||
|
'SelectOption',
|
||||||
)
|
)
|
||||||
|
|
||||||
C = TypeVar('C', bound='Component')
|
C = TypeVar('C', bound='Component')
|
||||||
@ -53,6 +57,7 @@ class Component:
|
|||||||
|
|
||||||
- :class:`ActionRow`
|
- :class:`ActionRow`
|
||||||
- :class:`Button`
|
- :class:`Button`
|
||||||
|
- :class:`SelectMenu`
|
||||||
|
|
||||||
This class is abstract and cannot be instantiated.
|
This class is abstract and cannot be instantiated.
|
||||||
|
|
||||||
@ -71,7 +76,7 @@ class Component:
|
|||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__repr_info__)
|
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
|
@classmethod
|
||||||
def _raw_construct(cls: Type[C], **kwargs) -> C:
|
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 is a component that holds up to 5 children components in a row.
|
||||||
|
|
||||||
|
This inherits from :class:`Component`.
|
||||||
|
|
||||||
.. versionadded:: 2.0
|
.. versionadded:: 2.0
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
@ -186,12 +193,155 @@ class Button(Component):
|
|||||||
return payload # type: ignore
|
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:
|
def _component_factory(data: ComponentPayload) -> Component:
|
||||||
component_type = data['type']
|
component_type = data['type']
|
||||||
if component_type == 1:
|
if component_type == 1:
|
||||||
return ActionRow(data)
|
return ActionRow(data)
|
||||||
elif component_type == 2:
|
elif component_type == 2:
|
||||||
return Button(data) # type: ignore
|
return Button(data) # type: ignore
|
||||||
|
elif component_type == 3:
|
||||||
|
return SelectMenu(data) # type: ignore
|
||||||
else:
|
else:
|
||||||
as_enum = try_enum(ComponentType, component_type)
|
as_enum = try_enum(ComponentType, component_type)
|
||||||
return Component._raw_construct(type=as_enum)
|
return Component._raw_construct(type=as_enum)
|
||||||
|
@ -458,6 +458,7 @@ class VideoQualityMode(Enum):
|
|||||||
class ComponentType(Enum):
|
class ComponentType(Enum):
|
||||||
action_row = 1
|
action_row = 1
|
||||||
button = 2
|
button = 2
|
||||||
|
select = 3
|
||||||
|
|
||||||
def __int__(self):
|
def __int__(self):
|
||||||
return self.value
|
return self.value
|
||||||
|
@ -27,7 +27,7 @@ from __future__ import annotations
|
|||||||
from typing import List, Literal, TypedDict, Union
|
from typing import List, Literal, TypedDict, Union
|
||||||
from .emoji import PartialEmoji
|
from .emoji import PartialEmoji
|
||||||
|
|
||||||
ComponentType = Literal[1, 2]
|
ComponentType = Literal[1, 2, 3]
|
||||||
ButtonStyle = Literal[1, 2, 3, 4, 5]
|
ButtonStyle = Literal[1, 2, 3, 4, 5]
|
||||||
|
|
||||||
|
|
||||||
@ -43,9 +43,33 @@ class _ButtonComponentOptional(TypedDict, total=False):
|
|||||||
emoji: PartialEmoji
|
emoji: PartialEmoji
|
||||||
label: str
|
label: str
|
||||||
|
|
||||||
|
|
||||||
class ButtonComponent(_ButtonComponentOptional):
|
class ButtonComponent(_ButtonComponentOptional):
|
||||||
type: Literal[2]
|
type: Literal[2]
|
||||||
style: ButtonStyle
|
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
|
.. attribute:: button
|
||||||
|
|
||||||
Represents a button component.
|
Represents a button component.
|
||||||
|
.. attribute:: select
|
||||||
|
|
||||||
|
Represents a select component.
|
||||||
|
|
||||||
|
|
||||||
.. class:: ButtonStyle
|
.. class:: ButtonStyle
|
||||||
|
|
||||||
@ -2902,6 +2906,15 @@ Button
|
|||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
|
SelectMenu
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: SelectMenu
|
||||||
|
|
||||||
|
.. autoclass:: SelectMenu()
|
||||||
|
:members:
|
||||||
|
:inherited-members:
|
||||||
|
|
||||||
|
|
||||||
DeletedReferencedMessage
|
DeletedReferencedMessage
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -3316,6 +3329,14 @@ PartialMessage
|
|||||||
.. autoclass:: PartialMessage
|
.. autoclass:: PartialMessage
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
SelectOption
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. attributetable:: SelectOption
|
||||||
|
|
||||||
|
.. autoclass:: SelectOption
|
||||||
|
:members:
|
||||||
|
|
||||||
Intents
|
Intents
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user