diff --git a/discord/embeds.py b/discord/embeds.py index d06ef9b93..e47b814e5 100644 --- a/discord/embeds.py +++ b/discord/embeds.py @@ -60,6 +60,9 @@ class Embed: is invalid or empty, then a special sentinel value is returned, :attr:`Embed.Empty`. + For ease of use, all parameters that expect a ``str`` are implicitly + casted to ``str`` for you. + Attributes ----------- title: str @@ -75,8 +78,8 @@ class Embed: colour: :class:`Colour` or int The colour code of the embed. Aliased to ``color`` as well. Empty - A special sentinel value used by ``EmbedProxy`` to denote - that the value or attribute is empty. + A special sentinel value used by ``EmbedProxy`` and this class + to denote that the value or attribute is empty. """ __slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer', @@ -90,15 +93,13 @@ class Embed: try: colour = kwargs['colour'] except KeyError: - colour = kwargs.get('color') - - if colour is not None: - self.colour = colour + colour = kwargs.get('color', EmptyEmbed) - self.title = kwargs.get('title') + self.colour = colour + self.title = kwargs.get('title', EmptyEmbed) self.type = kwargs.get('type', 'rich') - self.url = kwargs.get('url') - self.description = kwargs.get('description') + self.url = kwargs.get('url', EmptyEmbed) + self.description = kwargs.get('description', EmptyEmbed) try: timestamp = kwargs['timestamp'] @@ -114,10 +115,10 @@ class Embed: # fill in the basic fields - self.title = data.get('title') - self.type = data.get('type') - self.description = data.get('description') - self.url = data.get('url') + self.title = data.get('title', EmptyEmbed) + self.type = data.get('type', EmptyEmbed) + self.description = data.get('description', EmptyEmbed) + self.url = data.get('url', EmptyEmbed) # try to fill in the more rich fields @@ -143,29 +144,29 @@ class Embed: @property def colour(self): - return getattr(self, '_colour', None) + return getattr(self, '_colour', EmptyEmbed) @colour.setter def colour(self, value): - if isinstance(value, Colour): + if isinstance(value, (Colour, _EmptyEmbed)): self._colour = value elif isinstance(value, int): self._colour = Colour(value=value) 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 @property def timestamp(self): - return getattr(self, '_timestamp', None) + return getattr(self, '_timestamp', EmptyEmbed) @timestamp.setter def timestamp(self, value): - if isinstance(value, datetime.datetime): + if isinstance(value, (datetime.datetime, _EmptyEmbed)): self._timestamp = value 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 def footer(self): @@ -173,13 +174,16 @@ class Embed: 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', {})) - 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. + This function returns the class instance to allow for fluent-style + chaining. + Parameters ----------- text: str @@ -189,77 +193,79 @@ class Embed: """ self._footer = {} - if text is not None: - self._footer['text'] = text + if text is not EmptyEmbed: + self._footer['text'] = str(text) - if icon_url is not None: - self._footer['icon_url'] = icon_url + if icon_url is not EmptyEmbed: + self._footer['icon_url'] = str(icon_url) + + return self @property def image(self): """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', {})) - def set_image(self, *, url, height=None, width=None): + def set_image(self, *, url): """Sets the image for the embed content. + This function returns the class instance to allow for fluent-style + chaining. + Parameters ----------- url: str 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 = { - 'url': url + 'url': str(url) } - if height is not None: - self._image['height'] = height - - if width is not None: - self._image['width'] = width + return self @property def thumbnail(self): """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', {})) - def set_thumbnail(self, *, url, height=None, width=None): + def set_thumbnail(self, *, url): """Sets the thumbnail for the embed content. + This function returns the class instance to allow for fluent-style + chaining. + Parameters ----------- url: str 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 = { - 'url': url + 'url': str(url) } - if height is not None: - self._thumbnail['height'] = height - - if width is not None: - self._thumbnail['width'] = width + return self @property def video(self): @@ -271,7 +277,7 @@ class Embed: - ``height`` for the video height. - ``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', {})) @@ -281,7 +287,7 @@ class Embed: 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', {})) @@ -291,13 +297,16 @@ class Embed: 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', {})) - 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. + This function returns the class instance to allow for fluent-style + chaining. + Parameters ----------- name: str @@ -309,15 +318,16 @@ class Embed: """ self._author = { - 'name': name + 'name': str(name) } - if url is not None: - self._author['url'] = url + if url is not EmptyEmbed: + self._author['url'] = str(url) - if icon_url is not None: - self._author['icon_url'] = icon_url + if icon_url is not EmptyEmbed: + self._author['icon_url'] = str(icon_url) + return self @property def fields(self): @@ -325,13 +335,16 @@ class Embed: 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', [])] - 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. + This function returns the class instance to allow for fluent-style + chaining. + Parameters ----------- name: str @@ -343,19 +356,81 @@ class Embed: """ 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: self._fields.append(field) except AttributeError: 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): """Converts this embed object into a dict.""" @@ -373,14 +448,16 @@ class Embed: except KeyError: pass else: - result['color'] = colour.value + if colour: + result['color'] = colour.value try: timestamp = result.pop('timestamp') except KeyError: pass else: - result['timestamp'] = timestamp.isoformat() + if timestamp: + result['timestamp'] = timestamp.isoformat() # add in the non raw attribute ones if self.type: