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 '',
)
@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
def from_str(cls, value: str) -> Self:
"""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 typing import TYPE_CHECKING, List, Optional
from typing import TYPE_CHECKING, List, Optional, Tuple
from . import utils
from .application import ApplicationInstallParams
from .asset import Asset, AssetMixin
from .colour import Colour
from .connections import PartialConnection
from .enums import PremiumType, try_enum
from .flags import ApplicationFlags
from .member import Member
from .mixins import Hashable
from .partial_emoji import PartialEmoji
from .user import User
if TYPE_CHECKING:
@ -45,11 +47,13 @@ if TYPE_CHECKING:
Profile as ProfilePayload,
ProfileApplication as ProfileApplicationPayload,
ProfileBadge as ProfileBadgePayload,
ProfileMetadata as ProfileMetadataPayload,
MutualGuild as MutualGuildPayload,
)
from .types.user import PartialUser as PartialUserPayload
__all__ = (
'ProfileMetadata',
'ApplicationProfile',
'MutualGuild',
'ProfileBadge',
@ -71,6 +75,7 @@ class Profile:
mutual_friends: List[PartialUserPayload] = kwargs.pop('mutual_friends', None)
member = data.get('guild_member')
member_profile = data.get('guild_member_profile')
if member is not None:
member['user'] = user
kwargs['data'] = member
@ -84,6 +89,11 @@ class Profile:
super().__init__(**kwargs)
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
# 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:
self.guild_premium_since = guild_premium_since
self.premium_type: Optional[PremiumType] = (
try_enum(PremiumType, data.get('premium_type') or 0) if profile else None
)
self.premium_type: Optional[PremiumType] = 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_guild_since: Optional[datetime] = utils.parse_time(data.get('premium_guild_since'))
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
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):
"""Represents a Discord application profile.
@ -362,6 +474,14 @@ class UserProfile(Profile, User):
-----------
application: Optional[:class:`ApplicationProfile`]
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`]
The user's "about me" field. Could be ``None``.
premium_type: Optional[:class:`PremiumType`]
@ -449,6 +569,14 @@ class MemberProfile(Profile, Member):
-----------
application: Optional[:class:`ApplicationProfile`]
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`]
The user's "about me" field. Could be ``None``.
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.
"""
from typing import List, Optional, TypedDict
from typing_extensions import NotRequired
from typing import List, Optional, Tuple, TypedDict
from typing_extensions import NotRequired, Required
from .application import ApplicationInstallParams, RoleConnection
from .emoji import Emoji
from .member import PrivateMember as ProfileMember
from .snowflake import Snowflake
from .user import APIUser, PartialConnection, PremiumType
@ -35,12 +36,20 @@ class ProfileUser(APIUser):
bio: str
class ProfileMetadata(TypedDict):
guild_id: NotRequired[int]
bio: NotRequired[str]
banner: NotRequired[Optional[str]]
accent_color: NotRequired[Optional[int]]
theme_colors: NotRequired[List[int]]
class ProfileEffect(TypedDict):
id: Snowflake
class ProfileMetadata(TypedDict, total=False):
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):
@ -79,4 +88,5 @@ class Profile(TypedDict):
premium_type: Optional[PremiumType]
premium_since: Optional[str]
premium_guild_since: Optional[str]
legacy_username: Optional[str]
application: NotRequired[ProfileApplication]

5
docs/api.rst

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

Loading…
Cancel
Save