Skip to content

Commit

Permalink
Parse new avatar decoration format
Browse files Browse the repository at this point in the history
  • Loading branch information
dolfies committed Jan 2, 2024
1 parent f23da7d commit 26162e1
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 14 deletions.
8 changes: 8 additions & 0 deletions discord/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,14 @@ def avatar_decoration(self) -> Optional[Asset]:
"""
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
def default_avatar(self) -> Asset:
""":class:`~discord.Asset`: Returns the default avatar for a given user."""
Expand Down
8 changes: 2 additions & 6 deletions discord/asset.py
Original file line number Diff line number Diff line change
Expand Up @@ -239,12 +239,8 @@ def _from_avatar(cls, state: _State, user_id: int, avatar: str) -> Self:
)

@classmethod
def _from_avatar_decoration(cls, state: _State, user_id: int, decoration: str) -> Self:
# Avatar decoration presets are not available through the regular CDN endpoint
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'
def _from_avatar_decoration(cls, state: _State, decoration: str) -> Self:
url = f'{cls.BASE}/avatar-decoration-presets/{decoration}.png?size=256&passthrough=true'
return cls(state, url=url, key=decoration, animated=False, passthrough=True)

@classmethod
Expand Down
1 change: 1 addition & 0 deletions discord/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ class Member(discord.abc.Messageable, discord.abc.Connectable, _UserTag):
default_avatar: Asset
avatar: Optional[Asset]
avatar_decoration: Optional[Asset]
avatar_decoration_sku_id: Optional[int]
note: Note
relationship: Optional[Relationship]
is_friend: Callable[[], bool]
Expand Down
9 changes: 8 additions & 1 deletion discord/types/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
DEALINGS IN THE SOFTWARE.
"""

from __future__ import annotations

from typing import Any, Dict, List, Literal, Optional, TypedDict
from typing_extensions import NotRequired

Expand All @@ -34,7 +36,7 @@ class PartialUser(TypedDict):
username: str
discriminator: str
avatar: Optional[str]
avatar_decoration: NotRequired[Optional[str]]
avatar_decoration_data: NotRequired[Optional[UserAvatarDecorationData]]
public_flags: NotRequired[int]
bot: NotRequired[bool]
system: NotRequired[bool]
Expand Down Expand Up @@ -90,6 +92,11 @@ class User(APIUser, total=False):
nsfw_allowed: Optional[bool]


class UserAvatarDecorationData(TypedDict):
asset: str
sku_id: NotRequired[Snowflake]


class PomeloAttempt(TypedDict):
taken: bool

Expand Down
43 changes: 36 additions & 7 deletions discord/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
from .errors import ClientException, NotFound
from .flags import PublicUserFlags, PrivateUserFlags, PremiumUsageFlags, PurchasedFlags
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

if TYPE_CHECKING:
Expand All @@ -61,6 +61,7 @@
APIUser as APIUserPayload,
PartialUser as PartialUserPayload,
User as UserPayload,
UserAvatarDecorationData,
)
from .types.snowflake import Snowflake

Expand Down Expand Up @@ -243,6 +244,7 @@ class BaseUser(_UserTag):
'global_name',
'_avatar',
'_avatar_decoration',
'_avatar_decoration_sku_id',
'_banner',
'_accent_colour',
'bot',
Expand All @@ -262,6 +264,7 @@ class BaseUser(_UserTag):
_state: ConnectionState
_avatar: Optional[str]
_avatar_decoration: Optional[str]
_avatar_decoration_sku_id: Optional[Snowflake]
_banner: Optional[str]
_accent_colour: Optional[int]
_public_flags: int
Expand Down Expand Up @@ -296,13 +299,16 @@ def _update(self, data: Union[UserPayload, PartialUserPayload]) -> None:
self.discriminator = data['discriminator']
self.global_name = data.get('global_name')
self._avatar = data['avatar']
self._avatar_decoration = data.get('avatar_decoration')
self._banner = data.get('banner', None)
self._accent_colour = data.get('accent_color', None)
self._public_flags = data.get('public_flags', 0)
self.bot = data.get('bot', 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
def _copy(cls, user: Self) -> Self:
self = cls.__new__(cls) # bypass __init__
Expand All @@ -313,6 +319,7 @@ def _copy(cls, user: Self) -> Self:
self.global_name = user.global_name
self._avatar = user._avatar
self._avatar_decoration = user._avatar_decoration
self._avatar_decoration_sku_id = user._avatar_decoration_sku_id
self._banner = user._banner
self._accent_colour = user._accent_colour
self._public_flags = user._public_flags
Expand All @@ -323,11 +330,17 @@ def _copy(cls, user: Self) -> Self:
return self

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 = {
'username': self.name,
'id': self.id,
'avatar': self._avatar,
'avatar_decoration': self._avatar_decoration,
'avatar_decoration_data': decoration,
'discriminator': self.discriminator,
'global_name': self.global_name,
'bot': self.bot,
Expand Down Expand Up @@ -388,9 +401,19 @@ def avatar_decoration(self) -> Optional[Asset]:
.. versionadded:: 2.0
"""
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

@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
def banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the user's banner asset, if available.
Expand Down Expand Up @@ -1045,14 +1068,20 @@ def _update_self(self, user: Union[PartialUserPayload, Tuple[()]]) -> Optional[T
if len(user) == 0 or len(user) <= 1: # Done because of typing
return

original = (self.name, self._avatar, self.discriminator, self._public_flags, self._avatar_decoration)
# These keys seem to always be available
original = (
self.name,
self._avatar,
self.discriminator,
self._public_flags,
self._avatar_decoration,
self.global_name,
)
modified = (
user['username'],
user.get('avatar'),
user['discriminator'],
user.get('public_flags', 0),
user.get('avatar_decoration'),
(user.get('avatar_decoration_data') or {}).get('asset'),
user.get('global_name'),
)
if original != modified:
Expand Down

0 comments on commit 26162e1

Please sign in to comment.