Voice et bot modif
This commit is contained in:
parent
189d56026b
commit
7333a22bcd
10774 changed files with 634644 additions and 933308 deletions
|
|
@ -25,3 +25,6 @@ from .text_display import *
|
|||
from .thumbnail import *
|
||||
from .action_row import *
|
||||
from .label import *
|
||||
from .file_upload import *
|
||||
from .radio import *
|
||||
from .checkbox 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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -24,12 +24,12 @@ DEALINGS IN THE SOFTWARE.
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
|
|
@ -42,7 +42,7 @@ from typing import (
|
|||
overload,
|
||||
)
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
|
||||
from .button import Button, button as _button
|
||||
from .select import select as _select, Select, UserSelect, RoleSelect, ChannelSelect, MentionableSelect
|
||||
from ..components import ActionRow as ActionRowComponent
|
||||
|
|
@ -65,7 +65,6 @@ if TYPE_CHECKING:
|
|||
)
|
||||
from ..emoji import Emoji
|
||||
from ..components import SelectOption
|
||||
from ..interactions import Interaction
|
||||
from .container import Container
|
||||
from .dynamic import DynamicItem
|
||||
|
||||
|
|
@ -77,18 +76,6 @@ 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.
|
||||
|
||||
|
|
@ -143,8 +130,9 @@ class ActionRow(Item[V]):
|
|||
) -> 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)
|
||||
for child in children:
|
||||
self.add_item(child)
|
||||
|
||||
if self._weight > 5:
|
||||
raise ValueError('maximum number of children exceeded')
|
||||
|
|
@ -173,8 +161,8 @@ class ActionRow(Item[V]):
|
|||
|
||||
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)
|
||||
item.callback = _ItemCallback(func, self, item) # type: ignore
|
||||
item._parent = self
|
||||
setattr(self, func.__name__, item)
|
||||
children.append(item)
|
||||
return children
|
||||
|
|
@ -184,6 +172,23 @@ class ActionRow(Item[V]):
|
|||
for child in self._children:
|
||||
child._view = view
|
||||
|
||||
def copy(self) -> ActionRow[V]:
|
||||
new = copy.copy(self)
|
||||
children = []
|
||||
for child in new._children:
|
||||
newch = child.copy()
|
||||
newch._parent = new
|
||||
if isinstance(newch.callback, _ItemCallback):
|
||||
newch.callback.parent = new
|
||||
children.append(newch)
|
||||
new._children = children
|
||||
new._parent = self._parent
|
||||
new._update_view(self.view)
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo) -> ActionRow[V]:
|
||||
return self.copy()
|
||||
|
||||
def _has_children(self):
|
||||
return True
|
||||
|
||||
|
|
@ -227,8 +232,7 @@ class ActionRow(Item[V]):
|
|||
An item in the action row.
|
||||
"""
|
||||
|
||||
for child in self.children:
|
||||
yield child
|
||||
yield from self.children
|
||||
|
||||
def content_length(self) -> int:
|
||||
""":class:`int`: Returns the total length of all text content in this action row."""
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import inspect
|
|||
import os
|
||||
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
|
||||
from ..enums import ButtonStyle, ComponentType
|
||||
from ..partial_emoji import PartialEmoji, _EmojiTag
|
||||
from ..components import Button as ButtonComponent
|
||||
|
|
@ -304,6 +304,9 @@ class Button(Item[V]):
|
|||
sku_id=self.sku_id,
|
||||
id=self.id,
|
||||
)
|
||||
if isinstance(new.callback, _ItemCallback):
|
||||
new.callback.item = new
|
||||
new._update_view(self.view)
|
||||
return new
|
||||
|
||||
def __deepcopy__(self, memo) -> Self:
|
||||
|
|
|
|||
391
venv/lib/python3.12/site-packages/discord/ui/checkbox.py
Normal file
391
venv/lib/python3.12/site-packages/discord/ui/checkbox.py
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
"""
|
||||
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, List, Literal, Optional, Tuple, TypeVar, Dict
|
||||
|
||||
import os
|
||||
|
||||
from ..utils import MISSING
|
||||
from ..components import CheckboxGroupComponent, CheckboxComponent, CheckboxGroupOption
|
||||
from ..enums import ComponentType
|
||||
from .item import Item
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..interactions import Interaction
|
||||
from ..types.interactions import (
|
||||
ModalSubmitCheckboxGroupInteractionData as ModalSubmitCheckboxGroupInteractionDataPayload,
|
||||
ModalSubmitCheckboxInteractionData as ModalSubmitCheckboxInteractionDataPayload,
|
||||
)
|
||||
from ..types.components import (
|
||||
CheckboxGroupComponent as CheckboxGroupComponentPayload,
|
||||
CheckboxComponent as CheckboxComponentPayload,
|
||||
)
|
||||
from .view import BaseView
|
||||
from ..app_commands.namespace import ResolveKey
|
||||
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'CheckboxGroup',
|
||||
'Checkbox',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
|
||||
|
||||
class CheckboxGroup(Item[V]):
|
||||
"""Represents a checkbox group component within a modal.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
Parameters
|
||||
------------
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The custom ID of the component.
|
||||
options: List[:class:`discord.CheckboxGroupOption`]
|
||||
A list of options that can be selected in this checkbox group.
|
||||
Can only contain up to 10 items.
|
||||
max_values: Optional[:class:`int`]
|
||||
The maximum number of options that can be selected in this component.
|
||||
Must be between 1 and 10. Defaults to 1.
|
||||
min_values: Optional[:class:`int`]
|
||||
The minimum number of options that must be selected in this component.
|
||||
Must be between 0 and 10. Defaults to 0.
|
||||
required: :class:`bool`
|
||||
Whether this component is required to be filled before submitting the modal.
|
||||
Defaults to ``True``.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'id',
|
||||
'custom_id',
|
||||
'options',
|
||||
'required',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
custom_id: str = MISSING,
|
||||
required: bool = True,
|
||||
min_values: Optional[int] = None,
|
||||
max_values: Optional[int] = None,
|
||||
options: List[CheckboxGroupOption] = MISSING,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
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: CheckboxGroupComponent = CheckboxGroupComponent._raw_construct(
|
||||
id=id,
|
||||
custom_id=custom_id,
|
||||
required=required,
|
||||
options=options or [],
|
||||
min_values=min_values,
|
||||
max_values=max_values,
|
||||
)
|
||||
self.id = id
|
||||
self._values: List[str] = []
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def values(self) -> List[str]:
|
||||
"""List[:class:`str`]: A list of values that have been selected by the user."""
|
||||
return self._values
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the component 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 type(self) -> Literal[ComponentType.checkbox_group]:
|
||||
""":class:`.ComponentType`: The type of this component."""
|
||||
return ComponentType.checkbox_group
|
||||
|
||||
@property
|
||||
def options(self) -> List[CheckboxGroupOption]:
|
||||
"""List[:class:`discord.CheckboxGroupOption`]: A list of options that can be selected in this menu."""
|
||||
return self._underlying.options
|
||||
|
||||
@options.setter
|
||||
def options(self, value: List[CheckboxGroupOption]) -> None:
|
||||
if not isinstance(value, list) or not all(isinstance(obj, CheckboxGroupOption) for obj in value):
|
||||
raise TypeError('options must be a list of CheckboxGroupOption')
|
||||
self._underlying.options = value
|
||||
|
||||
@property
|
||||
def min_values(self) -> int:
|
||||
""":class:`int`: The minimum number of options that must be selected before submitting the modal."""
|
||||
return self._underlying.min_values
|
||||
|
||||
@min_values.setter
|
||||
def min_values(self, value: int) -> None:
|
||||
self._underlying.min_values = int(value)
|
||||
|
||||
@property
|
||||
def max_values(self) -> int:
|
||||
""":class:`int`: The maximum number of options that can be selected before submitting the modal."""
|
||||
return self._underlying.max_values
|
||||
|
||||
@max_values.setter
|
||||
def max_values(self, value: int) -> None:
|
||||
self._underlying.max_values = int(value)
|
||||
|
||||
def add_option(
|
||||
self,
|
||||
*,
|
||||
label: str,
|
||||
value: str = MISSING,
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
) -> None:
|
||||
"""Adds an option to the checkbox group.
|
||||
|
||||
To append a pre-existing :class:`discord.CheckboxGroupOption` use the
|
||||
:meth:`append_option` method instead.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
label: :class:`str`
|
||||
The label of the option. This is displayed to users.
|
||||
Can only be up to 100 characters.
|
||||
value: :class:`str`
|
||||
The value of the option. This is not displayed to users.
|
||||
If not given, defaults to the label.
|
||||
Can only be up to 100 characters.
|
||||
description: Optional[:class:`str`]
|
||||
An additional description of the option, if any.
|
||||
Can only be up to 100 characters.
|
||||
default: :class:`bool`
|
||||
Whether this option is selected by default.
|
||||
|
||||
Raises
|
||||
-------
|
||||
ValueError
|
||||
The number of options exceeds 10.
|
||||
"""
|
||||
|
||||
option = CheckboxGroupOption(
|
||||
label=label,
|
||||
value=value,
|
||||
description=description,
|
||||
default=default,
|
||||
)
|
||||
|
||||
self.append_option(option)
|
||||
|
||||
def append_option(self, option: CheckboxGroupOption) -> None:
|
||||
"""Appends an option to the checkbox group.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
option: :class:`discord.CheckboxGroupOption`
|
||||
The option to append to the checkbox group.
|
||||
|
||||
Raises
|
||||
-------
|
||||
ValueError
|
||||
The number of options exceeds 10.
|
||||
"""
|
||||
|
||||
if len(self._underlying.options) >= 10:
|
||||
raise ValueError('maximum number of options already provided (10)')
|
||||
|
||||
self._underlying.options.append(option)
|
||||
|
||||
@property
|
||||
def required(self) -> bool:
|
||||
""":class:`bool`: Whether the component is required or not."""
|
||||
return self._underlying.required
|
||||
|
||||
@required.setter
|
||||
def required(self, value: bool) -> None:
|
||||
self._underlying.required = bool(value)
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 5
|
||||
|
||||
def to_component_dict(self) -> CheckboxGroupComponentPayload:
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def _refresh_component(self, component: CheckboxGroupComponent) -> None:
|
||||
self._underlying = component
|
||||
|
||||
def _handle_submit(
|
||||
self, interaction: Interaction, data: ModalSubmitCheckboxGroupInteractionDataPayload, resolved: Dict[ResolveKey, Any]
|
||||
) -> None:
|
||||
self._values = data.get('values', [])
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: CheckboxGroupComponent) -> Self:
|
||||
self = cls(
|
||||
id=component.id,
|
||||
custom_id=component.custom_id,
|
||||
options=component.options,
|
||||
required=component.required,
|
||||
min_values=component.min_values,
|
||||
max_values=component.max_values,
|
||||
)
|
||||
return self
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
class Checkbox(Item[V]):
|
||||
"""Represents a checkbox component within a modal.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
Parameters
|
||||
------------
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The custom ID of the component.
|
||||
default: :class:`bool`
|
||||
Whether this checkbox is selected by default.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'id',
|
||||
'custom_id',
|
||||
'default',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
custom_id: str = MISSING,
|
||||
default: bool = False,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
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: CheckboxComponent = CheckboxComponent._raw_construct(
|
||||
id=id,
|
||||
custom_id=custom_id,
|
||||
default=default,
|
||||
)
|
||||
self.id = id
|
||||
self._value: bool = default
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def value(self) -> bool:
|
||||
""":class:`bool`: ``True`` if this checkbox was selected, otherwise ``False``."""
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the component 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 type(self) -> Literal[ComponentType.checkbox]:
|
||||
""":class:`.ComponentType`: The type of this component."""
|
||||
return ComponentType.checkbox
|
||||
|
||||
@property
|
||||
def default(self) -> bool:
|
||||
""":class:`bool`: Whether this checkbox is selected by default."""
|
||||
return self._underlying.default
|
||||
|
||||
@default.setter
|
||||
def default(self, value: bool) -> None:
|
||||
self._underlying.default = bool(value)
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 5
|
||||
|
||||
def to_component_dict(self) -> CheckboxComponentPayload:
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def _refresh_component(self, component: CheckboxComponent) -> None:
|
||||
self._underlying = component
|
||||
|
||||
def _handle_submit(
|
||||
self, interaction: Interaction, data: ModalSubmitCheckboxInteractionDataPayload, resolved: Dict[ResolveKey, Any]
|
||||
) -> None:
|
||||
self._value = data.get('value', False)
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: CheckboxComponent) -> Self:
|
||||
self = cls(
|
||||
id=component.id,
|
||||
custom_id=component.custom_id,
|
||||
default=component.default,
|
||||
)
|
||||
return self
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
|
|
@ -29,7 +29,6 @@ from typing import (
|
|||
TYPE_CHECKING,
|
||||
Any,
|
||||
ClassVar,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Generator,
|
||||
List,
|
||||
|
|
@ -39,7 +38,7 @@ from typing import (
|
|||
Union,
|
||||
)
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
|
||||
from .view import _component_to_item, LayoutView
|
||||
from ..enums import ComponentType
|
||||
from ..utils import get as _utils_get
|
||||
|
|
@ -49,7 +48,6 @@ 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)
|
||||
|
|
@ -58,18 +56,6 @@ 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.
|
||||
|
||||
|
|
@ -163,7 +149,7 @@ class Container(Item[V]):
|
|||
# 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
|
||||
item.callback = _ItemCallback(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
|
||||
|
|
@ -196,6 +182,15 @@ class Container(Item[V]):
|
|||
child._update_view(view)
|
||||
return True
|
||||
|
||||
def copy(self) -> Container[V]:
|
||||
new = copy.deepcopy(self)
|
||||
for child in new._children:
|
||||
newch = child.copy()
|
||||
newch._parent = new
|
||||
new._parent = self._parent
|
||||
new._update_view(self.view)
|
||||
return new
|
||||
|
||||
def _has_children(self):
|
||||
return True
|
||||
|
||||
|
|
|
|||
|
|
@ -100,7 +100,15 @@ class File(Item[V]):
|
|||
spoiler=bool(spoiler),
|
||||
id=id,
|
||||
)
|
||||
self.id = id
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this file component."""
|
||||
return self._underlying.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value: Optional[int]) -> None:
|
||||
self._underlying.id = value
|
||||
|
||||
def _is_v2(self):
|
||||
return True
|
||||
|
|
|
|||
199
venv/lib/python3.12/site-packages/discord/ui/file_upload.py
Normal file
199
venv/lib/python3.12/site-packages/discord/ui/file_upload.py
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
"""
|
||||
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, List, Literal, Optional, Tuple, TypeVar, Dict
|
||||
|
||||
import os
|
||||
|
||||
from ..utils import MISSING
|
||||
from ..components import FileUploadComponent
|
||||
from ..enums import ComponentType
|
||||
from .item import Item
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..message import Attachment
|
||||
from ..interactions import Interaction
|
||||
from ..types.interactions import ModalSubmitTextInputInteractionData as ModalSubmitFileUploadInteractionDataPayload
|
||||
from ..types.components import FileUploadComponent as FileUploadComponentPayload
|
||||
from .view import BaseView
|
||||
from ..app_commands.namespace import ResolveKey
|
||||
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'FileUpload',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
|
||||
|
||||
class FileUpload(Item[V]):
|
||||
"""Represents a file upload component within a modal.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
Parameters
|
||||
------------
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The custom ID of the file upload component.
|
||||
max_values: Optional[:class:`int`]
|
||||
The maximum number of files that can be uploaded in this component.
|
||||
Must be between 1 and 10. Defaults to 1.
|
||||
min_values: Optional[:class:`int`]
|
||||
The minimum number of files that must be uploaded in this component.
|
||||
Must be between 0 and 10. Defaults to 0.
|
||||
required: :class:`bool`
|
||||
Whether this component is required to be filled before submitting the modal.
|
||||
Defaults to ``True``.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'id',
|
||||
'custom_id',
|
||||
'max_values',
|
||||
'min_values',
|
||||
'required',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
custom_id: str = MISSING,
|
||||
required: bool = True,
|
||||
min_values: Optional[int] = None,
|
||||
max_values: Optional[int] = None,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
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: FileUploadComponent = FileUploadComponent._raw_construct(
|
||||
id=id,
|
||||
custom_id=custom_id,
|
||||
max_values=max_values,
|
||||
min_values=min_values,
|
||||
required=required,
|
||||
)
|
||||
self.id = id
|
||||
self._values: List[Attachment] = []
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def values(self) -> List[Attachment]:
|
||||
"""List[:class:`discord.Attachment`]: The list of attachments uploaded by the user.
|
||||
|
||||
You can call :meth:`~discord.Attachment.to_file` on each attachment
|
||||
to get a :class:`~discord.File` for sending.
|
||||
"""
|
||||
return self._values
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the component 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 min_values(self) -> int:
|
||||
""":class:`int`: The minimum number of files that must be user upload before submitting the modal."""
|
||||
return self._underlying.min_values
|
||||
|
||||
@min_values.setter
|
||||
def min_values(self, value: int) -> None:
|
||||
self._underlying.min_values = int(value)
|
||||
|
||||
@property
|
||||
def max_values(self) -> int:
|
||||
""":class:`int`: The maximum number of files that the user must upload before submitting the modal."""
|
||||
return self._underlying.max_values
|
||||
|
||||
@max_values.setter
|
||||
def max_values(self, value: int) -> None:
|
||||
self._underlying.max_values = int(value)
|
||||
|
||||
@property
|
||||
def required(self) -> bool:
|
||||
""":class:`bool`: Whether the component is required or not."""
|
||||
return self._underlying.required
|
||||
|
||||
@required.setter
|
||||
def required(self, value: bool) -> None:
|
||||
self._underlying.required = bool(value)
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 5
|
||||
|
||||
def to_component_dict(self) -> FileUploadComponentPayload:
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def _refresh_component(self, component: FileUploadComponent) -> None:
|
||||
self._underlying = component
|
||||
|
||||
def _handle_submit(
|
||||
self, interaction: Interaction, data: ModalSubmitFileUploadInteractionDataPayload, resolved: Dict[ResolveKey, Any]
|
||||
) -> None:
|
||||
self._values = [v for k, v in resolved.items() if k.id in data.get('values', [])]
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: FileUploadComponent) -> Self:
|
||||
self = cls(
|
||||
id=component.id,
|
||||
custom_id=component.custom_id,
|
||||
max_values=component.max_values,
|
||||
min_values=component.min_values,
|
||||
required=component.required,
|
||||
)
|
||||
return self
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.file_upload]:
|
||||
return self._underlying.type
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
|
|
@ -55,6 +55,21 @@ ItemCallbackType = Callable[[V, Interaction[Any], I], Coroutine[Any, Any, Any]]
|
|||
ContainedItemCallbackType = Callable[[C, Interaction[Any], I], Coroutine[Any, Any, Any]]
|
||||
|
||||
|
||||
class _ItemCallback:
|
||||
__slots__ = ('parent', 'callback', 'item')
|
||||
|
||||
def __init__(self, callback: ContainedItemCallbackType[Any, Any], parent: Any, item: Item[Any]) -> None:
|
||||
self.callback: ItemCallbackType[Any, Any] = callback
|
||||
self.parent: Any = parent
|
||||
self.item: Item[Any] = item
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<ItemCallback callback={self.callback!r} parent={self.parent!r} item={self.item!r}>'
|
||||
|
||||
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
|
||||
return self.callback(self.parent, interaction, self.item)
|
||||
|
||||
|
||||
class Item(Generic[V]):
|
||||
"""Represents the base UI item that all UI components inherit from.
|
||||
|
||||
|
|
@ -72,6 +87,9 @@ class Item(Generic[V]):
|
|||
- :class:`discord.ui.TextDisplay`
|
||||
- :class:`discord.ui.Thumbnail`
|
||||
- :class:`discord.ui.Label`
|
||||
- :class:`discord.ui.RadioGroup`
|
||||
- :class:`discord.ui.CheckboxGroup`
|
||||
- :class:`discord.ui.Checkbox`
|
||||
|
||||
.. versionadded:: 2.0
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -139,3 +139,8 @@ class Label(Item[V]):
|
|||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
|
||||
@property
|
||||
def _total_count(self) -> int:
|
||||
# Count the component and ourselves
|
||||
return 2
|
||||
|
|
|
|||
|
|
@ -75,11 +75,11 @@ class Modal(BaseView):
|
|||
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)
|
||||
name = ui.Label(text='Name', component=ui.TextInput())
|
||||
answer = ui.Label(text='Answer', component=ui.TextInput(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)
|
||||
await interaction.response.send_message(f'Thanks for your response, {self.name.component.value}!', ephemeral=True)
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
|
|
@ -223,7 +223,7 @@ class Modal(BaseView):
|
|||
|
||||
def to_components(self) -> List[Dict[str, Any]]:
|
||||
def key(item: Item) -> int:
|
||||
return item._rendered_row or 0
|
||||
return item._rendered_row or item.row or 0
|
||||
|
||||
children = sorted(self._children, key=key)
|
||||
components: List[Dict[str, Any]] = []
|
||||
|
|
|
|||
246
venv/lib/python3.12/site-packages/discord/ui/radio.py
Normal file
246
venv/lib/python3.12/site-packages/discord/ui/radio.py
Normal file
|
|
@ -0,0 +1,246 @@
|
|||
"""
|
||||
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, List, Literal, Optional, Tuple, TypeVar, Dict
|
||||
|
||||
import os
|
||||
|
||||
from ..utils import MISSING
|
||||
from ..components import RadioGroupComponent, RadioGroupOption
|
||||
from ..enums import ComponentType
|
||||
from .item import Item
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import Self
|
||||
|
||||
from ..interactions import Interaction
|
||||
from ..types.interactions import (
|
||||
ModalSubmitRadioGroupInteractionData as ModalSubmitRadioGroupInteractionDataPayload,
|
||||
)
|
||||
from ..types.components import RadioGroupComponent as RadioGroupComponentPayload
|
||||
from .view import BaseView
|
||||
from ..app_commands.namespace import ResolveKey
|
||||
|
||||
|
||||
# fmt: off
|
||||
__all__ = (
|
||||
'RadioGroup',
|
||||
)
|
||||
# fmt: on
|
||||
|
||||
V = TypeVar('V', bound='BaseView', covariant=True)
|
||||
|
||||
|
||||
class RadioGroup(Item[V]):
|
||||
"""Represents a radio group component within a modal.
|
||||
|
||||
.. versionadded:: 2.7
|
||||
|
||||
Parameters
|
||||
------------
|
||||
id: Optional[:class:`int`]
|
||||
The ID of the component. This must be unique across the view.
|
||||
custom_id: Optional[:class:`str`]
|
||||
The custom ID of the component.
|
||||
options: List[:class:`discord.RadioGroupOption`]
|
||||
A list of options that can be selected in this radio group.
|
||||
Can contain between 2 and 10 items.
|
||||
required: :class:`bool`
|
||||
Whether this component is required to be filled before submitting the modal.
|
||||
Defaults to ``True``.
|
||||
"""
|
||||
|
||||
__item_repr_attributes__: Tuple[str, ...] = (
|
||||
'id',
|
||||
'custom_id',
|
||||
'options',
|
||||
'required',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
custom_id: str = MISSING,
|
||||
required: bool = True,
|
||||
options: List[RadioGroupOption] = MISSING,
|
||||
id: Optional[int] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
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: RadioGroupComponent = RadioGroupComponent._raw_construct(
|
||||
id=id,
|
||||
custom_id=custom_id,
|
||||
required=required,
|
||||
options=options or [],
|
||||
)
|
||||
self.id = id
|
||||
self._value: Optional[str] = None
|
||||
|
||||
@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
|
||||
|
||||
@property
|
||||
def value(self) -> Optional[str]:
|
||||
"""Optional[:class:`str`]: The value have been selected by the user, if any."""
|
||||
return self._value
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the component 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 type(self) -> Literal[ComponentType.radio_group]:
|
||||
""":class:`.ComponentType`: The type of this component."""
|
||||
return ComponentType.radio_group
|
||||
|
||||
@property
|
||||
def options(self) -> List[RadioGroupOption]:
|
||||
"""List[:class:`discord.RadioGroupOption`]: A list of options that can be selected in this radio group."""
|
||||
return self._underlying.options
|
||||
|
||||
@options.setter
|
||||
def options(self, value: List[RadioGroupOption]) -> None:
|
||||
if not isinstance(value, list) or not all(isinstance(obj, RadioGroupOption) for obj in value):
|
||||
raise TypeError('options must be a list of RadioGroupOption')
|
||||
|
||||
self._underlying.options = value
|
||||
|
||||
def add_option(
|
||||
self,
|
||||
*,
|
||||
label: str,
|
||||
value: str = MISSING,
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
) -> None:
|
||||
"""Adds an option to the group.
|
||||
|
||||
To append a pre-existing :class:`discord.RadioGroupOption` use the
|
||||
:meth:`append_option` method instead.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
label: :class:`str`
|
||||
The label of the option. This is displayed to users.
|
||||
Can only be up to 100 characters.
|
||||
value: :class:`str`
|
||||
The value of the option. This is not displayed to users.
|
||||
If not given, defaults to the label.
|
||||
Can only be up to 100 characters.
|
||||
description: Optional[:class:`str`]
|
||||
An additional description of the option, if any.
|
||||
Can only be up to 100 characters.
|
||||
default: :class:`bool`
|
||||
Whether this option is selected by default.
|
||||
|
||||
Raises
|
||||
-------
|
||||
ValueError
|
||||
The number of options exceeds 10.
|
||||
"""
|
||||
|
||||
option = RadioGroupOption(
|
||||
label=label,
|
||||
value=value,
|
||||
description=description,
|
||||
default=default,
|
||||
)
|
||||
|
||||
self.append_option(option)
|
||||
|
||||
def append_option(self, option: RadioGroupOption) -> None:
|
||||
"""Appends an option to the group.
|
||||
|
||||
Parameters
|
||||
-----------
|
||||
option: :class:`discord.RadioGroupOption`
|
||||
The option to append to the group.
|
||||
|
||||
Raises
|
||||
-------
|
||||
ValueError
|
||||
The number of options exceeds 10.
|
||||
"""
|
||||
|
||||
if len(self._underlying.options) >= 10:
|
||||
raise ValueError('maximum number of options already provided (10)')
|
||||
|
||||
self._underlying.options.append(option)
|
||||
|
||||
@property
|
||||
def required(self) -> bool:
|
||||
""":class:`bool`: Whether the component is required or not."""
|
||||
return self._underlying.required
|
||||
|
||||
@required.setter
|
||||
def required(self, value: bool) -> None:
|
||||
self._underlying.required = bool(value)
|
||||
|
||||
@property
|
||||
def width(self) -> int:
|
||||
return 5
|
||||
|
||||
def to_component_dict(self) -> RadioGroupComponentPayload:
|
||||
return self._underlying.to_dict()
|
||||
|
||||
def _refresh_component(self, component: RadioGroupComponent) -> None:
|
||||
self._underlying = component
|
||||
|
||||
def _handle_submit(
|
||||
self, interaction: Interaction, data: ModalSubmitRadioGroupInteractionDataPayload, resolved: Dict[ResolveKey, Any]
|
||||
) -> None:
|
||||
self._value = data.get('value')
|
||||
|
||||
@classmethod
|
||||
def from_component(cls, component: RadioGroupComponent) -> Self:
|
||||
self = cls(
|
||||
id=component.id,
|
||||
custom_id=component.custom_id,
|
||||
options=component.options,
|
||||
required=component.required,
|
||||
)
|
||||
return self
|
||||
|
||||
def is_dispatchable(self) -> bool:
|
||||
return False
|
||||
|
|
@ -138,8 +138,7 @@ class Section(Item[V]):
|
|||
An item in this section.
|
||||
"""
|
||||
|
||||
for child in self.children:
|
||||
yield child
|
||||
yield from self.children
|
||||
yield self.accessory
|
||||
|
||||
def _update_view(self, view) -> None:
|
||||
|
|
|
|||
|
|
@ -39,10 +39,11 @@ from typing import (
|
|||
Sequence,
|
||||
)
|
||||
from contextvars import ContextVar
|
||||
import copy
|
||||
import inspect
|
||||
import os
|
||||
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType
|
||||
from .item import Item, ContainedItemCallbackType as ItemCallbackType, _ItemCallback
|
||||
from ..enums import ChannelType, ComponentType, SelectDefaultValueType
|
||||
from ..partial_emoji import PartialEmoji
|
||||
from ..emoji import Emoji
|
||||
|
|
@ -70,7 +71,7 @@ __all__ = (
|
|||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing_extensions import TypeAlias, TypeGuard
|
||||
from typing_extensions import TypeAlias, TypeGuard, Self
|
||||
|
||||
from .view import BaseView
|
||||
from .action_row import ActionRow
|
||||
|
|
@ -269,6 +270,14 @@ class BaseSelect(Item[V]):
|
|||
self.row = row
|
||||
self._values: List[PossibleValue] = []
|
||||
|
||||
def copy(self) -> Self:
|
||||
new = copy.copy(self)
|
||||
if isinstance(new.callback, _ItemCallback):
|
||||
new.callback.item = new
|
||||
new._parent = self._parent
|
||||
new._update_view(self.view)
|
||||
return new
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this select."""
|
||||
|
|
@ -497,10 +506,8 @@ class Select(BaseSelect[V]):
|
|||
|
||||
@options.setter
|
||||
def options(self, value: List[SelectOption]) -> None:
|
||||
if not isinstance(value, list):
|
||||
if not isinstance(value, list) or not all(isinstance(obj, SelectOption) for obj in value):
|
||||
raise TypeError('options must be a list of SelectOption')
|
||||
if not all(isinstance(obj, SelectOption) for obj in value):
|
||||
raise TypeError('all list items must subclass SelectOption')
|
||||
|
||||
self._underlying.options = value
|
||||
|
||||
|
|
@ -567,7 +574,7 @@ class Select(BaseSelect[V]):
|
|||
"""
|
||||
|
||||
if len(self._underlying.options) >= 25:
|
||||
raise ValueError('maximum number of options already provided')
|
||||
raise ValueError('maximum number of options already provided (25)')
|
||||
|
||||
self._underlying.options.append(option)
|
||||
|
||||
|
|
|
|||
|
|
@ -83,6 +83,15 @@ class Separator(Item[V]):
|
|||
def _is_v2(self):
|
||||
return True
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this separator."""
|
||||
return self._underlying.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value: Optional[int]) -> None:
|
||||
self._underlying.id = value
|
||||
|
||||
@property
|
||||
def visible(self) -> bool:
|
||||
""":class:`bool`: Whether this separator is visible.
|
||||
|
|
|
|||
|
|
@ -146,11 +146,19 @@ class TextInput(Item[V]):
|
|||
id=id,
|
||||
)
|
||||
self.row = row
|
||||
self.id = id
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.value
|
||||
|
||||
@property
|
||||
def id(self) -> Optional[int]:
|
||||
"""Optional[:class:`int`]: The ID of this text input."""
|
||||
return self._underlying.id
|
||||
|
||||
@id.setter
|
||||
def id(self, value: Optional[int]) -> None:
|
||||
self._underlying.id = value
|
||||
|
||||
@property
|
||||
def custom_id(self) -> str:
|
||||
""":class:`str`: The ID of the text input that gets received during an interaction."""
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ from typing import (
|
|||
Any,
|
||||
Callable,
|
||||
ClassVar,
|
||||
Coroutine,
|
||||
Dict,
|
||||
Generator,
|
||||
Iterator,
|
||||
|
|
@ -50,7 +49,7 @@ import sys
|
|||
import time
|
||||
import os
|
||||
|
||||
from .item import Item, ItemCallbackType
|
||||
from .item import Item, ItemCallbackType, _ItemCallback
|
||||
from .select import Select
|
||||
from .dynamic import DynamicItem
|
||||
from ..components import (
|
||||
|
|
@ -83,6 +82,7 @@ if TYPE_CHECKING:
|
|||
import re
|
||||
|
||||
from ..interactions import Interaction
|
||||
from .._types import ClientT
|
||||
from ..message import Message
|
||||
from ..types.components import ComponentBase as ComponentBasePayload
|
||||
from ..types.interactions import (
|
||||
|
|
@ -207,16 +207,22 @@ class _ViewWeights:
|
|||
self.weights = [0, 0, 0, 0, 0]
|
||||
|
||||
|
||||
class _ViewCallback:
|
||||
__slots__ = ('view', 'callback', 'item')
|
||||
class _ViewCacheSnapshot:
|
||||
__slots__ = ('items', 'dynamic_items')
|
||||
|
||||
def __init__(self, callback: ItemCallbackType[Any, Any], view: BaseView, item: Item[BaseView]) -> None:
|
||||
self.callback: ItemCallbackType[Any, Any] = callback
|
||||
self.view: BaseView = view
|
||||
self.item: Item[BaseView] = item
|
||||
def __init__(self) -> None:
|
||||
self.items: Set[Tuple[int, str]] = set()
|
||||
self.dynamic_items: Set[re.Pattern[str]] = set()
|
||||
|
||||
def __call__(self, interaction: Interaction) -> Coroutine[Any, Any, Any]:
|
||||
return self.callback(self.view, interaction, self.item)
|
||||
@classmethod
|
||||
def diff(cls, older: _ViewCacheSnapshot, newer: _ViewCacheSnapshot) -> Self:
|
||||
self = cls()
|
||||
self.items = older.items - newer.items
|
||||
self.dynamic_items = older.dynamic_items - newer.dynamic_items
|
||||
return self
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<_ViewCacheSnapshot items={self.items!r} dynamic_items={self.dynamic_items!r}>'
|
||||
|
||||
|
||||
class BaseView:
|
||||
|
|
@ -232,7 +238,15 @@ class BaseView:
|
|||
self.__cancel_callback: Optional[Callable[[BaseView], None]] = None
|
||||
self.__timeout_expiry: Optional[float] = None
|
||||
self.__timeout_task: Optional[asyncio.Task[None]] = None
|
||||
self.__stopped: asyncio.Future[bool] = asyncio.get_running_loop().create_future()
|
||||
self.__snapshot: Optional[_ViewCacheSnapshot] = None
|
||||
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
self.__stopped: Optional[asyncio.Future[bool]] = None
|
||||
else:
|
||||
self.__stopped: Optional[asyncio.Future[bool]] = loop.create_future()
|
||||
|
||||
self._total_children: int = len(tuple(self.walk_children()))
|
||||
|
||||
def _is_layout(self) -> bool:
|
||||
|
|
@ -252,13 +266,13 @@ class BaseView:
|
|||
item._update_view(self)
|
||||
parent = getattr(item, '__discord_ui_parent__', None)
|
||||
if parent and parent._view is None:
|
||||
parent._view = self
|
||||
parent._update_view(self)
|
||||
children.append(item)
|
||||
parents[raw] = item
|
||||
else:
|
||||
item: Item = raw.__discord_ui_model_type__(**raw.__discord_ui_model_kwargs__)
|
||||
item.callback = _ViewCallback(raw, self, item) # type: ignore
|
||||
item._view = self
|
||||
item.callback = _ItemCallback(raw, self, item) # type: ignore
|
||||
item._update_view(self)
|
||||
if isinstance(item, Select):
|
||||
item.options = [option.copy() for option in item.options]
|
||||
setattr(self, raw.__name__, item)
|
||||
|
|
@ -331,6 +345,31 @@ class BaseView:
|
|||
def _add_count(self, value: int) -> None:
|
||||
self._total_children = max(0, self._total_children + value)
|
||||
|
||||
@property
|
||||
def _snapshot(self) -> Optional[_ViewCacheSnapshot]:
|
||||
return self.__snapshot
|
||||
|
||||
def _get_snapshot_diff(self) -> Optional[_ViewCacheSnapshot]:
|
||||
if self.__snapshot is None:
|
||||
self.__snapshot = self._get_snapshot()
|
||||
return None
|
||||
|
||||
newer = self._get_snapshot()
|
||||
diff = _ViewCacheSnapshot.diff(older=self.__snapshot, newer=newer)
|
||||
# Update our snapshot to the newer version after diffing it
|
||||
self.__snapshot = newer
|
||||
return diff
|
||||
|
||||
def _get_snapshot(self) -> _ViewCacheSnapshot:
|
||||
snapshot = _ViewCacheSnapshot()
|
||||
for item in self.walk_children():
|
||||
if isinstance(item, DynamicItem):
|
||||
snapshot.dynamic_items.add(item.__discord_ui_compiled_template__)
|
||||
elif item.is_dispatchable():
|
||||
custom_id = item.custom_id # type: ignore
|
||||
snapshot.items.add((item.type.value, custom_id))
|
||||
return snapshot
|
||||
|
||||
@property
|
||||
def children(self) -> List[Item[Self]]:
|
||||
"""List[:class:`Item`]: The list of children attached to this view."""
|
||||
|
|
@ -452,6 +491,7 @@ class BaseView:
|
|||
pass
|
||||
else:
|
||||
self._add_count(-item._total_count)
|
||||
item._update_view(None)
|
||||
|
||||
return self
|
||||
|
||||
|
|
@ -461,6 +501,9 @@ class BaseView:
|
|||
This function returns the class instance to allow for fluent-style
|
||||
chaining.
|
||||
"""
|
||||
for child in self._children:
|
||||
child._update_view(None)
|
||||
|
||||
self._children.clear()
|
||||
self._total_children = 0
|
||||
return self
|
||||
|
|
@ -487,7 +530,7 @@ class BaseView:
|
|||
"""
|
||||
return _utils_get(self.walk_children(), id=id)
|
||||
|
||||
async def interaction_check(self, interaction: Interaction, /) -> bool:
|
||||
async def interaction_check(self, interaction: Interaction[ClientT], /) -> bool:
|
||||
"""|coro|
|
||||
|
||||
A callback that is called when an interaction happens within the view
|
||||
|
|
@ -522,7 +565,7 @@ class BaseView:
|
|||
"""
|
||||
pass
|
||||
|
||||
async def on_error(self, interaction: Interaction, error: Exception, item: Item[Any], /) -> None:
|
||||
async def on_error(self, interaction: Interaction[ClientT], error: Exception, item: Item[Any], /) -> None:
|
||||
"""|coro|
|
||||
|
||||
A callback that is called when an item's callback or :meth:`interaction_check`
|
||||
|
|
@ -541,7 +584,7 @@ class BaseView:
|
|||
"""
|
||||
_log.error('Ignoring exception in view %r for item %r', self, item, exc_info=error)
|
||||
|
||||
async def _scheduled_task(self, item: Item, interaction: Interaction):
|
||||
async def _scheduled_task(self, item: Item[Any], interaction: Interaction[ClientT]):
|
||||
try:
|
||||
item._refresh_state(interaction, interaction.data) # type: ignore
|
||||
|
||||
|
|
@ -566,7 +609,7 @@ class BaseView:
|
|||
self.__timeout_task = asyncio.create_task(self.__timeout_task_impl())
|
||||
|
||||
def _dispatch_timeout(self):
|
||||
if self.__stopped.done():
|
||||
if self.__stopped is None or self.__stopped.done():
|
||||
return
|
||||
|
||||
if self.__cancel_callback:
|
||||
|
|
@ -576,9 +619,9 @@ class BaseView:
|
|||
self.__stopped.set_result(True)
|
||||
asyncio.create_task(self.on_timeout(), name=f'discord-ui-view-timeout-{self.id}')
|
||||
|
||||
def _dispatch_item(self, item: Item, interaction: Interaction) -> Optional[asyncio.Task[None]]:
|
||||
if self.__stopped.done():
|
||||
return
|
||||
def _dispatch_item(self, item: Item[Any], interaction: Interaction[ClientT]) -> Optional[asyncio.Task[None]]:
|
||||
if self.__stopped is None or self.__stopped.done():
|
||||
return None
|
||||
|
||||
return asyncio.create_task(self._scheduled_task(item, interaction), name=f'discord-ui-view-dispatch-{self.id}')
|
||||
|
||||
|
|
@ -609,7 +652,7 @@ class BaseView:
|
|||
|
||||
This operation cannot be undone.
|
||||
"""
|
||||
if not self.__stopped.done():
|
||||
if self.__stopped is not None and not self.__stopped.done():
|
||||
self.__stopped.set_result(False)
|
||||
|
||||
self.__timeout_expiry = None
|
||||
|
|
@ -623,6 +666,9 @@ class BaseView:
|
|||
|
||||
def is_finished(self) -> bool:
|
||||
""":class:`bool`: Whether the view has finished interacting."""
|
||||
if self.__stopped is None:
|
||||
return False
|
||||
|
||||
return self.__stopped.done()
|
||||
|
||||
def is_dispatching(self) -> bool:
|
||||
|
|
@ -651,6 +697,9 @@ class BaseView:
|
|||
If ``True``, then the view timed out. If ``False`` then
|
||||
the view finished normally.
|
||||
"""
|
||||
if self.__stopped is None:
|
||||
self.__stopped = asyncio.get_running_loop().create_future()
|
||||
|
||||
return await self.__stopped
|
||||
|
||||
def walk_children(self) -> Generator[Item[Any], None, None]:
|
||||
|
|
@ -757,6 +806,8 @@ class View(BaseView):
|
|||
pass
|
||||
else:
|
||||
self.__weights.remove_item(item)
|
||||
item._update_view(None)
|
||||
|
||||
return self
|
||||
|
||||
def clear_items(self) -> Self:
|
||||
|
|
@ -892,8 +943,9 @@ class ViewStore:
|
|||
self._modals[view.custom_id] = view # type: ignore
|
||||
return
|
||||
|
||||
dispatch_info = self._views.setdefault(message_id, {})
|
||||
dispatch_info = self._views.get(message_id, {})
|
||||
is_fully_dynamic = True
|
||||
snapshot = view._get_snapshot_diff()
|
||||
for item in view.walk_children():
|
||||
if isinstance(item, DynamicItem):
|
||||
pattern = item.__discord_ui_compiled_template__
|
||||
|
|
@ -902,26 +954,34 @@ class ViewStore:
|
|||
dispatch_info[(item.type.value, item.custom_id)] = item # type: ignore
|
||||
is_fully_dynamic = False
|
||||
|
||||
if snapshot is not None:
|
||||
for key in snapshot.items:
|
||||
dispatch_info.pop(key, None)
|
||||
for key in snapshot.dynamic_items:
|
||||
self._dynamic_items.pop(key, None)
|
||||
|
||||
view._cache_key = message_id
|
||||
if dispatch_info:
|
||||
self._views[message_id] = dispatch_info
|
||||
|
||||
if message_id is not None and not is_fully_dynamic:
|
||||
self._synced_message_views[message_id] = view
|
||||
|
||||
def remove_view(self, view: View) -> None:
|
||||
def remove_view(self, view: BaseView) -> None:
|
||||
if view.__discord_ui_modal__:
|
||||
self._modals.pop(view.custom_id, None) # type: ignore
|
||||
return
|
||||
|
||||
dispatch_info = self._views.get(view._cache_key)
|
||||
if dispatch_info:
|
||||
for item in view._children:
|
||||
if isinstance(item, DynamicItem):
|
||||
pattern = item.__discord_ui_compiled_template__
|
||||
self._dynamic_items.pop(pattern, None)
|
||||
elif item.is_dispatchable():
|
||||
dispatch_info.pop((item.type.value, item.custom_id), None) # type: ignore
|
||||
snapshot = view._snapshot
|
||||
if dispatch_info and snapshot:
|
||||
for key in snapshot.items:
|
||||
dispatch_info.pop(key, None)
|
||||
for key in snapshot.dynamic_items:
|
||||
self._dynamic_items.pop(key, None)
|
||||
|
||||
if len(dispatch_info) == 0:
|
||||
self._views.pop(view._cache_key, None)
|
||||
if dispatch_info is not None and len(dispatch_info) == 0:
|
||||
self._views.pop(view._cache_key, None)
|
||||
|
||||
self._synced_message_views.pop(view._cache_key, None) # type: ignore
|
||||
|
||||
|
|
@ -929,7 +989,7 @@ class ViewStore:
|
|||
self,
|
||||
component_type: int,
|
||||
factory: Type[DynamicItem[Item[Any]]],
|
||||
interaction: Interaction,
|
||||
interaction: Interaction[ClientT],
|
||||
custom_id: str,
|
||||
match: re.Match[str],
|
||||
) -> None:
|
||||
|
|
@ -980,7 +1040,7 @@ class ViewStore:
|
|||
except Exception:
|
||||
_log.exception('Ignoring exception in dynamic item callback for %r', item)
|
||||
|
||||
def dispatch_dynamic_items(self, component_type: int, custom_id: str, interaction: Interaction) -> None:
|
||||
def dispatch_dynamic_items(self, component_type: int, custom_id: str, interaction: Interaction[ClientT]) -> None:
|
||||
for pattern, item in self._dynamic_items.items():
|
||||
match = pattern.fullmatch(custom_id)
|
||||
if match is not None:
|
||||
|
|
@ -991,17 +1051,14 @@ class ViewStore:
|
|||
)
|
||||
)
|
||||
|
||||
def dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction) -> None:
|
||||
def dispatch_view(self, component_type: int, custom_id: str, interaction: Interaction[ClientT]) -> None:
|
||||
self.dispatch_dynamic_items(component_type, custom_id, interaction)
|
||||
interaction_id: Optional[int] = None
|
||||
message_id: Optional[int] = None
|
||||
# Realistically, in a component based interaction the Interaction.message will never be None
|
||||
# However, this guard is just in case Discord screws up somehow
|
||||
msg = interaction.message
|
||||
if msg is not None:
|
||||
message_id = msg.id
|
||||
if msg.interaction_metadata:
|
||||
interaction_id = msg.interaction_metadata.id
|
||||
|
||||
key = (component_type, custom_id)
|
||||
|
||||
|
|
@ -1010,21 +1067,6 @@ class ViewStore:
|
|||
if message_id is not None:
|
||||
item = self._views.get(message_id, {}).get(key)
|
||||
|
||||
if item is None and interaction_id is not None:
|
||||
try:
|
||||
items = self._views.pop(interaction_id)
|
||||
except KeyError:
|
||||
item = None
|
||||
else:
|
||||
item = items.get(key)
|
||||
# If we actually got the items, then these keys should probably be moved
|
||||
# to the proper message_id instead of the interaction_id as they are now.
|
||||
# An interaction_id is only used as a temporary stop gap for
|
||||
# InteractionResponse.send_message so multiple view instances do not
|
||||
# override each other.
|
||||
# NOTE: Fix this mess if /callback endpoint ever gets proper return types
|
||||
self._views.setdefault(message_id, {}).update(items)
|
||||
|
||||
if item is None:
|
||||
# Fallback to None message_id searches in case a persistent view
|
||||
# was added without an associated message_id
|
||||
|
|
@ -1034,15 +1076,18 @@ class ViewStore:
|
|||
if item is None:
|
||||
return
|
||||
|
||||
# Note, at this point the View is *not* None
|
||||
task = item.view._dispatch_item(item, interaction) # type: ignore
|
||||
if item.view is None:
|
||||
_log.warning('View interaction referencing unknown view for item %s. Discarding', item)
|
||||
return
|
||||
|
||||
task = item.view._dispatch_item(item, interaction)
|
||||
if task is not None:
|
||||
self.add_task(task)
|
||||
|
||||
def dispatch_modal(
|
||||
self,
|
||||
custom_id: str,
|
||||
interaction: Interaction,
|
||||
interaction: Interaction[ClientT],
|
||||
components: List[ModalSubmitComponentInteractionDataPayload],
|
||||
resolved: ResolvedDataPayload,
|
||||
) -> None:
|
||||
|
|
@ -1053,11 +1098,6 @@ class ViewStore:
|
|||
|
||||
self.add_task(modal._dispatch_submit(interaction, components, resolved))
|
||||
|
||||
def remove_interaction_mapping(self, interaction_id: int) -> None:
|
||||
# This is called before re-adding the view
|
||||
self._views.pop(interaction_id, None)
|
||||
self._synced_message_views.pop(interaction_id, None)
|
||||
|
||||
def is_message_tracked(self, message_id: int) -> bool:
|
||||
return message_id in self._synced_message_views
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue