mirror of
https://github.com/Rapptz/discord.py.git
synced 2025-09-06 09:56:09 +00:00
Add support for components V2
Co-authored-by: Michael H <michael@michaelhall.tech> Co-authored-by: Soheab <33902984+Soheab@users.noreply.github.com> Co-authored-by: owocado <24418520+owocado@users.noreply.github.com> Co-authored-by: Jay3332 <40323796+jay3332@users.noreply.github.com> Co-authored-by: Danny <1695103+Rapptz@users.noreply.github.com>
This commit is contained in:
@ -24,9 +24,30 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import ClassVar, List, Literal, Optional, TYPE_CHECKING, Tuple, Union, overload
|
||||
from .enums import try_enum, ComponentType, ButtonStyle, TextStyle, ChannelType, SelectDefaultValueType
|
||||
from .utils import get_slots, MISSING
|
||||
from typing import (
|
||||
ClassVar,
|
||||
List,
|
||||
Literal,
|
||||
Optional,
|
||||
TYPE_CHECKING,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
|
||||
from .asset import AssetMixin
|
||||
from .enums import (
|
||||
try_enum,
|
||||
ComponentType,
|
||||
ButtonStyle,
|
||||
TextStyle,
|
||||
ChannelType,
|
||||
SelectDefaultValueType,
|
||||
SeparatorSpacing,
|
||||
MediaItemLoadingState,
|
||||
)
|
||||
from .flags import AttachmentFlags
|
||||
from .colour import Colour
|
||||
from .utils import get_slots, MISSING, _get_as_snowflake
|
||||
from .partial_emoji import PartialEmoji, _EmojiTag
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -39,13 +60,35 @@ if TYPE_CHECKING:
|
||||
SelectOption as SelectOptionPayload,
|
||||
ActionRow as ActionRowPayload,
|
||||
TextInput as TextInputPayload,
|
||||
ActionRowChildComponent as ActionRowChildComponentPayload,
|
||||
SelectDefaultValues as SelectDefaultValuesPayload,
|
||||
SectionComponent as SectionComponentPayload,
|
||||
TextComponent as TextComponentPayload,
|
||||
MediaGalleryComponent as MediaGalleryComponentPayload,
|
||||
FileComponent as FileComponentPayload,
|
||||
SeparatorComponent as SeparatorComponentPayload,
|
||||
MediaGalleryItem as MediaGalleryItemPayload,
|
||||
ThumbnailComponent as ThumbnailComponentPayload,
|
||||
ContainerComponent as ContainerComponentPayload,
|
||||
UnfurledMediaItem as UnfurledMediaItemPayload,
|
||||
)
|
||||
|
||||
from .emoji import Emoji
|
||||
from .abc import Snowflake
|
||||
from .state import ConnectionState
|
||||
|
||||
ActionRowChildComponentType = Union['Button', 'SelectMenu', 'TextInput']
|
||||
SectionComponentType = Union['TextDisplay']
|
||||
MessageComponentType = Union[
|
||||
ActionRowChildComponentType,
|
||||
SectionComponentType,
|
||||
'ActionRow',
|
||||
'SectionComponent',
|
||||
'ThumbnailComponent',
|
||||
'MediaGalleryComponent',
|
||||
'FileComponent',
|
||||
'SectionComponent',
|
||||
'Component',
|
||||
]
|
||||
|
||||
|
||||
__all__ = (
|
||||
@ -56,18 +99,35 @@ __all__ = (
|
||||
'SelectOption',
|
||||
'TextInput',
|
||||
'SelectDefaultValue',
|
||||
'SectionComponent',
|
||||
'ThumbnailComponent',
|
||||
'UnfurledMediaItem',
|
||||
'MediaGalleryItem',
|
||||
'MediaGalleryComponent',
|
||||
'FileComponent',
|
||||
'SectionComponent',
|
||||
'Container',
|
||||
'TextDisplay',
|
||||
'SeparatorComponent',
|
||||
)
|
||||
|
||||
|
||||
class Component:
|
||||
"""Represents a Discord Bot UI Kit Component.
|
||||
|
||||
Currently, the only components supported by Discord are:
|
||||
The components supported by Discord are:
|
||||
|
||||
- :class:`ActionRow`
|
||||
- :class:`Button`
|
||||
- :class:`SelectMenu`
|
||||
- :class:`TextInput`
|
||||
- :class:`SectionComponent`
|
||||
- :class:`TextDisplay`
|
||||
- :class:`ThumbnailComponent`
|
||||
- :class:`MediaGalleryComponent`
|
||||
- :class:`FileComponent`
|
||||
- :class:`SeparatorComponent`
|
||||
- :class:`Container`
|
||||
|
||||
This class is abstract and cannot be instantiated.
|
||||
|
||||
@ -116,20 +176,25 @@ class ActionRow(Component):
|
||||
------------
|
||||
children: List[Union[:class:`Button`, :class:`SelectMenu`, :class:`TextInput`]]
|
||||
The children components that this holds, if any.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
__slots__: Tuple[str, ...] = ('children',)
|
||||
__slots__: Tuple[str, ...] = ('children', 'id')
|
||||
|
||||
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
|
||||
|
||||
def __init__(self, data: ActionRowPayload, /) -> None:
|
||||
self.id: Optional[int] = data.get('id')
|
||||
self.children: List[ActionRowChildComponentType] = []
|
||||
|
||||
for component_data in data.get('components', []):
|
||||
component = _component_factory(component_data)
|
||||
|
||||
if component is not None:
|
||||
self.children.append(component)
|
||||
self.children.append(component) # type: ignore # should be the correct type here
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.action_row]:
|
||||
@ -137,10 +202,13 @@ class ActionRow(Component):
|
||||
return ComponentType.action_row
|
||||
|
||||
def to_dict(self) -> ActionRowPayload:
|
||||
return {
|
||||
payload: ActionRowPayload = {
|
||||
'type': self.type.value,
|
||||
'components': [child.to_dict() for child in self.children],
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
return payload
|
||||
|
||||
|
||||
class Button(Component):
|
||||
@ -174,6 +242,10 @@ class Button(Component):
|
||||
The SKU ID this button sends you to, if available.
|
||||
|
||||
.. versionadded:: 2.4
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
__slots__: Tuple[str, ...] = (
|
||||
@ -184,11 +256,13 @@ class Button(Component):
|
||||
'label',
|
||||
'emoji',
|
||||
'sku_id',
|
||||
'id',
|
||||
)
|
||||
|
||||
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
|
||||
|
||||
def __init__(self, data: ButtonComponentPayload, /) -> None:
|
||||
self.id: Optional[int] = data.get('id')
|
||||
self.style: ButtonStyle = try_enum(ButtonStyle, data['style'])
|
||||
self.custom_id: Optional[str] = data.get('custom_id')
|
||||
self.url: Optional[str] = data.get('url')
|
||||
@ -217,6 +291,9 @@ class Button(Component):
|
||||
'disabled': self.disabled,
|
||||
}
|
||||
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
|
||||
if self.sku_id:
|
||||
payload['sku_id'] = str(self.sku_id)
|
||||
|
||||
@ -268,6 +345,10 @@ class SelectMenu(Component):
|
||||
Whether the select is disabled or not.
|
||||
channel_types: List[:class:`.ChannelType`]
|
||||
A list of channel types that are allowed to be chosen in this select menu.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
__slots__: Tuple[str, ...] = (
|
||||
@ -280,6 +361,7 @@ class SelectMenu(Component):
|
||||
'disabled',
|
||||
'channel_types',
|
||||
'default_values',
|
||||
'id',
|
||||
)
|
||||
|
||||
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
|
||||
@ -296,6 +378,7 @@ class SelectMenu(Component):
|
||||
self.default_values: List[SelectDefaultValue] = [
|
||||
SelectDefaultValue.from_dict(d) for d in data.get('default_values', [])
|
||||
]
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
def to_dict(self) -> SelectMenuPayload:
|
||||
payload: SelectMenuPayload = {
|
||||
@ -305,6 +388,8 @@ class SelectMenu(Component):
|
||||
'max_values': self.max_values,
|
||||
'disabled': self.disabled,
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
if self.placeholder:
|
||||
payload['placeholder'] = self.placeholder
|
||||
if self.options:
|
||||
@ -312,7 +397,7 @@ class SelectMenu(Component):
|
||||
if self.channel_types:
|
||||
payload['channel_types'] = [t.value for t in self.channel_types]
|
||||
if self.default_values:
|
||||
payload["default_values"] = [v.to_dict() for v in self.default_values]
|
||||
payload['default_values'] = [v.to_dict() for v in self.default_values]
|
||||
|
||||
return payload
|
||||
|
||||
@ -473,6 +558,10 @@ class TextInput(Component):
|
||||
The minimum length of the text input.
|
||||
max_length: Optional[:class:`int`]
|
||||
The maximum length of the text input.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
"""
|
||||
|
||||
__slots__: Tuple[str, ...] = (
|
||||
@ -484,6 +573,7 @@ class TextInput(Component):
|
||||
'required',
|
||||
'min_length',
|
||||
'max_length',
|
||||
'id',
|
||||
)
|
||||
|
||||
__repr_info__: ClassVar[Tuple[str, ...]] = __slots__
|
||||
@ -497,6 +587,7 @@ class TextInput(Component):
|
||||
self.required: bool = data.get('required', True)
|
||||
self.min_length: Optional[int] = data.get('min_length')
|
||||
self.max_length: Optional[int] = data.get('max_length')
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.text_input]:
|
||||
@ -512,6 +603,9 @@ class TextInput(Component):
|
||||
'required': self.required,
|
||||
}
|
||||
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
|
||||
if self.placeholder:
|
||||
payload['placeholder'] = self.placeholder
|
||||
|
||||
@ -645,17 +739,577 @@ class SelectDefaultValue:
|
||||
)
|
||||
|
||||
|
||||
@overload
|
||||
def _component_factory(data: ActionRowChildComponentPayload) -> Optional[ActionRowChildComponentType]:
|
||||
...
|
||||
class SectionComponent(Component):
|
||||
"""Represents a section from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type to create a section is :class:`discord.ui.Section`
|
||||
not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
children: List[:class:`TextDisplay`]
|
||||
The components on this section.
|
||||
accessory: :class:`Component`
|
||||
The section accessory.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'children',
|
||||
'accessory',
|
||||
'id',
|
||||
)
|
||||
|
||||
__repr_info__ = __slots__
|
||||
|
||||
def __init__(self, data: SectionComponentPayload, state: Optional[ConnectionState]) -> None:
|
||||
self.children: List[SectionComponentType] = []
|
||||
self.accessory: Component = _component_factory(data['accessory'], state) # type: ignore
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
for component_data in data['components']:
|
||||
component = _component_factory(component_data, state)
|
||||
if component is not None:
|
||||
self.children.append(component) # type: ignore # should be the correct type here
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.section]:
|
||||
return ComponentType.section
|
||||
|
||||
def to_dict(self) -> SectionComponentPayload:
|
||||
payload: SectionComponentPayload = {
|
||||
'type': self.type.value,
|
||||
'components': [c.to_dict() for c in self.children],
|
||||
'accessory': self.accessory.to_dict(),
|
||||
}
|
||||
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
@overload
|
||||
def _component_factory(data: ComponentPayload) -> Optional[Union[ActionRow, ActionRowChildComponentType]]:
|
||||
...
|
||||
class ThumbnailComponent(Component):
|
||||
"""Represents a Thumbnail from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type to create a thumbnail is :class:`discord.ui.Thumbnail`
|
||||
not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
media: :class:`UnfurledMediaItem`
|
||||
The media for this thumbnail.
|
||||
description: Optional[:class:`str`]
|
||||
The description shown within this thumbnail.
|
||||
spoiler: :class:`bool`
|
||||
Whether this thumbnail is flagged as a spoiler.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'media',
|
||||
'spoiler',
|
||||
'description',
|
||||
'id',
|
||||
)
|
||||
|
||||
__repr_info__ = __slots__
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: ThumbnailComponentPayload,
|
||||
state: Optional[ConnectionState],
|
||||
) -> None:
|
||||
self.media: UnfurledMediaItem = UnfurledMediaItem._from_data(data['media'], state)
|
||||
self.description: Optional[str] = data.get('description')
|
||||
self.spoiler: bool = data.get('spoiler', False)
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.thumbnail]:
|
||||
return ComponentType.thumbnail
|
||||
|
||||
def to_dict(self) -> ThumbnailComponentPayload:
|
||||
payload = {
|
||||
'media': self.media.to_dict(),
|
||||
'description': self.description,
|
||||
'spoiler': self.spoiler,
|
||||
'type': self.type.value,
|
||||
}
|
||||
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
|
||||
return payload # type: ignore
|
||||
|
||||
|
||||
def _component_factory(data: ComponentPayload) -> Optional[Union[ActionRow, ActionRowChildComponentType]]:
|
||||
class TextDisplay(Component):
|
||||
"""Represents a text display from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type to create a text display is
|
||||
:class:`discord.ui.TextDisplay` not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
content: :class:`str`
|
||||
The content that this display shows.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
"""
|
||||
|
||||
__slots__ = ('content', 'id')
|
||||
|
||||
__repr_info__ = __slots__
|
||||
|
||||
def __init__(self, data: TextComponentPayload) -> None:
|
||||
self.content: str = data['content']
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.text_display]:
|
||||
return ComponentType.text_display
|
||||
|
||||
def to_dict(self) -> TextComponentPayload:
|
||||
payload: TextComponentPayload = {
|
||||
'type': self.type.value,
|
||||
'content': self.content,
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
return payload
|
||||
|
||||
|
||||
class UnfurledMediaItem(AssetMixin):
|
||||
"""Represents an unfurled media item.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
url: :class:`str`
|
||||
The URL of this media item. This can be an arbitrary url or a reference to a local
|
||||
file uploaded as an attachment within the message, which can be accessed with the
|
||||
``attachment://<filename>`` format.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
url: :class:`str`
|
||||
The URL of this media item.
|
||||
proxy_url: Optional[:class:`str`]
|
||||
The proxy URL. This is a cached version of the :attr:`.url` in the
|
||||
case of images. When the message is deleted, this URL might be valid for a few minutes
|
||||
or not valid at all.
|
||||
height: Optional[:class:`int`]
|
||||
The media item's height, in pixels. Only applicable to images and videos.
|
||||
width: Optional[:class:`int`]
|
||||
The media item's width, in pixels. Only applicable to images and videos.
|
||||
content_type: Optional[:class:`str`]
|
||||
The media item's `media type <https://en.wikipedia.org/wiki/Media_type>`_
|
||||
placeholder: Optional[:class:`str`]
|
||||
The media item's placeholder.
|
||||
loading_state: Optional[:class:`MediaItemLoadingState`]
|
||||
The loading state of this media item.
|
||||
attachment_id: Optional[:class:`int`]
|
||||
The attachment id this media item points to, only available if the url points to a local file
|
||||
uploaded within the component message.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'url',
|
||||
'proxy_url',
|
||||
'height',
|
||||
'width',
|
||||
'content_type',
|
||||
'_flags',
|
||||
'placeholder',
|
||||
'loading_state',
|
||||
'attachment_id',
|
||||
'_state',
|
||||
)
|
||||
|
||||
def __init__(self, url: str) -> None:
|
||||
self.url: str = url
|
||||
|
||||
self.proxy_url: Optional[str] = None
|
||||
self.height: Optional[int] = None
|
||||
self.width: Optional[int] = None
|
||||
self.content_type: Optional[str] = None
|
||||
self._flags: int = 0
|
||||
self.placeholder: Optional[str] = None
|
||||
self.loading_state: Optional[MediaItemLoadingState] = None
|
||||
self.attachment_id: Optional[int] = None
|
||||
self._state: Optional[ConnectionState] = None
|
||||
|
||||
@property
|
||||
def flags(self) -> AttachmentFlags:
|
||||
""":class:`AttachmentFlags`: This media item's flags."""
|
||||
return AttachmentFlags._from_value(self._flags)
|
||||
|
||||
@classmethod
|
||||
def _from_data(cls, data: UnfurledMediaItemPayload, state: Optional[ConnectionState]):
|
||||
self = cls(data['url'])
|
||||
self._update(data, state)
|
||||
return self
|
||||
|
||||
def _update(self, data: UnfurledMediaItemPayload, state: Optional[ConnectionState]) -> None:
|
||||
self.proxy_url = data.get('proxy_url')
|
||||
self.height = data.get('height')
|
||||
self.width = data.get('width')
|
||||
self.content_type = data.get('content_type')
|
||||
self._flags = data.get('flags', 0)
|
||||
self.placeholder = data.get('placeholder')
|
||||
|
||||
loading_state = data.get('loading_state')
|
||||
if loading_state is not None:
|
||||
self.loading_state = try_enum(MediaItemLoadingState, loading_state)
|
||||
self.attachment_id = _get_as_snowflake(data, 'attachment_id')
|
||||
self._state = state
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<UnfurledMediaItem url={self.url}>'
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'url': self.url,
|
||||
}
|
||||
|
||||
|
||||
class MediaGalleryItem:
|
||||
"""Represents a :class:`MediaGalleryComponent` media item.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Parameters
|
||||
----------
|
||||
media: Union[:class:`str`, :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.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'_media',
|
||||
'description',
|
||||
'spoiler',
|
||||
'_state',
|
||||
)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
media: Union[str, UnfurledMediaItem],
|
||||
*,
|
||||
description: Optional[str] = None,
|
||||
spoiler: bool = False,
|
||||
) -> None:
|
||||
self._media: UnfurledMediaItem = UnfurledMediaItem(media) if isinstance(media, str) else media
|
||||
self.description: Optional[str] = description
|
||||
self.spoiler: bool = spoiler
|
||||
self._state: Optional[ConnectionState] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f'<MediaGalleryItem media={self.media!r}>'
|
||||
|
||||
@property
|
||||
def media(self) -> UnfurledMediaItem:
|
||||
""":class:`UnfurledMediaItem`: This item's media data."""
|
||||
return self._media
|
||||
|
||||
@media.setter
|
||||
def media(self, value: Union[str, UnfurledMediaItem]) -> None:
|
||||
if isinstance(value, str):
|
||||
self._media = UnfurledMediaItem(value)
|
||||
elif isinstance(value, UnfurledMediaItem):
|
||||
self._media = value
|
||||
else:
|
||||
raise TypeError(f'Expected a str or UnfurledMediaItem, not {value.__class__.__name__}')
|
||||
|
||||
@classmethod
|
||||
def _from_data(cls, data: MediaGalleryItemPayload, state: Optional[ConnectionState]) -> MediaGalleryItem:
|
||||
media = data['media']
|
||||
self = cls(
|
||||
media=UnfurledMediaItem._from_data(media, state),
|
||||
description=data.get('description'),
|
||||
spoiler=data.get('spoiler', False),
|
||||
)
|
||||
self._state = state
|
||||
return self
|
||||
|
||||
@classmethod
|
||||
def _from_gallery(
|
||||
cls,
|
||||
items: List[MediaGalleryItemPayload],
|
||||
state: Optional[ConnectionState],
|
||||
) -> List[MediaGalleryItem]:
|
||||
return [cls._from_data(item, state) for item in items]
|
||||
|
||||
def to_dict(self) -> MediaGalleryItemPayload:
|
||||
payload: MediaGalleryItemPayload = {
|
||||
'media': self.media.to_dict(), # type: ignore
|
||||
'spoiler': self.spoiler,
|
||||
}
|
||||
|
||||
if self.description:
|
||||
payload['description'] = self.description
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
class MediaGalleryComponent(Component):
|
||||
"""Represents a Media Gallery component from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type for creating a media gallery is
|
||||
:class:`discord.ui.MediaGallery` not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
items: List[:class:`MediaGalleryItem`]
|
||||
The items this gallery has.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
"""
|
||||
|
||||
__slots__ = ('items', 'id')
|
||||
|
||||
__repr_info__ = __slots__
|
||||
|
||||
def __init__(self, data: MediaGalleryComponentPayload, state: Optional[ConnectionState]) -> None:
|
||||
self.items: List[MediaGalleryItem] = MediaGalleryItem._from_gallery(data['items'], state)
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.media_gallery]:
|
||||
return ComponentType.media_gallery
|
||||
|
||||
def to_dict(self) -> MediaGalleryComponentPayload:
|
||||
payload: MediaGalleryComponentPayload = {
|
||||
'type': self.type.value,
|
||||
'items': [item.to_dict() for item in self.items],
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
return payload
|
||||
|
||||
|
||||
class FileComponent(Component):
|
||||
"""Represents a File component from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type for create a file component is
|
||||
:class:`discord.ui.File` not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
media: :class:`UnfurledMediaItem`
|
||||
The unfurled attachment contents of the file.
|
||||
spoiler: :class:`bool`
|
||||
Whether this file is flagged as a spoiler.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
name: Optional[:class:`str`]
|
||||
The displayed file name, only available when received from the API.
|
||||
size: Optional[:class:`int`]
|
||||
The file size in MiB, only available when received from the API.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'media',
|
||||
'spoiler',
|
||||
'id',
|
||||
'name',
|
||||
'size',
|
||||
)
|
||||
|
||||
__repr_info__ = __slots__
|
||||
|
||||
def __init__(self, data: FileComponentPayload, state: Optional[ConnectionState]) -> None:
|
||||
self.media: UnfurledMediaItem = UnfurledMediaItem._from_data(data['file'], state)
|
||||
self.spoiler: bool = data.get('spoiler', False)
|
||||
self.id: Optional[int] = data.get('id')
|
||||
self.name: Optional[str] = data.get('name')
|
||||
self.size: Optional[int] = data.get('size')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.file]:
|
||||
return ComponentType.file
|
||||
|
||||
def to_dict(self) -> FileComponentPayload:
|
||||
payload: FileComponentPayload = {
|
||||
'type': self.type.value,
|
||||
'file': self.media.to_dict(), # type: ignore
|
||||
'spoiler': self.spoiler,
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
return payload
|
||||
|
||||
|
||||
class SeparatorComponent(Component):
|
||||
"""Represents a Separator from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type for creating a separator is
|
||||
:class:`discord.ui.Separator` not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
spacing: :class:`SeparatorSpacing`
|
||||
The spacing size of the separator.
|
||||
visible: :class:`bool`
|
||||
Whether this separator is visible and shows a divider.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'spacing',
|
||||
'visible',
|
||||
'id',
|
||||
)
|
||||
|
||||
__repr_info__ = __slots__
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
data: SeparatorComponentPayload,
|
||||
) -> None:
|
||||
self.spacing: SeparatorSpacing = try_enum(SeparatorSpacing, data.get('spacing', 1))
|
||||
self.visible: bool = data.get('divider', True)
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.separator]:
|
||||
return ComponentType.separator
|
||||
|
||||
def to_dict(self) -> SeparatorComponentPayload:
|
||||
payload: SeparatorComponentPayload = {
|
||||
'type': self.type.value,
|
||||
'divider': self.visible,
|
||||
'spacing': self.spacing.value,
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
return payload
|
||||
|
||||
|
||||
class Container(Component):
|
||||
"""Represents a Container from the Discord Bot UI Kit.
|
||||
|
||||
This inherits from :class:`Component`.
|
||||
|
||||
.. note::
|
||||
|
||||
The user constructible and usable type for creating a container is
|
||||
:class:`discord.ui.Container` not this one.
|
||||
|
||||
.. versionadded:: 2.6
|
||||
|
||||
Attributes
|
||||
----------
|
||||
children: :class:`Component`
|
||||
This container's children.
|
||||
spoiler: :class:`bool`
|
||||
Whether this container is flagged as a spoiler.
|
||||
id: Optional[:class:`int`]
|
||||
The ID of this component.
|
||||
"""
|
||||
|
||||
__slots__ = (
|
||||
'children',
|
||||
'id',
|
||||
'spoiler',
|
||||
'_colour',
|
||||
)
|
||||
|
||||
__repr_info__ = (
|
||||
'children',
|
||||
'id',
|
||||
'spoiler',
|
||||
'accent_colour',
|
||||
)
|
||||
|
||||
def __init__(self, data: ContainerComponentPayload, state: Optional[ConnectionState]) -> None:
|
||||
self.children: List[Component] = []
|
||||
self.id: Optional[int] = data.get('id')
|
||||
|
||||
for child in data['components']:
|
||||
comp = _component_factory(child, state)
|
||||
|
||||
if comp:
|
||||
self.children.append(comp)
|
||||
|
||||
self.spoiler: bool = data.get('spoiler', False)
|
||||
|
||||
colour = data.get('accent_color')
|
||||
self._colour: Optional[Colour] = None
|
||||
if colour is not None:
|
||||
self._colour = Colour(colour)
|
||||
|
||||
@property
|
||||
def accent_colour(self) -> Optional[Colour]:
|
||||
"""Optional[:class:`Colour`]: The container's accent colour."""
|
||||
return self._colour
|
||||
|
||||
accent_color = accent_colour
|
||||
|
||||
@property
|
||||
def type(self) -> Literal[ComponentType.container]:
|
||||
return ComponentType.container
|
||||
|
||||
def to_dict(self) -> ContainerComponentPayload:
|
||||
payload: ContainerComponentPayload = {
|
||||
'type': self.type.value,
|
||||
'spoiler': self.spoiler,
|
||||
'components': [c.to_dict() for c in self.children], # pyright: ignore[reportAssignmentType]
|
||||
}
|
||||
if self.id is not None:
|
||||
payload['id'] = self.id
|
||||
if self._colour:
|
||||
payload['accent_color'] = self._colour.value
|
||||
return payload
|
||||
|
||||
|
||||
def _component_factory(data: ComponentPayload, state: Optional[ConnectionState] = None) -> Optional[Component]:
|
||||
if data['type'] == 1:
|
||||
return ActionRow(data)
|
||||
elif data['type'] == 2:
|
||||
@ -663,4 +1317,18 @@ def _component_factory(data: ComponentPayload) -> Optional[Union[ActionRow, Acti
|
||||
elif data['type'] == 4:
|
||||
return TextInput(data)
|
||||
elif data['type'] in (3, 5, 6, 7, 8):
|
||||
return SelectMenu(data)
|
||||
return SelectMenu(data) # type: ignore
|
||||
elif data['type'] == 9:
|
||||
return SectionComponent(data, state)
|
||||
elif data['type'] == 10:
|
||||
return TextDisplay(data)
|
||||
elif data['type'] == 11:
|
||||
return ThumbnailComponent(data, state)
|
||||
elif data['type'] == 12:
|
||||
return MediaGalleryComponent(data, state)
|
||||
elif data['type'] == 13:
|
||||
return FileComponent(data, state)
|
||||
elif data['type'] == 14:
|
||||
return SeparatorComponent(data)
|
||||
elif data['type'] == 17:
|
||||
return Container(data, state)
|
||||
|
Reference in New Issue
Block a user