Browse Source

Parse new avatar decoration format

pull/10109/head
dolfies 1 year ago
parent
commit
26162e1c97
  1. 8
      discord/abc.py
  2. 8
      discord/asset.py
  3. 1
      discord/member.py
  4. 9
      discord/types/user.py
  5. 43
      discord/user.py

8
discord/abc.py

@ -513,6 +513,14 @@ class User(Snowflake, Protocol):
""" """
raise NotImplementedError raise NotImplementedError
@property
def avatar_decoration_sku_id(self) -> Optional[int]:
"""Optional[:class:`int`]: Returns the SKU ID of the user's avatar decoration, if present.
.. versionadded:: 2.1
"""
raise NotImplementedError
@property @property
def default_avatar(self) -> Asset: def default_avatar(self) -> Asset:
""":class:`~discord.Asset`: Returns the default avatar for a given user.""" """:class:`~discord.Asset`: Returns the default avatar for a given user."""

8
discord/asset.py

@ -239,12 +239,8 @@ 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, decoration: str) -> Self:
# Avatar decoration presets are not available through the regular CDN endpoint url = f'{cls.BASE}/avatar-decoration-presets/{decoration}.png?size=256&passthrough=true'
if decoration.startswith(('v1_', 'v2_')):
url = f'{cls.BASE}/avatar-decoration-presets/{decoration}.png?size=256&passthrough=true'
else:
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) return cls(state, url=url, key=decoration, animated=False, passthrough=True)
@classmethod @classmethod

1
discord/member.py

@ -289,6 +289,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag):
default_avatar: Asset default_avatar: Asset
avatar: Optional[Asset] avatar: Optional[Asset]
avatar_decoration: Optional[Asset] avatar_decoration: Optional[Asset]
avatar_decoration_sku_id: Optional[int]
note: Note note: Note
relationship: Optional[Relationship] relationship: Optional[Relationship]
is_friend: Callable[[], bool] is_friend: Callable[[], bool]

9
discord/types/user.py

@ -22,6 +22,8 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from __future__ import annotations
from typing import Any, Dict, List, Literal, Optional, TypedDict from typing import Any, Dict, List, Literal, Optional, TypedDict
from typing_extensions import NotRequired from typing_extensions import NotRequired
@ -34,7 +36,7 @@ class PartialUser(TypedDict):
username: str username: str
discriminator: str discriminator: str
avatar: Optional[str] avatar: Optional[str]
avatar_decoration: NotRequired[Optional[str]] avatar_decoration_data: NotRequired[Optional[UserAvatarDecorationData]]
public_flags: NotRequired[int] public_flags: NotRequired[int]
bot: NotRequired[bool] bot: NotRequired[bool]
system: NotRequired[bool] system: NotRequired[bool]
@ -90,6 +92,11 @@ class User(APIUser, total=False):
nsfw_allowed: Optional[bool] nsfw_allowed: Optional[bool]
class UserAvatarDecorationData(TypedDict):
asset: str
sku_id: NotRequired[Snowflake]
class PomeloAttempt(TypedDict): class PomeloAttempt(TypedDict):
taken: bool taken: bool

43
discord/user.py

