Source code for flare.components.select

from __future__ import annotations

import abc
import typing as t

import hikari
import hikari.api
import hikari.api.special_endpoints
from typing_extensions import Self

from flare import dataclass
from flare.components.base import CallbackComponent
from flare.components.functional import FunctionalComponent
from flare.exceptions import ComponentError

__all__: t.Final[t.Sequence[str]] = (
    "TextSelect",
    "UserSelect",
    "RoleSelect",
    "MentionableSelect",
    "ChannelSelect",
    "text_select",
    "user_select",
    "role_select",
    "mentionable_select",
    "channel_select",
)

P = t.ParamSpec("P")
T = t.TypeVar("T", bound=t.Any)


class _AbstractSelect(CallbackComponent, abc.ABC):
    """Abstract class for all select menu types."""

    __min_values: t.ClassVar[int | None]
    __max_values: t.ClassVar[int | None]
    __placeholder: t.ClassVar[hikari.UndefinedOr[str]]
    __disabled: t.ClassVar[bool | None]

    def __init_subclass__(
        cls,
        cookie: str | None = None,
        min_values: int | None = None,
        max_values: int | None = None,
        placeholder: hikari.UndefinedOr[str] = hikari.UNDEFINED,
        disabled: bool | None = None,
        _dataclass_fields: list[dataclass.Field] | None = None,
    ) -> None:
        super().__init_subclass__(cookie, _dataclass_fields)
        cls.__min_values = min_values
        cls.__max_values = max_values
        cls.__placeholder = placeholder
        cls.__disabled = disabled

    def __post_init__(self):
        super().__post_init__()
        self.min_values = self.__min_values
        self.max_values = self.__max_values
        self.placeholder = self.__placeholder
        self.disabled = self.__disabled

    @property
    @abc.abstractmethod
    def _component_type(self) -> hikari.ComponentType:
        ...

    @property
    def width(self) -> int:
        return 5

    def set_min_values(self, min_values: int | None) -> Self:
        self.min_values = min_values
        return self

    def set_max_values(self, max_values: int | None) -> Self:
        self.max_values = max_values
        return self

    def set_placeholder(self, placeholder: hikari.UndefinedOr[str]) -> Self:
        self.placeholder = placeholder
        return self

    def set_disabled(self, disabled: bool) -> Self:
        self.disabled = disabled
        return self

    def _verify_placeholder(self) -> None:
        if self.placeholder and len(self.placeholder) > 100:
            raise ComponentError("Placeholder text must be shorter than 100 characters.")

    @abc.abstractmethod
    def build(self, action_row: hikari.api.MessageActionRowBuilder) -> None:
        """
        Build the select menu into the passed in action row.
        """


class BaseSelect(_AbstractSelect):
    """
    Class for select menus that don't have any special properties.
    """

    def build(self, action_row: hikari.api.MessageActionRowBuilder) -> None:
        self._verify_placeholder()
        action_row.add_select_menu(
            self._component_type,
            self.custom_id,
            placeholder=self.placeholder,
            min_values=self.min_values or 0,  # default is 0
            max_values=self.max_values or 1,  # default is 1
            is_disabled=self.disabled or False,  # default is False
        )


