Browse Source

Add missing CDN parameters and fix avatar decoration URL

pull/10109/head
dolfies 2 years ago
parent
commit
c37574d9bb
  1. 91
      discord/asset.py
  2. 3
      discord/utils.py

91
discord/asset.py

@ -204,15 +204,17 @@ class Asset(AssetMixin):
'_state', '_state',
'_url', '_url',
'_animated', '_animated',
'_passthrough',
'_key', '_key',
) )
BASE = 'https://cdn.discordapp.com' BASE = 'https://cdn.discordapp.com'
def __init__(self, state: _State, *, url: str, key: str, animated: bool = False) -> None: def __init__(self, state: _State, *, url: str, key: str, animated: bool = False, passthrough: bool = MISSING) -> None:
self._state: _State = state self._state: _State = state
self._url: str = url self._url: str = url
self._animated: bool = animated self._animated: bool = animated
self._passthrough: bool = passthrough
self._key: str = key self._key: str = key
@classmethod @classmethod
@ -222,6 +224,7 @@ class Asset(AssetMixin):
url=f'{cls.BASE}/embed/avatars/{index}.png', url=f'{cls.BASE}/embed/avatars/{index}.png',
key=str(index), key=str(index),
animated=False, animated=False,
passthrough=True, # Cannot be overridden
) )
@classmethod @classmethod
@ -237,12 +240,12 @@ class Asset(AssetMixin):
@classmethod @classmethod
def _from_avatar_decoration(cls, state: _State, user_id: int, decoration: str) -> Self: def _from_avatar_decoration(cls, state: _State, user_id: int, decoration: str) -> Self:
return cls( # Avatar decoration presets are not available through the regular CDN endpoint
state, if decoration.startswith(('v1_', 'v2_')):
url=f'{cls.BASE}/avatar-decorations/{user_id}/{decoration}.png?size=128', url = f'{cls.BASE}/avatar-decoration-presets/{decoration}.png?size=256&passthrough=true'
key=decoration, else:
animated=False, url = f'{cls.BASE}/avatar-decorations/{user_id}/{decoration}.png?size=256&passthrough=true'
) return cls(state, url=url, key=decoration, animated=False, passthrough=True)
@classmethod @classmethod
def _from_guild_avatar(cls, state: _State, guild_id: int, member_id: int, avatar: str) -> Self: def _from_guild_avatar(cls, state: _State, guild_id: int, member_id: int, avatar: str) -> Self:
@ -374,16 +377,26 @@ class Asset(AssetMixin):
""":class:`bool`: Returns whether the asset is animated.""" """:class:`bool`: Returns whether the asset is animated."""
return self._animated return self._animated
def is_passthrough(self) -> bool:
""":class:`bool`: Returns whether the asset is passed through.
.. versionadded:: 2.0
"""
# The default when not set appears to be True, but this is not set in stone
# So we just return the MISSING value
return self._passthrough
def replace( def replace(
self, self,
*, *,
size: int = MISSING, size: int = MISSING,
format: ValidAssetFormatTypes = MISSING, format: ValidAssetFormatTypes = MISSING,
static_format: ValidStaticFormatTypes = MISSING, static_format: ValidStaticFormatTypes = MISSING,
passthrough: Optional[bool] = MISSING,
keep_aspect_ratio: bool = False,
) -> Self: ) -> Self:
"""Returns a new asset with the passed components replaced. """Returns a new asset with the passed components replaced.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
``static_format`` is now preferred over ``format`` ``static_format`` is now preferred over ``format``
if both are present and the asset is not animated. if both are present and the asset is not animated.
@ -402,6 +415,19 @@ class Asset(AssetMixin):
static_format: :class:`str` static_format: :class:`str`
The new format to change it to if the asset isn't animated. The new format to change it to if the asset isn't animated.
Must be either 'webp', 'jpeg', 'jpg', or 'png'. Must be either 'webp', 'jpeg', 'jpg', or 'png'.
passthrough: :class:`bool`
Whether to return the asset in the original, Discord-defined quality and format (usually APNG).
This only has an affect on specific asset types. This will cause the ``format``
and ``size`` parameters to be ignored by the CDN. By default, this is set to ``False``
if a size or format parameter is passed and the asset is marked as passed through, else untouched.
A value of ``None`` will unconditionally omit the parameter from the query string.
.. versionadded:: 2.0
keep_aspect_ratio: :class:`bool`
Whether to return the original aspect ratio of the asset instead of
having it resized to the endpoint's enforced aspect ratio.
.. versionadded:: 2.0
Raises Raises
------- -------
@ -423,25 +449,45 @@ class Asset(AssetMixin):
else: else:
if static_format is MISSING and format not in VALID_STATIC_FORMATS: if static_format is MISSING and format not in VALID_STATIC_FORMATS:
raise ValueError(f'format must be one of {VALID_STATIC_FORMATS}') raise ValueError(f'format must be one of {VALID_STATIC_FORMATS}')
url = url.with_path(f'{path}.{format}') query = url.query
if self._passthrough:
query['passthrough'] = 'false'
url = url.with_path(f'{path}.{format}').with_query(query)
if static_format is not MISSING and not self._animated: if static_format is not MISSING and not self._animated:
if static_format not in VALID_STATIC_FORMATS: if static_format not in VALID_STATIC_FORMATS:
raise ValueError(f'static_format must be one of {VALID_STATIC_FORMATS}') raise ValueError(f'static_format must be one of {VALID_STATIC_FORMATS}')
url = url.with_path(f'{path}.{static_format}') query = url.query
if self._passthrough:
query['passthrough'] = 'false'
url = url.with_path(f'{path}.{static_format}').with_query(query)
if size is not MISSING: if size is not MISSING or passthrough is not MISSING or keep_aspect_ratio:
if not utils.valid_icon_size(size): if size is not MISSING and not utils.valid_icon_size(size):
raise ValueError('size must be a power of 2 between 16 and 4096') raise ValueError('size must be a power of 2 between 16 and 4096')
url = url.with_query(size=size) query = url.query
if size is not MISSING:
query['size'] = str(size)
if passthrough is MISSING and self._passthrough:
passthrough = False
if passthrough is not MISSING:
if passthrough is None:
passthrough = MISSING
query.pop('passthrough', None)
else:
query['passthrough'] = str(passthrough).lower()
if keep_aspect_ratio:
query['keep_aspect_ratio'] = 'true'
url = url.with_query(query)
else: else:
url = url.with_query(url.raw_query_string) url = url.with_query(url.raw_query_string)
url = str(url) url = str(url)
return Asset(state=self._state, url=url, key=self._key, animated=self._animated) return Asset(state=self._state, url=url, key=self._key, animated=self._animated, passthrough=passthrough) # type: ignore
def with_size(self, size: int, /) -> Self: def with_size(self, size: int, /) -> Self:
"""Returns a new asset with the specified size. """Returns a new asset with the specified size.
Also sets ``passthrough`` to ``False`` if the asset is marked as passed through.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
This function will now raise :exc:`ValueError` instead of This function will now raise :exc:`ValueError` instead of
@ -465,11 +511,17 @@ class Asset(AssetMixin):
if not utils.valid_icon_size(size): if not utils.valid_icon_size(size):
raise ValueError('size must be a power of 2 between 16 and 4096') raise ValueError('size must be a power of 2 between 16 and 4096')
url = str(yarl.URL(self._url).with_query(size=size)) url = yarl.URL(self._url)
query = {**url.query, 'size': str(size)}
if self._passthrough:
query['passthrough'] = 'false'
url = str(url.with_query(query))
return Asset(state=self._state, url=url, key=self._key, animated=self._animated) return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
def with_format(self, format: ValidAssetFormatTypes, /) -> Self: def with_format(self, format: ValidAssetFormatTypes, /) -> Self:
"""Returns a new asset with the specified format. """Returns a new asset with the specified format.
Also sets ``passthrough`` to ``False`` if the asset is marked as passed through.
.. versionchanged:: 2.0 .. versionchanged:: 2.0
This function will now raise :exc:`ValueError` instead of This function will now raise :exc:`ValueError` instead of
@ -490,7 +542,6 @@ class Asset(AssetMixin):
:class:`Asset` :class:`Asset`
The new updated asset. The new updated asset.
""" """
if self._animated: if self._animated:
if format not in VALID_ASSET_FORMATS: if format not in VALID_ASSET_FORMATS:
raise ValueError(f'format must be one of {VALID_ASSET_FORMATS}') raise ValueError(f'format must be one of {VALID_ASSET_FORMATS}')
@ -500,11 +551,16 @@ class Asset(AssetMixin):
url = yarl.URL(self._url) url = yarl.URL(self._url)
path, _ = os.path.splitext(url.path) path, _ = os.path.splitext(url.path)
url = str(url.with_path(f'{path}.{format}').with_query(url.raw_query_string)) query = url.query
if self._passthrough:
query['passthrough'] = 'false'
url = str(url.with_path(f'{path}.{format}').with_query(query))
return Asset(state=self._state, url=url, key=self._key, animated=self._animated) return Asset(state=self._state, url=url, key=self._key, animated=self._animated)
def with_static_format(self, format: ValidStaticFormatTypes, /) -> Self: def with_static_format(self, format: ValidStaticFormatTypes, /) -> Self:
"""Returns a new asset with the specified static format. """Returns a new asset with the specified static format.
Also sets ``passthrough`` to ``False`` if the asset is marked as passed through.
This only changes the format if the underlying asset is This only changes the format if the underlying asset is
not animated. Otherwise, the asset is not changed. not animated. Otherwise, the asset is not changed.
@ -528,7 +584,6 @@ class Asset(AssetMixin):
:class:`Asset` :class:`Asset`
The new updated asset. The new updated asset.
""" """
if self._animated: if self._animated:
return self return self
return self.with_format(format) return self.with_format(format)

3
discord/utils.py

@ -861,7 +861,8 @@ def utcnow() -> datetime.datetime:
def valid_icon_size(size: int) -> bool: def valid_icon_size(size: int) -> bool:
"""Icons must be power of 2 within [16, 4096].""" """Icons must be power of 2 within [16, 4096]."""
return not size & (size - 1) and 4096 >= size >= 16 ADDITIONAL_SIZES = (20, 22, 24, 28, 40, 44, 48, 56, 60, 80, 96, 100, 160, 240, 300, 320, 480, 600, 640, 1280, 1536, 3072)
return (not size & (size - 1) and 4096 >= size >= 16) or size in ADDITIONAL_SIZES
class SnowflakeList(_SnowflakeListBase): class SnowflakeList(_SnowflakeListBase):

Loading…
Cancel
Save