From 2d6ba40b8cb607cc2508fe8a02c8a833ce602602 Mon Sep 17 00:00:00 2001 From: Dolfies Date: Thu, 2 Jan 2025 19:09:34 -0500 Subject: [PATCH] Implement friend tokens --- discord/client.py | 41 ++++++++++++++++++++++++++++++++++++----- discord/guild.py | 5 +++-- discord/http.py | 15 ++++++++++++++- discord/member.py | 4 +++- discord/relationship.py | 12 ++++++++++-- discord/types/user.py | 4 ++++ discord/user.py | 25 +++++++++++++++++++++---- 7 files changed, 91 insertions(+), 15 deletions(-) diff --git a/discord/client.py b/discord/client.py index 85c0aedea06f..d3559f03e0b3 100644 --- a/discord/client.py +++ b/discord/client.py @@ -2431,14 +2431,15 @@ async def fetch_user_profile( with_mutual_guilds: bool = True, with_mutual_friends_count: bool = False, with_mutual_friends: bool = True, + friend_token: str = MISSING, ) -> UserProfile: """|coro| Retrieves a :class:`.UserProfile` based on their user ID. - You must share a guild, be friends with this user, - or have an incoming friend request from them to - get this information (unless the user is a bot). + You must provide a valid ``friend_token``, share a guild with, + be friends with, or have an incoming friend request from this + user to get this information, unless the user is a bot. .. versionchanged:: 2.0 @@ -2463,13 +2464,16 @@ async def fetch_user_profile( This fills in :attr:`.UserProfile.mutual_friends` and :attr:`.UserProfile.mutual_friends_count`. .. versionadded:: 2.0 + friend_token: :class:`str` + The friend token to use for fetching the profile. + + .. versionadded:: 2.1 Raises ------- NotFound A user with this ID does not exist. - Forbidden - You do not have a mutual with this user, and the user is not a bot. + You do not have a mutual with this user and the user is not a bot. HTTPException Fetching the profile failed. @@ -2484,6 +2488,7 @@ async def fetch_user_profile( with_mutual_guilds=with_mutual_guilds, with_mutual_friends_count=with_mutual_friends_count, with_mutual_friends=with_mutual_friends, + friend_token=friend_token or None, ) return UserProfile(state=state, data=data) @@ -3020,6 +3025,32 @@ async def friend_suggestions(self) -> List[FriendSuggestion]: data = await state.http.get_friend_suggestions() return [FriendSuggestion(state=state, data=d) for d in data] + async def friend_token(self) -> str: + """|coro| + + Retrieves your friend token. + + These can be used to fetch the user's profile without a mutual + and add the user as a friend regardless of their friend request settings. + + To share, append it to the user's URL like so: + ``https://discord.com/users/{user.id?friend_token={friend_token}``. + + .. versionadded:: 2.1 + + Raises + ------- + HTTPException + Retrieving your friend token failed. + + Returns + -------- + :class:`str` + Your friend token. + """ + data = await self.http.get_friend_token() + return data['friend_token'] + async def fetch_country_code(self) -> str: """|coro| diff --git a/discord/guild.py b/discord/guild.py index 062cb1c4b3f0..919bd95f9482 100644 --- a/discord/guild.py +++ b/discord/guild.py @@ -2683,6 +2683,7 @@ async def fetch_member_profile( with_mutual_guilds: bool = True, with_mutual_friends_count: bool = False, with_mutual_friends: bool = True, + friend_token: str = MISSING, ) -> MemberProfile: """|coro| @@ -2708,8 +2709,7 @@ async def fetch_member_profile( ------- NotFound A user with this ID does not exist. - Forbidden - You do not have a mutual with this user, and and the user is not a bot. + You do not have a mutual with this user and the user is not a bot. HTTPException Fetching the profile failed. InvalidData @@ -2727,6 +2727,7 @@ async def fetch_member_profile( with_mutual_guilds=with_mutual_guilds, with_mutual_friends_count=with_mutual_friends_count, with_mutual_friends=with_mutual_friends, + friend_token=friend_token or None, ) if 'guild_member_profile' not in data: raise InvalidData('Member is not in this guild') diff --git a/discord/http.py b/discord/http.py index 91e6f9b1df1d..14f502d961f3 100644 --- a/discord/http.py +++ b/discord/http.py @@ -2894,11 +2894,18 @@ def remove_relationship(self, user_id: Snowflake, *, action: RelationshipAction) return self.request(Route('DELETE', '/users/@me/relationships/{user_id}', user_id=user_id), context_properties=props) def add_relationship( - self, user_id: Snowflake, type: Optional[int] = None, *, action: RelationshipAction + self, + user_id: Snowflake, + type: Optional[int] = None, + *, + friend_token: Optional[str] = None, + action: RelationshipAction, ) -> Response[None]: payload = {} if type is not None: payload['type'] = type + if friend_token: + payload['friend_token'] = friend_token if action is RelationshipAction.accept_request: # User Profile, Friends, DM Channel props = choice( @@ -2949,6 +2956,9 @@ def get_friend_suggestions(self) -> Response[List[user.FriendSuggestion]]: def delete_friend_suggestion(self, user_id: Snowflake) -> Response[None]: return self.request(Route('DELETE', '/friend-suggestions/{user_id}', user_id=user_id)) + def get_friend_token(self) -> Response[user.FriendToken]: + return self.request(Route('GET', '/users/@me/friend-token')) + # Connections def get_connections(self) -> Response[List[user.Connection]]: @@ -4389,6 +4399,7 @@ def get_user_profile( with_mutual_guilds: bool = True, with_mutual_friends: bool = False, with_mutual_friends_count: bool = False, + friend_token: Optional[str] = None, ) -> Response[profile.Profile]: params: Dict[str, Any] = { 'with_mutual_guilds': str(with_mutual_guilds).lower(), @@ -4397,6 +4408,8 @@ def get_user_profile( } if guild_id: params['guild_id'] = guild_id + if friend_token: + params['friend_token'] = friend_token return self.request(Route('GET', '/users/{user_id}/profile', user_id=user_id), params=params) diff --git a/discord/member.py b/discord/member.py index 66137b441737..d6aa47074589 100644 --- a/discord/member.py +++ b/discord/member.py @@ -1113,6 +1113,7 @@ async def profile( with_mutual_guilds: bool = True, with_mutual_friends_count: bool = False, with_mutual_friends: bool = True, + friend_token: str = MISSING, ) -> MemberProfile: """|coro| @@ -1138,7 +1139,7 @@ async def profile( Raises ------- - Forbidden + NotFound Not allowed to fetch this profile. HTTPException Fetching the profile failed. @@ -1155,4 +1156,5 @@ async def profile( with_mutual_guilds=with_mutual_guilds, with_mutual_friends_count=with_mutual_friends_count, with_mutual_friends=with_mutual_friends, + friend_token=friend_token, ) diff --git a/discord/relationship.py b/discord/relationship.py index 1292c01505d6..276d570d5b9e 100644 --- a/discord/relationship.py +++ b/discord/relationship.py @@ -399,18 +399,26 @@ def __repr__(self) -> str: f'' ) - async def accept(self) -> None: + async def accept(self, *, friend_token: str = MISSING) -> None: """|coro| Accepts the friend suggestion. This creates a :class:`Relationship` of type :class:`RelationshipType.outgoing_request`. + Parameters + ---------- + friend_token: :class:`str` + The friend token to accept the friend suggestion with. + This will bypass the user's friend request settings. + Raises ------- HTTPException Accepting the relationship failed. """ - await self._state.http.add_relationship(self.user.id, action=RelationshipAction.friend_suggestion) + await self._state.http.add_relationship( + self.user.id, friend_token=friend_token or None, action=RelationshipAction.friend_suggestion + ) async def delete(self) -> None: """|coro| diff --git a/discord/types/user.py b/discord/types/user.py index aeaea4625354..4dc27ba9243e 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -248,5 +248,9 @@ class FriendSuggestion(TypedDict): from_suggested_user_contacts: NotRequired[bool] +class FriendToken(TypedDict): + friend_token: str + + class Report(TypedDict): report_id: Snowflake diff --git a/discord/user.py b/discord/user.py index db773f6921a0..d0b1a88b3877 100644 --- a/discord/user.py +++ b/discord/user.py @@ -613,6 +613,7 @@ async def profile( with_mutual_guilds: bool = True, with_mutual_friends_count: bool = False, with_mutual_friends: bool = True, + friend_token: str = MISSING, ) -> UserProfile: """|coro| @@ -635,11 +636,16 @@ async def profile( This fills in :attr:`UserProfile.mutual_friends` and :attr:`UserProfile.mutual_friends_count`. .. versionadded:: 2.0 + friend_token: :class:`str` + The friend token to use for fetching the profile. + + .. versionadded:: 2.1 Raises ------- - Forbidden - Not allowed to fetch this profile. + NotFound + A user with this ID does not exist. + You do not have a mutual with this user, and the user is not a bot. HTTPException Fetching the profile failed. @@ -653,6 +659,7 @@ async def profile( with_mutual_guilds=with_mutual_guilds, with_mutual_friends_count=with_mutual_friends_count, with_mutual_friends=with_mutual_friends, + friend_token=friend_token, ) async def fetch_mutual_friends(self) -> List[User]: @@ -1236,11 +1243,19 @@ async def remove_friend(self) -> None: """ await self._state.http.remove_relationship(self.id, action=RelationshipAction.unfriend) - async def send_friend_request(self) -> None: + async def send_friend_request(self, *, friend_token: str = MISSING) -> None: """|coro| Sends the user a friend request. + Parameters + ----------- + friend_token: :class:`str` + The friend token to use for sending the friend request. + This will bypass the user's friend request settings. + + .. versionadded:: 2.1 + Raises ------- Forbidden @@ -1248,4 +1263,6 @@ async def send_friend_request(self) -> None: HTTPException Sending the friend request failed. """ - await self._state.http.add_relationship(self.id, action=RelationshipAction.send_friend_request) + await self._state.http.add_relationship( + self.id, friend_token=friend_token or None, action=RelationshipAction.send_friend_request + )