Skip to content

Commit

Permalink
Implement friend tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
dolfies committed Jan 3, 2025
1 parent d401d6d commit 2d6ba40
Show file tree
Hide file tree
Showing 7 changed files with 91 additions and 15 deletions.
41 changes: 36 additions & 5 deletions discord/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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)
Expand Down Expand Up @@ -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|
Expand Down
5 changes: 3 additions & 2 deletions discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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
Expand All @@ -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')
Expand Down
15 changes: 14 additions & 1 deletion discord/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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]]:
Expand Down Expand Up @@ -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(),
Expand All @@ -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)

Expand Down
4 changes: 3 additions & 1 deletion discord/member.py
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -1138,7 +1139,7 @@ async def profile(
Raises
-------
Forbidden
NotFound
Not allowed to fetch this profile.
HTTPException
Fetching the profile failed.
Expand All @@ -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,
)
12 changes: 10 additions & 2 deletions discord/relationship.py
Original file line number Diff line number Diff line change
Expand Up @@ -399,18 +399,26 @@ def __repr__(self) -> str:
f'<FriendSuggestion user={self.user!r} reasons={self.reasons!r} from_user_contacts={self.from_user_contacts!r}>'
)

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|
Expand Down
4 changes: 4 additions & 0 deletions discord/types/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
25 changes: 21 additions & 4 deletions discord/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -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|
Expand All @@ -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.
Expand All @@ -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]:
Expand Down Expand Up @@ -1236,16 +1243,26 @@ 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
Not allowed to send a friend request to the user.
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
)

0 comments on commit 2d6ba40

Please sign in to comment.