Browse Source

feat: Add (Layout)View.from_dict methods

pull/10166/head
DA-344 1 week ago
parent
commit
e3294223b6
  1. 202
      discord/ui/view.py

202
discord/ui/view.py

@ -49,11 +49,15 @@ import sys
import time import time
import os import os
import copy import copy
from .item import Item, ItemCallbackType from .item import Item, ItemCallbackType
from .dynamic import DynamicItem from .dynamic import DynamicItem
from ..components import ( from ..components import (
Component, Component,
ActionRow as ActionRowComponent, ActionRow as ActionRowComponent,
MediaGalleryItem,
SelectDefaultValue,
UnfurledMediaItem,
_component_factory, _component_factory,
Button as ButtonComponent, Button as ButtonComponent,
SelectMenu as SelectComponent, SelectMenu as SelectComponent,
@ -63,8 +67,11 @@ from ..components import (
FileComponent, FileComponent,
SeparatorComponent, SeparatorComponent,
ThumbnailComponent, ThumbnailComponent,
SelectOption,
) )
from ..utils import get as _utils_get from ..utils import MISSING, get as _utils_get, _get_as_snowflake
from ..enums import SeparatorSize, TextStyle, try_enum, ButtonStyle
from ..emoji import PartialEmoji
# fmt: off # fmt: off
__all__ = ( __all__ = (
@ -80,7 +87,7 @@ if TYPE_CHECKING:
from ..interactions import Interaction from ..interactions import Interaction
from ..message import Message from ..message import Message
from ..types.components import ComponentBase as ComponentBasePayload from ..types.components import ComponentBase as ComponentBasePayload, Component as ComponentPayload
from ..types.interactions import ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload from ..types.interactions import ModalSubmitComponentInteractionData as ModalSubmitComponentInteractionDataPayload
from ..state import ConnectionState from ..state import ConnectionState
from .modal import Modal from .modal import Modal
@ -100,6 +107,10 @@ def _walk_all_components(components: List[Component]) -> Iterator[Component]:
def _component_to_item(component: Component) -> Item: def _component_to_item(component: Component) -> Item:
if isinstance(component, ActionRowComponent):
from .action_row import ActionRow
return ActionRow.from_component(component)
if isinstance(component, ButtonComponent): if isinstance(component, ButtonComponent):
from .button import Button from .button import Button
@ -136,6 +147,141 @@ def _component_to_item(component: Component) -> Item:
return Item.from_component(component) return Item.from_component(component)
def _component_data_to_item(data: ComponentPayload) -> Item:
if data['type'] == 1:
from .action_row import ActionRow
return ActionRow(
*(_component_data_to_item(c) for c in data['components']),
id=data.get('id'),
)
elif data['type'] == 2:
from .button import Button
emoji = data.get('emoji')
return Button(
style=try_enum(ButtonStyle, data['style']),
custom_id=data.get('custom_id'),
url=data.get('url'),
disabled=data.get('disabled', False),
emoji=PartialEmoji.from_dict(emoji) if emoji else None,
label=data.get('label'),
sku_id=_get_as_snowflake(data, 'sku_id'),
)
elif data['type'] == 3:
from .select import Select
return Select(
custom_id=data['custom_id'],
placeholder=data.get('placeholder'),
min_values=data.get('min_values', 1),
max_values=data.get('max_values', 1),
disabled=data.get('disabled', False),
id=data.get('id'),
options=[
SelectOption.from_dict(o)
for o in data.get('options', [])
],
)
elif data['type'] == 4:
from .text_input import TextInput
return TextInput(
label=data['label'],
style=try_enum(TextStyle, data['style']),
custom_id=data['custom_id'],
placeholder=data.get('placeholder'),
default=data.get('value'),
required=data.get('required', True),
min_length=data.get('min_length'),
max_length=data.get('max_length'),
id=data.get('id'),
)
elif data['type'] in (5, 6, 7, 8):
from .select import (
UserSelect,
RoleSelect,
MentionableSelect,
ChannelSelect,
)
cls_map: Dict[int, Type[Union[UserSelect, RoleSelect, MentionableSelect, ChannelSelect]]] = {
5: UserSelect,
6: RoleSelect,
7: MentionableSelect,
8: ChannelSelect,
}
return cls_map[data['type']](
custom_id=data['custom_id'], # type: ignore # will always be present in this point
placeholder=data.get('placeholder'),
min_values=data.get('min_values', 1),
max_values=data.get('max_values', 1),
disabled=data.get('disabled', False),
default_values=[
SelectDefaultValue.from_dict(v)
for v in data.get('default_values', [])
],
id=data.get('id'),
)
elif data['type'] == 9:
from .section import Section
return Section(
*(_component_data_to_item(c) for c in data['components']),
accessory=_component_data_to_item(data['accessory']),
id=data.get('id'),
)
elif data['type'] == 10:
from .text_display import TextDisplay
return TextDisplay(data['content'], id=data.get('id'))
elif data['type'] == 11:
from .thumbnail import Thumbnail
return Thumbnail(
UnfurledMediaItem._from_data(data['media'], None),
description=data.get('description'),
spoiler=data.get('spoiler', False),
id=data.get('id'),
)
elif data['type'] == 12:
from .media_gallery import MediaGallery
return MediaGallery(
*(MediaGalleryItem._from_data(m, None) for m in data['items']),
id=data.get('id'),
)
elif data['type'] == 13:
from .file import File
return File(
UnfurledMediaItem._from_data(data['file'], None),
spoiler=data.get('spoiler', False),
id=data.get('id'),
)
elif data['type'] == 14:
from .separator import Separator
return Separator(
visible=data.get('divider', True),
spacing=try_enum(SeparatorSize, data.get('spacing', 1)),
id=data.get('id'),
)
elif data['type'] == 17:
from .container import Container
return Container(
*(_component_data_to_item(c) for c in data['components']),
accent_colour=data.get('accent_color'),
spoiler=data.get('spoiler', False),
id=data.get('type'),
)
else:
raise ValueError(f'invalid item with type {data["type"]} provided')
class _ViewWeights: class _ViewWeights:
# fmt: off # fmt: off
__slots__ = ( __slots__ = (
@ -599,6 +745,28 @@ class BaseView:
# if it has this attribute then it can contain children # if it has this attribute then it can contain children
yield from child.walk_children() # type: ignore yield from child.walk_children() # type: ignore
@classmethod
def _to_minimal_cls(cls) -> Type[Union[View, LayoutView]]:
if issubclass(cls, View):
return View
elif issubclass(cls, LayoutView):
return LayoutView
raise RuntimeError
@classmethod
def from_dict(cls, data: List[ComponentPayload], *, timeout: Optional[float] = 180.0) -> Any:
cls = cls._to_minimal_cls()
self = cls(timeout=timeout)
for raw in data:
item = _component_data_to_item(raw)
if item._is_v2() and not self._is_v2():
continue
self.add_item(item)
return self
class View(BaseView): class View(BaseView):
"""Represents a UI view. """Represents a UI view.
@ -616,6 +784,21 @@ class View(BaseView):
__discord_ui_view__: ClassVar[bool] = True __discord_ui_view__: ClassVar[bool] = True
if TYPE_CHECKING:
@classmethod
def from_dict(cls, data: List[ComponentPayload], *, timeout: Optional[float] = 180.0) -> View:
"""Converts a :class:`list` of :class:`dict` s to a :class:`View` provided it is in the
format that Discord expects it to be in.
You can find out about this format in the :ddocs:`official Discord documentation <components/reference#anatomy-of-a-component>`.
Parameters
----------
data: List[:class:`dict`]
The array of dictionaries to convert into a View.
"""
...
def __init_subclass__(cls) -> None: def __init_subclass__(cls) -> None:
super().__init_subclass__() super().__init_subclass__()
@ -754,6 +937,21 @@ class LayoutView(BaseView):
__discord_ui_layout_view__: ClassVar[bool] = True __discord_ui_layout_view__: ClassVar[bool] = True
if TYPE_CHECKING:
@classmethod
def from_dict(cls, data: List[ComponentPayload], *, timeout: Optional[float] = 180.0) -> LayoutView:
"""Converts a :class:`list` of :class:`dict` s to a :class:`LayoutView` provided it is in the
format that Discord expects it to be in.
You can find out about this format in the :ddocs:`official Discord documentation <components/reference#anatomy-of-a-component>`.
Parameters
----------
data: List[:class:`dict`]
The array of dictionaries to convert into a LayoutView.
"""
...
def __init__(self, *, timeout: Optional[float] = 180.0) -> None: def __init__(self, *, timeout: Optional[float] = 180.0) -> None:
super().__init__(timeout=timeout) super().__init__(timeout=timeout)

Loading…
Cancel
Save