Browse Source

Support for avatar decorations

Co-authored-by: Danny <[email protected]>
Co-authored-by: owocado <[email protected]>
docs/guide
Andrin 1 year ago
committed by GitHub
parent
commit
e25b7ff3f8
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 16
      discord/abc.py
  2. 9
      discord/asset.py
  3. 29
      discord/member.py
  4. 3
      discord/types/gateway.py
  5. 5
      discord/types/member.py
  6. 7
      discord/types/user.py
  7. 35
      discord/user.py
  8. 3
      discord/webhook/async_.py

16
discord/abc.py

@ -248,6 +248,22 @@ class User(Snowflake, Protocol):
"""Optional[:class:`~discord.Asset`]: Returns an Asset that represents the user's avatar, if present."""
raise NotImplementedError
@property
def avatar_decoration(self) -> Optional[Asset]:
"""Optional[:class:`~discord.Asset`]: Returns an Asset that represents the user's avatar decoration, if present.
.. versionadded:: 2.4
"""
raise NotImplementedError
@property
def avatar_decoration_sku_id(self) -> Optional[int]:
"""Optional[:class:`int`]: Returns an integer that represents the user's avatar decoration SKU ID, if present.
.. versionadded:: 2.4
"""
raise NotImplementedError
@property
def default_avatar(self) -> Asset:
""":class:`~discord.Asset`: Returns the default avatar for a given user."""

9
discord/asset.py

@ -246,6 +246,15 @@ class Asset(AssetMixin):
animated=animated,
)
@classmethod
def _from_avatar_decoration(cls, state: _State, avatar_decoration: str) -> Self:
return cls(
state,
url=f'{cls.BASE}/avatar-decoration-presets/{avatar_decoration}.png?size=96',
key=avatar_decoration,
animated=True,
)
@classmethod
def _from_icon(cls, state: _State, object_id: int, icon_hash: str, path: str) -> Self:
return cls(

29
discord/member.py

@ -67,7 +67,7 @@ if TYPE_CHECKING:
UserWithMember as UserWithMemberPayload,
)
from .types.gateway import GuildMemberUpdateEvent
from .types.user import User as UserPayload
from .types.user import User as UserPayload, AvatarDecorationData
from .abc import Snowflake
from .state import ConnectionState
from .message import Message
@ -323,6 +323,7 @@ class Member(discord.abc.Messageable, _UserTag):
'_state',
'_avatar',
'_flags',
'_avatar_decoration_data',
)
if TYPE_CHECKING:
@ -342,6 +343,8 @@ class Member(discord.abc.Messageable, _UserTag):
banner: Optional[Asset]
accent_color: Optional[Colour]
accent_colour: Optional[Colour]
avatar_decoration: Optional[Asset]
avatar_decoration_sku_id: Optional[int]
def __init__(self, *, data: MemberWithUserPayload, guild: Guild, state: ConnectionState):
self._state: ConnectionState = state
@ -357,6 +360,7 @@ class Member(discord.abc.Messageable, _UserTag):
self._avatar: Optional[str] = data.get('avatar')
self._permissions: Optional[int]
self._flags: int = data['flags']
self._avatar_decoration_data: Optional[AvatarDecorationData] = data.get('avatar_decoration_data')
try:
self._permissions = int(data['permissions'])
except KeyError:
@ -425,6 +429,7 @@ class Member(discord.abc.Messageable, _UserTag):
self._permissions = member._permissions
self._state = member._state
self._avatar = member._avatar
self._avatar_decoration_data = member._avatar_decoration_data
# Reference will not be copied unless necessary by PRESENCE_UPDATE
# See below
@ -453,6 +458,7 @@ class Member(discord.abc.Messageable, _UserTag):
self._roles = utils.SnowflakeList(map(int, data['roles']))
self._avatar = data.get('avatar')
self._flags = data.get('flags', 0)
self._avatar_decoration_data = data.get('avatar_decoration_data')
def _presence_update(self, data: PartialPresenceUpdate, user: UserPayload) -> Optional[Tuple[User, User]]:
self.activities = tuple(create_activity(d, self._state) for d in data['activities'])
@ -464,7 +470,16 @@ class Member(discord.abc.Messageable, _UserTag):
def _update_inner_user(self, user: UserPayload) -> Optional[Tuple[User, User]]:
u = self._user
original = (u.name, u.discriminator, u._avatar, u.global_name, u._public_flags)
original = (
u.name,
u.discriminator,
u._avatar,
u.global_name,
u._public_flags,
u._avatar_decoration_data['sku_id'] if u._avatar_decoration_data is not None else None,
)
decoration_payload = user.get('avatar_decoration_data')
# These keys seem to always be available
modified = (
user['username'],
@ -472,10 +487,18 @@ class Member(discord.abc.Messageable, _UserTag):
user['avatar'],
user.get('global_name'),
user.get('public_flags', 0),
decoration_payload['sku_id'] if decoration_payload is not None else None,
)
if original != modified:
to_return = User._copy(self._user)
u.name, u.discriminator, u._avatar, u.global_name, u._public_flags = modified
u.name, u.discriminator, u._avatar, u.global_name, u._public_flags, u._avatar_decoration_data = (
user['username'],
user['discriminator'],
user['avatar'],
user.get('global_name'),
user.get('public_flags', 0),
decoration_payload,
)
# Signal to dispatch on_user_update
return to_return, u

