Browse Source

Expose profile metadata

pull/10109/head
dolfies 1 year ago
parent
commit
29d224e25c
  1. 8
      discord/partial_emoji.py
  2. 136
      discord/profile.py
  3. 26
      discord/types/profile.py
  4. 5
      docs/api.rst

8
discord/partial_emoji.py

@ -115,6 +115,14 @@ class PartialEmoji(_EmojiTag, AssetMixin):
name=data.get('name') or '', name=data.get('name') or '',
) )
@classmethod
def from_dict_stateful(
cls, data: Union[PartialEmojiPayload, ActivityEmoji, Dict[str, Any]], state: ConnectionState
) -> Self:
self = cls.from_dict(data)
self._state = state
return self
@classmethod @classmethod
def from_str(cls, value: str) -> Self: def from_str(cls, value: str) -> Self:
"""Converts a Discord string representation of an emoji to a :class:`PartialEmoji`. """Converts a Discord string representation of an emoji to a :class:`PartialEmoji`.

136
discord/profile.py

@ -24,16 +24,18 @@ DEALINGS IN THE SOFTWARE.
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING, List, Optional from typing import TYPE_CHECKING, List, Optional, Tuple
from . import utils from . import utils
from .application import ApplicationInstallParams from .application import ApplicationInstallParams
from .asset import Asset, AssetMixin from .asset import Asset, AssetMixin
from .colour import Colour
from .connections import PartialConnection from .connections import PartialConnection
from .enums import PremiumType, try_enum from .enums import PremiumType, try_enum
from .flags import ApplicationFlags from .flags import ApplicationFlags
from .member import Member from .member import Member
from .mixins import Hashable from .mixins import Hashable
from .partial_emoji import PartialEmoji
from .user import User from .user import User
if TYPE_CHECKING: if TYPE_CHECKING:
@ -45,11 +47,13 @@ if TYPE_CHECKING:
Profile as ProfilePayload, Profile as ProfilePayload,
ProfileApplication as ProfileApplicationPayload, ProfileApplication as ProfileApplicationPayload,
ProfileBadge as ProfileBadgePayload, ProfileBadge as ProfileBadgePayload,
ProfileMetadata as ProfileMetadataPayload,
MutualGuild as MutualGuildPayload, MutualGuild as MutualGuildPayload,
) )
from .types.user import PartialUser as PartialUserPayload from .types.user import PartialUser as PartialUserPayload
__all__ = ( __all__ = (
'ProfileMetadata',
'ApplicationProfile', 'ApplicationProfile',
'MutualGuild', 'MutualGuild',
'ProfileBadge', 'ProfileBadge',
@ -71,6 +75,7 @@ class Profile:
mutual_friends: List[PartialUserPayload] = kwargs.pop('mutual_friends', None) mutual_friends: List[PartialUserPayload] = kwargs.pop('mutual_friends', None)
member = data.get('guild_member') member = data.get('guild_member')
member_profile = data.get('guild_member_profile')
if member is not None: if member is not None:
member['user'] = user member['user'] = user
kwargs['data'] = member kwargs['data'] = member
@ -84,6 +89,11 @@ class Profile:
super().__init__(**kwargs) super().__init__(**kwargs)
state = self._state state = self._state
self.metadata = ProfileMetadata(id=self.id, state=state, data=profile)
if member is not None:
self.guild_metadata = ProfileMetadata(id=self.id, state=state, data=member_profile)
self.legacy_username: Optional[str] = data.get('legacy_username')
self.bio: Optional[str] = user['bio'] or None self.bio: Optional[str] = user['bio'] or None
# We need to do a bit of a hack here because premium_since is massively overloaded # We need to do a bit of a hack here because premium_since is massively overloaded
@ -91,9 +101,7 @@ class Profile:
if guild_premium_since is not utils.MISSING: if guild_premium_since is not utils.MISSING:
self.guild_premium_since = guild_premium_since self.guild_premium_since = guild_premium_since
self.premium_type: Optional[PremiumType] = ( self.premium_type: Optional[PremiumType] = try_enum(PremiumType, data.get('premium_type') or 0) if profile else None
try_enum(PremiumType, data.get('premium_type') or 0) if profile else None
)
self.premium_since: Optional[datetime] = utils.parse_time(data.get('premium_since')) self.premium_since: Optional[datetime] = utils.parse_time(data.get('premium_since'))
self.premium_guild_since: Optional[datetime] = utils.parse_time(data.get('premium_guild_since')) self.premium_guild_since: Optional[datetime] = utils.parse_time(data.get('premium_guild_since'))
self.connections: List[PartialConnection] = [PartialConnection(d) for d in data['connected_accounts']] self.connections: List[PartialConnection] = [PartialConnection(d) for d in data['connected_accounts']]
@ -137,6 +145,110 @@ class Profile:
return self.premium_since is not None return self.premium_since is not None
class ProfileMetadata:
"""Represents global or per-user Discord profile metadata.
.. versionadded:: 2.1
Attributes
------------
bio: Optional[:class:`str`]
The profile's "about me" field. Could be ``None``.
pronouns: Optional[:class:`str`]
The profile's pronouns, if any.
effect_id: Optional[:class:`int`]
The ID of the profile effect the user has, if any.
"""
__slots__ = (
'_id',
'_state',
'bio',
'pronouns',
'emoji',
'popout_animation_particle_type',
'effect_id',
'_banner',
'_accent_colour',
'_theme_colours',
'_guild_id',
)
def __init__(self, *, id: int, state: ConnectionState, data: Optional[ProfileMetadataPayload]) -> None:
self._id = id
self._state = state
# user_profile is null if blocked
if data is None:
data = {'pronouns': ''}
self.bio: Optional[str] = data.get('bio') or None
self.pronouns: Optional[str] = data.get('pronouns') or None
self.emoji: Optional[PartialEmoji] = PartialEmoji.from_dict_stateful(data['emoji'], state) if data.get('emoji') else None # type: ignore
self.popout_animation_particle_type: Optional[int] = utils._get_as_snowflake(data, 'popout_animation_particle_type')
self.effect_id: Optional[int] = utils._get_as_snowflake(data['profile_effect'], 'id') if data.get('profile_effect') else None # type: ignore
self._banner: Optional[str] = data.get('banner')
self._accent_colour: Optional[int] = data.get('accent_color')
self._theme_colours: Optional[Tuple[int, int]] = tuple(data['theme_colors']) if data.get('theme_colors') else None # type: ignore
self._guild_id: Optional[int] = utils._get_as_snowflake(data, 'guild_id')
def __repr__(self) -> str:
return f'<ProfileMetadata bio={self.bio!r} pronouns={self.pronouns!r}>'
@property
def banner(self) -> Optional[Asset]:
"""Optional[:class:`Asset`]: Returns the user's banner asset, if available."""
if self._banner is None:
return None
return Asset._from_user_banner(self._state, self._id, self._banner)
@property
def accent_colour(self) -> Optional[Colour]:
"""Optional[:class:`Colour`]: Returns the profile's accent colour, if applicable.
A user's accent colour is only shown if they do not have a banner.
This will only be available if the user explicitly sets a colour.
There is an alias for this named :attr:`accent_color`.
"""
if self._accent_colour is None:
return None
return Colour(self._accent_colour)
@property
def accent_color(self) -> Optional[Colour]:
"""Optional[:class:`Colour`]: Returns the profile's accent color, if applicable.
A user's accent color is only shown if they do not have a banner.
This will only be available if the user explicitly sets a color.
There is an alias for this named :attr:`accent_colour`.
"""
return self.accent_colour
@property
def theme_colours(self) -> Optional[Tuple[Colour, Colour]]:
"""Optional[Tuple[:class:`Colour`, :class:`Colour`]]: Returns the profile's theme colours, if applicable.
The first colour is the user's background colour and the second is the user's foreground colour.
There is an alias for this named :attr:`theme_colors`.
"""
if self._theme_colours is None:
return None
return tuple(Colour(c) for c in self._theme_colours) # type: ignore
@property
def theme_colors(self) -> Optional[Tuple[Colour, Colour]]:
"""Optional[Tuple[:class:`Colour`, :class:`Colour`]]: Returns the profile's theme colors, if applicable.
The first color is the user's background color and the second is the user's foreground color.
There is an alias for this named :attr:`theme_colours`.
"""
return self.theme_colours
class ApplicationProfile(Hashable): class ApplicationProfile(Hashable):
"""Represents a Discord application profile. """Represents a Discord application profile.
@ -362,6 +474,14 @@ class UserProfile(Profile, User):
----------- -----------
application: Optional[:class:`ApplicationProfile`] application: Optional[:class:`ApplicationProfile`]
The application profile of the user, if it is a bot. The application profile of the user, if it is a bot.
metadata: :class:`ProfileMetadata`
The global profile metadata of the user.
.. versionadded:: 2.1
legacy_username: Optional[:class:`str`]
The user's legacy username (Username#Discriminator), if public.
.. versionadded:: 2.1
bio: Optional[:class:`str`] bio: Optional[:class:`str`]
The user's "about me" field. Could be ``None``. The user's "about me" field. Could be ``None``.
premium_type: Optional[:class:`PremiumType`] premium_type: Optional[:class:`PremiumType`]
@ -449,6 +569,14 @@ class MemberProfile(Profile, Member):
----------- -----------
application: Optional[:class:`ApplicationProfile`] application: Optional[:class:`ApplicationProfile`]
The application profile of the user, if it is a bot. The application profile of the user, if it is a bot.
metadata: :class:`ProfileMetadata`
The global profile metadata of the user.
.. versionadded:: 2.1
legacy_username: Optional[:class:`str`]
The user's legacy username (Username#Discriminator), if public.
.. versionadded:: 2.1
bio: Optional[:class:`str`] bio: Optional[:class:`str`]
The user's "about me" field. Could be ``None``. The user's "about me" field. Could be ``None``.
guild_bio: Optional[:class:`str`] guild_bio: Optional[:class:`str`]

26
discord/types/profile.py

@ -22,10 +22,11 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
""" """
from typing import List, Optional, TypedDict from typing import List, Optional, Tuple, TypedDict
from typing_extensions import NotRequired from typing_extensions import NotRequired, Required
from .application import ApplicationInstallParams, RoleConnection from .application import ApplicationInstallParams, RoleConnection
from .emoji import Emoji
from .member import PrivateMember as ProfileMember from .member import PrivateMember as ProfileMember
from .snowflake import Snowflake from .snowflake import Snowflake
from .user import APIUser, PartialConnection, PremiumType from .user import APIUser, PartialConnection, PremiumType
@ -35,12 +36,20 @@ class ProfileUser(APIUser):
bio: str bio: str
class ProfileMetadata(TypedDict): class ProfileEffect(TypedDict):
guild_id: NotRequired[int] id: Snowflake
bio: NotRequired[str]
banner: NotRequired[Optional[str]]
accent_color: NotRequired[Optional[int]] class ProfileMetadata(TypedDict, total=False):
theme_colors: NotRequired[List[int]] guild_id: int
bio: str
banner: Optional[str]
accent_color: Optional[int]
theme_colors: Optional[Tuple[int, int]]
emoji: Optional[Emoji]
popout_animation_particle_type: Optional[Snowflake]
profile_effect: Optional[ProfileEffect]
pronouns: Required[str]
class MutualGuild(TypedDict): class MutualGuild(TypedDict):
@ -79,4 +88,5 @@ class Profile(TypedDict):
premium_type: Optional[PremiumType] premium_type: Optional[PremiumType]
premium_since: Optional[str] premium_since: Optional[str]
premium_guild_since: Optional[str] premium_guild_since: Optional[str]
legacy_username: Optional[str]
application: NotRequired[ProfileApplication] application: NotRequired[ProfileApplication]

5
docs/api.rst

@ -6826,6 +6826,11 @@ User
:members: :members:
:inherited-members: :inherited-members:
.. attributetable:: ProfileMetadata
.. autoclass:: ProfileMetadata()
:members:
.. attributetable:: ProfileBadge .. attributetable:: ProfileBadge
.. autoclass:: ProfileBadge() .. autoclass:: ProfileBadge()

Loading…
Cancel
Save