From 26280fd24d0d68fd02d7c438244fa97df6bc047e Mon Sep 17 00:00:00 2001 From: dolfies Date: Mon, 17 Jul 2023 15:43:28 -0400 Subject: [PATCH] Implement pomelo migrating --- discord/client.py | 63 ++++++++++++++++++++++++++++++++++++++++ discord/http.py | 27 ++++++++++++----- discord/state.py | 4 +++ discord/types/gateway.py | 1 + discord/types/user.py | 8 +++++ discord/user.py | 46 ++++++++++++++++++++++++----- 6 files changed, 135 insertions(+), 14 deletions(-) diff --git a/discord/client.py b/discord/client.py index 0f3d027c7..f21c4c483 100644 --- a/discord/client.py +++ b/discord/client.py @@ -571,6 +571,14 @@ class Client: exp.name = name return exp + @property + def disclose(self) -> Sequence[str]: + """Sequence[:class:`str`]: Upcoming changes to the user's account. + + .. versionadded:: 2.1 + """ + return utils.SequenceProxy(self._connection.disclose) + def is_ready(self) -> bool: """:class:`bool`: Specifies if the client's internal cache is ready for use.""" return self._ready is not MISSING and self._ready.is_set() @@ -5054,3 +5062,58 @@ class Client: experiments.append(GuildExperiment(state=state, data=exp)) return experiments + + async def pomelo_suggestion(self) -> str: + """|coro| + + Gets the suggested pomelo username for your account. + This username can be used with :meth:`edit` to migrate your account + to Discord's `new unique username system `_ + + .. note:: + + This method requires you to be in the pomelo rollout. + + .. versionadded:: 2.1 + + Raises + ------- + HTTPException + You are not in the pomelo rollout. + + Returns + -------- + :class:`str` + The suggested username. + """ + data = await self.http.pomelo_suggestion() + return data['username'] + + async def check_pomelo_username(self, username: str) -> bool: + """|coro| + + Checks if a pomelo username is taken. + + .. note:: + + This method requires you to be in the pomelo rollout. + + .. versionadded:: 2.1 + + Parameters + ----------- + username: :class:`str` + The username to check. + + Raises + ------- + HTTPException + You are not in the pomelo rollout. + + Returns + -------- + :class:`bool` + Whether the username is taken. + """ + data = await self.http.pomelo_attempt(username) + return data['taken'] diff --git a/discord/http.py b/discord/http.py index 641e18d19..0ab638d70 100644 --- a/discord/http.py +++ b/discord/http.py @@ -1032,10 +1032,6 @@ class HTTPClient: self.token = token self.ack_token = None - def get_me(self, with_analytics_token: bool = True) -> Response[user.User]: - params = {'with_analytics_token': str(with_analytics_token).lower()} - return self.request(Route('GET', '/users/@me'), params=params) - async def static_login(self, token: str) -> user.User: old_token, self.token = self.token, token @@ -1049,6 +1045,26 @@ class HTTPClient: return data + # Self user + + def get_me(self, with_analytics_token: bool = True) -> Response[user.User]: + params = {'with_analytics_token': str(with_analytics_token).lower()} + return self.request(Route('GET', '/users/@me'), params=params) + + def edit_profile(self, payload: Dict[str, Any]) -> Response[user.User]: + return self.request(Route('PATCH', '/users/@me'), json=payload) + + def pomelo(self, username: str) -> Response[user.User]: + payload = {'username': username} + return self.request(Route('POST', '/users/@me/pomelo'), json=payload) + + def pomelo_suggestion(self) -> Response[user.PomeloSuggestion]: + return self.request(Route('GET', '/users/@me/pomelo-suggestions')) + + def pomelo_attempt(self, username: str) -> Response[user.PomeloAttempt]: + payload = {'username': username} + return self.request(Route('POST', '/users/@me/pomelo-attempt'), json=payload) + # PM functionality def start_group(self, recipients: SnowflakeList) -> Response[channel.GroupDMChannel]: @@ -1429,9 +1445,6 @@ class HTTPClient: return self.request(r, json=payload, reason=reason) - def edit_profile(self, payload: Dict[str, Any]) -> Response[user.User]: - return self.request(Route('PATCH', '/users/@me'), json=payload) - def edit_my_voice_state(self, guild_id: Snowflake, payload: Dict[str, Any]) -> Response[None]: # TODO: remove payload r = Route('PATCH', '/guilds/{guild_id}/voice-states/@me', guild_id=guild_id) return self.request(r, json=payload) diff --git a/discord/state.py b/discord/state.py index de0b1c0e6..c0e25c0df 100644 --- a/discord/state.py +++ b/discord/state.py @@ -614,6 +614,7 @@ class ConnectionState: self.auth_session_id: Optional[str] = None self.required_action: Optional[RequiredActionType] = None self.friend_suggestion_count: int = 0 + self.disclose: List[str] = [] self._emojis: Dict[int, Emoji] = {} self._stickers: Dict[int, GuildSticker] = {} self._guilds: Dict[int, Guild] = {} @@ -1080,6 +1081,9 @@ class ConnectionState: pm['recipients'] = [temp_users[int(u_id)] for u_id in pm.pop('recipient_ids')] self._add_private_channel(factory(me=user, data=pm, state=self)) # type: ignore + # Disloses + self.dislose = data.get('dislose', []) + # We're done del self._ready_data self.call_handlers('connect') diff --git a/discord/types/gateway.py b/discord/types/gateway.py index 84aebcef1..445fdf0f9 100644 --- a/discord/types/gateway.py +++ b/discord/types/gateway.py @@ -135,6 +135,7 @@ class ReadySupplementalEvent(TypedDict): merged_members: List[List[MemberWithUser]] merged_presences: MergedPresences lazy_private_channels: List[Union[DMChannel, GroupDMChannel]] + disclose: List[str] class VersionedReadState(TypedDict): diff --git a/discord/types/user.py b/discord/types/user.py index 34f23ac8f..064054947 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -90,6 +90,14 @@ class User(APIUser, total=False): nsfw_allowed: Optional[bool] +class PomeloAttempt(TypedDict): + taken: bool + + +class PomeloSuggestion(TypedDict): + username: str + + class PartialConnection(TypedDict): id: str type: ConnectionType diff --git a/discord/user.py b/discord/user.py index 0e83098c6..c95e191e9 100644 --- a/discord/user.py +++ b/discord/user.py @@ -523,6 +523,13 @@ class BaseUser(_UserTag): return any(user.id == self.id for user in message.mentions) + def is_pomelo(self) -> bool: + """:class:`bool`: Checks if the user has migrated to Discord's `new unique username system `_ + + .. versionadded:: 2.1 + """ + return self.discriminator == '0' + @property def relationship(self) -> Optional[Relationship]: """Optional[:class:`Relationship`]: Returns the :class:`Relationship` with this user if applicable, ``None`` otherwise.""" @@ -781,6 +788,7 @@ class ClientUser(BaseUser): accent_color: Colour = MISSING, bio: Optional[str] = MISSING, date_of_birth: datetime = MISSING, + pomelo: bool = MISSING, ) -> ClientUser: """|coro| @@ -813,10 +821,10 @@ class ClientUser(BaseUser): The hypesquad house you wish to change to. Could be ``None`` to leave the current house. username: :class:`str` - The new username you wish to change to. + The new username you wish to change to. discriminator: :class:`int` The new discriminator you wish to change to. - Can only be used if you have Nitro. + This is a legacy concept that is no longer used. Can only be used if you have Nitro. avatar: Optional[:class:`bytes`] A :term:`py:bytes-like object` representing the image to upload. Could be ``None`` to denote no avatar. @@ -841,12 +849,23 @@ class ClientUser(BaseUser): Your date of birth. Can only ever be set once. .. versionadded:: 2.0 + pomelo: :class:`bool` + Whether to migrate your account to Discord's `new unique username system `_. + + .. note:: + + This change cannot be undone and requires you to be in the pomelo rollout. + + .. versionadded:: 2.1 Raises ------ HTTPException Editing your profile failed. + You are not in the pomelo rollout. ValueError + Username was not passed when migrating to pomelo. + Discriminator was passed when migrated to pomelo. Password was not passed when it was required. `house` field was not a :class:`HypeSquadHouse`. `date_of_birth` field was not a :class:`datetime.datetime`. @@ -857,7 +876,17 @@ class ClientUser(BaseUser): :class:`ClientUser` The newly edited client user. """ + state = self._state args: Dict[str, Any] = {} + data = None + + if pomelo: + if not username: + raise ValueError('Username is required for pomelo migration') + if discriminator: + raise ValueError('Discriminator cannot be changed when migrated to pomelo') + data = await state.http.pomelo(username) + username = MISSING if any(x is not MISSING for x in (new_password, email, username, discriminator)): if password is MISSING: @@ -898,6 +927,8 @@ class ClientUser(BaseUser): args['username'] = username if discriminator is not MISSING: + if self.is_pomelo(): + raise ValueError('Discriminator cannot be changed when migrated to pomelo') args['discriminator'] = discriminator if new_password is not MISSING: @@ -921,11 +952,12 @@ class ClientUser(BaseUser): else: await http.change_hypesquad_house(house.value) - data = await http.edit_profile(args) - try: - http._token(data['token']) - except KeyError: - pass + if args or data is None: + data = await http.edit_profile(args) + try: + http._token(data['token']) + except KeyError: + pass return ClientUser(state=self._state, data=data)