3
discord/types/gateway.py

@ -41,7 +41,7 @@ from .message import Message
from .sticker import GuildSticker
from .appinfo import GatewayAppInfo, PartialAppInfo
from .guild import Guild, UnavailableGuild
from .user import User
from .user import User, AvatarDecorationData
from .threads import Thread, ThreadMember
from .scheduled_event import GuildScheduledEvent
from .audit_log import AuditLogEntry
@ -228,6 +228,7 @@ class GuildMemberUpdateEvent(TypedDict):
mute: NotRequired[bool]
pending: NotRequired[bool]
communication_disabled_until: NotRequired[str]
avatar_decoration_data: NotRequired[AvatarDecorationData]
class GuildEmojisUpdateEvent(TypedDict):

5
discord/types/member.py

@ -24,7 +24,8 @@ DEALINGS IN THE SOFTWARE.
from typing import Optional, TypedDict
from .snowflake import SnowflakeList
from .user import User
from .user import User, AvatarDecorationData
from typing_extensions import NotRequired
class Nickname(TypedDict):
@ -47,6 +48,7 @@ class Member(PartialMember, total=False):
pending: bool
permissions: str
communication_disabled_until: str
avatar_decoration_data: NotRequired[AvatarDecorationData]
class _OptionalMemberWithUser(PartialMember, total=False):
@ -56,6 +58,7 @@ class _OptionalMemberWithUser(PartialMember, total=False):
pending: bool
permissions: str
communication_disabled_until: str
avatar_decoration_data: NotRequired[AvatarDecorationData]
class MemberWithUser(_OptionalMemberWithUser):

7
discord/types/user.py

@ -24,6 +24,12 @@ DEALINGS IN THE SOFTWARE.
from .snowflake import Snowflake
from typing import Literal, Optional, TypedDict
from typing_extensions import NotRequired
class AvatarDecorationData(TypedDict):
asset: str
sku_id: Snowflake
class PartialUser(TypedDict):
@ -32,6 +38,7 @@ class PartialUser(TypedDict):
discriminator: str
avatar: Optional[str]
global_name: Optional[str]
avatar_decoration_data: NotRequired[AvatarDecorationData]
PremiumType = Literal[0, 1, 2, 3]

35
discord/user.py

@ -31,7 +31,7 @@ from .asset import Asset
from .colour import Colour
from .enums import DefaultAvatar
from .flags import PublicUserFlags
from .utils import snowflake_time, _bytes_to_base64_data, MISSING
from .utils import snowflake_time, _bytes_to_base64_data, MISSING, _get_as_snowflake
if TYPE_CHECKING:
from typing_extensions import Self
@ -43,10 +43,7 @@ if TYPE_CHECKING:
from .message import Message
from .state import ConnectionState
from .types.channel import DMChannel as DMChannelPayload
from .types.user import (
PartialUser as PartialUserPayload,
User as UserPayload,
)
from .types.user import PartialUser as PartialUserPayload, User as UserPayload, AvatarDecorationData
__all__ = (
@ -73,6 +70,7 @@ class BaseUser(_UserTag):
'system',
'_public_flags',
'_state',
'_avatar_decoration_data',
)
if TYPE_CHECKING:
@ -87,6 +85,7 @@ class BaseUser(_UserTag):
_banner: Optional[str]
_accent_colour: Optional[int]
_public_flags: int
_avatar_decoration_data: Optional[AvatarDecorationData]
def __init__(self, *, state: ConnectionState, data: Union[UserPayload, PartialUserPayload]) -> None:
self._state = state
@ -123,6 +122,7 @@ class BaseUser(_UserTag):
self._public_flags = data.get('public_flags', 0)
self.bot = data.get('bot', False)
self.system = data.get('system', False)
self._avatar_decoration_data = data.get('avatar_decoration_data')
@classmethod
def _copy(cls, user: Self) -> Self:
@ -138,6 +138,7 @@ class BaseUser(_UserTag):
self.bot = user.bot
self._state = user._state
self._public_flags = user._public_flags
self._avatar_decoration_data = user._avatar_decoration_data
return self
@ -187,6 +188,30 @@ class BaseUser(_UserTag):
"""
return self.avatar or self.default_avatar
@property
def avatar_decoration(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns an :class:`Asset` for the avatar decoration the user has.
If the user has not set an avatar decoration, ``None`` is returned.
.. versionadded:: 2.4
"""
if self._avatar_decoration_data is not None:
return Asset._from_avatar_decoration(self._state, self._avatar_decoration_data['asset'])
return None
@property
def avatar_decoration_sku_id(self) -> Optional[int]:
"""Optional[:class:`int`]: Returns the SKU ID of the avatar decoration the user has.
If the user has not set an avatar decoration, ``None`` is returned.
.. versionadded:: 2.4
"""
if self._avatar_decoration_data is not None:
return _get_as_snowflake(self._avatar_decoration_data, 'sku_id')
return None
@property
def banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the user's banner asset, if available.

3
discord/webhook/async_.py

@ -1305,9 +1305,10 @@ class Webhook(BaseWebhook):
'user': {
'username': user.name,
'discriminator': user.discriminator,
'global_name': user.global_name,
'id': user.id,
'avatar': user._avatar,
'avatar_decoration_data': user._avatar_decoration_data,
'global_name': user.global_name,
},
}

Loading…
Cancel
Save