Add support for rich embeds.
This commit is contained in:
		| @@ -37,6 +37,7 @@ from . import utils, opus, compat | ||||
| from .voice_client import VoiceClient | ||||
| from .enums import ChannelType, ServerRegion, Status, MessageType, VerificationLevel | ||||
| from collections import namedtuple | ||||
| from .embeds import Embed | ||||
|  | ||||
| import logging | ||||
|  | ||||
|   | ||||
| @@ -1043,7 +1043,7 @@ class Client: | ||||
|         return [User(**user) for user in data] | ||||
|  | ||||
|     @asyncio.coroutine | ||||
|     def send_message(self, destination, content, *, tts=False): | ||||
|     def send_message(self, destination, content=None, *, tts=False, embed=None): | ||||
|         """|coro| | ||||
|  | ||||
|         Sends a message to the destination given with the content given. | ||||
| @@ -1062,15 +1062,23 @@ class Client: | ||||
|             ``str`` being allowed was removed and replaced with :class:`Object`. | ||||
|  | ||||
|         The content must be a type that can convert to a string through ``str(content)``. | ||||
|         If the content is set to ``None`` (the default), then the ``embed`` parameter must | ||||
|         be provided. | ||||
|  | ||||
|         If the ``embed`` parameter is provided, it must be of type :class:`Embed` and | ||||
|         it must be a rich embed type. | ||||
|  | ||||
|         Parameters | ||||
|         ------------ | ||||
|         destination | ||||
|             The location to send the message. | ||||
|         content | ||||
|             The content of the message to send. | ||||
|             The content of the message to send. If this is missing, | ||||
|             then the ``embed`` parameter must be present. | ||||
|         tts : bool | ||||
|             Indicates if the message should be sent using text-to-speech. | ||||
|         embed: :class:`Embed` | ||||
|             The rich embed for the content. | ||||
|  | ||||
|         Raises | ||||
|         -------- | ||||
| @@ -1083,6 +1091,25 @@ class Client: | ||||
|         InvalidArgument | ||||
|             The destination parameter is invalid. | ||||
|  | ||||
|         Examples | ||||
|         ---------- | ||||
|  | ||||
|         Sending a regular message: | ||||
|  | ||||
|         .. code-block:: python | ||||
|  | ||||
|             await client.send_message(message.channel, 'Hello') | ||||
|  | ||||
|         Sending a TTS message: | ||||
|  | ||||
|             await client.send_message(message.channel, 'Goodbye.', tts=True) | ||||
|  | ||||
|         Sending an embed message: | ||||
|  | ||||
|             em = discord.Embed(title='My Embed Title', description='My Embed Content.', colour=0xDEADBF) | ||||
|             em.set_author(name='Someone', icon_url=client.user.default_avatar_url) | ||||
|             await client.send_message(message.channel, embed=em) | ||||
|  | ||||
|         Returns | ||||
|         --------- | ||||
|         :class:`Message` | ||||
| @@ -1091,9 +1118,12 @@ class Client: | ||||
|  | ||||
|         channel_id, guild_id = yield from self._resolve_destination(destination) | ||||
|  | ||||
|         content = str(content) | ||||
|         content = str(content) if content else None | ||||
|  | ||||
|         data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts) | ||||
|         if embed is not None: | ||||
|             embed = embed.to_dict() | ||||
|  | ||||
|         data = yield from self.http.send_message(channel_id, content, guild_id=guild_id, tts=tts, embed=embed) | ||||
|         channel = self.get_channel(data.get('channel_id')) | ||||
|         message = self.connection._create_message(channel=channel, **data) | ||||
|         return message | ||||
|   | ||||
							
								
								
									
										398
									
								
								discord/embeds.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										398
									
								
								discord/embeds.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,398 @@ | ||||
| # -*- coding: utf-8 -*- | ||||
|  | ||||
| """ | ||||
| The MIT License (MIT) | ||||
|  | ||||
| Copyright (c) 2015-2016 Rapptz | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a | ||||
| copy of this software and associated documentation files (the "Software"), | ||||
| to deal in the Software without restriction, including without limitation | ||||
| the rights to use, copy, modify, merge, publish, distribute, sublicense, | ||||
| and/or sell copies of the Software, and to permit persons to whom the | ||||
| Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in | ||||
| all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS | ||||
| OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
| FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||||
| DEALINGS IN THE SOFTWARE. | ||||
| """ | ||||
|  | ||||
| import datetime | ||||
|  | ||||
| from .colour import Colour | ||||
| from . import utils | ||||
|  | ||||
| class _EmptyEmbed: | ||||
|     def __bool__(self): | ||||
|         return False | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return 'Embed.Empty' | ||||
|  | ||||
| EmptyEmbed = _EmptyEmbed() | ||||
|  | ||||
| class EmbedProxy: | ||||
|     def __init__(self, layer): | ||||
|         self.__dict__.update(layer) | ||||
|  | ||||
|     def __repr__(self): | ||||
|         return 'EmbedProxy(%s)' % ', '.join(('%s=%r' % (k, v) for k, v in self.__dict__.items() if not k.startswith('_'))) | ||||
|  | ||||
|     def __getattr__(self, attr): | ||||
|         return EmptyEmbed | ||||
|  | ||||
| class Embed: | ||||
|     """Represents a Discord embed. | ||||
|  | ||||
|     The following attributes can be set during creation | ||||
|     of the object: | ||||
|  | ||||
|     Certain properties return an ``EmbedProxy``. Which is a type | ||||
|     that acts similar to a regular `dict` except access the attributes | ||||
|     via dotted access, e.g. ``embed.author.icon_url``. If the attribute | ||||
|     is invalid or empty, then a special sentinel value is returned, | ||||
|     :attr:`Embed.Empty`. | ||||
|  | ||||
|     Attributes | ||||
|     ----------- | ||||
|     title: str | ||||
|         The title of the embed. | ||||
|     type: str | ||||
|         The type of embed. Usually "rich". | ||||
|     description: str | ||||
|         The description of the embed. | ||||
|     url: str | ||||
|         The URL of the embed. | ||||
|     timestamp: `datetime.datetime` | ||||
|         The timestamp of the embed content. | ||||
|     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. | ||||
|     """ | ||||
|  | ||||
|     __slots__ = ('title', 'url', 'type', '_timestamp', '_colour', '_footer', | ||||
|                  '_image', '_thumbnail', '_video', '_provider', '_author', | ||||
|                  '_fields', 'description') | ||||
|  | ||||
|     Empty = EmptyEmbed | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         # swap the colour/color aliases | ||||
|         try: | ||||
|             colour = kwargs['colour'] | ||||
|         except KeyError: | ||||
|             colour = kwargs.get('color') | ||||
|  | ||||
|         if colour is not None: | ||||
|             self.colour = colour | ||||
|  | ||||
|         self.title = kwargs.get('title') | ||||
|         self.type = kwargs.get('type', 'rich') | ||||
|         self.url = kwargs.get('url') | ||||
|         self.description = kwargs.get('description') | ||||
|  | ||||
|         try: | ||||
|             timestamp = kwargs['timestamp'] | ||||
|         except KeyError: | ||||
|             pass | ||||
|         else: | ||||
|             self.timestamp = timestamp | ||||
|  | ||||
|     @classmethod | ||||
|     def from_data(cls, data): | ||||
|         # we are bypassing __init__ here since it doesn't apply here | ||||
|         self = cls.__new__(cls) | ||||
|  | ||||
|         # 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') | ||||
|  | ||||
|         # try to fill in the more rich fields | ||||
|  | ||||
|         try: | ||||
|             self._colour = Colour(value=data['color']) | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|         try: | ||||
|             self._timestamp = utils.parse_time(data['timestamp']) | ||||
|         except KeyError: | ||||
|             pass | ||||
|  | ||||
|         for attr in ('thumbnail', 'video', 'provider', 'author', 'fields'): | ||||
|             try: | ||||
|                 value = data[attr] | ||||
|             except KeyError: | ||||
|                 continue | ||||
|             else: | ||||
|                 setattr(self, '_' + attr, value) | ||||
|  | ||||
|         return self | ||||
|  | ||||
|     @property | ||||
|     def colour(self): | ||||
|         return getattr(self, '_colour', None) | ||||
|  | ||||
|     @colour.setter | ||||
|     def colour(self, value): | ||||
|         if isinstance(value, Colour): | ||||
|             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__) | ||||
|  | ||||
|     color = colour | ||||
|  | ||||
|     @property | ||||
|     def timestamp(self): | ||||
|         return getattr(self, '_timestamp', None) | ||||
|  | ||||
|     @timestamp.setter | ||||
|     def timestamp(self, value): | ||||
|         if isinstance(value, datetime.datetime): | ||||
|             self._timestamp = value | ||||
|         else: | ||||
|             raise TypeError("Expected datetime.datetime received %s instead" % value.__class__.__name__) | ||||
|  | ||||
|     @property | ||||
|     def footer(self): | ||||
|         """Returns a ``EmbedProxy`` denoting the footer contents. | ||||
|  | ||||
|         See :meth:`set_footer` for possible values you can access. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return EmbedProxy(getattr(self, '_footer', {})) | ||||
|  | ||||
|     def set_footer(self, *, text=None, icon_url=None): | ||||
|         """Sets the footer for the embed content. | ||||
|  | ||||
|         Parameters | ||||
|         ----------- | ||||
|         text: str | ||||
|             The footer text. | ||||
|         icon_url: str | ||||
|             The URL of the footer icon. Only HTTP(S) is supported. | ||||
|         """ | ||||
|  | ||||
|         self._footer = {} | ||||
|         if text is not None: | ||||
|             self._footer['text'] = text | ||||
|  | ||||
|         if icon_url is not None: | ||||
|             self._footer['icon_url'] = icon_url | ||||
|  | ||||
|     @property | ||||
|     def image(self): | ||||
|         """Returns a ``EmbedProxy`` denoting the image contents. | ||||
|  | ||||
|         See :meth:`set_image` for possible values you can access. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return EmbedProxy(getattr(self, '_image', {})) | ||||
|  | ||||
|     def set_image(self, *, url, height=None, width=None): | ||||
|         """Sets the image for the embed content. | ||||
|  | ||||
|         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 | ||||
|         } | ||||
|  | ||||
|         if height is not None: | ||||
|             self._image['height'] = height | ||||
|  | ||||
|         if width is not None: | ||||
|             self._image['width'] = width | ||||
|  | ||||
|     @property | ||||
|     def thumbnail(self): | ||||
|         """Returns a ``EmbedProxy`` denoting the thumbnail contents. | ||||
|  | ||||
|         See :meth:`set_thumbnail` for possible values you can access. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return EmbedProxy(getattr(self, '_thumbnail', {})) | ||||
|  | ||||
|     def set_thumbnail(self, *, url, height=None, width=None): | ||||
|         """Sets the thumbnail for the embed content. | ||||
|  | ||||
|         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 | ||||
|         } | ||||
|  | ||||
|         if height is not None: | ||||
|             self._thumbnail['height'] = height | ||||
|  | ||||
|         if width is not None: | ||||
|             self._thumbnail['width'] = width | ||||
|  | ||||
|     @property | ||||
|     def video(self): | ||||
|         """Returns a ``EmbedProxy`` denoting the video contents. | ||||
|  | ||||
|         Possible attributes include: | ||||
|  | ||||
|         - ``url`` for the video URL. | ||||
|         - ``height`` for the video height. | ||||
|         - ``width`` for the video width. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return EmbedProxy(getattr(self, '_video', {})) | ||||
|  | ||||
|     @property | ||||
|     def provider(self): | ||||
|         """Returns a ``EmbedProxy`` denoting the provider contents. | ||||
|  | ||||
|         The only attributes that might be accessed are ``name`` and ``url``. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return EmbedProxy(getattr(self, '_provider', {})) | ||||
|  | ||||
|     @property | ||||
|     def author(self): | ||||
|         """Returns a ``EmbedProxy`` denoting the author contents. | ||||
|  | ||||
|         See :meth:`set_author` for possible values you can access. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return EmbedProxy(getattr(self, '_author', {})) | ||||
|  | ||||
|     def set_author(self, *, name, url=None, icon_url=None): | ||||
|         """Sets the author for the embed content. | ||||
|  | ||||
|         Parameters | ||||
|         ----------- | ||||
|         name: str | ||||
|             The name of the author. | ||||
|         url: str | ||||
|             The URL for the author. | ||||
|         icon_url: str | ||||
|             The URL of the author icon. Only HTTP(S) is supported. | ||||
|         """ | ||||
|  | ||||
|         self._author = { | ||||
|             'name': name | ||||
|         } | ||||
|  | ||||
|         if url is not None: | ||||
|             self._author['url'] = url | ||||
|  | ||||
|         if icon_url is not None: | ||||
|             self._author['icon_url'] = icon_url | ||||
|  | ||||
|  | ||||
|     @property | ||||
|     def fields(self): | ||||
|         """Returns a list of ``EmbedProxy`` denoting the field contents. | ||||
|  | ||||
|         See :meth:`add_field` for possible values you can access. | ||||
|  | ||||
|         If the attribute cannot be accessed then ``None`` is returned. | ||||
|         """ | ||||
|         return [EmbedProxy(d) for d in getattr(self, '_fields', [])] | ||||
|  | ||||
|     def add_field(self, *, name=None, value=None, inline=True): | ||||
|         """Adds a field to the embed object. | ||||
|  | ||||
|         Parameters | ||||
|         ----------- | ||||
|         name: str | ||||
|             The name of the field. | ||||
|         value: str | ||||
|             The value of the field. | ||||
|         inline: bool | ||||
|             Whether the field should be displayed inline. | ||||
|         """ | ||||
|  | ||||
|         field = { | ||||
|             'inline': inline | ||||
|         } | ||||
|         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] | ||||
|  | ||||
|     def to_dict(self): | ||||
|         """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) | ||||
|         } | ||||
|  | ||||
|         # deal with basic convenience wrappers | ||||
|  | ||||
|         try: | ||||
|             colour = result.pop('colour') | ||||
|         except KeyError: | ||||
|             pass | ||||
|         else: | ||||
|             result['color'] = colour.value | ||||
|  | ||||
|         try: | ||||
|             timestamp = result.pop('timestamp') | ||||
|         except KeyError: | ||||
|             pass | ||||
|         else: | ||||
|             result['timestamp'] = timestamp.isoformat() | ||||
|  | ||||
|         # 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 | ||||
| @@ -213,16 +213,21 @@ class HTTPClient: | ||||
|  | ||||
|         return self.post(self.ME + '/channels', json=payload, bucket=_func_()) | ||||
|  | ||||
|     def send_message(self, channel_id, content, *, guild_id=None, tts=False): | ||||
|     def send_message(self, channel_id, content, *, guild_id=None, tts=False, embed=None): | ||||
|         url = '{0.CHANNELS}/{1}/messages'.format(self, channel_id) | ||||
|         payload = { | ||||
|             'content': str(content), | ||||
|             'nonce': random_integer(-2**63, 2**63 - 1) | ||||
|         } | ||||
|  | ||||
|         if content: | ||||
|             payload['content'] = content | ||||
|  | ||||
|         if tts: | ||||
|             payload['tts'] = True | ||||
|  | ||||
|         if embed: | ||||
|             payload['embed'] = embed | ||||
|  | ||||
|         return self.post(url, json=payload, bucket='messages:' + str(guild_id)) | ||||
|  | ||||
|     def send_typing(self, channel_id): | ||||
|   | ||||
		Reference in New Issue
	
	Block a user