Browse Source

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.
pull/408/head
Rapptz 9 years ago
parent
commit
c4ee4c1db4
  1. 217
      discord/embeds.py

217
discord/embeds.py

@ -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.title = kwargs.get('title') self.colour = colour
self.title = kwargs.get('title', EmptyEmbed)
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:
- ``url``
- ``proxy_url``
- ``width``
- ``height``
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, '_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:
- ``url``
- ``proxy_url``
- ``width``
- ``height``
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, '_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…
Cancel
Save