@ -40,7 +40,7 @@ from .enums import (
from .errors import ClientException, NotFound from .errors import ClientException, NotFound
from .flags import PublicUserFlags, PrivateUserFlags, PremiumUsageFlags, PurchasedFlags from .flags import PublicUserFlags, PrivateUserFlags, PremiumUsageFlags, PurchasedFlags
from .relationship import Relationship from .relationship import Relationship
from .utils import _bytes_to_base64_data, cached_slot_property, copy_doc, snowflake_time, MISSING from .utils import _bytes_to_base64_data, _get_as_snowflake, cached_slot_property, copy_doc, snowflake_time, MISSING
from .voice_client import VoiceClient from .voice_client import VoiceClient
if TYPE_CHECKING: if TYPE_CHECKING:
@ -61,6 +61,7 @@ if TYPE_CHECKING:
APIUser as APIUserPayload, APIUser as APIUserPayload,
PartialUser as PartialUserPayload, PartialUser as PartialUserPayload,
User as UserPayload, User as UserPayload,
UserAvatarDecorationData,
) )
from .types.snowflake import Snowflake from .types.snowflake import Snowflake
@ -243,6 +244,7 @@ class BaseUser(_UserTag):
'global_name', 'global_name',
'_avatar', '_avatar',
'_avatar_decoration', '_avatar_decoration',
'_avatar_decoration_sku_id',
'_banner', '_banner',
'_accent_colour', '_accent_colour',
'bot', 'bot',
@ -262,6 +264,7 @@ class BaseUser(_UserTag):
_state: ConnectionState _state: ConnectionState
_avatar: Optional[str] _avatar: Optional[str]
_avatar_decoration: Optional[str] _avatar_decoration: Optional[str]
_avatar_decoration_sku_id: Optional[Snowflake]
_banner: Optional[str] _banner: Optional[str]
_accent_colour: Optional[int] _accent_colour: Optional[int]
_public_flags: int _public_flags: int
@ -296,13 +299,16 @@ class BaseUser(_UserTag):
self.discriminator = data['discriminator'] self.discriminator = data['discriminator']
self.global_name = data.get('global_name') self.global_name = data.get('global_name')
self._avatar = data['avatar'] self._avatar = data['avatar']
self._avatar_decoration = data.get('avatar_decoration')
self._banner = data.get('banner', None) self._banner = data.get('banner', None)
self._accent_colour = data.get('accent_color', None) self._accent_colour = data.get('accent_color', None)
self._public_flags = data.get('public_flags', 0) self._public_flags = data.get('public_flags', 0)
self.bot = data.get('bot', False) self.bot = data.get('bot', False)
self.system = data.get('system', False) self.system = data.get('system', False)
decoration_data = data.get('avatar_decoration_data')
self._avatar_decoration = decoration_data.get('asset') if decoration_data else None
self._avatar_decoration_sku_id = _get_as_snowflake(decoration_data, 'sku_id') if decoration_data else None
@classmethod @classmethod
def _copy(cls, user: Self) -> Self: def _copy(cls, user: Self) -> Self:
self = cls.__new__(cls) # bypass __init__ self = cls.__new__(cls) # bypass __init__
@ -313,6 +319,7 @@ class BaseUser(_UserTag):
self.global_name = user.global_name self.global_name = user.global_name
self._avatar = user._avatar self._avatar = user._avatar
self._avatar_decoration = user._avatar_decoration self._avatar_decoration = user._avatar_decoration
self._avatar_decoration_sku_id = user._avatar_decoration_sku_id
self._banner = user._banner self._banner = user._banner
self._accent_colour = user._accent_colour self._accent_colour = user._accent_colour
self._public_flags = user._public_flags self._public_flags = user._public_flags
@ -323,11 +330,17 @@ class BaseUser(_UserTag):
return self return self
def _to_minimal_user_json(self) -> APIUserPayload: def _to_minimal_user_json(self) -> APIUserPayload:
decoration: Optional[UserAvatarDecorationData] = None
if self._avatar_decoration is not None:
decoration = {'asset': self._avatar_decoration}
if self._avatar_decoration_sku_id is not None:
decoration['sku_id'] = self._avatar_decoration_sku_id
user: APIUserPayload = { user: APIUserPayload = {
'username': self.name, 'username': self.name,
'id': self.id, 'id': self.id,
'avatar': self._avatar, 'avatar': self._avatar,
'avatar_decoration': self._avatar_decoration, 'avatar_decoration_data': decoration,
'discriminator': self.discriminator, 'discriminator': self.discriminator,
'global_name': self.global_name, 'global_name': self.global_name,
'bot': self.bot, 'bot': self.bot,
@ -388,9 +401,19 @@ class BaseUser(_UserTag):
.. versionadded:: 2.0 .. versionadded:: 2.0
""" """
if self._avatar_decoration is not None: if self._avatar_decoration is not None:
return Asset._from_avatar_decoration(self._state, self.id, self._avatar_decoration) return Asset._from_avatar_decoration(self._state, self._avatar_decoration)
return None return None
@property
def avatar_decoration_sku_id(self) -> Optional[Snowflake]:
"""Optional[:class:`int`]: Returns the avatar decoration's SKU ID.
If the user does not have a preset avatar decoration, ``None`` is returned.
.. versionadded:: 2.1
"""
return self._avatar_decoration_sku_id
@property @property
def banner(self) -> Optional[Asset]: def banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the user's banner asset, if available. """Optional[:class:`Asset`]: Returns the user's banner asset, if available.
@ -1045,14 +1068,20 @@ class User(BaseUser, discord.abc.Connectable, discord.abc.Messageable):
if len(user) == 0 or len(user) <= 1: # Done because of typing if len(user) == 0 or len(user) <= 1: # Done because of typing
return return
original = (self.name, self._avatar, self.discriminator, self._public_flags, self._avatar_decoration) original = (
# These keys seem to always be available self.name,
self._avatar,
self.discriminator,
self._public_flags,
self._avatar_decoration,
self.global_name,
)
modified = ( modified = (
user['username'], user['username'],
user.get('avatar'), user.get('avatar'),
user['discriminator'], user['discriminator'],
user.get('public_flags', 0), user.get('public_flags', 0),
user.get('avatar_decoration'), (user.get('avatar_decoration_data') or {}).get('asset'),
user.get('global_name'), user.get('global_name'),
) )
if original != modified: if original != modified:

Loading…
Cancel
Save