[docs] class TextSelect(_AbstractSelect): """ Class for a select menu message component. Args: options: An array of options for the select menu. This must be provided when the class is created or using `Select.set_options`. min_vales: The minimum amount of values a user must select. max_values: The maximum amount of values a user must select. placeholder: Placeholder text when no option is selected. disabled: Whether the button is disabled. cookie: An identifier to use for the select menu. A custom cookie can be supplied so a shorter one is used in serializing and deserializing. """ __options: t.ClassVar[t.Sequence[tuple[str, str] | str | hikari.SelectMenuOption] | None] def __init_subclass__( cls, cookie: str | None = None, options: t.Sequence[tuple[str, str] | str | hikari.SelectMenuOption] | None = None, min_values: int | None = None, max_values: int | None = None, placeholder: hikari.UndefinedOr[str] = hikari.UNDEFINED, disabled: bool | None = None, _dataclass_fields: list[dataclass.Field] | None = None, ) -> None: super().__init_subclass__( cookie=cookie, min_values=min_values, max_values=max_values, placeholder=placeholder, disabled=disabled, _dataclass_fields=_dataclass_fields, ) cls.__options = options def __post_init__(self): super().__post_init__() self.options = self.__options def set_options(self, *options: tuple[str, str] | str | hikari.SelectMenuOption) -> Self: self.options = options return self @property def _component_type(self) -> hikari.ComponentType: return hikari.ComponentType.TEXT_SELECT_MENU
[docs] def build(self, action_row: hikari.api.MessageActionRowBuilder) -> None: self._verify_placeholder() if not self.options: raise ComponentError("Expected one or more options for select menu. Got zero.") if len(self.options) > 25: raise ComponentError("Cannot create a select menu with more than 25 options.") if self.min_values and self.min_values > len(self.options): raise ComponentError("Cannot create a select menu with greater min options than options.") if self.max_values and self.max_values > len(self.options): raise ComponentError("Cannot create a select menu with greater max options than options.") select = action_row.add_text_menu( self.custom_id, placeholder=self.placeholder, min_values=self.min_values or 0, # default is 0 max_values=self.max_values or 1, # default is 1 is_disabled=self.disabled or False, # default is False ) for option in self.options: if isinstance(option, str): select.add_option(option, option) elif isinstance(option, hikari.SelectMenuOption): select.add_option( option.label, option.value, description=option.description or hikari.UNDEFINED, emoji=option.emoji or hikari.UNDEFINED, is_default=option.is_default, ) else: select.add_option(*option)
[docs] class UserSelect(BaseSelect): """ Class for a user select menu message component. Args: min_vales: The minimum amount of values a user must select. max_values: The maximum amount of values a user must select. placeholder: Placeholder text when no option is selected. disabled: Whether the button is disabled. cookie: An identifier to use for the select menu. A custom cookie can be supplied so a shorter one is used in serializing and deserializing. """ @property def _component_type(self) -> hikari.ComponentType: return hikari.ComponentType.USER_SELECT_MENU
[docs] class RoleSelect(BaseSelect): """ Class for a role select menu message component. Args: min_vales: The minimum amount of values a user must select. max_values: The maximum amount of values a user must select. placeholder: Placeholder text when no option is selected. disabled: Whether the button is disabled. cookie: An identifier to use for the select menu. A custom cookie can be supplied so a shorter one is used in serializing and deserializing. """ @property def _component_type(self) -> hikari.ComponentType: return hikari.ComponentType.ROLE_SELECT_MENU
[docs] class MentionableSelect(BaseSelect): """ Class for a mentionable select menu message component. Args: min_vales: The minimum amount of values a user must select. max_values: The maximum amount of values a user must select. placeholder: Placeholder text when no option is selected. disabled: Whether the button is disabled. cookie: An identifier to use for the select menu. A custom cookie can be supplied so a shorter one is used in serializing and deserializing. """ @property def _component_type(self) -> hikari.ComponentType: return hikari.ComponentType.MENTIONABLE_SELECT_MENU
[docs] class ChannelSelect(_AbstractSelect): """ Class for a channel select menu message component. Args: channel_types: The channel types that will appear in this select menu. min_vales: The minimum amount of values a user must select. max_values: The maximum amount of values a user must select. placeholder: Placeholder text when no option is selected. disabled: Whether the button is disabled. cookie: An identifier to use for the select menu. A custom cookie can be supplied so a shorter one is used in serializing and deserializing. """ __channel_types: t.ClassVar[t.Sequence[hikari.ChannelType] | None] def __init_subclass__( cls, cookie: str | None = None, channel_types: t.Sequence[hikari.ChannelType] | None = None, min_values: int | None = None, max_values: int | None = None, placeholder: hikari.UndefinedOr[str] = hikari.UNDEFINED, disabled: bool | None = None, _dataclass_fields: list[dataclass.Field] | None = None, ) -> None: super().__init_subclass__( cookie=cookie, min_values=min_values, max_values=max_values, placeholder=placeholder, disabled=disabled, _dataclass_fields=_dataclass_fields, ) cls.__channel_types = channel_types def __post_init__(self): super().__post_init__() self.channel_types = self.__channel_types def set_channel_types(self, *channel_types: hikari.ChannelType) -> Self: self.channel_types = channel_types return self
[docs] def build(self, action_row: hikari.api.MessageActionRowBuilder) -> None: self._verify_placeholder() action_row.add_channel_menu( self.custom_id, channel_types=self.channel_types or (), # default is () placeholder=self.placeholder or hikari.UNDEFINED, min_values=self.min_values or 0, # default is 0 max_values=self.max_values or 1, # default is 1 is_disabled=self.disabled or False, )
@property def _component_type(self) -> hikari.ComponentType: return hikari.ComponentType.CHANNEL_SELECT_MENU
class _AbstractSelectFunction(FunctionalComponent[T], abc.ABC): """ A decorator to create a `flare.Select`. This is a shorthand for when type safety is not needed. """ def __init__( self, *, cookie: str | None = None, min_values: int | None = None, max_values: int | None = None, placeholder: hikari.UndefinedOr[str] = hikari.UNDEFINED, disabled: bool | None = None, ) -> None: self.cookie = cookie self.min_values = min_values self.max_values = max_values self.placeholder = placeholder self.disabled = disabled @property @abc.abstractmethod def component_type(self) -> type[T]: return TextSelect @property def kwargs(self) -> dict[str, t.Any]: return { "cookie": self.cookie, "min_values": self.min_values, "max_values": self.max_values, "placeholder": self.placeholder, "disabled": self.disabled, }
[docs] class text_select(_AbstractSelectFunction[TextSelect]): def __init__( self, *, cookie: str | None = None, options: t.Sequence[tuple[str, str] | str | hikari.SelectMenuOption] | None = None, min_values: int | None = None, max_values: int | None = None, placeholder: hikari.UndefinedOr[str] = hikari.UNDEFINED, disabled: bool | None = None, ) -> None: self.options = options super().__init__( cookie=cookie, min_values=min_values, max_values=max_values, placeholder=placeholder, disabled=disabled, ) @property def component_type(self) -> type[TextSelect]: return TextSelect @property def kwargs(self) -> dict[str, t.Any]: kwargs = super().kwargs kwargs["options"] = self.options return kwargs
[docs] class user_select(_AbstractSelectFunction[UserSelect]): @property def component_type(self) -> type[UserSelect]: return UserSelect
[docs] class role_select(_AbstractSelectFunction[RoleSelect]): @property def component_type(self) -> type[RoleSelect]: return RoleSelect
[docs] class mentionable_select(_AbstractSelectFunction[MentionableSelect]): @property def component_type(self) -> type[MentionableSelect]: return MentionableSelect
[docs] class channel_select(_AbstractSelectFunction[ChannelSelect]): def __init__( self, *, cookie: str | None = None, channel_types: t.Sequence[hikari.ChannelType] | None = None, min_values: int | None = None, max_values: int | None = None, placeholder: hikari.UndefinedOr[str] = hikari.UNDEFINED, disabled: bool | None = None, ) -> None: self.channel_types = channel_types super().__init__( cookie=cookie, min_values=min_values, max_values=max_values, placeholder=placeholder, disabled=disabled, ) @property def component_type(self) -> type[ChannelSelect]: return ChannelSelect @property def kwargs(self) -> dict[str, t.Any]: kwargs = super().kwargs kwargs["channel_types"] = self.channel_types return kwargs
# MIT License # # Copyright (c) 2022-present Lunarmagpie # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE.