Browse Source

Implement friend suggestions

pull/10109/head
dolfies 2 years ago
parent
commit
e0c6612922
  1. 23
      discord/client.py
  2. 15
      discord/enums.py
  3. 18
      discord/http.py
  4. 101
      discord/relationship.py
  5. 12
      discord/state.py
  6. 9
      discord/types/gateway.py
  7. 11
      discord/types/user.py
  8. 39
      docs/api.rst

23
discord/client.py

@ -85,7 +85,7 @@ from .entitlements import Entitlement, Gift
from .store import SKU, StoreListing, SubscriptionPlan
from .guild_premium import *
from .library import LibraryApplication
from .relationship import Relationship
from .relationship import FriendSuggestion, Relationship
from .settings import UserSettings, LegacyUserSettings, TrackingSettings, EmailSettings
from .affinity import *
@ -2743,6 +2743,27 @@ class Client:
data = await state.http.get_relationships()
return [Relationship(state=state, data=d) for d in data]
async def friend_suggestions(self) -> List[FriendSuggestion]:
"""|coro|
Retrieves all your friend suggestions.
.. versionadded:: 2.1
Raises
-------
HTTPException
Retrieving your friend suggestions failed.
Returns
--------
List[:class:`.FriendSuggestion`]
All your current friend suggestions.
"""
state = self._connection
data = await state.http.get_friend_suggestions()
return [FriendSuggestion(state=state, data=d) for d in data]
async def fetch_country_code(self) -> str:
"""|coro|

15
discord/enums.py

@ -785,13 +785,14 @@ class ReportType(Enum):
class RelationshipAction(Enum):
send_friend_request = 'request'
unfriend = 'unfriend'
accept_request = 'accept'
deny_request = 'deny'
block = 'block'
unblock = 'unblock'
remove_pending_request = 'remove'
send_friend_request = 1
unfriend = 2
accept_request = 3
deny_request = 4
block = 5
unblock = 6
remove_pending_request = 7
friend_suggestion = 8
class RequiredActionType(Enum):

18
discord/http.py

@ -2556,7 +2556,6 @@ class HTTPClient:
return self.request(Route('GET', '/users/@me/relationships'))
def remove_relationship(self, user_id: Snowflake, *, action: RelationshipAction) -> Response[None]:
r = Route('DELETE', '/users/@me/relationships/{user_id}', user_id=user_id)
if action is RelationshipAction.deny_request: # User Profile, Friends, DM Channel
props = choice(
(
@ -2582,12 +2581,16 @@ class HTTPClient:
else:
props = ContextProperties.empty()
return self.request(r, context_properties=props)
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
) -> Response[None]:
r = Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id)
payload = {}
if type is not None:
payload['type'] = type
if action is RelationshipAction.accept_request: # User Profile, Friends, DM Channel
props = choice(
(
@ -2613,10 +2616,13 @@ class HTTPClient:
ContextProperties.from_dm_channel,
)
)()
elif action is RelationshipAction.friend_suggestion: # Friends
props = ContextProperties.from_friends()
payload['from_friend_suggestion'] = True
else:
props = ContextProperties.empty()
return self.request(r, context_properties=props, json={'type': type} if type else None)
return self.request(r, context_properties=props, json=payload if payload else None)
def send_friend_request(self, username: str, discriminator: Snowflake) -> Response[None]:
r = Route('POST', '/users/@me/relationships')
@ -2628,6 +2634,12 @@ class HTTPClient:
def edit_relationship(self, user_id: Snowflake, **payload) -> Response[None]:
return self.request(Route('PATCH', '/users/@me/relationships/{user_id}', user_id=user_id), json=payload)
def get_friend_suggestions(self) -> Response[List[user.FriendSuggestion]]:
return self.request(Route('GET', '/friend-suggestions'))
def delete_friend_suggestion(self, user_id: Snowflake) -> Response[None]:
return self.request(Route('DELETE', '/friend-suggestions/{user_id}', user_id=user_id))
# Connections
def get_connections(self) -> Response[List[user.Connection]]:

101
discord/relationship.py

@ -26,7 +26,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Tuple, Union
from .enums import RelationshipAction, RelationshipType, Status, try_enum
from .enums import ConnectionType, RelationshipAction, RelationshipType, Status, try_enum
from .mixins import Hashable
from .object import Object
from .utils import MISSING, parse_time
@ -38,14 +38,18 @@ if TYPE_CHECKING:
from .activity import ActivityTypes
from .state import ConnectionState, Presence
from .types.gateway import RelationshipEvent
from .types.user import Relationship as RelationshipPayload
from .types.user import (
FriendSuggestion as FriendSuggestionPayload,
FriendSuggestionReason as FriendSuggestionReasonPayload,
Relationship as RelationshipPayload,
)
from .user import User
# fmt: off
__all__ = (
'Relationship',
'FriendSuggestionReason',
'FriendSuggestion',
)
# fmt: on
class Relationship(Hashable):
@ -323,3 +327,92 @@ class Relationship(Hashable):
payload['nickname'] = nick
await self._state.http.edit_relationship(self.user.id, **payload)
class FriendSuggestionReason:
"""Represents a reason why a user was suggested as a friend to you.
.. versionadded:: 2.1
Attributes
-----------
platform: :class:`ConnectionType`
The platform the user was suggested from.
name: :class:`str`
The user's name on the platform.
"""
__slots__ = ('type', 'platform', 'name')
def __init__(self, data: FriendSuggestionReasonPayload):
# This entire model is unused by any client, so I have no idea what the type is
# Also because of this, I'm treating everything as optional just in case
self.type: int = data.get('type', 0)
self.platform: ConnectionType = try_enum(ConnectionType, data.get('platform', 'contacts'))
self.name: str = data.get('name', '')
def __repr__(self) -> str:
return f'<FriendSuggestionReason platform={self.platform!r} name={self.name!r}>'
class FriendSuggestion(Hashable):
"""Represents a friend suggestion on Discord.
.. container:: operations
.. describe:: x == y
Checks if two friend suggestions are equal.
.. describe:: x != y
Checks if two friend suggestions are not equal.
.. describe:: hash(x)
Return the suggestion's hash.
.. versionadded:: 2.1
Attributes
-----------
user: :class:`User`
The suggested user.
reasons: List[:class:`FriendSuggestionReason`]
The reasons why the user was suggested.
"""
__slots__ = ('user', 'reasons', '_state')
def __init__(self, *, state: ConnectionState, data: FriendSuggestionPayload):
self._state = state
self.user = state.store_user(data['suggested_user'])
self.reasons = [FriendSuggestionReason(r) for r in data.get('reasons', [])]
def __repr__(self) -> str:
return f'<FriendSuggestion user={self.user!r} reasons={self.reasons!r}>'
async def accept(self) -> None:
"""|coro|
Accepts the friend suggestion.
This creates a :class:`Relationship` of type :class:`RelationshipType.outgoing_request`.
Raises
-------
HTTPException
Accepting the relationship failed.
"""
await self._state.http.add_relationship(self.user.id, action=RelationshipAction.friend_suggestion)
async def delete(self) -> None:
"""|coro|
Ignores the friend suggestion.
Raises
------
HTTPException
Deleting the relationship failed.
"""
await self._state.http.delete_friend_suggestion(self.user.id)

12
discord/state.py

@ -63,7 +63,7 @@ from .channel import *
from .channel import _channel_factory, _private_channel_factory
from .raw_models import *
from .member import Member
from .relationship import Relationship
from .relationship import Relationship, FriendSuggestion
from .role import Role
from .enums import (
ChannelType,
@ -2625,6 +2625,16 @@ class ConnectionState:
new._update(data)
self.dispatch('relationship_update', old, new)
def parse_friend_suggestion_create(self, data: gw.FriendSuggestionCreateEvent):
self.dispatch('friend_suggestion_add', FriendSuggestion(state=self, data=data))
def parse_friend_suggestion_delete(self, data: gw.FriendSuggestionDeleteEvent):
user_id = int(data['suggested_user_id'])
user = self.get_user(user_id)
if user:
self.dispatch('friend_suggestion_remove', user)
self.dispatch('raw_friend_suggestion_remove', user_id)
def parse_interaction_create(self, data) -> None:
if 'nonce' not in data: # Sometimes interactions seem to be missing the nonce
return

9
discord/types/gateway.py

@ -40,7 +40,7 @@ from .message import Message
from .sticker import GuildSticker
from .application import BaseAchievement, PartialApplication
from .guild import ApplicationCommandCounts, Guild, UnavailableGuild, SupplementalGuild
from .user import Connection, User, PartialUser, ProtoSettingsType, Relationship, RelationshipType
from .user import Connection, FriendSuggestion, User, PartialUser, ProtoSettingsType, Relationship, RelationshipType
from .threads import Thread, ThreadMember
from .scheduled_event import GuildScheduledEvent
from .channel import DMChannel, GroupDMChannel
@ -455,6 +455,13 @@ class RelationshipEvent(TypedDict):
nickname: Optional[str]
FriendSuggestionCreateEvent = FriendSuggestion
class FriendSuggestionDeleteEvent(TypedDict):
suggested_user_id: Snowflake
class ProtoSettings(TypedDict):
proto: str
type: ProtoSettingsType

11
discord/types/user.py

@ -156,3 +156,14 @@ class Note(TypedDict):
note: str
user_id: Snowflake
note_user_id: Snowflake
class FriendSuggestionReason(TypedDict):
name: str
platform_type: ConnectionType
type: int
class FriendSuggestion(TypedDict):
suggested_user: PartialUser
reasons: List[FriendSuggestionReason]

39
docs/api.rst

@ -665,6 +665,35 @@ Relationships
:param after: The updated relationship.
:type after: :class:`Relationship`
.. function:: on_friend_suggestion_add(friend_suggestion)
Called when a :class:`FriendSuggestion` is created.
.. versionadded:: 2.1
:param friend_suggestion: The friend suggestion that was created.
:type friend_suggestion: :class:`FriendSuggestion`
.. function:: on_friend_suggestion_remove(user)
Called when a :class:`FriendSuggestion` is removed.
.. versionadded:: 2.1
:param user: The friend suggestion that was removed.
:type user: :class:`User`
.. function:: on_raw_friend_suggestion_remove(user_id)
Called when a :class:`FriendSuggestion` is removed.
Unlike :func:`on_message_edit`, this is called regardless
of the user being in the internal user cache or not.
.. versionadded:: 2.1
:param user_id: The ID of the friend suggestion that was removed.
:type user_id: :class:`int`
Notes
~~~~~~
@ -6426,6 +6455,16 @@ Relationship
.. autoclass:: Relationship()
:members:
.. attributetable:: FriendSuggestion
.. autoclass:: FriendSuggestion()
:members:
.. attributetable:: FriendSuggestionReason
.. autoclass:: FriendSuggestionReason()
:members:
Settings
~~~~~~~~

Loading…
Cancel
Save