-
Notifications
You must be signed in to change notification settings - Fork 134
/
base.py
201 lines (158 loc) · 5.79 KB
/
base.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# SPDX-License-Identifier: MIT
from __future__ import annotations
import asyncio
import os
from abc import ABC, abstractmethod
from typing import (
TYPE_CHECKING,
Any,
Callable,
Generic,
List,
Optional,
Tuple,
Type,
TypeVar,
get_origin,
)
from ...components import AnySelectMenu
from ...enums import ComponentType
from ...utils import MISSING
from ..item import DecoratedItem, Item, Object
__all__ = ("BaseSelect",)
if TYPE_CHECKING:
from typing_extensions import ParamSpec, Self
from ...interactions import MessageInteraction
from ..item import ItemCallbackType
from ..view import View
else:
ParamSpec = TypeVar
S_co = TypeVar("S_co", bound="BaseSelect", covariant=True)
V_co = TypeVar("V_co", bound="Optional[View]", covariant=True)
SelectMenuT = TypeVar("SelectMenuT", bound=AnySelectMenu)
SelectValueT = TypeVar("SelectValueT")
P = ParamSpec("P")
class BaseSelect(Generic[SelectMenuT, SelectValueT, V_co], Item[V_co], ABC):
"""Represents an abstract UI select menu.
This is usually represented as a drop down menu.
This isn't meant to be used directly, instead use one of the concrete select menu types:
- :class:`disnake.ui.StringSelect`
- :class:`disnake.ui.UserSelect`
- :class:`disnake.ui.RoleSelect`
- :class:`disnake.ui.MentionableSelect`
- :class:`disnake.ui.ChannelSelect`
.. versionadded:: 2.7
"""
__repr_attributes__: Tuple[str, ...] = (
"placeholder",
"min_values",
"max_values",
"disabled",
)
# We have to set this to MISSING in order to overwrite the abstract property from WrappedComponent
_underlying: SelectMenuT = MISSING
def __init__(
self,
underlying_type: Type[SelectMenuT],
component_type: ComponentType,
*,
custom_id: str,
placeholder: Optional[str],
min_values: int,
max_values: int,
disabled: bool,
row: Optional[int],
) -> None:
super().__init__()
self._selected_values: List[SelectValueT] = []
self._provided_custom_id = custom_id is not MISSING
custom_id = os.urandom(16).hex() if custom_id is MISSING else custom_id
self._underlying = underlying_type._raw_construct(
custom_id=custom_id,
type=component_type,
placeholder=placeholder,
min_values=min_values,
max_values=max_values,
disabled=disabled,
)
self.row = row
@property
def custom_id(self) -> str:
""":class:`str`: The ID of the select menu that gets received during an interaction."""
return self._underlying.custom_id
@custom_id.setter
def custom_id(self, value: str):
if not isinstance(value, str):
raise TypeError("custom_id must be None or str")
self._underlying.custom_id = value
@property
def placeholder(self) -> Optional[str]:
"""Optional[:class:`str`]: The placeholder text that is shown if nothing is selected, if any."""
return self._underlying.placeholder
@placeholder.setter
def placeholder(self, value: Optional[str]):
if value is not None and not isinstance(value, str):
raise TypeError("placeholder must be None or str")
self._underlying.placeholder = value
@property
def min_values(self) -> int:
""":class:`int`: The minimum number of items that must be chosen for this select menu."""
return self._underlying.min_values
@min_values.setter
def min_values(self, value: int):
self._underlying.min_values = int(value)
@property
def max_values(self) -> int:
""":class:`int`: The maximum number of items that must be chosen for this select menu."""
return self._underlying.max_values
@max_values.setter
def max_values(self, value: int):
self._underlying.max_values = int(value)
@property
def disabled(self) -> bool:
""":class:`bool`: Whether the select menu is disabled."""
return self._underlying.disabled
@disabled.setter
def disabled(self, value: bool):
self._underlying.disabled = bool(value)
@property
def values(self) -> List[SelectValueT]:
return self._selected_values
@property
def width(self) -> int:
return 5
def refresh_component(self, component: SelectMenuT) -> None:
self._underlying = component
def refresh_state(self, interaction: MessageInteraction) -> None:
self._selected_values = interaction.resolved_values # type: ignore
@classmethod
@abstractmethod
def from_component(cls, component: SelectMenuT) -> Self:
raise NotImplementedError
def is_dispatchable(self) -> bool:
"""Whether the select menu is dispatchable. This will always return ``True``.
:return type: :class:`bool`
"""
return True
def _create_decorator(
cls: Type[Object[S_co, P]],
# only for input validation
base_cls: Type[BaseSelect[Any, Any, Any]],
/,
*args: P.args,
**kwargs: P.kwargs,
) -> Callable[[ItemCallbackType[S_co]], DecoratedItem[S_co]]:
if args:
# the `*args` def above is just to satisfy the typechecker
raise RuntimeError("expected no *args")
if (origin := get_origin(cls)) is not None:
cls = origin
if not isinstance(cls, type) or not issubclass(cls, base_cls):
raise TypeError(f"cls argument must be a subclass of {base_cls.__name__}, got {cls!r}")
def decorator(func: ItemCallbackType[S_co]) -> DecoratedItem[S_co]:
if not asyncio.iscoroutinefunction(func):
raise TypeError("select function must be a coroutine function")
func.__discord_ui_model_type__ = cls
func.__discord_ui_model_kwargs__ = kwargs
return func # type: ignore
return decorator