Browse Source
Most assets now return a new class named `Asset`. This allows for the assets to be consistently saved via a `save` method instead of special casing for `Attachment`. `AppInfo` is no longer a namedtuple it is a fully documented dataclass, as well as having the state attached to it. Fixes #1997pull/2049/head
committed by
Rapptz
16 changed files with 332 additions and 155 deletions
@ -0,0 +1,73 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
""" |
||||
|
The MIT License (MIT) |
||||
|
|
||||
|
Copyright (c) 2015-2019 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. |
||||
|
""" |
||||
|
|
||||
|
from .user import User |
||||
|
from .asset import Asset |
||||
|
|
||||
|
|
||||
|
class AppInfo: |
||||
|
"""Represents the application info for the bot provided by Discord. |
||||
|
|
||||
|
|
||||
|
Attributes |
||||
|
------------- |
||||
|
id: :class:`int` |
||||
|
The application ID. |
||||
|
name: :class:`str` |
||||
|
The application name. |
||||
|
owner: :class:`User` |
||||
|
The application's owner. |
||||
|
icon: Optional[:class:`str`] |
||||
|
The icon hash. |
||||
|
description: Optional[:class:`str`] |
||||
|
The application description. |
||||
|
bot_public: :class:`bool` |
||||
|
Whether the bot is public. |
||||
|
bot_require_code_grant: :class:`bool` |
||||
|
Whether the bot requires the completion of the full oauth2 code |
||||
|
grant flow to join. |
||||
|
rpc_origins: Optional[List[:class:`str`]] |
||||
|
A list of RPC origin URLs, if RPC is enabled. |
||||
|
""" |
||||
|
__slots__ = ('_state', 'description', 'id', 'name', 'rpc_origins', |
||||
|
'bot_public', 'bot_require_code_grant', 'owner', 'icon') |
||||
|
|
||||
|
def __init__(self, state, data): |
||||
|
self._state = state |
||||
|
|
||||
|
self.id = int(data['id']) |
||||
|
self.name = data['name'] |
||||
|
self.description = data['description'] |
||||
|
self.icon = data['icon'] |
||||
|
self.rpc_origins = data['rpc_origins'] |
||||
|
self.bot_public = data['bot_public'] |
||||
|
self.bot_require_code_grant = data['bot_require_code_grant'] |
||||
|
self.owner = User(state=self._state, data=data['owner']) |
||||
|
|
||||
|
@property |
||||
|
def icon_url(self): |
||||
|
""":class:`.Asset`: Retrieves the application's icon asset.""" |
||||
|
return Asset._from_icon(self._state, self, 'app') |
@ -0,0 +1,157 @@ |
|||||
|
# -*- coding: utf-8 -*- |
||||
|
|
||||
|
""" |
||||
|
The MIT License (MIT) |
||||
|
|
||||
|
Copyright (c) 2015-2019 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 io |
||||
|
from .errors import DiscordException |
||||
|
from .errors import InvalidArgument |
||||
|
from . import utils |
||||
|
|
||||
|
VALID_STATIC_FORMATS = frozenset({"jpeg", "jpg", "webp", "png"}) |
||||
|
VALID_AVATAR_FORMATS = VALID_STATIC_FORMATS | {"gif"} |
||||
|
|
||||
|
class Asset: |
||||
|
"""Represents a CDN asset on Discord. |
||||
|
|
||||
|
.. container:: operations |
||||
|
|
||||
|
.. describe:: str(x) |
||||
|
|
||||
|
Returns the URL of the CDN asset. |
||||
|
|
||||
|
.. describe:: len(x) |
||||
|
|
||||
|
Returns the length of the CDN asset's URL. |
||||
|
|
||||
|
.. describe:: bool(x) |
||||
|
|
||||
|
Checks if the Asset has a URL. |
||||
|
""" |
||||
|
__slots__ = ('_state', '_url') |
||||
|
|
||||
|
def __init__(self, state, url=None): |
||||
|
self._state = state |
||||
|
self._url = url |
||||
|
|
||||
|
@classmethod |
||||
|
def _from_avatar(cls, state, user, *, format=None, static_format='webp', size=1024): |
||||
|
if not utils.valid_icon_size(size): |
||||
|
raise InvalidArgument("size must be a power of 2 between 16 and 1024") |
||||
|
if format is not None and format not in VALID_AVATAR_FORMATS: |
||||
|
raise InvalidArgument("format must be None or one of {}".format(VALID_AVATAR_FORMATS)) |
||||
|
if format == "gif" and not user.is_avatar_animated(): |
||||
|
raise InvalidArgument("non animated avatars do not support gif format") |
||||
|
if static_format not in VALID_STATIC_FORMATS: |
||||
|
raise InvalidArgument("static_format must be one of {}".format(VALID_STATIC_FORMATS)) |
||||
|
|
||||
|
if user.avatar is None: |
||||
|
return user.default_avatar_url |
||||
|
|
||||
|
if format is None: |
||||
|
format = 'gif' if user.is_avatar_animated() else static_format |
||||
|
|
||||
|
return cls(state, 'https://cdn.discordapp.com/avatars/{0.id}/{0.avatar}.{1}?size={2}'.format(user, format, size)) |
||||
|
|
||||
|
@classmethod |
||||
|
def _from_icon(cls, state, object, path): |
||||
|
if object.icon is None: |
||||
|
return cls(state) |
||||
|
|
||||
|
url = 'https://cdn.discordapp.com/{0}-icons/{1.id}/{1.icon}.jpg'.format(path, object) |
||||
|
return cls(state, url) |
||||
|
|
||||
|
@classmethod |
||||
|
def _from_guild_image(cls, state, id, hash, key, *, format='webp', size=1024): |
||||
|
if not utils.valid_icon_size(size): |
||||
|
raise InvalidArgument("size must be a power of 2 between 16 and 4096") |
||||
|
if format not in VALID_STATIC_FORMATS: |
||||
|
raise InvalidArgument("format must be one of {}".format(VALID_STATIC_FORMATS)) |
||||
|
|
||||
|
if hash is None: |
||||
|
return Asset(state) |
||||
|
|
||||
|
url = 'https://cdn.discordapp.com/{key}/{0}/{1}.{2}?size={3}' |
||||
|
return cls(state, url.format(id, hash, format, size, key=key)) |
||||
|
|
||||
|
def __str__(self): |
||||
|
return self._url |
||||
|
|
||||
|
def __len__(self): |
||||
|
return len(self._url) |
||||
|
|
||||
|
def __bool__(self): |
||||
|
return self._url is not None |
||||
|
|
||||
|
def __repr__(self): |
||||
|
return '<Asset url={0._url!r}>'.format(self) |
||||
|
|
||||
|
async def save(self, fp, *, seek_begin=True): |
||||
|
"""|coro| |
||||
|
|
||||
|
Saves this asset into a file-like object. |
||||
|
|
||||
|
Parameters |
||||
|
----------- |
||||
|
fp: Union[BinaryIO, :class:`os.PathLike`] |
||||
|
Same as in :meth:`Attachment.save`. |
||||
|
seek_begin: :class:`bool` |
||||
|
Same as in :meth:`Attachment.save`. |
||||
|
|
||||
|
Raises |
||||
|
-------- |
||||
|
DiscordException |
||||
|
There was no valid URL or internal connection state. |
||||
|
|
||||
|
.. note:: |
||||
|
|
||||
|
:class:`PartialEmoji` will not have a state if you make |
||||
|
your own instance via ``PartialEmoji(animated=False, name='x', id=2345678)``. |
||||
|
|
||||
|
The URL will not be provided if there is no custom image. |
||||
|
HTTPException |
||||
|
Saving the asset failed. |
||||
|
NotFound |
||||
|
The asset was deleted. |
||||
|
|
||||
|
Returns |
||||
|
-------- |
||||
|
:class:`int` |
||||
|
The number of bytes written. |
||||
|
""" |
||||
|
if not self._url: |
||||
|
raise DiscordException('Invalid asset (no URL provided)') |
||||
|
|
||||
|
if self._state is None: |
||||
|
raise DiscordException('Invalid state (no ConnectionState provided)') |
||||
|
|
||||
|
data = await self._state.http.get_from_cdn(self._url) |
||||
|
if isinstance(fp, io.IOBase) and fp.writable(): |
||||
|
written = fp.write(data) |
||||
|
if seek_begin: |
||||
|
fp.seek(0) |
||||
|
return written |
||||
|
else: |
||||
|
with open(fp, 'wb') as f: |
||||
|
return f.write(data) |
Loading…
Reference in new issue