Files
2026-02-03 20:32:43 +02:00

359 lines
12 KiB
Python

# SPDX-License-Identifier: MIT
from __future__ import annotations
from typing import (
TYPE_CHECKING,
Any,
Callable,
ClassVar,
Dict,
List,
Mapping,
Optional,
Tuple,
Type,
TypeVar,
Union,
overload,
)
from ...abc import Snowflake
from ...components import SelectOption, StringSelectMenu
from ...enums import ComponentType, SelectDefaultValueType
from ...utils import MISSING
from .base import BaseSelect, P, V_co, _create_decorator
if TYPE_CHECKING:
from typing_extensions import Self
from ...emoji import Emoji
from ...partial_emoji import PartialEmoji
from ..item import DecoratedItem, ItemCallbackType
__all__ = (
"StringSelect",
"Select",
"string_select",
"select",
)
SelectOptionInput = Union[List[SelectOption], List[str], Dict[str, str]]
def _parse_select_options(options: SelectOptionInput) -> List[SelectOption]:
if isinstance(options, dict):
return [SelectOption(label=key, value=val) for key, val in options.items()]
return [opt if isinstance(opt, SelectOption) else SelectOption(label=opt) for opt in options]
class StringSelect(BaseSelect[StringSelectMenu, str, V_co]):
"""Represents a UI string select menu.
This is usually represented as a drop down menu.
In order to get the selected items that the user has chosen, use :attr:`.values`.
.. versionadded:: 2.0
.. versionchanged:: 2.7
Renamed from ``Select`` to ``StringSelect``.
Parameters
----------
custom_id: :class:`str`
The ID of the select menu that gets received during an interaction.
If not given then one is generated for you.
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.
disabled: :class:`bool`
Whether the select is disabled.
options: Union[List[:class:`disnake.SelectOption`], List[:class:`str`], Dict[:class:`str`, :class:`str`]]
A list of options that can be selected in this menu. Use explicit :class:`.SelectOption`\\s
for fine-grained control over the options. Alternatively, a list of strings will be treated
as a list of labels, and a dict will be treated as a mapping of labels to values.
.. versionchanged:: 2.5
Now also accepts a list of str or a dict of str to str, which are then appropriately parsed as
:class:`.SelectOption` labels and values.
required: :class:`bool`
Whether the select menu is required. Only applies to components in modals.
Defaults to ``True``.
.. versionadded:: 2.11
id: :class:`int`
The numeric identifier for the component. Must be unique within the message.
If set to ``0`` (the default) when sending a component, the API will assign
sequential identifiers to the components in the message.
.. versionadded:: 2.11
row: Optional[:class:`int`]
The relative row this select menu belongs to. A Discord component can only have 5
rows. By default, items are arranged automatically into those 5 rows. If you'd
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
Attributes
----------
values: List[:class:`str`]
A list of values that have been selected by the user.
"""
__repr_attributes__: ClassVar[Tuple[str, ...]] = (*BaseSelect.__repr_attributes__, "options")
# In practice this should never be used by anything, might as well have it anyway though.
_default_value_type_map: ClassVar[
Mapping[SelectDefaultValueType, Tuple[Type[Snowflake], ...]]
] = {}
@overload
def __init__(
self: StringSelect[None],
*,
custom_id: str = ...,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
options: SelectOptionInput = ...,
required: bool = True,
id: int = 0,
row: Optional[int] = None,
) -> None: ...
@overload
def __init__(
self: StringSelect[V_co],
*,
custom_id: str = ...,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
options: SelectOptionInput = ...,
required: bool = True,
id: int = 0,
row: Optional[int] = None,
) -> None: ...
def __init__(
self,
*,
custom_id: str = MISSING,
placeholder: Optional[str] = None,
min_values: int = 1,
max_values: int = 1,
disabled: bool = False,
options: SelectOptionInput = MISSING,
required: bool = True,
id: int = 0,
row: Optional[int] = None,
) -> None:
super().__init__(
StringSelectMenu,
ComponentType.string_select,
custom_id=custom_id,
placeholder=placeholder,
min_values=min_values,
max_values=max_values,
disabled=disabled,
default_values=None,
required=required,
id=id,
row=row,
)
self._underlying.options = [] if options is MISSING else _parse_select_options(options)
@classmethod
def from_component(cls, component: StringSelectMenu) -> Self:
return cls(
custom_id=component.custom_id,
placeholder=component.placeholder,
min_values=component.min_values,
max_values=component.max_values,
disabled=component.disabled,
options=component.options,
required=component.required,
id=component.id,
row=None,
)
@property
def options(self) -> List[SelectOption]:
"""List[:class:`disnake.SelectOption`]: A list of options that can be selected in this select menu."""
return self._underlying.options
@options.setter
def options(self, value: List[SelectOption]) -> None:
if not isinstance(value, list):
raise TypeError("options must be a list of SelectOption")
if not all(isinstance(obj, SelectOption) for obj in value):
raise TypeError("all list items must subclass SelectOption")
self._underlying.options = value
def add_option(
self,
*,
label: str,
value: str = MISSING,
description: Optional[str] = None,
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
default: bool = False,
) -> None:
"""Adds an option to the select menu.
To append a pre-existing :class:`.SelectOption` use the
:meth:`append_option` method instead.
Parameters
----------
label: :class:`str`
The label of the option. This is displayed to users.
Can only be up to 100 characters.
value: :class:`str`
The value of the option. This is not displayed to users.
If not given, defaults to the label. Can only be up to 100 characters.
description: Optional[:class:`str`]
An additional description of the option, if any.
Can only be up to 100 characters.
emoji: Optional[Union[:class:`str`, :class:`.Emoji`, :class:`.PartialEmoji`]]
The emoji of the option, if available. This can either be a string representing
the custom or unicode emoji or an instance of :class:`.PartialEmoji` or :class:`.Emoji`.
default: :class:`bool`
Whether this option is selected by default.
Raises
------
ValueError
The number of options exceeds 25.
"""
option = SelectOption(
label=label,
value=value,
description=description,
emoji=emoji,
default=default,
)
self.append_option(option)
def append_option(self, option: SelectOption) -> None:
"""Appends an option to the select menu.
Parameters
----------
option: :class:`disnake.SelectOption`
The option to append to the select menu.
Raises
------
ValueError
The number of options exceeds 25.
"""
if len(self._underlying.options) >= 25:
raise ValueError("maximum number of options already provided")
self._underlying.options.append(option)
Select = StringSelect # backwards compatibility
S_co = TypeVar("S_co", bound="StringSelect", covariant=True)
@overload
def string_select(
*,
placeholder: Optional[str] = None,
custom_id: str = ...,
min_values: int = 1,
max_values: int = 1,
options: SelectOptionInput = ...,
disabled: bool = False,
id: int = 0,
row: Optional[int] = None,
) -> Callable[[ItemCallbackType[V_co, StringSelect[V_co]]], DecoratedItem[StringSelect[V_co]]]: ...
@overload
def string_select(
cls: Callable[P, S_co], *_: P.args, **kwargs: P.kwargs
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]: ...
def string_select(
cls: Callable[..., S_co] = StringSelect[Any], **kwargs: Any
) -> Callable[[ItemCallbackType[V_co, S_co]], DecoratedItem[S_co]]:
"""A decorator that attaches a string select menu to a component.
The function being decorated should have three parameters, ``self`` representing
the :class:`disnake.ui.View`, the :class:`disnake.ui.StringSelect` that was
interacted with, and the :class:`disnake.MessageInteraction`.
In order to get the selected items that the user has chosen within the callback
use :attr:`StringSelect.values`.
.. versionchanged:: 2.7
Renamed from ``select`` to ``string_select``.
Parameters
----------
cls: Callable[..., :class:`StringSelect`]
A callable (may be a :class:`StringSelect` subclass) to create a new instance of this component.
If provided, the other parameters described below do not apply.
Instead, this decorator will accept the same keywords as the passed callable/class does.
.. versionadded:: 2.6
placeholder: Optional[:class:`str`]
The placeholder text that is shown if nothing is selected, if any.
custom_id: :class:`str`
The ID of the select menu that gets received during an interaction.
It is recommended not to set this parameter to prevent conflicts.
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: Union[List[:class:`disnake.SelectOption`], List[:class:`str`], Dict[:class:`str`, :class:`str`]]
A list of options that can be selected in this menu. Use explicit :class:`.SelectOption`\\s
for fine-grained control over the options. Alternatively, a list of strings will be treated
as a list of labels, and a dict will be treated as a mapping of labels to values.
.. versionchanged:: 2.5
Now also accepts a list of str or a dict of str to str, which are then appropriately parsed as
:class:`.SelectOption` labels and values.
disabled: :class:`bool`
Whether the select is disabled. Defaults to ``False``.
id: :class:`int`
The numeric identifier for the component. Must be unique within the message.
If set to ``0`` (the default) when sending a component, the API will assign
sequential identifiers to the components in the message.
.. versionadded:: 2.11
row: Optional[:class:`int`]
The relative row this select menu belongs to. A Discord component can only have 5
rows. By default, items are arranged automatically into those 5 rows. If you'd
like to control the relative positioning of the row then passing an index is advised.
For example, row=1 will show up before row=2. Defaults to ``None``, which is automatic
ordering. The row number must be between 0 and 4 (i.e. zero indexed).
"""
return _create_decorator(cls, **kwargs)
select = string_select # backwards compatibility