896 lines
27 KiB
Python
896 lines
27 KiB
Python
# SPDX-License-Identifier: MIT
|
|
|
|
from __future__ import annotations
|
|
|
|
import datetime
|
|
from typing import (
|
|
TYPE_CHECKING,
|
|
Any,
|
|
ClassVar,
|
|
Dict,
|
|
List,
|
|
Literal,
|
|
Mapping,
|
|
Optional,
|
|
Protocol,
|
|
Sized,
|
|
Union,
|
|
cast,
|
|
overload,
|
|
)
|
|
|
|
from . import utils
|
|
from .colour import Colour
|
|
from .file import File
|
|
from .utils import MISSING, classproperty, warn_deprecated
|
|
|
|
__all__ = ("Embed",)
|
|
|
|
|
|
# backwards compatibility, hidden from type-checkers to have them show errors when accessed
|
|
if not TYPE_CHECKING:
|
|
|
|
def __getattr__(name: str) -> None:
|
|
if name == "EmptyEmbed":
|
|
warn_deprecated(
|
|
"`EmptyEmbed` is deprecated and will be removed in a future version. Use `None` instead.",
|
|
stacklevel=2,
|
|
)
|
|
return None
|
|
raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
|
|
|
|
|
|
class EmbedProxy:
|
|
def __init__(self, layer: Optional[Mapping[str, Any]]) -> None:
|
|
if layer is not None:
|
|
self.__dict__.update(layer)
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.__dict__)
|
|
|
|
def __repr__(self) -> str:
|
|
inner = ", ".join((f"{k}={v!r}" for k, v in self.__dict__.items() if not k.startswith("_")))
|
|
return f"EmbedProxy({inner})"
|
|
|
|
def __getattr__(self, attr: str) -> None:
|
|
return None
|
|
|
|
def __eq__(self, other: Any) -> bool:
|
|
return isinstance(other, EmbedProxy) and self.__dict__ == other.__dict__
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
from typing_extensions import Self
|
|
|
|
from disnake.types.embed import (
|
|
Embed as EmbedData,
|
|
EmbedAuthor as EmbedAuthorPayload,
|
|
EmbedField as EmbedFieldPayload,
|
|
EmbedFooter as EmbedFooterPayload,
|
|
EmbedImage as EmbedImagePayload,
|
|
EmbedProvider as EmbedProviderPayload,
|
|
EmbedThumbnail as EmbedThumbnailPayload,
|
|
EmbedType,
|
|
EmbedVideo as EmbedVideoPayload,
|
|
)
|
|
|
|
class _EmbedFooterProxy(Sized, Protocol):
|
|
text: Optional[str]
|
|
icon_url: Optional[str]
|
|
proxy_icon_url: Optional[str]
|
|
|
|
class _EmbedFieldProxy(Sized, Protocol):
|
|
name: Optional[str]
|
|
value: Optional[str]
|
|
inline: Optional[bool]
|
|
|
|
class _EmbedMediaProxy(Sized, Protocol):
|
|
url: Optional[str]
|
|
proxy_url: Optional[str]
|
|
height: Optional[int]
|
|
width: Optional[int]
|
|
|
|
class _EmbedVideoProxy(Sized, Protocol):
|
|
url: Optional[str]
|
|
proxy_url: Optional[str]
|
|
height: Optional[int]
|
|
width: Optional[int]
|
|
|
|
class _EmbedProviderProxy(Sized, Protocol):
|
|
name: Optional[str]
|
|
url: Optional[str]
|
|
|
|
class _EmbedAuthorProxy(Sized, Protocol):
|
|
name: Optional[str]
|
|
url: Optional[str]
|
|
icon_url: Optional[str]
|
|
proxy_icon_url: Optional[str]
|
|
|
|
_FileKey = Literal["image", "thumbnail"]
|
|
|
|
|
|
class Embed:
|
|
"""Represents a Discord embed.
|
|
|
|
.. container:: operations
|
|
|
|
.. describe:: x == y
|
|
|
|
Checks if two embeds are equal.
|
|
|
|
.. versionadded:: 2.6
|
|
|
|
.. describe:: x != y
|
|
|
|
Checks if two embeds are not equal.
|
|
|
|
.. versionadded:: 2.6
|
|
|
|
.. describe:: len(x)
|
|
|
|
Returns the total size of the embed.
|
|
Useful for checking if it's within the 6000 character limit.
|
|
Check if all aspects of the embed are within the limits with :func:`Embed.check_limits`.
|
|
|
|
.. describe:: bool(b)
|
|
|
|
Returns whether the embed has any data set.
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
Certain properties return an ``EmbedProxy``, a type
|
|
that acts similar to a regular :class:`dict` except using dotted access,
|
|
e.g. ``embed.author.icon_url``.
|
|
|
|
For ease of use, all parameters that expect a :class:`str` are implicitly
|
|
cast to :class:`str` for you.
|
|
|
|
Attributes
|
|
----------
|
|
title: Optional[:class:`str`]
|
|
The title of the embed.
|
|
type: Optional[:class:`str`]
|
|
The type of embed. Usually "rich".
|
|
Possible strings for embed types can be found on Discord's
|
|
:ddocs:`api-docs <resources/channel#embed-object-embed-types>`.
|
|
description: Optional[:class:`str`]
|
|
The description of the embed.
|
|
url: Optional[:class:`str`]
|
|
The URL of the embed.
|
|
timestamp: Optional[:class:`datetime.datetime`]
|
|
The timestamp of the embed content. This is an aware datetime.
|
|
If a naive datetime is passed, it is converted to an aware
|
|
datetime with the local timezone.
|
|
colour: Optional[:class:`Colour`]
|
|
The colour code of the embed. Aliased to ``color`` as well.
|
|
In addition to :class:`Colour`, :class:`int` can also be assigned to it,
|
|
in which case the value will be converted to a :class:`Colour` object.
|
|
"""
|
|
|
|
__slots__ = (
|
|
"title",
|
|
"url",
|
|
"type",
|
|
"_timestamp",
|
|
"_colour",
|
|
"_footer",
|
|
"_image",
|
|
"_thumbnail",
|
|
"_video",
|
|
"_provider",
|
|
"_author",
|
|
"_fields",
|
|
"description",
|
|
"_files",
|
|
)
|
|
|
|
_default_colour: ClassVar[Optional[Colour]] = None
|
|
_colour: Optional[Colour]
|
|
|
|
def __init__(
|
|
self,
|
|
*,
|
|
title: Optional[Any] = None,
|
|
type: Optional[EmbedType] = "rich",
|
|
description: Optional[Any] = None,
|
|
url: Optional[Any] = None,
|
|
timestamp: Optional[datetime.datetime] = None,
|
|
colour: Optional[Union[int, Colour]] = MISSING,
|
|
color: Optional[Union[int, Colour]] = MISSING,
|
|
) -> None:
|
|
self.title: Optional[str] = str(title) if title is not None else None
|
|
self.type: Optional[EmbedType] = type
|
|
self.description: Optional[str] = str(description) if description is not None else None
|
|
self.url: Optional[str] = str(url) if url is not None else None
|
|
|
|
self.timestamp = timestamp
|
|
|
|
# possible values:
|
|
# - MISSING: embed color will be _default_color
|
|
# - None: embed color will not be set
|
|
# - Color: embed color will be set to specified color
|
|
if colour is not MISSING:
|
|
color = colour
|
|
self.colour = color
|
|
|
|
self._thumbnail: Optional[EmbedThumbnailPayload] = None
|
|
self._video: Optional[EmbedVideoPayload] = None
|
|
self._provider: Optional[EmbedProviderPayload] = None
|
|
self._author: Optional[EmbedAuthorPayload] = None
|
|
self._image: Optional[EmbedImagePayload] = None
|
|
self._footer: Optional[EmbedFooterPayload] = None
|
|
self._fields: Optional[List[EmbedFieldPayload]] = None
|
|
|
|
self._files: Dict[_FileKey, File] = {}
|
|
|
|
# see `EmptyEmbed` above
|
|
if not TYPE_CHECKING:
|
|
|
|
@classproperty
|
|
def Empty(self) -> None:
|
|
warn_deprecated(
|
|
"`Embed.Empty` is deprecated and will be removed in a future version. Use `None` instead.",
|
|
stacklevel=3,
|
|
)
|
|
return None
|
|
|
|
@classmethod
|
|
def from_dict(cls, data: EmbedData) -> Self:
|
|
"""Converts a :class:`dict` to a :class:`Embed` 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 <resources/channel#embed-object>`.
|
|
|
|
Parameters
|
|
----------
|
|
data: :class:`dict`
|
|
The dictionary to convert into an embed.
|
|
"""
|
|
# we are bypassing __init__ here since it doesn't apply here
|
|
self = cls.__new__(cls)
|
|
|
|
# fill in the basic fields
|
|
|
|
self.title = str(title) if (title := data.get("title")) is not None else None
|
|
self.type = data.get("type")
|
|
self.description = (
|
|
str(description) if (description := data.get("description")) is not None else None
|
|
)
|
|
self.url = str(url) if (url := data.get("url")) is not None else None
|
|
|
|
self._files = {}
|
|
|
|
# try to fill in the more rich fields
|
|
|
|
self.colour = data.get("color")
|
|
self.timestamp = utils.parse_time(data.get("timestamp"))
|
|
|
|
self._thumbnail = data.get("thumbnail")
|
|
self._video = data.get("video")
|
|
self._provider = data.get("provider")
|
|
self._author = data.get("author")
|
|
self._image = data.get("image")
|
|
self._footer = data.get("footer")
|
|
self._fields = data.get("fields")
|
|
|
|
return self
|
|
|
|
def copy(self) -> Self:
|
|
"""Returns a shallow copy of the embed."""
|
|
embed = type(self).from_dict(self.to_dict())
|
|
|
|
# assign manually to keep behavior of default colors
|
|
embed._colour = self._colour
|
|
|
|
# copy files and fields collections
|
|
embed._files = self._files.copy()
|
|
if self._fields is not None:
|
|
embed._fields = self._fields.copy()
|
|
|
|
return embed
|
|
|
|
def __len__(self) -> int:
|
|
total = len((self.title or "").strip()) + len((self.description or "").strip())
|
|
if self._fields:
|
|
for field in self._fields:
|
|
total += len(field["name"].strip()) + len(field["value"].strip())
|
|
|
|
if self._footer and (footer_text := self._footer.get("text")):
|
|
total += len(footer_text.strip())
|
|
|
|
if self._author and (author_name := self._author.get("name")):
|
|
total += len(author_name.strip())
|
|
|
|
return total
|
|
|
|
def __bool__(self) -> bool:
|
|
return any(
|
|
(
|
|
self.title,
|
|
self.url,
|
|
self.description,
|
|
self._colour,
|
|
self._fields,
|
|
self._timestamp,
|
|
self._author,
|
|
self._thumbnail,
|
|
self._footer,
|
|
self._image,
|
|
self._provider,
|
|
self._video,
|
|
)
|
|
)
|
|
|
|
def __eq__(self, other: Any) -> bool:
|
|
if not isinstance(other, Embed):
|
|
return False
|
|
for slot in self.__slots__:
|
|
if slot == "_colour":
|
|
slot = "color"
|
|
if (getattr(self, slot) or None) != (getattr(other, slot) or None):
|
|
return False
|
|
return True
|
|
|
|
@property
|
|
def colour(self) -> Optional[Colour]:
|
|
col = self._colour
|
|
return col if col is not MISSING else type(self)._default_colour
|
|
|
|
@colour.setter
|
|
def colour(self, value: Optional[Union[int, Colour]]) -> None:
|
|
if isinstance(value, int):
|
|
self._colour = Colour(value=value)
|
|
elif value is MISSING or value is None or isinstance(value, Colour):
|
|
self._colour = value
|
|
else:
|
|
raise TypeError(
|
|
f"Expected disnake.Colour, int, or None but received {type(value).__name__} instead."
|
|
)
|
|
|
|
@colour.deleter
|
|
def colour(self) -> None:
|
|
self._colour = MISSING
|
|
|
|
color = colour
|
|
|
|
@property
|
|
def timestamp(self) -> Optional[datetime.datetime]:
|
|
return self._timestamp
|
|
|
|
@timestamp.setter
|
|
def timestamp(self, value: Optional[datetime.datetime]) -> None:
|
|
if isinstance(value, datetime.datetime):
|
|
if value.tzinfo is None:
|
|
value = value.astimezone()
|
|
self._timestamp = value
|
|
elif value is None:
|
|
self._timestamp = value
|
|
else:
|
|
raise TypeError(
|
|
f"Expected datetime.datetime or None received {type(value).__name__} instead"
|
|
)
|
|
|
|
@property
|
|
def footer(self) -> _EmbedFooterProxy:
|
|
"""Returns an ``EmbedProxy`` denoting the footer contents.
|
|
|
|
Possible attributes you can access are:
|
|
|
|
- ``text``
|
|
- ``icon_url``
|
|
- ``proxy_icon_url``
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("_EmbedFooterProxy", EmbedProxy(self._footer))
|
|
|
|
def set_footer(self, *, text: Any, icon_url: Optional[Any] = None) -> Self:
|
|
"""Sets the footer for the embed content.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
Parameters
|
|
----------
|
|
text: :class:`str`
|
|
The footer text.
|
|
|
|
.. versionchanged:: 2.6
|
|
No longer optional, must be set to a valid string.
|
|
|
|
icon_url: Optional[:class:`str`]
|
|
The URL of the footer icon. Only HTTP(S) is supported.
|
|
"""
|
|
self._footer = {
|
|
"text": str(text),
|
|
}
|
|
|
|
if icon_url is not None:
|
|
self._footer["icon_url"] = str(icon_url)
|
|
|
|
return self
|
|
|
|
def remove_footer(self) -> Self:
|
|
"""Clears embed's footer information.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
.. versionadded:: 2.0
|
|
"""
|
|
self._footer = None
|
|
return self
|
|
|
|
@property
|
|
def image(self) -> _EmbedMediaProxy:
|
|
"""Returns an ``EmbedProxy`` denoting the image contents.
|
|
|
|
Possible attributes you can access are:
|
|
|
|
- ``url``
|
|
- ``proxy_url``
|
|
- ``width``
|
|
- ``height``
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("_EmbedMediaProxy", EmbedProxy(self._image))
|
|
|
|
@overload
|
|
def set_image(self, url: Optional[Any]) -> Self:
|
|
...
|
|
|
|
@overload
|
|
def set_image(self, *, file: File) -> Self:
|
|
...
|
|
|
|
def set_image(self, url: Optional[Any] = MISSING, *, file: File = MISSING) -> Self:
|
|
"""Sets the image for the embed content.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
Exactly one of ``url`` or ``file`` must be passed.
|
|
|
|
.. warning::
|
|
Passing a :class:`disnake.File` object will make the embed not
|
|
reusable.
|
|
|
|
.. versionchanged:: 1.4
|
|
Passing ``None`` removes the image.
|
|
|
|
Parameters
|
|
----------
|
|
url: Optional[:class:`str`]
|
|
The source URL for the image. Only HTTP(S) is supported.
|
|
file: :class:`File`
|
|
The file to use as the image.
|
|
|
|
.. versionadded:: 2.2
|
|
"""
|
|
result = self._handle_resource(url, file, key="image")
|
|
self._image = {"url": result} if result is not None else None
|
|
return self
|
|
|
|
@property
|
|
def thumbnail(self) -> _EmbedMediaProxy:
|
|
"""Returns an ``EmbedProxy`` denoting the thumbnail contents.
|
|
|
|
Possible attributes you can access are:
|
|
|
|
- ``url``
|
|
- ``proxy_url``
|
|
- ``width``
|
|
- ``height``
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("_EmbedMediaProxy", EmbedProxy(self._thumbnail))
|
|
|
|
@overload
|
|
def set_thumbnail(self, url: Optional[Any]) -> Self:
|
|
...
|
|
|
|
@overload
|
|
def set_thumbnail(self, *, file: File) -> Self:
|
|
...
|
|
|
|
def set_thumbnail(self, url: Optional[Any] = MISSING, *, file: File = MISSING) -> Self:
|
|
"""Sets the thumbnail for the embed content.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
Exactly one of ``url`` or ``file`` must be passed.
|
|
|
|
.. warning::
|
|
Passing a :class:`disnake.File` object will make the embed not
|
|
reusable.
|
|
|
|
.. versionchanged:: 1.4
|
|
Passing ``None`` removes the thumbnail.
|
|
|
|
Parameters
|
|
----------
|
|
url: Optional[:class:`str`]
|
|
The source URL for the thumbnail. Only HTTP(S) is supported.
|
|
file: :class:`File`
|
|
The file to use as the image.
|
|
|
|
.. versionadded:: 2.2
|
|
"""
|
|
result = self._handle_resource(url, file, key="thumbnail")
|
|
self._thumbnail = {"url": result} if result is not None else None
|
|
return self
|
|
|
|
@property
|
|
def video(self) -> _EmbedVideoProxy:
|
|
"""Returns an ``EmbedProxy`` denoting the video contents.
|
|
|
|
Possible attributes include:
|
|
|
|
- ``url`` for the video URL.
|
|
- ``proxy_url`` for the proxied video URL.
|
|
- ``height`` for the video height.
|
|
- ``width`` for the video width.
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("_EmbedVideoProxy", EmbedProxy(self._video))
|
|
|
|
@property
|
|
def provider(self) -> _EmbedProviderProxy:
|
|
"""Returns an ``EmbedProxy`` denoting the provider contents.
|
|
|
|
The only attributes that might be accessed are ``name`` and ``url``.
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("_EmbedProviderProxy", EmbedProxy(self._provider))
|
|
|
|
@property
|
|
def author(self) -> _EmbedAuthorProxy:
|
|
"""Returns an ``EmbedProxy`` denoting the author contents.
|
|
|
|
See :meth:`set_author` for possible values you can access.
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("_EmbedAuthorProxy", EmbedProxy(self._author))
|
|
|
|
def set_author(
|
|
self,
|
|
*,
|
|
name: Any,
|
|
url: Optional[Any] = None,
|
|
icon_url: Optional[Any] = None,
|
|
) -> Self:
|
|
"""Sets the author for the embed content.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
Parameters
|
|
----------
|
|
name: :class:`str`
|
|
The name of the author.
|
|
url: Optional[:class:`str`]
|
|
The URL for the author.
|
|
icon_url: Optional[:class:`str`]
|
|
The URL of the author icon. Only HTTP(S) is supported.
|
|
"""
|
|
self._author = {
|
|
"name": str(name),
|
|
}
|
|
|
|
if url is not None:
|
|
self._author["url"] = str(url)
|
|
|
|
if icon_url is not None:
|
|
self._author["icon_url"] = str(icon_url)
|
|
|
|
return self
|
|
|
|
def remove_author(self) -> Self:
|
|
"""Clears embed's author information.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
.. versionadded:: 1.4
|
|
"""
|
|
self._author = None
|
|
return self
|
|
|
|
@property
|
|
def fields(self) -> List[_EmbedFieldProxy]:
|
|
"""List[``EmbedProxy``]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
|
|
|
|
See :meth:`add_field` for possible values you can access.
|
|
|
|
If an attribute is not set, it will be ``None``.
|
|
"""
|
|
return cast("List[_EmbedFieldProxy]", [EmbedProxy(d) for d in (self._fields or [])])
|
|
|
|
def add_field(self, name: Any, value: Any, *, inline: bool = True) -> Self:
|
|
"""Adds a field to the embed object.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
Parameters
|
|
----------
|
|
name: :class:`str`
|
|
The name of the field.
|
|
value: :class:`str`
|
|
The value of the field.
|
|
inline: :class:`bool`
|
|
Whether the field should be displayed inline.
|
|
Defaults to ``True``.
|
|
"""
|
|
field: EmbedFieldPayload = {
|
|
"inline": inline,
|
|
"name": str(name),
|
|
"value": str(value),
|
|
}
|
|
|
|
if self._fields is not None:
|
|
self._fields.append(field)
|
|
else:
|
|
self._fields = [field]
|
|
|
|
return self
|
|
|
|
def insert_field_at(self, index: int, name: Any, value: Any, *, inline: bool = True) -> Self:
|
|
"""Inserts a field before a specified index to the embed.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
.. versionadded:: 1.2
|
|
|
|
Parameters
|
|
----------
|
|
index: :class:`int`
|
|
The index of where to insert the field.
|
|
name: :class:`str`
|
|
The name of the field.
|
|
value: :class:`str`
|
|
The value of the field.
|
|
inline: :class:`bool`
|
|
Whether the field should be displayed inline.
|
|
Defaults to ``True``.
|
|
"""
|
|
field: EmbedFieldPayload = {
|
|
"inline": inline,
|
|
"name": str(name),
|
|
"value": str(value),
|
|
}
|
|
|
|
if self._fields is not None:
|
|
self._fields.insert(index, field)
|
|
else:
|
|
self._fields = [field]
|
|
|
|
return self
|
|
|
|
def clear_fields(self) -> None:
|
|
"""Removes all fields from this embed."""
|
|
self._fields = None
|
|
|
|
def remove_field(self, index: int) -> None:
|
|
"""Removes a field at a specified index.
|
|
|
|
If the index is invalid or out of bounds then the error is
|
|
silently swallowed.
|
|
|
|
.. note::
|
|
|
|
When deleting a field by index, the index of the other fields
|
|
shift to fill the gap just like a regular list.
|
|
|
|
Parameters
|
|
----------
|
|
index: :class:`int`
|
|
The index of the field to remove.
|
|
"""
|
|
if self._fields is not None:
|
|
try:
|
|
del self._fields[index]
|
|
except IndexError:
|
|
pass
|
|
|
|
def set_field_at(self, index: int, name: Any, value: Any, *, inline: bool = True) -> Self:
|
|
"""Modifies a field to the embed object.
|
|
|
|
The index must point to a valid pre-existing field.
|
|
|
|
This function returns the class instance to allow for fluent-style
|
|
chaining.
|
|
|
|
Parameters
|
|
----------
|
|
index: :class:`int`
|
|
The index of the field to modify.
|
|
name: :class:`str`
|
|
The name of the field.
|
|
value: :class:`str`
|
|
The value of the field.
|
|
inline: :class:`bool`
|
|
Whether the field should be displayed inline.
|
|
Defaults to ``True``.
|
|
|
|
Raises
|
|
------
|
|
IndexError
|
|
An invalid index was provided.
|
|
"""
|
|
if not self._fields:
|
|
raise IndexError("field index out of range")
|
|
try:
|
|
self._fields[index]
|
|
except IndexError:
|
|
raise IndexError("field index out of range") from None
|
|
|
|
field: EmbedFieldPayload = {
|
|
"inline": inline,
|
|
"name": str(name),
|
|
"value": str(value),
|
|
}
|
|
self._fields[index] = field
|
|
return self
|
|
|
|
def to_dict(self) -> EmbedData:
|
|
"""Converts this embed object into a dict."""
|
|
# add in the raw data into the dict
|
|
result: EmbedData = {}
|
|
if self._footer is not None:
|
|
result["footer"] = self._footer
|
|
if self._image is not None:
|
|
result["image"] = self._image
|
|
if self._thumbnail is not None:
|
|
result["thumbnail"] = self._thumbnail
|
|
if self._video is not None:
|
|
result["video"] = self._video
|
|
if self._provider is not None:
|
|
result["provider"] = self._provider
|
|
if self._author is not None:
|
|
result["author"] = self._author
|
|
if self._fields is not None:
|
|
result["fields"] = self._fields
|
|
|
|
# deal with basic convenience wrappers
|
|
if self.colour:
|
|
result["color"] = self.colour.value
|
|
|
|
if self._timestamp:
|
|
result["timestamp"] = utils.isoformat_utc(self._timestamp)
|
|
|
|
# add in the non raw attribute ones
|
|
if self.type:
|
|
result["type"] = self.type
|
|
|
|
if self.description:
|
|
result["description"] = self.description
|
|
|
|
if self.url:
|
|
result["url"] = self.url
|
|
|
|
if self.title:
|
|
result["title"] = self.title
|
|
|
|
return result
|
|
|
|
@classmethod
|
|
def set_default_colour(cls, value: Optional[Union[int, Colour]]):
|
|
"""Set the default colour of all new embeds.
|
|
|
|
.. versionadded:: 2.4
|
|
|
|
Returns
|
|
-------
|
|
Optional[:class:`Colour`]
|
|
The colour that was set.
|
|
"""
|
|
if value is None or isinstance(value, Colour):
|
|
cls._default_colour = value
|
|
elif isinstance(value, int):
|
|
cls._default_colour = Colour(value=value)
|
|
else:
|
|
raise TypeError(
|
|
f"Expected disnake.Colour, int, or None but received {type(value).__name__} instead."
|
|
)
|
|
return cls._default_colour
|
|
|
|
set_default_color = set_default_colour
|
|
|
|
@classmethod
|
|
def get_default_colour(cls) -> Optional[Colour]:
|
|
"""Get the default colour of all new embeds.
|
|
|
|
.. versionadded:: 2.4
|
|
|
|
Returns
|
|
-------
|
|
Optional[:class:`Colour`]
|
|
The default colour.
|
|
|
|
"""
|
|
return cls._default_colour
|
|
|
|
get_default_color = get_default_colour
|
|
|
|
def _handle_resource(self, url: Optional[Any], file: File, *, key: _FileKey) -> Optional[str]:
|
|
if not (url is MISSING) ^ (file is MISSING):
|
|
raise TypeError("Exactly one of url or file must be provided")
|
|
|
|
if file:
|
|
if file.filename is None:
|
|
raise TypeError("File must have a filename")
|
|
self._files[key] = file
|
|
return f"attachment://{file.filename}"
|
|
else:
|
|
self._files.pop(key, None)
|
|
return str(url) if url is not None else None
|
|
|
|
def check_limits(self) -> None:
|
|
"""Checks if this embed fits within the limits dictated by Discord.
|
|
There is also a 6000 character limit across all embeds in a message.
|
|
|
|
Returns nothing on success, raises :exc:`ValueError` if an attribute exceeds the limits.
|
|
|
|
+--------------------------+------------------------------------+
|
|
| Field | Limit |
|
|
+--------------------------+------------------------------------+
|
|
| title | 256 characters |
|
|
+--------------------------+------------------------------------+
|
|
| description | 4096 characters |
|
|
+--------------------------+------------------------------------+
|
|
| fields | Up to 25 field objects |
|
|
+--------------------------+------------------------------------+
|
|
| field.name | 256 characters |
|
|
+--------------------------+------------------------------------+
|
|
| field.value | 1024 characters |
|
|
+--------------------------+------------------------------------+
|
|
| footer.text | 2048 characters |
|
|
+--------------------------+------------------------------------+
|
|
| author.name | 256 characters |
|
|
+--------------------------+------------------------------------+
|
|
|
|
.. versionadded:: 2.6
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
One or more of the embed attributes are too long.
|
|
"""
|
|
if self.title and len(self.title.strip()) > 256:
|
|
raise ValueError("Embed title cannot be longer than 256 characters")
|
|
|
|
if self.description and len(self.description.strip()) > 4096:
|
|
raise ValueError("Embed description cannot be longer than 4096 characters")
|
|
|
|
if self._footer and len(self._footer.get("text", "").strip()) > 2048:
|
|
raise ValueError("Embed footer text cannot be longer than 2048 characters")
|
|
|
|
if self._author and len(self._author.get("name", "").strip()) > 256:
|
|
raise ValueError("Embed author name cannot be longer than 256 characters")
|
|
|
|
if self._fields:
|
|
if len(self._fields) > 25:
|
|
raise ValueError("Embeds cannot have more than 25 fields")
|
|
|
|
for field_index, field in enumerate(self._fields):
|
|
if len(field["name"].strip()) > 256:
|
|
raise ValueError(
|
|
f"Embed field {field_index} name cannot be longer than 256 characters"
|
|
)
|
|
if len(field["value"].strip()) > 1024:
|
|
raise ValueError(
|
|
f"Embed field {field_index} value cannot be longer than 1024 characters"
|
|
)
|
|
|
|
if len(self) > 6000:
|
|
raise ValueError("Embed total size cannot be longer than 6000 characters")
|