Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add EmbedField object to allow for easier embed class instance creation #1181

Merged
merged 29 commits into from Apr 17, 2022
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cfb8083
add EmbedField object to allow for easier embed class instance creation
krittick Mar 18, 2022
4bcca9b
Merge branch 'master' into embed-fields
krittick Mar 20, 2022
772d0e6
add TypeError if `fields` setter is passed a list containing anything…
krittick Mar 20, 2022
53ba029
initial pass at removing proxy object reliance from embed fields
krittick Mar 20, 2022
a16c316
Merge branch 'Pycord-Development:master' into embed-fields
krittick Apr 1, 2022
c4ed8eb
Merge branch 'Pycord-Development:master' into embed-fields
krittick Apr 8, 2022
4b6c23a
Merge branch 'master' into embed-fields
krittick Apr 8, 2022
22e1702
Merge branch 'master' into embed-fields
krittick Apr 9, 2022
ab18423
add self._fields to Embed.__init__ (prevents AttributeError)
krittick Apr 9, 2022
a1d8b19
add EmbedField to __all__
krittick Apr 9, 2022
70a7942
fix init loop
krittick Apr 9, 2022
b508b46
fix init loop
krittick Apr 9, 2022
aadebba
fix to_dict for _fields attr
krittick Apr 9, 2022
b4081a6
remove now-unused _EmbedFieldProxy class
krittick Apr 9, 2022
571a650
add from_dict classmethod to EmbedField for better handling with Embe…
krittick Apr 9, 2022
50d3e35
update EmbedField.from_dict to more closely match the behavior of Emb…
krittick Apr 9, 2022
c1c0d18
add Embed.append_field option to allow directly adding EmbedField obj…
krittick Apr 9, 2022
068c86d
add EmbedField to docs
krittick Apr 9, 2022
dacdfe0
doc fix
krittick Apr 9, 2022
fb9a701
add docstring for EmbedField.to_dict
krittick Apr 9, 2022
0c097cf
fix for embeds with no initial fields
krittick Apr 12, 2022
870d82b
revert
krittick Apr 12, 2022
1e53637
fix for copying embeds with no fields
krittick Apr 12, 2022
408e7bd
Merge branch 'master' into embed-fields
krittick Apr 13, 2022
85bacab
Merge branch 'master' into embed-fields
krittick Apr 15, 2022
bcc106c
Merge branch 'master' into embed-fields
krittick Apr 16, 2022
1f4ec44
Merge branch 'master' into embed-fields
krittick Apr 17, 2022
6c113fd
add versionadded to docstrings
krittick Apr 17, 2022
0fda011
Merge branch 'master' into embed-fields
krittick Apr 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
169 changes: 124 additions & 45 deletions discord/embeds.py
Expand Up @@ -33,6 +33,7 @@
Final,
List,
Mapping,
Optional,
Protocol,
Type,
TypeVar,
Expand All @@ -42,7 +43,10 @@
from . import utils
from .colour import Colour

__all__ = ("Embed",)
__all__ = (
"Embed",
"EmbedField",
)


class _EmptyEmbed:
Expand Down Expand Up @@ -87,11 +91,6 @@ class _EmbedFooterProxy(Protocol):
text: MaybeEmpty[str]
icon_url: MaybeEmpty[str]

class _EmbedFieldProxy(Protocol):
name: MaybeEmpty[str]
value: MaybeEmpty[str]
inline: bool

class _EmbedMediaProxy(Protocol):
url: MaybeEmpty[str]
proxy_url: MaybeEmpty[str]
Expand All @@ -114,6 +113,57 @@ class _EmbedAuthorProxy(Protocol):
proxy_icon_url: MaybeEmpty[str]


class EmbedField:
"""Represents a field on the :class:`Embed` object.

krittick marked this conversation as resolved.
Show resolved Hide resolved
Attributes
----------
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.
"""

def __init__(self, name: str, value: str, inline: Optional[bool] = False):
self.name = name
self.value = value
self.inline = inline

@classmethod
def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E:
"""Converts a :class:`dict` to a :class:`EmbedField` provided it is in the
format that Discord expects it to be in.

You can find out about this format in the `official Discord documentation`__.

.. _DiscordDocsEF: https://discord.com/developers/docs/resources/channel#embed-object-embed-field-structure

__ DiscordDocsEF_

Parameters
-----------
data: :class:`dict`
The dictionary to convert into an EmbedField object.
"""
self: E = cls.__new__(cls)

self.name = data["name"]
self.value = data["value"]
self.inline = data.get("inline", False)

return self

def to_dict(self) -> Dict[str, Union[str, bool]]:
"""Converts this EmbedField object into a dict."""
return {
"name": self.name,
"value": self.value,
"inline": self.inline,
}


class Embed:
"""Represents a Discord embed.

Expand All @@ -137,7 +187,7 @@ class Embed:
:attr:`Embed.Empty`.

For ease of use, all parameters that expect a :class:`str` are implicitly
casted to :class:`str` for you.
cast to :class:`str` for you.

Attributes
-----------
Expand Down Expand Up @@ -195,6 +245,7 @@ def __init__(
url: MaybeEmpty[Any] = EmptyEmbed,
description: MaybeEmpty[Any] = EmptyEmbed,
timestamp: datetime.datetime = None,
fields: Optional[List[EmbedField]] = None,
):

self.colour = colour if colour is not EmptyEmbed else color
Expand All @@ -214,6 +265,7 @@ def __init__(

if timestamp:
self.timestamp = timestamp
self._fields: List[EmbedField] = fields or []

@classmethod
def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E:
Expand Down Expand Up @@ -271,12 +323,17 @@ def from_dict(cls: Type[E], data: Mapping[str, Any]) -> E:
"image",
"footer",
):
try:
value = data[attr]
except KeyError:
continue
if attr == "fields":
value = data.get(attr, [])
if value:
self._fields = [EmbedField.from_dict(d) for d in value]
else:
setattr(self, f"_{attr}", value)
try:
value = data[attr]
except KeyError:
continue
else:
setattr(self, f"_{attr}", value)

return self

Expand All @@ -287,7 +344,7 @@ def copy(self: E) -> E:
def __len__(self) -> int:
total = len(self.title) + len(self.description)
for field in getattr(self, "_fields", []):
total += len(field["name"]) + len(field["value"])
total += len(field.name) + len(field.value)

try:
footer_text = self._footer["text"]
Expand Down Expand Up @@ -606,16 +663,48 @@ def remove_author(self: E) -> E:
return self

@property
def fields(self) -> List[_EmbedFieldProxy]:
"""List[Union[``EmbedProxy``, :attr:`Empty`]]: Returns a :class:`list` of ``EmbedProxy`` denoting the field contents.
def fields(self) -> Optional[List[EmbedField]]:
"""Returns a :class:`list` of :class:`EmbedField` objects denoting the field contents.

See :meth:`add_field` for possible values you can access.

If the attribute has no value then :attr:`Empty` is returned.
If the attribute has no value then ``None`` is returned.
"""
if self._fields:
return self._fields
else:
return None
krittick marked this conversation as resolved.
Show resolved Hide resolved

@fields.setter
def fields(self, value: List[EmbedField]) -> None:
Dorukyum marked this conversation as resolved.
Show resolved Hide resolved
"""Sets the fields for the embed. This overwrites any existing fields.

Parameters
----------
value: List[:class:`EmbedField`]
The list of :class:`EmbedField` objects to include in the embed.
"""
if not all(isinstance(x, EmbedField) for x in value):
raise TypeError("Expected a list of EmbedField objects.")

self.clear_fields()
for field in value:
self.add_field(name=field.name, value=field.value, inline=field.inline)

def append_field(self, field: EmbedField) -> None:
"""Appends an :class:`EmbedField` object to the embed.

krittick marked this conversation as resolved.
Show resolved Hide resolved
Parameters
----------
field: :class:`EmbedField`
The field to add.
"""
return [EmbedProxy(d) for d in getattr(self, "_fields", [])] # type: ignore
if not isinstance(field, EmbedField):
raise TypeError("Expected an EmbedField object.")

def add_field(self: E, *, name: Any, value: Any, inline: bool = True) -> E:
self._fields.append(field)

def add_field(self: E, *, name: str, value: str, inline: bool = True) -> E:
"""Adds a field to the embed object.

This function returns the class instance to allow for fluent-style
Expand All @@ -630,17 +719,7 @@ def add_field(self: E, *, name: Any, value: Any, inline: bool = True) -> E:
inline: :class:`bool`
Whether the field should be displayed inline.
"""

field = {
"inline": inline,
"name": str(name),
"value": str(value),
}

try:
self._fields.append(field)
except AttributeError:
self._fields = [field]
self._fields.append(EmbedField(name=str(name), value=str(value), inline=inline))

return self

Expand All @@ -664,16 +743,9 @@ def insert_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool
Whether the field should be displayed inline.
"""

field = {
"inline": inline,
"name": str(name),
"value": str(value),
}
field = EmbedField(name=str(name), value=str(value), inline=inline)

try:
self._fields.insert(index, field)
except AttributeError:
self._fields = [field]
self._fields.insert(index, field)

return self

Expand Down Expand Up @@ -702,7 +774,7 @@ def remove_field(self, index: int) -> None:
"""
try:
del self._fields[index]
except (AttributeError, IndexError):
except IndexError:
pass

def set_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = True) -> E:
Expand Down Expand Up @@ -732,19 +804,26 @@ def set_field_at(self: E, index: int, *, name: Any, value: Any, inline: bool = T

try:
field = self._fields[index]
except (TypeError, IndexError, AttributeError):
except (TypeError, IndexError):
raise IndexError("field index out of range")

field["name"] = str(name)
field["value"] = str(value)
field["inline"] = inline
field.name = str(name)
field.value = str(value)
field.inline = inline
return self

def to_dict(self) -> EmbedData:
"""Converts this embed object into a dict."""

# add in the raw data into the dict
result = {key[1:]: getattr(self, key) for key in self.__slots__ if key[0] == "_" and hasattr(self, key)}
result = {
key[1:]: getattr(self, key)
for key in self.__slots__
if key != "_fields" and key[0] == "_" and hasattr(self, key)
}

# add in the fields
result["fields"] = [field.to_dict() for field in self._fields]

# deal with basic convenience wrappers

Expand All @@ -767,7 +846,7 @@ def to_dict(self) -> EmbedData:
else:
result["timestamp"] = timestamp.replace(tzinfo=datetime.timezone.utc).isoformat()

# add in the non raw attribute ones
# add in the non-raw attribute ones
if self.type:
result["type"] = self.type

Expand Down
8 changes: 8 additions & 0 deletions docs/api.rst
Expand Up @@ -4673,6 +4673,14 @@ Embed
.. autoclass:: Embed
:members:

EmbedField
~~~~~~~~~~

.. attributetable:: EmbedField

.. autoclass:: EmbedField
:members:

AllowedMentions
~~~~~~~~~~~~~~~~~

Expand Down