Make discord.Embed builder more strict and easier to use.
Allow for easier use when trying to "reuse" the same discord.Embed object by providing new methods such as Embed.clear_fields, Embed.set_field_at, and allowing you to set things to Embed.Empty to clear out an attribute. For ease of use, things are automatically casted to ``str`` to prevent the user from having HTTP 400 errors if they forgot to do so. The new embed builder also supports "fluent-style" interface to allow you to chain methods in a single line if necessary. Certain parameters were removed since they were ignored by Discord anyway such as `width` and `height` in Embed.set_image and Embed.set_thumbnail.
This commit is contained in:
parent
fb1f9ac659
commit
c4ee4c1db4
@ -60,6 +60,9 @@ class Embed:
|
|||||||
is invalid or empty, then a special sentinel value is returned,
|
is invalid or empty, then a special sentinel value is returned,
|
||||||
:attr:`Embed.Empty`.
|
:attr:`Embed.Empty`.
|
||||||
|
|
||||||
|
For ease of use, all parameters that expect a ``str`` are implicitly
|
||||||
|
casted to ``str`` for you.
|
||||||
|
|
||||||
Attributes
|
Attributes
|
||||||
-----------
|
-----------
|
||||||
title: str
|
title: str
|
||||||
@ -75,8 +78,8 @@ class Embed:
|
|||||||
colour: :class:`Colour` or int
|
colour: :class:`Colour` or int
|
||||||
The colour code of the embed. Aliased to ``color`` as well.
|
The colour code of the embed. Aliased to ``color`` as well.
|
||||||
Empty
|
Empty
|
||||||
A special sentinel value used by ``EmbedProxy`` to denote
|
A special sentinel value used by ``EmbedProxy`` and this class
|
||||||
that the value or attribute is empty.
|
to denote that the value or attribute is empty.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer',
|
__slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer',
|
||||||
@ -90,15 +93,13 @@ class Embed:
|
|||||||
try:
|
try:
|
||||||
colour = kwargs['colour']
|
colour = kwargs['colour']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
colour = kwargs.get('color')
|
colour = kwargs.get('color', EmptyEmbed)
|
||||||
|
|
||||||
if colour is not None:
|
self.colour = colour
|
||||||
self.colour = colour
|
self.title = kwargs.get('title', EmptyEmbed)
|
||||||
|
|
||||||
self.title = kwargs.get('title')
|
|
||||||
self.type = kwargs.get('type', 'rich')
|
self.type = kwargs.get('type', 'rich')
|
||||||
self.url = kwargs.get('url')
|
self.url = kwargs.get('url', EmptyEmbed)
|
||||||
self.description = kwargs.get('description')
|
self.description = kwargs.get('description', EmptyEmbed)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestamp = kwargs['timestamp']
|
timestamp = kwargs['timestamp']
|
||||||
@ -114,10 +115,10 @@ class Embed:
|
|||||||
|
|
||||||
# fill in the basic fields
|
# fill in the basic fields
|
||||||
|
|
||||||
self.title = data.get('title')
|
self.title = data.get('title', EmptyEmbed)
|
||||||
self.type = data.get('type')
|
self.type = data.get('type', EmptyEmbed)
|
||||||
self.description = data.get('description')
|
self.description = data.get('description', EmptyEmbed)
|
||||||
self.url = data.get('url')
|
self.url = data.get('url', EmptyEmbed)
|
||||||
|
|
||||||
# try to fill in the more rich fields
|
# try to fill in the more rich fields
|
||||||
|
|
||||||
@ -143,29 +144,29 @@ class Embed:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def colour(self):
|
def colour(self):
|
||||||
return getattr(self, '_colour', None)
|
return getattr(self, '_colour', EmptyEmbed)
|
||||||
|
|
||||||
@colour.setter
|
@colour.setter
|
||||||
def colour(self, value):
|
def colour(self, value):
|
||||||
if isinstance(value, Colour):
|
if isinstance(value, (Colour, _EmptyEmbed)):
|
||||||
self._colour = value
|
self._colour = value
|
||||||
elif isinstance(value, int):
|
elif isinstance(value, int):
|
||||||
self._colour = Colour(value=value)
|
self._colour = Colour(value=value)
|
||||||
else:
|
else:
|
||||||
raise TypeError('Expected discord.Colour or int, received %s instead.' % value.__class__.__name__)
|
raise TypeError('Expected discord.Colour, int, or Embed.Empty but received %s instead.' % value.__class__.__name__)
|
||||||
|
|
||||||
color = colour
|
color = colour
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def timestamp(self):
|
def timestamp(self):
|
||||||
return getattr(self, '_timestamp', None)
|
return getattr(self, '_timestamp', EmptyEmbed)
|
||||||
|
|
||||||
@timestamp.setter
|
@timestamp.setter
|
||||||
def timestamp(self, value):
|
def timestamp(self, value):
|
||||||
if isinstance(value, datetime.datetime):
|
if isinstance(value, (datetime.datetime, _EmptyEmbed)):
|
||||||
self._timestamp = value
|
self._timestamp = value
|
||||||
else:
|
else:
|
||||||
raise TypeError("Expected datetime.datetime received %s instead" % value.__class__.__name__)
|
raise TypeError("Expected datetime.datetime or Embed.Empty received %s instead" % value.__class__.__name__)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def footer(self):
|
def footer(self):
|
||||||
@ -173,13 +174,16 @@ class Embed:
|
|||||||
|
|
||||||
See :meth:`set_footer` for possible values you can access.
|
See :meth:`set_footer` for possible values you can access.
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return EmbedProxy(getattr(self, '_footer', {}))
|
return EmbedProxy(getattr(self, '_footer', {}))
|
||||||
|
|
||||||
def set_footer(self, *, text=None, icon_url=None):
|
def set_footer(self, *, text=EmptyEmbed, icon_url=EmptyEmbed):
|
||||||
"""Sets the footer for the embed content.
|
"""Sets the footer for the embed content.
|
||||||
|
|
||||||
|
This function returns the class instance to allow for fluent-style
|
||||||
|
chaining.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
text: str
|
text: str
|
||||||
@ -189,77 +193,79 @@ class Embed:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self._footer = {}
|
self._footer = {}
|
||||||
if text is not None:
|
if text is not EmptyEmbed:
|
||||||
self._footer['text'] = text
|
self._footer['text'] = str(text)
|
||||||
|
|
||||||
if icon_url is not None:
|
if icon_url is not EmptyEmbed:
|
||||||
self._footer['icon_url'] = icon_url
|
self._footer['icon_url'] = str(icon_url)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def image(self):
|
def image(self):
|
||||||
"""Returns a ``EmbedProxy`` denoting the image contents.
|
"""Returns a ``EmbedProxy`` denoting the image contents.
|
||||||
|
|
||||||
See :meth:`set_image` for possible values you can access.
|
Possible attributes you can access are:
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
- ``url``
|
||||||
|
- ``proxy_url``
|
||||||
|
- ``width``
|
||||||
|
- ``height``
|
||||||
|
|
||||||
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return EmbedProxy(getattr(self, '_image', {}))
|
return EmbedProxy(getattr(self, '_image', {}))
|
||||||
|
|
||||||
def set_image(self, *, url, height=None, width=None):
|
def set_image(self, *, url):
|
||||||
"""Sets the image for the embed content.
|
"""Sets the image for the embed content.
|
||||||
|
|
||||||
|
This function returns the class instance to allow for fluent-style
|
||||||
|
chaining.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
url: str
|
url: str
|
||||||
The source URL for the image. Only HTTP(S) is supported.
|
The source URL for the image. Only HTTP(S) is supported.
|
||||||
height: int
|
|
||||||
The height of the image.
|
|
||||||
width: int
|
|
||||||
The width of the image.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._image = {
|
self._image = {
|
||||||
'url': url
|
'url': str(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
if height is not None:
|
return self
|
||||||
self._image['height'] = height
|
|
||||||
|
|
||||||
if width is not None:
|
|
||||||
self._image['width'] = width
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def thumbnail(self):
|
def thumbnail(self):
|
||||||
"""Returns a ``EmbedProxy`` denoting the thumbnail contents.
|
"""Returns a ``EmbedProxy`` denoting the thumbnail contents.
|
||||||
|
|
||||||
See :meth:`set_thumbnail` for possible values you can access.
|
Possible attributes you can access are:
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
- ``url``
|
||||||
|
- ``proxy_url``
|
||||||
|
- ``width``
|
||||||
|
- ``height``
|
||||||
|
|
||||||
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return EmbedProxy(getattr(self, '_thumbnail', {}))
|
return EmbedProxy(getattr(self, '_thumbnail', {}))
|
||||||
|
|
||||||
def set_thumbnail(self, *, url, height=None, width=None):
|
def set_thumbnail(self, *, url):
|
||||||
"""Sets the thumbnail for the embed content.
|
"""Sets the thumbnail for the embed content.
|
||||||
|
|
||||||
|
This function returns the class instance to allow for fluent-style
|
||||||
|
chaining.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
url: str
|
url: str
|
||||||
The source URL for the thumbnail. Only HTTP(S) is supported.
|
The source URL for the thumbnail. Only HTTP(S) is supported.
|
||||||
height: int
|
|
||||||
The height of the thumbnail.
|
|
||||||
width: int
|
|
||||||
The width of the thumbnail.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self._thumbnail = {
|
self._thumbnail = {
|
||||||
'url': url
|
'url': str(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
if height is not None:
|
return self
|
||||||
self._thumbnail['height'] = height
|
|
||||||
|
|
||||||
if width is not None:
|
|
||||||
self._thumbnail['width'] = width
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def video(self):
|
def video(self):
|
||||||
@ -271,7 +277,7 @@ class Embed:
|
|||||||
- ``height`` for the video height.
|
- ``height`` for the video height.
|
||||||
- ``width`` for the video width.
|
- ``width`` for the video width.
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return EmbedProxy(getattr(self, '_video', {}))
|
return EmbedProxy(getattr(self, '_video', {}))
|
||||||
|
|
||||||
@ -281,7 +287,7 @@ class Embed:
|
|||||||
|
|
||||||
The only attributes that might be accessed are ``name`` and ``url``.
|
The only attributes that might be accessed are ``name`` and ``url``.
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return EmbedProxy(getattr(self, '_provider', {}))
|
return EmbedProxy(getattr(self, '_provider', {}))
|
||||||
|
|
||||||
@ -291,13 +297,16 @@ class Embed:
|
|||||||
|
|
||||||
See :meth:`set_author` for possible values you can access.
|
See :meth:`set_author` for possible values you can access.
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return EmbedProxy(getattr(self, '_author', {}))
|
return EmbedProxy(getattr(self, '_author', {}))
|
||||||
|
|
||||||
def set_author(self, *, name, url=None, icon_url=None):
|
def set_author(self, *, name, url=EmptyEmbed, icon_url=EmptyEmbed):
|
||||||
"""Sets the author for the embed content.
|
"""Sets the author for the embed content.
|
||||||
|
|
||||||
|
This function returns the class instance to allow for fluent-style
|
||||||
|
chaining.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: str
|
name: str
|
||||||
@ -309,15 +318,16 @@ class Embed:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self._author = {
|
self._author = {
|
||||||
'name': name
|
'name': str(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if url is not None:
|
if url is not EmptyEmbed:
|
||||||
self._author['url'] = url
|
self._author['url'] = str(url)
|
||||||
|
|
||||||
if icon_url is not None:
|
if icon_url is not EmptyEmbed:
|
||||||
self._author['icon_url'] = icon_url
|
self._author['icon_url'] = str(icon_url)
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def fields(self):
|
def fields(self):
|
||||||
@ -325,13 +335,16 @@ class Embed:
|
|||||||
|
|
||||||
See :meth:`add_field` for possible values you can access.
|
See :meth:`add_field` for possible values you can access.
|
||||||
|
|
||||||
If the attribute cannot be accessed then ``None`` is returned.
|
If the attribute has no value then :attr:`Empty` is returned.
|
||||||
"""
|
"""
|
||||||
return [EmbedProxy(d) for d in getattr(self, '_fields', [])]
|
return [EmbedProxy(d) for d in getattr(self, '_fields', [])]
|
||||||
|
|
||||||
def add_field(self, *, name=None, value=None, inline=True):
|
def add_field(self, *, name, value, inline=True):
|
||||||
"""Adds a field to the embed object.
|
"""Adds a field to the embed object.
|
||||||
|
|
||||||
|
This function returns the class instance to allow for fluent-style
|
||||||
|
chaining.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
-----------
|
-----------
|
||||||
name: str
|
name: str
|
||||||
@ -343,19 +356,81 @@ class Embed:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
field = {
|
field = {
|
||||||
'inline': inline
|
'inline': inline,
|
||||||
|
'name': str(name),
|
||||||
|
'value': str(value)
|
||||||
}
|
}
|
||||||
if name is not None:
|
|
||||||
field['name'] = name
|
|
||||||
|
|
||||||
if value is not None:
|
|
||||||
field['value'] = value
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._fields.append(field)
|
self._fields.append(field)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._fields = [field]
|
self._fields = [field]
|
||||||
|
|
||||||
|
return self
|
||||||
|
|
||||||
|
def clear_fields(self):
|
||||||
|
"""Removes all fields from this embed."""
|
||||||
|
try:
|
||||||
|
self._fields.clear()
|
||||||
|
except AttributeError:
|
||||||
|
self._fields = []
|
||||||
|
|
||||||
|
def remove_field(self, index):
|
||||||
|
"""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: int
|
||||||
|
The index of the field to remove.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
del self._fields[index]
|
||||||
|
except (AttributeError, IndexError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_field_at(self, index, *, name, value, inline=True):
|
||||||
|
"""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: int
|
||||||
|
The index of the field to modify.
|
||||||
|
name: str
|
||||||
|
The name of the field.
|
||||||
|
value: str
|
||||||
|
The value of the field.
|
||||||
|
inline: bool
|
||||||
|
Whether the field should be displayed inline.
|
||||||
|
|
||||||
|
Raises
|
||||||
|
-------
|
||||||
|
IndexError
|
||||||
|
An invalid index was provided.
|
||||||
|
"""
|
||||||
|
|
||||||
|
try:
|
||||||
|
field = self._fields[index]
|
||||||
|
except (TypeError, IndexError, AttributeError):
|
||||||
|
raise IndexError('field index out of range')
|
||||||
|
|
||||||
|
field['name'] = str(name)
|
||||||
|
field['value'] = str(value)
|
||||||
|
field['inline'] = inline
|
||||||
|
return self
|
||||||
|
|
||||||
def to_dict(self):
|
def to_dict(self):
|
||||||
"""Converts this embed object into a dict."""
|
"""Converts this embed object into a dict."""
|
||||||
|
|
||||||
@ -373,14 +448,16 @@ class Embed:
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
result['color'] = colour.value
|
if colour:
|
||||||
|
result['color'] = colour.value
|
||||||
|
|
||||||
try:
|
try:
|
||||||
timestamp = result.pop('timestamp')
|
timestamp = result.pop('timestamp')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
result['timestamp'] = timestamp.isoformat()
|
if timestamp:
|
||||||
|
result['timestamp'] = timestamp.isoformat()
|
||||||
|
|
||||||
# add in the non raw attribute ones
|
# add in the non raw attribute ones
|
||||||
if self.type:
|
if self.type:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user