Initialisation du repository de Beta
This commit is contained in:
commit
14985f6dbb
9469 changed files with 1903273 additions and 0 deletions
27
venv/lib/python3.12/site-packages/discord/ui/__init__.py
Normal file
27
venv/lib/python3.12/site-packages/discord/ui/__init__.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
"""
|
||||
discord.ui
|
||||
~~~~~~~~~~~
|
||||
|
||||
Bot UI Kit helper for the Discord API
|
||||
|
||||
:copyright: (c) 2015-present Rapptz
|
||||
:license: MIT, see LICENSE for more details.
|
||||
|
||||
"""
|
||||
|
||||
from .view import *
|
||||
from .modal import *
|
||||
from .item import *
|
||||
from .button import *
|
||||
from .select import *
|
||||
from .text_input import *
|
||||
from .dynamic import *
|
||||
from .container import *
|
||||
from .file import *
|
||||
from .media_gallery import *
|
||||
from .section import *
|
||||
from .separator import *
|
||||
from .text_display import *
|
||||
from .thumbnail import *
|
||||
from .action_row import *
|
||||
from .label import *
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
599
venv/lib/python3.12/site-packages/discord/ui/action_row.py
Normal file
599
venv/lib/python3.12/site-packages/discord/ui/action_row.py
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
Sequence,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
overload,
|
||||
)
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from .button import Button, button as _button
|
||||
from .select import select as _select, Select, UserSelect, RoleSelect, ChannelSelect, MentionableSelect
|
||||
from ..components import ActionRow as ActionRowComponent
|
||||
from ..enums import ButtonStyle, ComponentType, ChannelType
|
||||
from ..partial_emoji import PartialEmoji
|
||||
from ..utils import MISSING, get as _utils_get
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
from .select import (
|
||||
BaseSelectT,
|
||||
ValidDefaultValues,
|
||||
MentionableSelectT,
|
||||
ChannelSelectT,
|
||||
RoleSelectT,
|
||||
UserSelectT,
|
||||
SelectT,
|
||||
)
|
||||
from ..emoji import Emoji
|
||||
from ..components import SelectOption
|
||||
from ..interactions import Interaction
|
||||
from .container import Container
|
||||
from .dynamic import DynamicItem
|
||||
|
||||
SelectCallbackDecorator = Callable[[ItemCallbackType['S', BaseSelectT]], BaseSelectT]
|
||||
|
||||
S = TypeVar('S', bound=Union['ActionRow', 'Container', 'LayoutView'], covariant=True)
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('ActionRow',)
|
||||
|
||||
|
||||
class _ActionRowCallback:
|
||||
__slots__ = ('row', 'callback', 'item')
|
||||
|
||||
def __init__(self, callback: ItemCallbackType[S, Any], row: ActionRow, item: Item[Any]) -> None:
|
||||
self.callback: ItemCallbackType[Any, Any] = callback
|
||||
self.row: ActionRow = row
|
||||
self.item: Item[Any] = item
|
||||
|
||||
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
|
||||
return self.callback(self.row, interaction, self.item)
|
||||
|
||||
|
||||
class ActionRow(Item[V]):
|
||||
r"""Represents a UI action row.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`
|
||||
and can contain :class:`Button`\s and :class:`Select`\s in it.
|
||||
|
||||
Action rows can only have 5 children. This can be inherited.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
import discord
|
||||
from discord import ui
|
||||
|
||||
# you can subclass it and add components with the decorators
|
||||
class MyActionRow(ui.ActionRow):
|
||||
@ui.button(label='Click Me!')
|
||||
async def click_me(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await interaction.response.send_message('You clicked me!')
|
||||
|
||||
# or use it directly on LayoutView
|
||||
class MyView(ui.LayoutView):
|
||||
row = ui.ActionRow()
|
||||
# or you can use your subclass:
|
||||
# row = MyActionRow()
|
||||
|
||||
# you can add items with row.button and row.select
|
||||
@row.button(label='A button!')
|
||||
async def row_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await interaction.response.send_message('You clicked a button!')
|
||||
|
||||
Parameters
|
||||
----------
|
||||
\*children: :class:`Item`
|
||||
The initial children of this action row.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__action_row_children_items__: ClassVar[List[ItemCallbackType[Self, Any]]] = []
|
||||
__discord_ui_action_row__: ClassVar[bool] = True
|
||||
__item_repr_attributes__ = ('id',)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*children: Item[V],
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._children: List[Item[V]] = self._init_children()
|
||||
self._children.extend(children)
|
||||
self._weight: int = sum(i.width for i in self._children)
|
||||
|
||||
if self._weight > 5:
|
||||
raise ValueError('maximum number of children exceeded')
|
||||
|
||||
self.id = id
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
children: Dict[str, ItemCallbackType[Self, Any]] = {}
|
||||
for base in reversed(cls.__mro__):
|
||||
for name, member in base.__dict__.items():
|
||||
if hasattr(member, '__discord_ui_model_type__'):
|
||||
children[name] = member
|
||||
|
||||
if len(children) > 5:
|
||||
raise TypeError('ActionRow cannot have more than 5 children')
|
||||
|
||||
cls.__action_row_children_items__ = list(children.values())
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<{self.__class__.__name__} children={len(self._children)}>'
|
||||
|
||||
def _init_children(self) -> List[Item[Any]]:
|
||||
children = []
|
||||
|
||||
for func in self.__action_row_children_items__:
|
||||
item: Item = func.__discord_ui_model_type__(**func.__discord_ui_model_kwargs__)
|
||||
item.callback = _ActionRowCallback(func, self, item) # type: ignore
|
||||
item._parent = getattr(func, '__discord_ui_parent__', self)
|
||||
setattr(self, func.__name__, item)
|
||||
children.append(item)
|
||||
return children
|
||||
|
||||
def _update_view(self, view) -> None:
|
||||
self._view = view
|
||||
for child in self._children:
|
||||
child._view = view
|
||||
|
||||
def _has_children(self):
|
||||
return True
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
# although it is not really a v2 component the only usecase here is for
|
||||
# LayoutView which basically represents the top-level payload of components
|
||||
# and ActionRow is only allowed there anyways.
|
||||
# If the user tries to add any V2 component to a View instead of LayoutView
|
||||
# it should error anyways.
|
||||
return True
|
||||
|
||||
def _swap_item(self, base: Item, new: DynamicItem, custom_id: str) -> None:
|
||||
child_index = self._children.index(base)
|
||||
self._children[child_index] = new # type: ignore
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def _total_count(self) -> int:
|
||||
# 1 for self and all children
|
||||
return 1 + len(self._children)
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.action_row]:
|
||||
return ComponentType.action_row
|
||||
|
||||
@property
|
||||
def children(self) -> List[Item[V]]:
|
||||
"""List[:class:`Item`]: The list of children attached to this action row."""
|
||||
return self._children.copy()
|
||||
|
||||
def walk_children(self) -> Generator[Item[V], Any, None]:
|
||||
"""An iterator that recursively walks through all the children of this action row
|
||||
and its children, if applicable.
|
||||
|
||||
Yields
|
||||
------
|
||||
:class:`Item`
|
||||
An item in the action row.
|
||||
"""
|
||||
|
||||
for child in self.children:
|
||||
yield child
|
||||
|
||||
def content_length(self) -> int:
|
||||
""":class:`int`: Returns the total length of all text content in this action row."""
|
||||
from .text_display import TextDisplay
|
||||
|
||||
return sum(len(item.content) for item in self._children if isinstance(item, TextDisplay))
|
||||
|
||||
def add_item(self, item: Item[Any]) -> Self:
|
||||
"""Adds an item to this action row.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`Item`
|
||||
The item to add to the action row.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
An :class:`Item` was not passed.
|
||||
ValueError
|
||||
Maximum number of children has been exceeded (5)
|
||||
or (40) for the entire view.
|
||||
"""
|
||||
|
||||
if (self._weight + item.width) > 5:
|
||||
raise ValueError('maximum number of children exceeded')
|
||||
|
||||
if len(self._children) >= 5:
|
||||
raise ValueError('maximum number of children exceeded')
|
||||
|
||||
if not isinstance(item, Item):
|
||||
raise TypeError(f'expected Item not {item.__class__.__name__}')
|
||||
|
||||
if self._view:
|
||||
self._view._add_count(1)
|
||||
|
||||
item._update_view(self.view)
|
||||
item._parent = self
|
||||
self._weight += 1
|
||||
self._children.append(item)
|
||||
|
||||
return self
|
||||
|
||||
def remove_item(self, item: Item[Any]) -> Self:
|
||||
"""Removes an item from the action row.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`Item`
|
||||
The item to remove from the action row.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._children.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if self._view:
|
||||
self._view._add_count(-1)
|
||||
self._weight -= 1
|
||||
|
||||
return self
|
||||
|
||||
def find_item(self, id: int, /) -> Optional[Item[V]]:
|
||||
"""Gets an item with :attr:`Item.id` set as ``id``, or ``None`` if
|
||||
not found.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is **not the same** as ``custom_id``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
id: :class:`int`
|
||||
The ID of the component.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[:class:`Item`]
|
||||
The item found, or ``None``.
|
||||
"""
|
||||
return _utils_get(self.walk_children(), id=id)
|
||||
|
||||
def clear_items(self) -> Self:
|
||||
"""Removes all items from the action row.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
"""
|
||||
if self._view:
|
||||
self._view._add_count(-len(self._children))
|
||||
self._children.clear()
|
||||
self._weight = 0
|
||||
return self
|
||||
|
||||
def to_component_dict(self) -> Dict[str, Any]:
|
||||
components = []
|
||||
for component in self.children:
|
||||
components.append(component.to_component_dict())
|
||||
|
||||
base = {
|
||||
'type': self.type.value,
|
||||
'components': components,
|
||||
}
|
||||
if self.id is not None:
|
||||
base['id'] = self.id
|
||||
return base
|
||||
|
||||
def button(
|
||||
self,
|
||||
*,
|
||||
label: Optional[str] = None,
|
||||
custom_id: Optional[str] = None,
|
||||
disabled: bool = False,
|
||||
style: ButtonStyle = ButtonStyle.secondary,
|
||||
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
|
||||
id: Optional[int] = None,
|
||||
) -> Callable[[ItemCallbackType[S, Button[V]]], Button[V]]:
|
||||
"""A decorator that attaches a button to the action row.
|
||||
|
||||
The function being decorated should have three parameters, ``self`` representing
|
||||
the :class:`discord.ui.ActionRow`, the :class:`discord.Interaction` you receive and
|
||||
the :class:`discord.ui.Button` being pressed.
|
||||
|
||||
.. note::
|
||||
|
||||
Buttons with a URL or a SKU cannot be created with this function.
|
||||
Consider creating a :class:`Button` manually and adding it via
|
||||
:meth:`ActionRow.add_item` instead. This is beacuse these buttons
|
||||
cannot have a callback associated with them since Discord does not
|
||||
do any processing with them.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
label: Optional[:class:`str`]
|
||||
The label of the button, if any.
|
||||
Can only be up to 80 characters.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The ID of the button that gets received during an interaction.
|
||||
It is recommended to not set this parameters to prevent conflicts.
|
||||
Can only be up to 100 characters.
|
||||
style: :class:`.ButtonStyle`
|
||||
The style of the button. Defaults to :attr:`.ButtonStyle.grey`.
|
||||
disabled: :class:`bool`
|
||||
Whether the button is disabled or not. Defaults to ``False``.
|
||||
emoji: Optional[Union[:class:`str`, :class:`.Emoji`, :class:`.PartialEmoji`]]
|
||||
The emoji of the button. This can be in string form or a :class:`.PartialEmoji`
|
||||
or a full :class:`.Emoji`.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
def decorator(func: ItemCallbackType[S, Button[V]]) -> ItemCallbackType[S, Button[V]]:
|
||||
ret = _button(
|
||||
label=label,
|
||||
custom_id=custom_id,
|
||||
disabled=disabled,
|
||||
style=style,
|
||||
emoji=emoji,
|
||||
row=None,
|
||||
id=id,
|
||||
)(func)
|
||||
ret.__discord_ui_parent__ = self # type: ignore
|
||||
return ret # type: ignore
|
||||
|
||||
return decorator # type: ignore
|
||||
|
||||
@overload
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
cls: Type[SelectT] = Select[Any],
|
||||
options: List[SelectOption] = MISSING,
|
||||
channel_types: List[ChannelType] = ...,
|
||||
placeholder: Optional[str] = ...,
|
||||
custom_id: str = ...,
|
||||
min_values: int = ...,
|
||||
max_values: int = ...,
|
||||
disabled: bool = ...,
|
||||
id: Optional[int] = ...,
|
||||
) -> SelectCallbackDecorator[S, SelectT]: ...
|
||||
|
||||
@overload
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
cls: Type[UserSelectT] = UserSelect[Any],
|
||||
options: List[SelectOption] = MISSING,
|
||||
channel_types: List[ChannelType] = ...,
|
||||
placeholder: Optional[str] = ...,
|
||||
custom_id: str = ...,
|
||||
min_values: int = ...,
|
||||
max_values: int = ...,
|
||||
disabled: bool = ...,
|
||||
default_values: Sequence[ValidDefaultValues] = ...,
|
||||
id: Optional[int] = ...,
|
||||
) -> SelectCallbackDecorator[S, UserSelectT]: ...
|
||||
|
||||
@overload
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
cls: Type[RoleSelectT] = RoleSelect[Any],
|
||||
options: List[SelectOption] = MISSING,
|
||||
channel_types: List[ChannelType] = ...,
|
||||
placeholder: Optional[str] = ...,
|
||||
custom_id: str = ...,
|
||||
min_values: int = ...,
|
||||
max_values: int = ...,
|
||||
disabled: bool = ...,
|
||||
default_values: Sequence[ValidDefaultValues] = ...,
|
||||
id: Optional[int] = ...,
|
||||
) -> SelectCallbackDecorator[S, RoleSelectT]: ...
|
||||
|
||||
@overload
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
cls: Type[ChannelSelectT] = ChannelSelect[Any],
|
||||
options: List[SelectOption] = MISSING,
|
||||
channel_types: List[ChannelType] = ...,
|
||||
placeholder: Optional[str] = ...,
|
||||
custom_id: str = ...,
|
||||
min_values: int = ...,
|
||||
max_values: int = ...,
|
||||
disabled: bool = ...,
|
||||
default_values: Sequence[ValidDefaultValues] = ...,
|
||||
id: Optional[int] = ...,
|
||||
) -> SelectCallbackDecorator[S, ChannelSelectT]: ...
|
||||
|
||||
@overload
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
cls: Type[MentionableSelectT] = MentionableSelect[Any],
|
||||
options: List[SelectOption] = MISSING,
|
||||
channel_types: List[ChannelType] = MISSING,
|
||||
placeholder: Optional[str] = ...,
|
||||
custom_id: str = ...,
|
||||
min_values: int = ...,
|
||||
max_values: int = ...,
|
||||
disabled: bool = ...,
|
||||
default_values: Sequence[ValidDefaultValues] = ...,
|
||||
id: Optional[int] = ...,
|
||||
) -> SelectCallbackDecorator[S, MentionableSelectT]: ...
|
||||
|
||||
def select(
|
||||
self,
|
||||
*,
|
||||
cls: Type[BaseSelectT] = Select[Any],
|
||||
options: List[SelectOption] = MISSING,
|
||||
channel_types: List[ChannelType] = MISSING,
|
||||
placeholder: Optional[str] = None,
|
||||
custom_id: str = MISSING,
|
||||
min_values: int = 1,
|
||||
max_values: int = 1,
|
||||
disabled: bool = False,
|
||||
default_values: Sequence[ValidDefaultValues] = MISSING,
|
||||
id: Optional[int] = None,
|
||||
) -> SelectCallbackDecorator[S, BaseSelectT]:
|
||||
"""A decorator that attaches a select menu to the action row.
|
||||
|
||||
The function being decorated should have three parameters, ``self`` representing
|
||||
the :class:`discord.ui.ActionRow`, the :class:`discord.Interaction` you receive and
|
||||
the chosen select class.
|
||||
|
||||
To obtain the selected values inside the callback, you can use the ``values`` attribute of the chosen class in the callback. The list of values
|
||||
will depend on the type of select menu used. View the table below for more information.
|
||||
|
||||
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
|
||||
| Select Type | Resolved Values |
|
||||
+========================================+=================================================================================================================+
|
||||
| :class:`discord.ui.Select` | List[:class:`str`] |
|
||||
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
|
||||
| :class:`discord.ui.UserSelect` | List[Union[:class:`discord.Member`, :class:`discord.User`]] |
|
||||
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
|
||||
| :class:`discord.ui.RoleSelect` | List[:class:`discord.Role`] |
|
||||
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
|
||||
| :class:`discord.ui.MentionableSelect` | List[Union[:class:`discord.Role`, :class:`discord.Member`, :class:`discord.User`]] |
|
||||
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
|
||||
| :class:`discord.ui.ChannelSelect` | List[Union[:class:`~discord.app_commands.AppCommandChannel`, :class:`~discord.app_commands.AppCommandThread`]] |
|
||||
+----------------------------------------+-----------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
Example
|
||||
---------
|
||||
.. code-block:: python3
|
||||
|
||||
class MyView(discord.ui.LayoutView):
|
||||
action_row = discord.ui.ActionRow()
|
||||
|
||||
@action_row.select(cls=ChannelSelect, channel_types=[discord.ChannelType.text])
|
||||
async def select_channels(self, interaction: discord.Interaction, select: ChannelSelect):
|
||||
return await interaction.response.send_message(f'You selected {select.values[0].mention}')
|
||||
|
||||
Parameters
|
||||
------------
|
||||
cls: Union[Type[:class:`discord.ui.Select`], Type[:class:`discord.ui.UserSelect`], Type[:class:`discord.ui.RoleSelect`], \
|
||||
Type[:class:`discord.ui.MentionableSelect`], Type[:class:`discord.ui.ChannelSelect`]]
|
||||
The class to use for the select menu. Defaults to :class:`discord.ui.Select`. You can use other
|
||||
select types to display different select menus to the user. See the table above for the different
|
||||
values you can get from each select type. Subclasses work as well, however the callback in the subclass will
|
||||
get overridden.
|
||||
placeholder: Optional[:class:`str`]
|
||||
The placeholder text that is shown if nothing is selected, if any.
|
||||
Can only be up to 150 characters.
|
||||
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.
|
||||
Can only be up to 100 characters.
|
||||
min_values: :class:`int`
|
||||
The minimum number of items that must be chosen for this select menu.
|
||||
Defaults to 1 and must be between 0 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:`discord.SelectOption`]
|
||||
A list of options that can be selected in this menu. This can only be used with
|
||||
:class:`Select` instances.
|
||||
Can only contain up to 25 items.
|
||||
channel_types: List[:class:`~discord.ChannelType`]
|
||||
The types of channels to show in the select menu. Defaults to all channels. This can only be used
|
||||
with :class:`ChannelSelect` instances.
|
||||
disabled: :class:`bool`
|
||||
Whether the select is disabled or not. Defaults to ``False``.
|
||||
default_values: Sequence[:class:`~discord.abc.Snowflake`]
|
||||
A list of objects representing the default values for the select menu. This cannot be used with regular :class:`Select` instances.
|
||||
If ``cls`` is :class:`MentionableSelect` and :class:`.Object` is passed, then the type must be specified in the constructor.
|
||||
Number of items must be in range of ``min_values`` and ``max_values``.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
def decorator(func: ItemCallbackType[S, BaseSelectT]) -> ItemCallbackType[S, BaseSelectT]:
|
||||
r = _select( # type: ignore
|
||||
cls=cls, # type: ignore
|
||||
placeholder=placeholder,
|
||||
custom_id=custom_id,
|
||||
min_values=min_values,
|
||||
max_values=max_values,
|
||||
options=options,
|
||||
channel_types=channel_types,
|
||||
disabled=disabled,
|
||||
default_values=default_values,
|
||||
id=id,
|
||||
)(func)
|
||||
r.__discord_ui_parent__ = self
|
||||
return r
|
||||
|
||||
return decorator # type: ignore
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: ActionRowComponent) -> ActionRow:
|
||||
from .view import _component_to_item
|
||||
|
||||
self = cls(id=component.id)
|
||||
for cmp in component.children:
|
||||
self.add_item(_component_to_item(cmp, self))
|
||||
return self
|
||||
387
venv/lib/python3.12/site-packages/discord/ui/button.py
Normal file
387
venv/lib/python3.12/site-packages/discord/ui/button.py
Normal file
|
|
@ -0,0 +1,387 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import Callable, Literal, Optional, TYPE_CHECKING, Tuple, TypeVar, Union
|
||||
import inspect
|
||||
import os
|
||||
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from ..enums import ButtonStyle, ComponentType
|
||||
from ..partial_emoji import PartialEmoji, _EmojiTag
|
||||
from ..components import Button as ButtonComponent
|
||||
|
||||
__all__ = (
|
||||
'Button',
|
||||
'button',
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import BaseView
|
||||
from .action_row import ActionRow
|
||||
from .container import Container
|
||||
from ..emoji import Emoji
|
||||
from ..types.components import ButtonComponent as ButtonComponentPayload
|
||||
|
||||
S = TypeVar('S', bound='Union[BaseView, Container, ActionRow]', covariant=True)
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
|
||||
|
||||
class Button(Item[V]):
|
||||
"""Represents a UI button.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
------------
|
||||
style: :class:`discord.ButtonStyle`
|
||||
The style of the button.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The ID of the button that gets received during an interaction.
|
||||
If this button is for a URL, it does not have a custom ID.
|
||||
Can only be up to 100 characters.
|
||||
url: Optional[:class:`str`]
|
||||
The URL this button sends you to.
|
||||
disabled: :class:`bool`
|
||||
Whether the button is disabled or not.
|
||||
label: Optional[:class:`str`]
|
||||
The label of the button, if any.
|
||||
Can only be up to 80 characters.
|
||||
emoji: Optional[Union[:class:`.PartialEmoji`, :class:`.Emoji`, :class:`str`]]
|
||||
The emoji of the button, if available.
|
||||
row: Optional[:class:`int`]
|
||||
The relative row this button 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).
|
||||
|
||||
.. note::
|
||||
|
||||
This parameter is ignored when used in a :class:`ActionRow` or v2 component.
|
||||
sku_id: Optional[:class:`int`]
|
||||
The SKU ID this button sends you to. Can't be combined with ``url``, ``label``, ``emoji``
|
||||
nor ``custom_id``.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'style',
|
||||
'url',
|
||||
'disabled',
|
||||
'label',
|
||||
'emoji',
|
||||
'row',
|
||||
'sku_id',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
style: ButtonStyle = ButtonStyle.secondary,
|
||||
label: Optional[str] = None,
|
||||
disabled: bool = False,
|
||||
custom_id: Optional[str] = None,
|
||||
url: Optional[str] = None,
|
||||
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
|
||||
row: Optional[int] = None,
|
||||
sku_id: Optional[int] = None,
|
||||
id: Optional[int] = None,
|
||||
):
|
||||
super().__init__()
|
||||
if custom_id is not None and (url is not None or sku_id is not None):
|
||||
raise TypeError('cannot mix both url or sku_id and custom_id with Button')
|
||||
|
||||
if url is not None and sku_id is not None:
|
||||
raise TypeError('cannot mix both url and sku_id')
|
||||
|
||||
requires_custom_id = url is None and sku_id is None
|
||||
self._provided_custom_id = custom_id is not None
|
||||
if requires_custom_id and custom_id is None:
|
||||
custom_id = os.urandom(16).hex()
|
||||
|
||||
if custom_id is not None and not isinstance(custom_id, str):
|
||||
raise TypeError(f'expected custom_id to be str not {custom_id.__class__.__name__}')
|
||||
|
||||
if url is not None:
|
||||
style = ButtonStyle.link
|
||||
|
||||
if sku_id is not None:
|
||||
style = ButtonStyle.premium
|
||||
|
||||
if emoji is not None:
|
||||
if isinstance(emoji, str):
|
||||
emoji = PartialEmoji.from_str(emoji)
|
||||
elif isinstance(emoji, _EmojiTag):
|
||||
emoji = emoji._to_partial()
|
||||
else:
|
||||
raise TypeError(f'expected emoji to be str, Emoji, or PartialEmoji not {emoji.__class__.__name__}')
|
||||
|
||||
self._underlying = ButtonComponent._raw_construct(
|
||||
custom_id=custom_id,
|
||||
url=url,
|
||||
disabled=disabled,
|
||||
label=label,
|
||||
style=style,
|
||||
emoji=emoji,
|
||||
sku_id=sku_id,
|
||||
id=id,
|
||||
)
|
||||
self.row = row
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this button."""
|
||||
return self._underlying.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value: Optional[int]) -> None:
|
||||
self._underlying.id = value
|
||||
|
||||
@property
|
||||
def style(self) -> ButtonStyle:
|
||||
""":class:`discord.ButtonStyle`: The style of the button."""
|
||||
return self._underlying.style
|
||||
|
||||
@style.setter
|
||||
def style(self, value: ButtonStyle) -> None:
|
||||
self._underlying.style = value
|
||||
|
||||
@property
|
||||
def custom_id(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: The ID of the button that gets received during an interaction.
|
||||
|
||||
If this button is for a URL, it does not have a custom ID.
|
||||
"""
|
||||
return self._underlying.custom_id
|
||||
|
||||
@custom_id.setter
|
||||
def custom_id(self, value: Optional[str]) -> None:
|
||||
if value is not None and not isinstance(value, str):
|
||||
raise TypeError('custom_id must be None or str')
|
||||
|
||||
self._underlying.custom_id = value
|
||||
self._provided_custom_id = value is not None
|
||||
|
||||
@property
|
||||
def url(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: The URL this button sends you to."""
|
||||
return self._underlying.url
|
||||
|
||||
@url.setter
|
||||
def url(self, value: Optional[str]) -> None:
|
||||
if value is not None and not isinstance(value, str):
|
||||
raise TypeError('url must be None or str')
|
||||
self._underlying.url = value
|
||||
|
||||
@property
|
||||
def disabled(self) -> bool:
|
||||
""":class:`bool`: Whether the button is disabled or not."""
|
||||
return self._underlying.disabled
|
||||
|
||||
@disabled.setter
|
||||
def disabled(self, value: bool) -> None:
|
||||
self._underlying.disabled = bool(value)
|
||||
|
||||
@property
|
||||
def label(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: The label of the button, if available."""
|
||||
return self._underlying.label
|
||||
|
||||
@label.setter
|
||||
def label(self, value: Optional[str]) -> None:
|
||||
self._underlying.label = str(value) if value is not None else value
|
||||
|
||||
@property
|
||||
def emoji(self) -> Optional[PartialEmoji]:
|
||||
"""Optional[:class:`.PartialEmoji`]: The emoji of the button, if available."""
|
||||
return self._underlying.emoji
|
||||
|
||||
@emoji.setter
|
||||
def emoji(self, value: Optional[Union[str, Emoji, PartialEmoji]]) -> None:
|
||||
if value is not None:
|
||||
if isinstance(value, str):
|
||||
self._underlying.emoji = PartialEmoji.from_str(value)
|
||||
elif isinstance(value, _EmojiTag):
|
||||
self._underlying.emoji = value._to_partial()
|
||||
else:
|
||||
raise TypeError(f'expected str, Emoji, or PartialEmoji, received {value.__class__.__name__} instead')
|
||||
else:
|
||||
self._underlying.emoji = None
|
||||
|
||||
@property
|
||||
def sku_id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The SKU ID this button sends you to.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
"""
|
||||
return self._underlying.sku_id
|
||||
|
||||
@sku_id.setter
|
||||
def sku_id(self, value: Optional[int]) -> None:
|
||||
if value is not None:
|
||||
self.style = ButtonStyle.premium
|
||||
self._underlying.sku_id = value
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, button: ButtonComponent) -> Self:
|
||||
return cls(
|
||||
style=button.style,
|
||||
label=button.label,
|
||||
disabled=button.disabled,
|
||||
custom_id=button.custom_id,
|
||||
url=button.url,
|
||||
emoji=button.emoji,
|
||||
row=None,
|
||||
sku_id=button.sku_id,
|
||||
id=button.id,
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.button]:
|
||||
return self._underlying.type
|
||||
|
||||
def to_component_dict(self) -> ButtonComponentPayload:
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return self.custom_id is not None
|
||||
|
||||
def is_persistent(self) -> bool:
|
||||
if self.style is ButtonStyle.link:
|
||||
return self.url is not None
|
||||
return super().is_persistent()
|
||||
|
||||
def _refresh_component(self, button: ButtonComponent) -> None:
|
||||
self._underlying = button
|
||||
|
||||
def copy(self) -> Self:
|
||||
new = copy.copy(self)
|
||||
custom_id = self.custom_id
|
||||
|
||||
if self.custom_id is not None and not self._provided_custom_id:
|
||||
custom_id = os.urandom(16).hex()
|
||||
|
||||
new._underlying = ButtonComponent._raw_construct(
|
||||
custom_id=custom_id,
|
||||
url=self.url,
|
||||
disabled=self.disabled,
|
||||
label=self.label,
|
||||
style=self.style,
|
||||
emoji=self.emoji,
|
||||
sku_id=self.sku_id,
|
||||
id=self.id,
|
||||
)
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo) -> Self:
|
||||
return self.copy()
|
||||
|
||||
|
||||
def button(
|
||||
*,
|
||||
label: Optional[str] = None,
|
||||
custom_id: Optional[str] = None,
|
||||
disabled: bool = False,
|
||||
style: ButtonStyle = ButtonStyle.secondary,
|
||||
emoji: Optional[Union[str, Emoji, PartialEmoji]] = None,
|
||||
row: Optional[int] = None,
|
||||
id: Optional[int] = None,
|
||||
) -> Callable[[ItemCallbackType[S, Button[V]]], Button[V]]:
|
||||
"""A decorator that attaches a button to a component.
|
||||
|
||||
The function being decorated should have three parameters, ``self`` representing
|
||||
the :class:`discord.ui.View`, the :class:`discord.Interaction` you receive and
|
||||
the :class:`discord.ui.Button` being pressed.
|
||||
|
||||
.. note::
|
||||
|
||||
Buttons with a URL or an SKU cannot be created with this function.
|
||||
Consider creating a :class:`Button` manually instead.
|
||||
This is because these buttons cannot have a callback
|
||||
associated with them since Discord does not do any processing
|
||||
with them.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
label: Optional[:class:`str`]
|
||||
The label of the button, if any.
|
||||
Can only be up to 80 characters.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The ID of the button that gets received during an interaction.
|
||||
It is recommended not to set this parameter to prevent conflicts.
|
||||
Can only be up to 100 characters.
|
||||
style: :class:`.ButtonStyle`
|
||||
The style of the button. Defaults to :attr:`.ButtonStyle.grey`.
|
||||
disabled: :class:`bool`
|
||||
Whether the button is disabled or not. Defaults to ``False``.
|
||||
emoji: Optional[Union[:class:`str`, :class:`.Emoji`, :class:`.PartialEmoji`]]
|
||||
The emoji of the button. This can be in string form or a :class:`.PartialEmoji`
|
||||
or a full :class:`.Emoji`.
|
||||
row: Optional[:class:`int`]
|
||||
The relative row this button 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).
|
||||
|
||||
.. note::
|
||||
|
||||
This parameter is ignored when used in a :class:`ActionRow` or v2 component.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
def decorator(func: ItemCallbackType[S, Button[V]]) -> ItemCallbackType[S, Button[V]]:
|
||||
if not inspect.iscoroutinefunction(func):
|
||||
raise TypeError('button function must be a coroutine function')
|
||||
|
||||
func.__discord_ui_model_type__ = Button
|
||||
func.__discord_ui_model_kwargs__ = {
|
||||
'style': style,
|
||||
'custom_id': custom_id,
|
||||
'url': None,
|
||||
'disabled': disabled,
|
||||
'label': label,
|
||||
'emoji': emoji,
|
||||
'row': row,
|
||||
'sku_id': None,
|
||||
'id': id,
|
||||
}
|
||||
return func
|
||||
|
||||
return decorator # type: ignore
|
||||
376
venv/lib/python3.12/site-packages/discord/ui/container.py
Normal file
376
venv/lib/python3.12/site-packages/discord/ui/container.py
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from .view import _component_to_item, LayoutView
|
||||
from ..enums import ComponentType
|
||||
from ..utils import get as _utils_get
|
||||
from ..colour import Colour, Color
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..components import Container as ContainerComponent
|
||||
from ..interactions import Interaction
|
||||
from .dynamic import DynamicItem
|
||||
|
||||
S = TypeVar('S', bound='Container', covariant=True)
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('Container',)
|
||||
|
||||
|
||||
class _ContainerCallback:
|
||||
__slots__ = ('container', 'callback', 'item')
|
||||
|
||||
def __init__(self, callback: ItemCallbackType[S, Any], container: Container, item: Item[Any]) -> None:
|
||||
self.callback: ItemCallbackType[Any, Any] = callback
|
||||
self.container: Container = container
|
||||
self.item: Item[Any] = item
|
||||
|
||||
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
|
||||
return self.callback(self.container, interaction, self.item)
|
||||
|
||||
|
||||
class Container(Item[V]):
|
||||
r"""Represents a UI container.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`
|
||||
and can contain :class:`ActionRow`\s, :class:`TextDisplay`\s, :class:`Section`\s,
|
||||
:class:`MediaGallery`\s, :class:`File`\s, and :class:`Separator`\s in it.
|
||||
|
||||
This can be inherited.
|
||||
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
import discord
|
||||
from discord import ui
|
||||
|
||||
# you can subclass it and add components as you would add them
|
||||
# in a LayoutView
|
||||
class MyContainer(ui.Container):
|
||||
action_row = ui.ActionRow()
|
||||
|
||||
@action_row.button(label='A button in a container!')
|
||||
async def a_button(self, interaction: discord.Interaction, button: discord.ui.Button):
|
||||
await interaction.response.send_message('You clicked a button!')
|
||||
|
||||
# or use it directly on LayoutView
|
||||
class MyView(ui.LayoutView):
|
||||
container = ui.Container(ui.TextDisplay('I am a text display on a container!'))
|
||||
# or you can use your subclass:
|
||||
# container = MyContainer()
|
||||
|
||||
Parameters
|
||||
----------
|
||||
\*children: :class:`Item`
|
||||
The initial children of this container.
|
||||
accent_colour: Optional[Union[:class:`.Colour`, :class:`int`]]
|
||||
The colour of the container. Defaults to ``None``.
|
||||
accent_color: Optional[Union[:class:`.Colour`, :class:`int`]]
|
||||
The color of the container. Defaults to ``None``.
|
||||
spoiler: :class:`bool`
|
||||
Whether to flag this container as a spoiler. Defaults
|
||||
to ``False``.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__container_children_items__: ClassVar[Dict[str, Union[ItemCallbackType[Self, Any], Item[Any]]]] = {}
|
||||
__discord_ui_container__: ClassVar[bool] = True
|
||||
__item_repr_attributes__ = (
|
||||
'accent_colour',
|
||||
'spoiler',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*children: Item[V],
|
||||
accent_colour: Optional[Union[Colour, int]] = None,
|
||||
accent_color: Optional[Union[Color, int]] = None,
|
||||
spoiler: bool = False,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._children: List[Item[V]] = self._init_children()
|
||||
for child in children:
|
||||
self.add_item(child)
|
||||
|
||||
self.spoiler: bool = spoiler
|
||||
self._colour = accent_colour if accent_colour is not None else accent_color
|
||||
self.id = id
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<{self.__class__.__name__} children={len(self._children)}>'
|
||||
|
||||
def _init_children(self) -> List[Item[Any]]:
|
||||
children = []
|
||||
parents = {}
|
||||
|
||||
for name, raw in self.__container_children_items__.items():
|
||||
if isinstance(raw, Item):
|
||||
item = raw.copy()
|
||||
item._parent = self
|
||||
setattr(self, name, item)
|
||||
children.append(item)
|
||||
parents[raw] = item
|
||||
else:
|
||||
# action rows can be created inside containers, and then callbacks can exist here
|
||||
# so we create items based off them
|
||||
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__)
|
||||
item.callback = _ContainerCallback(raw, self, item) # type: ignore
|
||||
setattr(self, raw.__name__, item)
|
||||
# this should not fail because in order for a function to be here it should be from
|
||||
# an action row and must have passed the check in __init_subclass__, but still
|
||||
# guarding it
|
||||
parent = getattr(raw, '__discord_ui_parent__', None)
|
||||
if parent is None:
|
||||
raise ValueError(f'{raw.__name__} is not a valid item for a Container')
|
||||
parents.get(parent, parent)._children.append(item)
|
||||
# we do not append it to the children list because technically these buttons and
|
||||
# selects are not from the container but the action row itself.
|
||||
|
||||
return children
|
||||
|
||||
def __init_subclass__(cls) -> None:
|
||||
super().__init_subclass__()
|
||||
|
||||
children: Dict[str, Union[ItemCallbackType[Self, Any], Item[Any]]] = {}
|
||||
for base in reversed(cls.__mro__):
|
||||
for name, member in base.__dict__.items():
|
||||
if isinstance(member, Item):
|
||||
children[name] = member
|
||||
if hasattr(member, '__discord_ui_model_type__') and getattr(member, '__discord_ui_parent__', None):
|
||||
children[name] = copy.copy(member)
|
||||
|
||||
cls.__container_children_items__ = children
|
||||
|
||||
def _update_view(self, view) -> bool:
|
||||
self._view = view
|
||||
for child in self._children:
|
||||
child._update_view(view)
|
||||
return True
|
||||
|
||||
def _has_children(self):
|
||||
return True
|
||||
|
||||
def _swap_item(self, base: Item, new: DynamicItem, custom_id: str) -> None:
|
||||
child_index = self._children.index(base)
|
||||
self._children[child_index] = new # type: ignore
|
||||
|
||||
@property
|
||||
def children(self) -> List[Item[V]]:
|
||||
"""List[:class:`Item`]: The children of this container."""
|
||||
return self._children.copy()
|
||||
|
||||
@property
|
||||
def accent_colour(self) -> Optional[Union[Colour, int]]:
|
||||
"""Optional[Union[:class:`discord.Colour`, :class:`int`]]: The colour of the container, or ``None``."""
|
||||
return self._colour
|
||||
|
||||
@accent_colour.setter
|
||||
def accent_colour(self, value: Optional[Union[Colour, int]]) -> None:
|
||||
if value is not None and not isinstance(value, (int, Colour)):
|
||||
raise TypeError(f'expected an int, or Colour, not {value.__class__.__name__!r}')
|
||||
|
||||
self._colour = value
|
||||
|
||||
accent_color = accent_colour
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.container]:
|
||||
return ComponentType.container
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def _total_count(self) -> int:
|
||||
# 1 for self and all children
|
||||
return 1 + len(tuple(self.walk_children()))
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
return True
|
||||
|
||||
def to_components(self) -> List[Dict[str, Any]]:
|
||||
components = []
|
||||
for i in self._children:
|
||||
components.append(i.to_component_dict())
|
||||
return components
|
||||
|
||||
def to_component_dict(self) -> Dict[str, Any]:
|
||||
components = self.to_components()
|
||||
|
||||
colour = None
|
||||
if self._colour:
|
||||
colour = self._colour if isinstance(self._colour, int) else self._colour.value
|
||||
|
||||
base = {
|
||||
'type': self.type.value,
|
||||
'accent_color': colour,
|
||||
'spoiler': self.spoiler,
|
||||
'components': components,
|
||||
}
|
||||
if self.id is not None:
|
||||
base['id'] = self.id
|
||||
return base
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: ContainerComponent) -> Self:
|
||||
self = cls(
|
||||
accent_colour=component.accent_colour,
|
||||
spoiler=component.spoiler,
|
||||
id=component.id,
|
||||
)
|
||||
self._children = [_component_to_item(cmp, self) for cmp in component.children]
|
||||
return self
|
||||
|
||||
def walk_children(self) -> Generator[Item[V], None, None]:
|
||||
"""An iterator that recursively walks through all the children of this container
|
||||
and its children, if applicable.
|
||||
|
||||
Yields
|
||||
------
|
||||
:class:`Item`
|
||||
An item in the container.
|
||||
"""
|
||||
|
||||
for child in self.children:
|
||||
yield child
|
||||
|
||||
if child._has_children():
|
||||
yield from child.walk_children() # type: ignore
|
||||
|
||||
def content_length(self) -> int:
|
||||
""":class:`int`: Returns the total length of all text content in this container."""
|
||||
from .text_display import TextDisplay
|
||||
|
||||
return sum(len(item.content) for item in self.walk_children() if isinstance(item, TextDisplay))
|
||||
|
||||
def add_item(self, item: Item[Any]) -> Self:
|
||||
"""Adds an item to this container.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`Item`
|
||||
The item to append.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
An :class:`Item` was not passed.
|
||||
ValueError
|
||||
Maximum number of children has been exceeded (40) for the entire view.
|
||||
"""
|
||||
if not isinstance(item, Item):
|
||||
raise TypeError(f'expected Item not {item.__class__.__name__}')
|
||||
|
||||
if self._view:
|
||||
self._view._add_count(item._total_count)
|
||||
|
||||
self._children.append(item)
|
||||
item._update_view(self.view)
|
||||
item._parent = self
|
||||
return self
|
||||
|
||||
def remove_item(self, item: Item[Any]) -> Self:
|
||||
"""Removes an item from this container.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`Item`
|
||||
The item to remove from the container.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._children.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if self._view:
|
||||
self._view._add_count(-item._total_count)
|
||||
return self
|
||||
|
||||
def find_item(self, id: int, /) -> Optional[Item[V]]:
|
||||
"""Gets an item with :attr:`Item.id` set as ``id``, or ``None`` if
|
||||
not found.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is **not the same** as ``custom_id``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
id: :class:`int`
|
||||
The ID of the component.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[:class:`Item`]
|
||||
The item found, or ``None``.
|
||||
"""
|
||||
return _utils_get(self.walk_children(), id=id)
|
||||
|
||||
def clear_items(self) -> Self:
|
||||
"""Removes all the items from the container.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
"""
|
||||
|
||||
if self._view:
|
||||
self._view._add_count(-len(tuple(self.walk_children())))
|
||||
self._children.clear()
|
||||
return self
|
||||
219
venv/lib/python3.12/site-packages/discord/ui/dynamic.py
Normal file
219
venv/lib/python3.12/site-packages/discord/ui/dynamic.py
Normal file
|
|
@ -0,0 +1,219 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import ClassVar, Dict, Generic, Optional, Tuple, Type, TypeVar, TYPE_CHECKING, Any, Union
|
||||
import re
|
||||
|
||||
from .item import Item
|
||||
from .._types import ClientT
|
||||
|
||||
__all__ = ('DynamicItem',)
|
||||
|
||||
BaseT = TypeVar('BaseT', bound='Item[Any]', covariant=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeVar, Self
|
||||
from ..interactions import Interaction
|
||||
from ..components import Component
|
||||
from ..enums import ComponentType
|
||||
from .view import View, LayoutView
|
||||
else:
|
||||
View = LayoutView = Any
|
||||
|
||||
|
||||
class DynamicItem(Generic[BaseT], Item[Union[View, LayoutView]]):
|
||||
"""Represents an item with a dynamic ``custom_id`` that can be used to store state within
|
||||
that ``custom_id``.
|
||||
|
||||
The ``custom_id`` parsing is done using the ``re`` module by passing a ``template``
|
||||
parameter to the class parameter list.
|
||||
|
||||
This item is generated every time the component is dispatched. This means that
|
||||
any variable that holds an instance of this class will eventually be out of date
|
||||
and should not be used long term. Their only purpose is to act as a "template"
|
||||
for the actual dispatched item.
|
||||
|
||||
When this item is generated, :attr:`view` is set to a regular :class:`View` instance,
|
||||
but to a :class:`LayoutView` if the component was sent with one, this is obtained from
|
||||
the original message given from the interaction. This means that custom view subclasses
|
||||
cannot be accessed from this item.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Parameters
|
||||
------------
|
||||
item: :class:`Item`
|
||||
The item to wrap with dynamic custom ID parsing.
|
||||
template: Union[:class:`str`, ``re.Pattern``]
|
||||
The template to use for parsing the ``custom_id``. This can be a string or a compiled
|
||||
regular expression. This must be passed as a keyword argument to the class creation.
|
||||
row: Optional[:class:`int`]
|
||||
The relative row this button 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
|
||||
-----------
|
||||
item: :class:`Item`
|
||||
The item that is wrapped with dynamic custom ID parsing.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'item',
|
||||
'template',
|
||||
)
|
||||
|
||||
__discord_ui_compiled_template__: ClassVar[re.Pattern[str]]
|
||||
|
||||
def __init_subclass__(cls, *, template: Union[str, re.Pattern[str]]) -> None:
|
||||
super().__init_subclass__()
|
||||
cls.__discord_ui_compiled_template__ = re.compile(template) if isinstance(template, str) else template
|
||||
if not isinstance(cls.__discord_ui_compiled_template__, re.Pattern):
|
||||
raise TypeError('template must be a str or a re.Pattern')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
item: BaseT,
|
||||
*,
|
||||
row: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.item: BaseT = item
|
||||
if row is not None:
|
||||
self.row = row
|
||||
|
||||
if not self.item.is_dispatchable():
|
||||
raise TypeError('item must be dispatchable, e.g. not a URL button')
|
||||
|
||||
if not self.template.match(self.custom_id):
|
||||
raise ValueError(f'item custom_id {self.custom_id!r} must match the template {self.template.pattern!r}')
|
||||
|
||||
@property
|
||||
def template(self) -> re.Pattern[str]:
|
||||
"""``re.Pattern``: The compiled regular expression that is used to parse the ``custom_id``."""
|
||||
return self.__class__.__discord_ui_compiled_template__
|
||||
|
||||
def to_component_dict(self) -> Dict[str, Any]:
|
||||
return self.item.to_component_dict()
|
||||
|
||||
def _refresh_component(self, component: Component) -> None:
|
||||
self.item._refresh_component(component)
|
||||
|
||||
def _refresh_state(self, interaction: Interaction, data: Dict[str, Any]) -> None:
|
||||
self.item._refresh_state(interaction, data)
|
||||
|
||||
@classmethod
|
||||
def from_component(cls: Type[Self], component: Component) -> Self:
|
||||
raise TypeError('Dynamic items cannot be created from components')
|
||||
|
||||
@property
|
||||
def type(self) -> ComponentType:
|
||||
return self.item.type
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return self.item.is_dispatchable()
|
||||
|
||||
def is_persistent(self) -> bool:
|
||||
return True
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the dynamic item that gets received during an interaction."""
|
||||
return self.item.custom_id # type: ignore # This attribute exists for dispatchable items
|
||||
|
||||
@custom_id.setter
|
||||
def custom_id(self, value: str) -> None:
|
||||
if not isinstance(value, str):
|
||||
raise TypeError('custom_id must be a str')
|
||||
|
||||
if not self.template.match(value):
|
||||
raise ValueError(f'custom_id must match the template {self.template.pattern!r}')
|
||||
|
||||
self.item.custom_id = value # type: ignore # This attribute exists for dispatchable items
|
||||
self._provided_custom_id = True
|
||||
|
||||
@property
|
||||
def row(self) -> Optional[int]:
|
||||
return self.item._row
|
||||
|
||||
@row.setter
|
||||
def row(self, value: Optional[int]) -> None:
|
||||
self.item.row = value
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return self.item.width
|
||||
|
||||
@property
|
||||
def _total_count(self) -> int:
|
||||
return self.item._total_count
|
||||
|
||||
@classmethod
|
||||
async def from_custom_id(
|
||||
cls: Type[Self], interaction: Interaction[ClientT], item: Item[Any], match: re.Match[str], /
|
||||
) -> Self:
|
||||
"""|coro|
|
||||
|
||||
A classmethod that is called when the ``custom_id`` of a component matches the
|
||||
``template`` of the class. This is called when the component is dispatched.
|
||||
|
||||
It must return a new instance of the :class:`DynamicItem`.
|
||||
|
||||
Subclasses *must* implement this method.
|
||||
|
||||
Exceptions raised in this method are logged and ignored.
|
||||
|
||||
.. warning::
|
||||
|
||||
This method is called before the callback is dispatched, therefore
|
||||
it means that it is subject to the same timing restrictions as the callback.
|
||||
Ergo, you must reply to an interaction within 3 seconds of it being
|
||||
dispatched.
|
||||
|
||||
Parameters
|
||||
------------
|
||||
interaction: :class:`~discord.Interaction`
|
||||
The interaction that the component belongs to.
|
||||
item: :class:`~discord.ui.Item`
|
||||
The base item that is being dispatched.
|
||||
match: ``re.Match``
|
||||
The match object that was created from the ``template``
|
||||
matching the ``custom_id``.
|
||||
|
||||
Returns
|
||||
--------
|
||||
:class:`DynamicItem`
|
||||
The new instance of the :class:`DynamicItem` with information
|
||||
from the ``match`` object.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
async def callback(self, interaction: Interaction[ClientT]) -> Any:
|
||||
return await self.item.callback(interaction)
|
||||
|
||||
async def interaction_check(self, interaction: Interaction[ClientT], /) -> bool:
|
||||
return await self.item.interaction_check(interaction)
|
||||
159
venv/lib/python3.12/site-packages/discord/ui/file.py
Normal file
159
venv/lib/python3.12/site-packages/discord/ui/file.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, Optional, TypeVar, Union
|
||||
|
||||
|
||||
from .item import Item
|
||||
from ..components import FileComponent, UnfurledMediaItem
|
||||
from ..enums import ComponentType
|
||||
from ..utils import MISSING
|
||||
from ..file import File as SendableFile
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('File',)
|
||||
|
||||
|
||||
class File(Item[V]):
|
||||
"""Represents a UI file component.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
import discord
|
||||
from discord import ui
|
||||
|
||||
class MyView(ui.LayoutView):
|
||||
file = ui.File('attachment://file.txt')
|
||||
# attachment://file.txt points to an attachment uploaded alongside this view
|
||||
|
||||
Parameters
|
||||
----------
|
||||
media: Union[:class:`str`, :class:`.UnfurledMediaItem`, :class:`discord.File`]
|
||||
This file's media. If this is a string it must point to a local
|
||||
file uploaded within the parent view of this item, and must
|
||||
meet the ``attachment://<filename>`` format.
|
||||
spoiler: :class:`bool`
|
||||
Whether to flag this file as a spoiler. Defaults to ``False``.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__ = (
|
||||
'media',
|
||||
'spoiler',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[str, UnfurledMediaItem, SendableFile],
|
||||
*,
|
||||
spoiler: bool = MISSING,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if isinstance(media, SendableFile):
|
||||
self._underlying = FileComponent._raw_construct(
|
||||
media=UnfurledMediaItem(media.uri),
|
||||
spoiler=media.spoiler if spoiler is MISSING else spoiler,
|
||||
id=id,
|
||||
)
|
||||
else:
|
||||
self._underlying = FileComponent._raw_construct(
|
||||
media=UnfurledMediaItem(media) if isinstance(media, str) else media,
|
||||
spoiler=bool(spoiler),
|
||||
id=id,
|
||||
)
|
||||
self.id = id
|
||||
|
||||
def _is_v2(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.file]:
|
||||
return self._underlying.type
|
||||
|
||||
@property
|
||||
def media(self) -> UnfurledMediaItem:
|
||||
""":class:`.UnfurledMediaItem`: Returns this file media."""
|
||||
return self._underlying.media
|
||||
|
||||
@media.setter
|
||||
def media(self, value: Union[str, SendableFile, UnfurledMediaItem]) -> None:
|
||||
if isinstance(value, str):
|
||||
self._underlying.media = UnfurledMediaItem(value)
|
||||
elif isinstance(value, UnfurledMediaItem):
|
||||
self._underlying.media = value
|
||||
elif isinstance(value, SendableFile):
|
||||
self._underlying.media = UnfurledMediaItem(value.uri)
|
||||
else:
|
||||
raise TypeError(f'expected a str or UnfurledMediaItem or File, not {value.__class__.__name__!r}')
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
""":class:`str`: Returns this file's url."""
|
||||
return self._underlying.media.url
|
||||
|
||||
@url.setter
|
||||
def url(self, value: str) -> None:
|
||||
self._underlying.media = UnfurledMediaItem(value)
|
||||
|
||||
@property
|
||||
def spoiler(self) -> bool:
|
||||
""":class:`bool`: Returns whether this file should be flagged as a spoiler."""
|
||||
return self._underlying.spoiler
|
||||
|
||||
@spoiler.setter
|
||||
def spoiler(self, value: bool) -> None:
|
||||
self._underlying.spoiler = value
|
||||
|
||||
def to_component_dict(self):
|
||||
return self._underlying.to_dict()
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: FileComponent) -> Self:
|
||||
return cls(
|
||||
media=component.media,
|
||||
spoiler=component.spoiler,
|
||||
id=component.id,
|
||||
)
|
||||
243
venv/lib/python3.12/site-packages/discord/ui/item.py
Normal file
243
venv/lib/python3.12/site-packages/discord/ui/item.py
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import Any, Callable, Coroutine, Dict, Generic, Optional, TYPE_CHECKING, Union, Tuple, Type, TypeVar
|
||||
|
||||
from ..interactions import Interaction
|
||||
from .._types import ClientT
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'Item',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..enums import ComponentType
|
||||
from .view import BaseView
|
||||
from ..components import Component
|
||||
from .action_row import ActionRow
|
||||
from .container import Container
|
||||
from .dynamic import DynamicItem
|
||||
from ..app_commands.namespace import ResolveKey
|
||||
|
||||
I = TypeVar('I', bound='Item[Any]')
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
ContainerType = Union['BaseView', 'ActionRow', 'Container']
|
||||
C = TypeVar('C', bound=ContainerType, covariant=True)
|
||||
ItemCallbackType = Callable[[V, Interaction[Any], I], Coroutine[Any, Any, Any]]
|
||||
ContainedItemCallbackType = Callable[[C, Interaction[Any], I], Coroutine[Any, Any, Any]]
|
||||
|
||||
|
||||
class Item(Generic[V]):
|
||||
"""Represents the base UI item that all UI components inherit from.
|
||||
|
||||
The current UI items supported are:
|
||||
|
||||
- :class:`discord.ui.Button`
|
||||
- :class:`discord.ui.Select`
|
||||
- :class:`discord.ui.TextInput`
|
||||
- :class:`discord.ui.ActionRow`
|
||||
- :class:`discord.ui.Container`
|
||||
- :class:`discord.ui.File`
|
||||
- :class:`discord.ui.MediaGallery`
|
||||
- :class:`discord.ui.Section`
|
||||
- :class:`discord.ui.Separator`
|
||||
- :class:`discord.ui.TextDisplay`
|
||||
- :class:`discord.ui.Thumbnail`
|
||||
- :class:`discord.ui.Label`
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = ('row', 'id')
|
||||
|
||||
def __init__(self):
|
||||
self._view: Optional[V] = None
|
||||
self._row: Optional[int] = None
|
||||
self._rendered_row: Optional[int] = None
|
||||
# This works mostly well but there is a gotcha with
|
||||
# the interaction with from_component, since that technically provides
|
||||
# a custom_id most dispatchable items would get this set to True even though
|
||||
# it might not be provided by the library user. However, this edge case doesn't
|
||||
# actually affect the intended purpose of this check because from_component is
|
||||
# only called upon edit and we're mainly interested during initial creation time.
|
||||
self._provided_custom_id: bool = False
|
||||
self._id: Optional[int] = None
|
||||
self._parent: Optional[Item] = None
|
||||
|
||||
def to_component_dict(self) -> Dict[str, Any]:
|
||||
raise NotImplementedError
|
||||
|
||||
def _refresh_component(self, component: Component) -> None:
|
||||
return None
|
||||
|
||||
def _handle_submit(self, interaction: Interaction, data: Dict[str, Any], resolved: Dict[ResolveKey, Any]) -> None:
|
||||
return self._refresh_state(interaction, data)
|
||||
|
||||
def _refresh_state(self, interaction: Interaction, data: Dict[str, Any]) -> None:
|
||||
return None
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def from_component(cls: Type[I], component: Component) -> I:
|
||||
return cls()
|
||||
|
||||
@property
|
||||
def type(self) -> ComponentType:
|
||||
raise NotImplementedError
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
|
||||
def is_persistent(self) -> bool:
|
||||
if self.is_dispatchable():
|
||||
return self._provided_custom_id
|
||||
return True
|
||||
|
||||
def _swap_item(self, base: Item, new: DynamicItem, custom_id: str) -> None:
|
||||
raise ValueError
|
||||
|
||||
def __repr__(self) -> str:
|
||||
attrs = ' '.join(f'{key}={getattr(self, key)!r}' for key in self.__item_repr_attributes__)
|
||||
return f'<{self.__class__.__name__} {attrs}>'
|
||||
|
||||
@property
|
||||
def row(self) -> Optional[int]:
|
||||
return self._row
|
||||
|
||||
@row.setter
|
||||
def row(self, value: Optional[int]) -> None:
|
||||
if self._is_v2():
|
||||
# row is ignored on v2 components
|
||||
return
|
||||
|
||||
if value is None:
|
||||
self._row = None
|
||||
elif 5 > value >= 0:
|
||||
self._row = value
|
||||
else:
|
||||
raise ValueError('row cannot be negative or greater than or equal to 5')
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 1
|
||||
|
||||
@property
|
||||
def _total_count(self) -> int:
|
||||
return 1
|
||||
|
||||
@property
|
||||
def view(self) -> Optional[V]:
|
||||
"""Optional[Union[:class:`View`, :class:`LayoutView`]]: The underlying view for this item."""
|
||||
return self._view
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this component."""
|
||||
return self._id
|
||||
|
||||
@id.setter
|
||||
def id(self, value: Optional[int]) -> None:
|
||||
self._id = value
|
||||
|
||||
@property
|
||||
def parent(self) -> Optional[Item[V]]:
|
||||
"""Optional[:class:`Item`]: This item's parent, if applicable. Only available on items with children.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
return self._parent
|
||||
|
||||
async def _run_checks(self, interaction: Interaction[ClientT]) -> bool:
|
||||
can_run = await self.interaction_check(interaction)
|
||||
|
||||
if can_run and self._parent:
|
||||
can_run = await self._parent._run_checks(interaction)
|
||||
|
||||
return can_run
|
||||
|
||||
def _update_view(self, view) -> None:
|
||||
self._view = view
|
||||
|
||||
def copy(self) -> Self:
|
||||
return copy.deepcopy(self)
|
||||
|
||||
def _has_children(self) -> bool:
|
||||
return False
|
||||
|
||||
async def callback(self, interaction: Interaction[ClientT]) -> Any:
|
||||
"""|coro|
|
||||
|
||||
The callback associated with this UI item.
|
||||
|
||||
This can be overridden by subclasses.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
interaction: :class:`.Interaction`
|
||||
The interaction that triggered this UI item.
|
||||
"""
|
||||
pass
|
||||
|
||||
async def interaction_check(self, interaction: Interaction[ClientT], /) -> bool:
|
||||
"""|coro|
|
||||
|
||||
A callback that is called when an interaction happens within this item
|
||||
that checks whether the callback should be processed.
|
||||
|
||||
This is useful to override if, for example, you want to ensure that the
|
||||
interaction author is a given user.
|
||||
|
||||
The default implementation of this returns ``True``.
|
||||
|
||||
.. note::
|
||||
|
||||
If an exception occurs within the body then the check
|
||||
is considered a failure and :meth:`View.on_error`
|
||||
(or :meth:`LayoutView.on_error`) is called.
|
||||
|
||||
For :class:`~discord.ui.DynamicItem` this does not call the ``on_error``
|
||||
handler.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
interaction: :class:`~discord.Interaction`
|
||||
The interaction that occurred.
|
||||
|
||||
Returns
|
||||
---------
|
||||
:class:`bool`
|
||||
Whether the callback should be called.
|
||||
"""
|
||||
return True
|
||||
141
venv/lib/python3.12/site-packages/discord/ui/label.py
Normal file
141
venv/lib/python3.12/site-packages/discord/ui/label.py
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Generator, Literal, Optional, Tuple, TypeVar
|
||||
|
||||
from ..components import LabelComponent
|
||||
from ..enums import ComponentType
|
||||
from ..utils import MISSING
|
||||
from .item import Item
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..types.components import LabelComponent as LabelComponentPayload
|
||||
from .view import BaseView
|
||||
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'Label',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
|
||||
|
||||
class Label(Item[V]):
|
||||
"""Represents a UI label within a modal.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`Modal`.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
------------
|
||||
text: :class:`str`
|
||||
The text to display above the input field.
|
||||
Can only be up to 45 characters.
|
||||
description: Optional[:class:`str`]
|
||||
The description text to display right below the label text.
|
||||
Can only be up to 100 characters.
|
||||
component: :class:`Item`
|
||||
The component to display below the label.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
|
||||
Attributes
|
||||
------------
|
||||
text: :class:`str`
|
||||
The text to display above the input field.
|
||||
Can only be up to 45 characters.
|
||||
description: Optional[:class:`str`]
|
||||
The description text to display right below the label text.
|
||||
Can only be up to 100 characters.
|
||||
component: :class:`Item`
|
||||
The component to display below the label.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'text',
|
||||
'description',
|
||||
'component',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
text: str,
|
||||
component: Item[V],
|
||||
description: Optional[str] = None,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.component: Item[V] = component
|
||||
self.text: str = text
|
||||
self.description: Optional[str] = description
|
||||
self.id = id
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 5
|
||||
|
||||
def _has_children(self) -> bool:
|
||||
return True
|
||||
|
||||
def walk_children(self) -> Generator[Item[V], None, None]:
|
||||
yield self.component
|
||||
|
||||
def to_component_dict(self) -> LabelComponentPayload:
|
||||
payload: LabelComponentPayload = {
|
||||
'type': ComponentType.label.value,
|
||||
'label': self.text,
|
||||
'component': self.component.to_component_dict(), # type: ignore
|
||||
}
|
||||
if self.description:
|
||||
payload['description'] = self.description
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
return payload
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: LabelComponent) -> Self:
|
||||
from .view import _component_to_item
|
||||
|
||||
self = cls(
|
||||
text=component.label,
|
||||
component=MISSING,
|
||||
description=component.description,
|
||||
)
|
||||
self.component = _component_to_item(component.component, self)
|
||||
return self
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.label]:
|
||||
return ComponentType.label
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
263
venv/lib/python3.12/site-packages/discord/ui/media_gallery.py
Normal file
263
venv/lib/python3.12/site-packages/discord/ui/media_gallery.py
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, List, Literal, Optional, TypeVar, Union
|
||||
|
||||
from .item import Item
|
||||
from ..enums import ComponentType
|
||||
from ..utils import MISSING
|
||||
from ..file import File
|
||||
from ..components import (
|
||||
MediaGalleryItem,
|
||||
MediaGalleryComponent,
|
||||
UnfurledMediaItem,
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('MediaGallery',)
|
||||
|
||||
|
||||
class MediaGallery(Item[V]):
|
||||
r"""Represents a UI media gallery.
|
||||
|
||||
Can contain up to 10 :class:`.MediaGalleryItem`\s.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
\*items: :class:`.MediaGalleryItem`
|
||||
The initial items of this gallery.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__ = (
|
||||
'items',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*items: MediaGalleryItem,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._underlying = MediaGalleryComponent._raw_construct(
|
||||
items=list(items),
|
||||
id=id,
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<{self.__class__.__name__} items={len(self._underlying.items)}>'
|
||||
|
||||
@property
|
||||
def items(self) -> List[MediaGalleryItem]:
|
||||
"""List[:class:`.MediaGalleryItem`]: Returns a read-only list of this gallery's items."""
|
||||
return self._underlying.items.copy()
|
||||
|
||||
@items.setter
|
||||
def items(self, value: List[MediaGalleryItem]) -> None:
|
||||
if len(value) > 10:
|
||||
raise ValueError('media gallery only accepts up to 10 items')
|
||||
|
||||
self._underlying.items = value
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this component."""
|
||||
return self._underlying.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value: Optional[int]) -> None:
|
||||
self._underlying.id = value
|
||||
|
||||
def to_component_dict(self):
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
return True
|
||||
|
||||
def add_item(
|
||||
self,
|
||||
*,
|
||||
media: Union[str, File, UnfurledMediaItem],
|
||||
description: Optional[str] = MISSING,
|
||||
spoiler: bool = MISSING,
|
||||
) -> Self:
|
||||
"""Adds an item to this gallery.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
media: Union[:class:`str`, :class:`discord.File`, :class:`.UnfurledMediaItem`]
|
||||
The media item data. This can be a string representing a local
|
||||
file uploaded as an attachment in the message, which can be accessed
|
||||
using the ``attachment://<filename>`` format, or an arbitrary url.
|
||||
description: Optional[:class:`str`]
|
||||
The description to show within this item. Up to 256 characters. Defaults
|
||||
to ``None``.
|
||||
spoiler: :class:`bool`
|
||||
Whether this item should be flagged as a spoiler. Defaults to ``False``.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
Maximum number of items has been exceeded (10).
|
||||
"""
|
||||
|
||||
if len(self._underlying.items) >= 10:
|
||||
raise ValueError('maximum number of items has been exceeded')
|
||||
|
||||
item = MediaGalleryItem(media, description=description, spoiler=spoiler)
|
||||
self._underlying.items.append(item)
|
||||
return self
|
||||
|
||||
def append_item(self, item: MediaGalleryItem) -> Self:
|
||||
"""Appends an item to this gallery.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`.MediaGalleryItem`
|
||||
The item to add to the gallery.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
A :class:`.MediaGalleryItem` was not passed.
|
||||
ValueError
|
||||
Maximum number of items has been exceeded (10).
|
||||
"""
|
||||
|
||||
if len(self._underlying.items) >= 10:
|
||||
raise ValueError('maximum number of items has been exceeded')
|
||||
|
||||
if not isinstance(item, MediaGalleryItem):
|
||||
raise TypeError(f'expected MediaGalleryItem, not {item.__class__.__name__!r}')
|
||||
|
||||
self._underlying.items.append(item)
|
||||
return self
|
||||
|
||||
def insert_item_at(
|
||||
self,
|
||||
index: int,
|
||||
*,
|
||||
media: Union[str, File, UnfurledMediaItem],
|
||||
description: Optional[str] = MISSING,
|
||||
spoiler: bool = MISSING,
|
||||
) -> Self:
|
||||
"""Inserts an item before a specified index to the media gallery.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
index: :class:`int`
|
||||
The index of where to insert the field.
|
||||
media: Union[:class:`str`, :class:`discord.File`, :class:`.UnfurledMediaItem`]
|
||||
The media item data. This can be a string representing a local
|
||||
file uploaded as an attachment in the message, which can be accessed
|
||||
using the ``attachment://<filename>`` format, or an arbitrary url.
|
||||
description: Optional[:class:`str`]
|
||||
The description to show within this item. Up to 256 characters. Defaults
|
||||
to ``None``.
|
||||
spoiler: :class:`bool`
|
||||
Whether this item should be flagged as a spoiler. Defaults to ``False``.
|
||||
|
||||
Raises
|
||||
------
|
||||
ValueError
|
||||
Maximum number of items has been exceeded (10).
|
||||
"""
|
||||
|
||||
if len(self._underlying.items) >= 10:
|
||||
raise ValueError('maximum number of items has been exceeded')
|
||||
|
||||
item = MediaGalleryItem(
|
||||
media,
|
||||
description=description,
|
||||
spoiler=spoiler,
|
||||
)
|
||||
self._underlying.items.insert(index, item)
|
||||
return self
|
||||
|
||||
def remove_item(self, item: MediaGalleryItem) -> Self:
|
||||
"""Removes an item from the gallery.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`.MediaGalleryItem`
|
||||
The item to remove from the gallery.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._underlying.items.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
return self
|
||||
|
||||
def clear_items(self) -> Self:
|
||||
"""Removes all items from the gallery.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
"""
|
||||
|
||||
self._underlying.items.clear()
|
||||
return self
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.media_gallery]:
|
||||
return self._underlying.type
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: MediaGalleryComponent) -> Self:
|
||||
return cls(
|
||||
*component.items,
|
||||
id=component.id,
|
||||
)
|
||||
273
venv/lib/python3.12/site-packages/discord/ui/modal.py
Normal file
273
venv/lib/python3.12/site-packages/discord/ui/modal.py
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import os
|
||||
from copy import deepcopy
|
||||
from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, ClassVar, List
|
||||
|
||||
from ..utils import MISSING, find
|
||||
from .._types import ClientT
|
||||
from .item import Item
|
||||
from .view import BaseView
|
||||
from .select import BaseSelect
|
||||
from .text_input import TextInput
|
||||
from ..interactions import Namespace
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..interactions import Interaction
|
||||
from ..types.interactions import (
|
||||
ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload,
|
||||
ResolvedData as ResolvedDataPayload,
|
||||
)
|
||||
from ..app_commands.namespace import ResolveKey
|
||||
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'Modal',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
|
||||
_log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Modal(BaseView):
|
||||
"""Represents a UI modal.
|
||||
|
||||
This object must be inherited to create a modal popup window within discord.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Examples
|
||||
----------
|
||||
|
||||
.. code-block:: python3
|
||||
|
||||
import discord
|
||||
from discord import ui
|
||||
|
||||
class Questionnaire(ui.Modal, title='Questionnaire Response'):
|
||||
name = ui.TextInput(label='Name')
|
||||
answer = ui.TextInput(label='Answer', style=discord.TextStyle.paragraph)
|
||||
|
||||
async def on_submit(self, interaction: discord.Interaction):
|
||||
await interaction.response.send_message(f'Thanks for your response, {self.name}!', ephemeral=True)
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
title: :class:`str`
|
||||
The title of the modal.
|
||||
Can only be up to 45 characters.
|
||||
timeout: Optional[:class:`float`]
|
||||
Timeout in seconds from last interaction with the UI before no longer accepting input.
|
||||
If ``None`` then there is no timeout.
|
||||
custom_id: :class:`str`
|
||||
The ID of the modal that gets received during an interaction.
|
||||
If not given then one is generated for you.
|
||||
Can only be up to 100 characters.
|
||||
|
||||
Attributes
|
||||
------------
|
||||
title: :class:`str`
|
||||
The title of the modal.
|
||||
custom_id: :class:`str`
|
||||
The ID of the modal that gets received during an interaction.
|
||||
"""
|
||||
|
||||
if TYPE_CHECKING:
|
||||
title: str
|
||||
|
||||
__discord_ui_modal__ = True
|
||||
__modal_children_items__: ClassVar[Dict[str, Item[Self]]] = {}
|
||||
|
||||
def __init_subclass__(cls, *, title: str = MISSING) -> None:
|
||||
if title is not MISSING:
|
||||
cls.title = title
|
||||
|
||||
children = {}
|
||||
for base in reversed(cls.__mro__):
|
||||
for name, member in base.__dict__.items():
|
||||
if isinstance(member, Item):
|
||||
children[name] = member
|
||||
|
||||
cls.__modal_children_items__ = children
|
||||
|
||||
def _init_children(self) -> List[Item]:
|
||||
children = []
|
||||
for name, item in self.__modal_children_items__.items():
|
||||
item = deepcopy(item)
|
||||
setattr(self, name, item)
|
||||
item._view = self
|
||||
children.append(item)
|
||||
return children
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
title: str = MISSING,
|
||||
timeout: Optional[float] = None,
|
||||
custom_id: str = MISSING,
|
||||
) -> None:
|
||||
if title is MISSING and getattr(self, 'title', MISSING) is MISSING:
|
||||
raise ValueError('Modal must have a title')
|
||||
elif title is not MISSING:
|
||||
self.title = title
|
||||
self.custom_id: str = os.urandom(16).hex() if custom_id is MISSING else custom_id
|
||||
|
||||
super().__init__(timeout=timeout)
|
||||
|
||||
async def on_submit(self, interaction: Interaction[ClientT], /) -> None:
|
||||
"""|coro|
|
||||
|
||||
Called when the modal is submitted.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
interaction: :class:`.Interaction`
|
||||
The interaction that submitted this modal.
|
||||
"""
|
||||
pass
|
||||
|
||||
async def on_error(self, interaction: Interaction[ClientT], error: Exception, /) -> None:
|
||||
"""|coro|
|
||||
|
||||
A callback that is called when :meth:`on_submit`
|
||||
fails with an error.
|
||||
|
||||
The default implementation logs to the library logger.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
interaction: :class:`~discord.Interaction`
|
||||
The interaction that led to the failure.
|
||||
error: :class:`Exception`
|
||||
The exception that was raised.
|
||||
"""
|
||||
_log.error('Ignoring exception in modal %r:', self, exc_info=error)
|
||||
|
||||
def _refresh(
|
||||
self,
|
||||
interaction: Interaction,
|
||||
components: Sequence[ModalSubmitComponentInteractionDataPayload],
|
||||
resolved: Dict[ResolveKey, Any],
|
||||
) -> None:
|
||||
for component in components:
|
||||
if component['type'] == 1:
|
||||
self._refresh(interaction, component['components'], resolved) # type: ignore
|
||||
elif component['type'] == 18:
|
||||
self._refresh(interaction, [component['component']], resolved) # type: ignore
|
||||
else:
|
||||
custom_id = component.get('custom_id')
|
||||
if custom_id is None:
|
||||
continue
|
||||
|
||||
item = find(
|
||||
lambda i: getattr(i, 'custom_id', None) == custom_id,
|
||||
self.walk_children(),
|
||||
)
|
||||
if item is None:
|
||||
_log.debug('Modal interaction referencing unknown item custom_id %s. Discarding', custom_id)
|
||||
continue
|
||||
|
||||
item._handle_submit(interaction, component, resolved) # type: ignore
|
||||
|
||||
async def _scheduled_task(
|
||||
self,
|
||||
interaction: Interaction,
|
||||
components: List[ModalSubmitComponentInteractionDataPayload],
|
||||
resolved: Dict[ResolveKey, Any],
|
||||
):
|
||||
try:
|
||||
self._refresh_timeout()
|
||||
self._refresh(interaction, components, resolved)
|
||||
|
||||
allow = await self.interaction_check(interaction)
|
||||
if not allow:
|
||||
return
|
||||
|
||||
await self.on_submit(interaction)
|
||||
except Exception as e:
|
||||
return await self.on_error(interaction, e)
|
||||
else:
|
||||
# No error, so assume this will always happen
|
||||
# In the future, maybe this will require checking if we set an error response.
|
||||
self.stop()
|
||||
|
||||
def to_components(self) -> List[Dict[str, Any]]:
|
||||
def key(item: Item) -> int:
|
||||
return item._rendered_row or 0
|
||||
|
||||
children = sorted(self._children, key=key)
|
||||
components: List[Dict[str, Any]] = []
|
||||
for child in children:
|
||||
if isinstance(child, (BaseSelect, TextInput)):
|
||||
# Every implicit child wrapped in an ActionRow in a modal
|
||||
# has a single child of width 5
|
||||
# It's also deprecated to use ActionRow in modals
|
||||
components.append(
|
||||
{
|
||||
'type': 1,
|
||||
'components': [child.to_component_dict()],
|
||||
}
|
||||
)
|
||||
else:
|
||||
components.append(child.to_component_dict())
|
||||
|
||||
return components
|
||||
|
||||
def _dispatch_submit(
|
||||
self,
|
||||
interaction: Interaction,
|
||||
components: List[ModalSubmitComponentInteractionDataPayload],
|
||||
resolved: ResolvedDataPayload,
|
||||
) -> asyncio.Task[None]:
|
||||
try:
|
||||
namespace = Namespace._get_resolved_items(interaction, resolved)
|
||||
except KeyError:
|
||||
namespace = {}
|
||||
|
||||
return asyncio.create_task(
|
||||
self._scheduled_task(interaction, components, namespace), name=f'discord-ui-modal-dispatch-{self.id}'
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
payload = {
|
||||
'custom_id': self.custom_id,
|
||||
'title': self.title,
|
||||
'components': self.to_components(),
|
||||
}
|
||||
|
||||
return payload
|
||||
|
||||
def add_item(self, item: Item[Any]) -> Self:
|
||||
if len(self._children) >= 5:
|
||||
raise ValueError('maximum number of children exceeded (5)')
|
||||
return super().add_item(item)
|
||||
277
venv/lib/python3.12/site-packages/discord/ui/section.py
Normal file
277
venv/lib/python3.12/site-packages/discord/ui/section.py
Normal file
|
|
@ -0,0 +1,277 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Generator, List, Literal, Optional, TypeVar, Union, ClassVar
|
||||
|
||||
from .item import Item
|
||||
from .text_display import TextDisplay
|
||||
from ..enums import ComponentType
|
||||
from ..utils import get as _utils_get
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
from .dynamic import DynamicItem
|
||||
from ..components import SectionComponent
|
||||
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('Section',)
|
||||
|
||||
|
||||
class Section(Item[V]):
|
||||
r"""Represents a UI section.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
\*children: Union[:class:`str`, :class:`TextDisplay`]
|
||||
The text displays of this section. Up to 3.
|
||||
accessory: :class:`Item`
|
||||
The section accessory. This is usually either a :class:`Button` or :class:`Thumbnail`.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__ = (
|
||||
'accessory',
|
||||
'id',
|
||||
)
|
||||
__discord_ui_section__: ClassVar[bool] = True
|
||||
|
||||
__slots__ = (
|
||||
'_children',
|
||||
'_accessory',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*children: Union[Item[V], str],
|
||||
accessory: Item[V],
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._children: List[Item[V]] = []
|
||||
for child in children:
|
||||
self.add_item(child)
|
||||
|
||||
accessory._parent = self
|
||||
self._accessory: Item[V] = accessory
|
||||
self.id = id
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<{self.__class__.__name__} children={len(self._children)} accessory={self._accessory!r}>'
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.section]:
|
||||
return ComponentType.section
|
||||
|
||||
@property
|
||||
def children(self) -> List[Item[V]]:
|
||||
"""List[:class:`Item`]: The list of children attached to this section."""
|
||||
return self._children.copy()
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def _total_count(self) -> int:
|
||||
# Count the accessory, ourselves, and all children
|
||||
return 2 + len(self._children)
|
||||
|
||||
@property
|
||||
def accessory(self) -> Item[V]:
|
||||
""":class:`Item`: The section's accessory."""
|
||||
return self._accessory
|
||||
|
||||
@accessory.setter
|
||||
def accessory(self, value: Item[V]) -> None:
|
||||
if not isinstance(value, Item):
|
||||
raise TypeError(f'Expected an Item, got {value.__class__.__name__!r} instead')
|
||||
|
||||
value._update_view(self.view)
|
||||
value._parent = self
|
||||
self._accessory = value
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
return True
|
||||
|
||||
def _swap_item(self, base: Item, new: DynamicItem, custom_id: str) -> None:
|
||||
if self.accessory.is_dispatchable() and getattr(self.accessory, 'custom_id', None) == custom_id:
|
||||
self.accessory = new # type: ignore
|
||||
|
||||
def walk_children(self) -> Generator[Item[V], None, None]:
|
||||
"""An iterator that recursively walks through all the children of this section
|
||||
and its children, if applicable. This includes the `accessory`.
|
||||
|
||||
Yields
|
||||
------
|
||||
:class:`Item`
|
||||
An item in this section.
|
||||
"""
|
||||
|
||||
for child in self.children:
|
||||
yield child
|
||||
yield self.accessory
|
||||
|
||||
def _update_view(self, view) -> None:
|
||||
self._view = view
|
||||
self.accessory._view = view
|
||||
for child in self._children:
|
||||
child._view = view
|
||||
|
||||
def _has_children(self):
|
||||
return True
|
||||
|
||||
def content_length(self) -> int:
|
||||
""":class:`int`: Returns the total length of all text content in this section."""
|
||||
from .text_display import TextDisplay
|
||||
|
||||
return sum(len(item.content) for item in self._children if isinstance(item, TextDisplay))
|
||||
|
||||
def add_item(self, item: Union[str, Item[Any]]) -> Self:
|
||||
"""Adds an item to this section.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: Union[:class:`str`, :class:`Item`]
|
||||
The item to append, if it is a string it automatically wrapped around
|
||||
:class:`TextDisplay`.
|
||||
|
||||
Raises
|
||||
------
|
||||
TypeError
|
||||
An :class:`Item` or :class:`str` was not passed.
|
||||
ValueError
|
||||
Maximum number of children has been exceeded (3) or (40)
|
||||
for the entire view.
|
||||
"""
|
||||
|
||||
if len(self._children) >= 3:
|
||||
raise ValueError('maximum number of children exceeded (3)')
|
||||
|
||||
if not isinstance(item, (Item, str)):
|
||||
raise TypeError(f'expected Item or str not {item.__class__.__name__}')
|
||||
|
||||
if self._view:
|
||||
self._view._add_count(1)
|
||||
|
||||
item = item if isinstance(item, Item) else TextDisplay(item)
|
||||
item._update_view(self.view)
|
||||
item._parent = self
|
||||
self._children.append(item)
|
||||
|
||||
return self
|
||||
|
||||
def remove_item(self, item: Item[Any]) -> Self:
|
||||
"""Removes an item from this section.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
item: :class:`Item`
|
||||
The item to remove from the section.
|
||||
"""
|
||||
|
||||
try:
|
||||
self._children.remove(item)
|
||||
except ValueError:
|
||||
pass
|
||||
else:
|
||||
if self._view:
|
||||
self._view._add_count(-1)
|
||||
|
||||
return self
|
||||
|
||||
def find_item(self, id: int, /) -> Optional[Item[V]]:
|
||||
"""Gets an item with :attr:`Item.id` set as ``id``, or ``None`` if
|
||||
not found.
|
||||
|
||||
.. warning::
|
||||
|
||||
This is **not the same** as ``custom_id``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
id: :class:`int`
|
||||
The ID of the component.
|
||||
|
||||
Returns
|
||||
-------
|
||||
Optional[:class:`Item`]
|
||||
The item found, or ``None``.
|
||||
"""
|
||||
return _utils_get(self.walk_children(), id=id)
|
||||
|
||||
def clear_items(self) -> Self:
|
||||
"""Removes all the items from the section.
|
||||
|
||||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
"""
|
||||
if self._view:
|
||||
self._view._add_count(-len(self._children)) # we don't count the accessory because it is required
|
||||
|
||||
self._children.clear()
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: SectionComponent) -> Self:
|
||||
from .view import _component_to_item
|
||||
|
||||
accessory = _component_to_item(component.accessory, None)
|
||||
self = cls(id=component.id, accessory=accessory)
|
||||
self.id = component.id
|
||||
self._children = [_component_to_item(c, self) for c in component.children]
|
||||
|
||||
return self
|
||||
|
||||
def to_components(self) -> List[Dict[str, Any]]:
|
||||
components = []
|
||||
|
||||
for component in self._children:
|
||||
components.append(component.to_component_dict())
|
||||
return components
|
||||
|
||||
def to_component_dict(self) -> Dict[str, Any]:
|
||||
data = {
|
||||
'type': self.type.value,
|
||||
'components': self.to_components(),
|
||||
'accessory': self.accessory.to_component_dict(),
|
||||
}
|
||||
if self.id is not None:
|
||||
data['id'] = self.id
|
||||
return data
|
||||
1247
venv/lib/python3.12/site-packages/discord/ui/select.py
Normal file
1247
venv/lib/python3.12/site-packages/discord/ui/select.py
Normal file
File diff suppressed because it is too large
Load diff
125
venv/lib/python3.12/site-packages/discord/ui/separator.py
Normal file
125
venv/lib/python3.12/site-packages/discord/ui/separator.py
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, Optional, TypeVar
|
||||
|
||||
from .item import Item
|
||||
from ..components import SeparatorComponent
|
||||
from ..enums import SeparatorSpacing, ComponentType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('Separator',)
|
||||
|
||||
|
||||
class Separator(Item[V]):
|
||||
"""Represents a UI separator.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
visible: :class:`bool`
|
||||
Whether this separator is visible. On the client side this
|
||||
is whether a divider line should be shown or not.
|
||||
spacing: :class:`.SeparatorSpacing`
|
||||
The spacing of this separator.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__slots__ = ('_underlying',)
|
||||
__item_repr_attributes__ = (
|
||||
'visible',
|
||||
'spacing',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
visible: bool = True,
|
||||
spacing: SeparatorSpacing = SeparatorSpacing.small,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._underlying = SeparatorComponent._raw_construct(
|
||||
spacing=spacing,
|
||||
visible=visible,
|
||||
id=id,
|
||||
)
|
||||
self.id = id
|
||||
|
||||
def _is_v2(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
""":class:`bool`: Whether this separator is visible.
|
||||
|
||||
On the client side this is whether a divider line should
|
||||
be shown or not.
|
||||
"""
|
||||
return self._underlying.visible
|
||||
|
||||
@visible.setter
|
||||
def visible(self, value: bool) -> None:
|
||||
self._underlying.visible = value
|
||||
|
||||
@property
|
||||
def spacing(self) -> SeparatorSpacing:
|
||||
""":class:`.SeparatorSpacing`: The spacing of this separator."""
|
||||
return self._underlying.spacing
|
||||
|
||||
@spacing.setter
|
||||
def spacing(self, value: SeparatorSpacing) -> None:
|
||||
self._underlying.spacing = value
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.separator]:
|
||||
return self._underlying.type
|
||||
|
||||
def to_component_dict(self):
|
||||
return self._underlying.to_dict()
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: SeparatorComponent) -> Self:
|
||||
return cls(
|
||||
visible=component.visible,
|
||||
spacing=component.spacing,
|
||||
id=component.id,
|
||||
)
|
||||
91
venv/lib/python3.12/site-packages/discord/ui/text_display.py
Normal file
91
venv/lib/python3.12/site-packages/discord/ui/text_display.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, Optional, TypeVar
|
||||
|
||||
from .item import Item
|
||||
from ..components import TextDisplay as TextDisplayComponent
|
||||
from ..enums import ComponentType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('TextDisplay',)
|
||||
|
||||
|
||||
class TextDisplay(Item[V]):
|
||||
"""Represents a UI text display.
|
||||
|
||||
This is a top-level layout component that can only be used on :class:`LayoutView`,
|
||||
:class:`Section`, :class:`Container`, or :class:`Modal`.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
content: :class:`str`
|
||||
The content of this text display. Up to 4000 characters.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__slots__ = ('content',)
|
||||
|
||||
def __init__(self, content: str, *, id: Optional[int] = None) -> None:
|
||||
super().__init__()
|
||||
self.content: str = content
|
||||
self.id = id
|
||||
|
||||
def to_component_dict(self):
|
||||
base = {
|
||||
'type': self.type.value,
|
||||
'content': self.content,
|
||||
}
|
||||
if self.id is not None:
|
||||
base['id'] = self.id
|
||||
return base
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.text_display]:
|
||||
return ComponentType.text_display
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: TextDisplayComponent) -> Self:
|
||||
return cls(
|
||||
content=component.content,
|
||||
id=component.id,
|
||||
)
|
||||
270
venv/lib/python3.12/site-packages/discord/ui/text_input.py
Normal file
270
venv/lib/python3.12/site-packages/discord/ui/text_input.py
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import TYPE_CHECKING, Literal, Optional, Tuple, TypeVar
|
||||
|
||||
from ..components import TextInput as TextInputComponent
|
||||
from ..enums import ComponentType, TextStyle
|
||||
from ..utils import MISSING, deprecated
|
||||
from .item import Item
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..types.components import TextInput as TextInputPayload
|
||||
from ..types.interactions import ModalSubmitTextInputInteractionData as ModalSubmitTextInputInteractionDataPayload
|
||||
from .view import BaseView
|
||||
from ..interactions import Interaction
|
||||
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'TextInput',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
|
||||
|
||||
class TextInput(Item[V]):
|
||||
"""Represents a UI text input.
|
||||
|
||||
This a top-level layout component that can only be used in :class:`Label`.
|
||||
|
||||
.. container:: operations
|
||||
|
||||
.. describe:: str(x)
|
||||
|
||||
Returns the value of the text input or an empty string if the value is ``None``.
|
||||
|
||||
.. versionadded:: 2.0
|
||||
|
||||
Parameters
|
||||
------------
|
||||
label: Optional[:class:`str`]
|
||||
The label to display above the text input.
|
||||
Can only be up to 45 characters.
|
||||
|
||||
.. deprecated:: 2.6
|
||||
This parameter is deprecated, use :class:`discord.ui.Label` instead.
|
||||
|
||||
.. versionchanged:: 2.6
|
||||
This parameter is now optional and defaults to ``None``.
|
||||
custom_id: :class:`str`
|
||||
The ID of the text input that gets received during an interaction.
|
||||
If not given then one is generated for you.
|
||||
Can only be up to 100 characters.
|
||||
style: :class:`discord.TextStyle`
|
||||
The style of the text input.
|
||||
placeholder: Optional[:class:`str`]
|
||||
The placeholder text to display when the text input is empty.
|
||||
Can only be up to 100 characters.
|
||||
default: Optional[:class:`str`]
|
||||
The default value of the text input.
|
||||
Can only be up to 4000 characters.
|
||||
required: :class:`bool`
|
||||
Whether the text input is required.
|
||||
min_length: Optional[:class:`int`]
|
||||
The minimum length of the text input.
|
||||
Must be between 0 and 4000.
|
||||
max_length: Optional[:class:`int`]
|
||||
The maximum length of the text input.
|
||||
Must be between 1 and 4000.
|
||||
row: Optional[:class:`int`]
|
||||
The relative row this text input 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).
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'label',
|
||||
'placeholder',
|
||||
'required',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
label: Optional[str] = None,
|
||||
style: TextStyle = TextStyle.short,
|
||||
custom_id: str = MISSING,
|
||||
placeholder: Optional[str] = None,
|
||||
default: Optional[str] = None,
|
||||
required: bool = True,
|
||||
min_length: Optional[int] = None,
|
||||
max_length: Optional[int] = None,
|
||||
row: Optional[int] = None,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._value: Optional[str] = default
|
||||
self._provided_custom_id = custom_id is not MISSING
|
||||
custom_id = os.urandom(16).hex() if custom_id is MISSING else custom_id
|
||||
if not isinstance(custom_id, str):
|
||||
raise TypeError(f'expected custom_id to be str not {custom_id.__class__.__name__}')
|
||||
|
||||
self._underlying = TextInputComponent._raw_construct(
|
||||
label=label,
|
||||
style=style,
|
||||
custom_id=custom_id,
|
||||
placeholder=placeholder,
|
||||
value=default,
|
||||
required=required,
|
||||
min_length=min_length,
|
||||
max_length=max_length,
|
||||
id=id,
|
||||
)
|
||||
self.row = row
|
||||
self.id = id
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the text input that gets received during an interaction."""
|
||||
return self._underlying.custom_id
|
||||
|
||||
@custom_id.setter
|
||||
def custom_id(self, value: str) -> None:
|
||||
if not isinstance(value, str):
|
||||
raise TypeError('custom_id must be a str')
|
||||
|
||||
self._underlying.custom_id = value
|
||||
self._provided_custom_id = True
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 5
|
||||
|
||||
@property
|
||||
def value(self) -> str:
|
||||
""":class:`str`: The value of the text input."""
|
||||
return self._value or ''
|
||||
|
||||
@property
|
||||
@deprecated('discord.ui.Label')
|
||||
def label(self) -> Optional[str]:
|
||||
""":class:`str`: The label of the text input."""
|
||||
return self._underlying.label
|
||||
|
||||
@label.setter
|
||||
@deprecated('discord.ui.Label')
|
||||
def label(self, value: Optional[str]) -> None:
|
||||
self._underlying.label = value
|
||||
|
||||
@property
|
||||
def placeholder(self) -> Optional[str]:
|
||||
""":class:`str`: The placeholder text to display when the text input is empty."""
|
||||
return self._underlying.placeholder
|
||||
|
||||
@placeholder.setter
|
||||
def placeholder(self, value: Optional[str]) -> None:
|
||||
self._underlying.placeholder = value
|
||||
|
||||
@property
|
||||
def required(self) -> bool:
|
||||
""":class:`bool`: Whether the text input is required."""
|
||||
return self._underlying.required
|
||||
|
||||
@required.setter
|
||||
def required(self, value: bool) -> None:
|
||||
self._underlying.required = value
|
||||
|
||||
@property
|
||||
def min_length(self) -> Optional[int]:
|
||||
""":class:`int`: The minimum length of the text input."""
|
||||
return self._underlying.min_length
|
||||
|
||||
@min_length.setter
|
||||
def min_length(self, value: Optional[int]) -> None:
|
||||
self._underlying.min_length = value
|
||||
|
||||
@property
|
||||
def max_length(self) -> Optional[int]:
|
||||
""":class:`int`: The maximum length of the text input."""
|
||||
return self._underlying.max_length
|
||||
|
||||
@max_length.setter
|
||||
def max_length(self, value: Optional[int]) -> None:
|
||||
self._underlying.max_length = value
|
||||
|
||||
@property
|
||||
def style(self) -> TextStyle:
|
||||
""":class:`discord.TextStyle`: The style of the text input."""
|
||||
return self._underlying.style
|
||||
|
||||
@style.setter
|
||||
def style(self, value: TextStyle) -> None:
|
||||
self._underlying.style = value
|
||||
|
||||
@property
|
||||
def default(self) -> Optional[str]:
|
||||
""":class:`str`: The default value of the text input."""
|
||||
return self._underlying.value
|
||||
|
||||
@default.setter
|
||||
def default(self, value: Optional[str]) -> None:
|
||||
self._underlying.value = value
|
||||
|
||||
def to_component_dict(self) -> TextInputPayload:
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def _refresh_component(self, component: TextInputComponent) -> None:
|
||||
self._underlying = component
|
||||
|
||||
def _refresh_state(self, interaction: Interaction, data: ModalSubmitTextInputInteractionDataPayload) -> None:
|
||||
self._value = data.get('value', None)
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: TextInputComponent) -> Self:
|
||||
return cls(
|
||||
label=component.label,
|
||||
style=component.style,
|
||||
custom_id=component.custom_id,
|
||||
placeholder=component.placeholder,
|
||||
default=component.value,
|
||||
required=component.required,
|
||||
min_length=component.min_length,
|
||||
max_length=component.max_length,
|
||||
row=None,
|
||||
id=component.id,
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.text_input]:
|
||||
return self._underlying.type
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
144
venv/lib/python3.12/site-packages/discord/ui/thumbnail.py
Normal file
144
venv/lib/python3.12/site-packages/discord/ui/thumbnail.py
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015-present Rapptz
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any, Dict, Literal, Optional, TypeVar, Union
|
||||
|
||||
from .item import Item
|
||||
from ..enums import ComponentType
|
||||
from ..components import UnfurledMediaItem
|
||||
from ..file import File
|
||||
from ..utils import MISSING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from .view import LayoutView
|
||||
from ..components import ThumbnailComponent
|
||||
|
||||
V = TypeVar('V', bound='LayoutView', covariant=True)
|
||||
|
||||
__all__ = ('Thumbnail',)
|
||||
|
||||
|
||||
class Thumbnail(Item[V]):
|
||||
"""Represents a UI Thumbnail. This currently can only be used as a :class:`Section`\'s accessory.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
media: Union[:class:`str`, :class:`discord.File`, :class:`discord.UnfurledMediaItem`]
|
||||
The media of the thumbnail. This can be a URL or a reference
|
||||
to an attachment that matches the ``attachment://filename.extension``
|
||||
structure.
|
||||
description: Optional[:class:`str`]
|
||||
The description of this thumbnail. Up to 256 characters. Defaults to ``None``.
|
||||
spoiler: :class:`bool`
|
||||
Whether to flag this thumbnail as a spoiler. Defaults to ``False``.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component. This must be unique across the view.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_media',
|
||||
'description',
|
||||
'spoiler',
|
||||
)
|
||||
__item_repr_attributes__ = (
|
||||
'media',
|
||||
'description',
|
||||
'spoiler',
|
||||
'row',
|
||||
'id',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[str, File, UnfurledMediaItem],
|
||||
*,
|
||||
description: Optional[str] = MISSING,
|
||||
spoiler: bool = MISSING,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
|
||||
if isinstance(media, File):
|
||||
description = description if description is not MISSING else media.description
|
||||
spoiler = spoiler if spoiler is not MISSING else media.spoiler
|
||||
media = media.uri
|
||||
|
||||
self._media: UnfurledMediaItem = UnfurledMediaItem(media) if isinstance(media, str) else media
|
||||
self.description: Optional[str] = None if description is MISSING else description
|
||||
self.spoiler: bool = bool(spoiler)
|
||||
|
||||
self.id = id
|
||||
|
||||
@property
|
||||
def width(self):
|
||||
return 5
|
||||
|
||||
@property
|
||||
def media(self) -> UnfurledMediaItem:
|
||||
""":class:`discord.UnfurledMediaItem`: This thumbnail unfurled media data."""
|
||||
return self._media
|
||||
|
||||
@media.setter
|
||||
def media(self, value: Union[str, File, UnfurledMediaItem]) -> None:
|
||||
if isinstance(value, str):
|
||||
self._media = UnfurledMediaItem(value)
|
||||
elif isinstance(value, UnfurledMediaItem):
|
||||
self._media = value
|
||||
elif isinstance(value, File):
|
||||
self._media = UnfurledMediaItem(value.uri)
|
||||
else:
|
||||
raise TypeError(f'expected a str or UnfurledMediaItem, not {value.__class__.__name__!r}')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.thumbnail]:
|
||||
return ComponentType.thumbnail
|
||||
|
||||
def _is_v2(self) -> bool:
|
||||
return True
|
||||
|
||||
def to_component_dict(self) -> Dict[str, Any]:
|
||||
base = {
|
||||
'type': self.type.value,
|
||||
'spoiler': self.spoiler,
|
||||
'media': self.media.to_dict(),
|
||||
'description': self.description,
|
||||
}
|
||||
if self.id is not None:
|
||||
base['id'] = self.id
|
||||
return base
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: ThumbnailComponent) -> Self:
|
||||
return cls(
|
||||
media=component.media.url,
|
||||
description=component.description,
|
||||
spoiler=component.spoiler,
|
||||
id=component.id,
|
||||
)
|
||||
1078
venv/lib/python3.12/site-packages/discord/ui/view.py
Normal file
1078
venv/lib/python3.12/site-packages/discord/ui/view.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue