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 .store import SKU, StoreListing, SubscriptionPlan
from .guild_premium import * from .guild_premium import *
from .library import LibraryApplication from .library import LibraryApplication
from .relationship import Relationship from .relationship import FriendSuggestion, Relationship
from .settings import UserSettings, LegacyUserSettings, TrackingSettings, EmailSettings from .settings import UserSettings, LegacyUserSettings, TrackingSettings, EmailSettings
from .affinity import * from .affinity import *
@ -2743,6 +2743,27 @@ class Client:
data = await state.http.get_relationships() data = await state.http.get_relationships()
return [Relationship(state=state, data=d) for d in data] 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: async def fetch_country_code(self) -> str:
"""|coro| """|coro|

15
discord/enums.py

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

18
discord/http.py

@ -2556,7 +2556,6 @@ class HTTPClient:
return self.request(Route('GET', '/users/@me/relationships')) return self.request(Route('GET', '/users/@me/relationships'))
def remove_relationship(self, user_id: Snowflake, *, action: RelationshipAction) -> Response[None]: 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 if action is RelationshipAction.deny_request: # User Profile, Friends, DM Channel
props = choice( props = choice(
( (
@ -2582,12 +2581,16 @@ class HTTPClient:
else: else:
props = ContextProperties.empty() 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( def add_relationship(
self, user_id: Snowflake, type: Optional[int] = None, *, action: RelationshipAction self, user_id: Snowflake, type: Optional[int] = None, *, action: RelationshipAction
) -> Response[None]: ) -> Response[None]:
r = Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id) 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 if action is RelationshipAction.accept_request: # User Profile, Friends, DM Channel
props = choice( props = choice(
( (
@ -2613,10 +2616,13 @@ class HTTPClient:
ContextProperties.from_dm_channel, ContextProperties.from_dm_channel,
) )
)() )()
elif action is RelationshipAction.friend_suggestion: # Friends
props = ContextProperties.from_friends()
payload['from_friend_suggestion'] = True
else: else:
props = ContextProperties.empty() 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]: def send_friend_request(self, username: str, discriminator: Snowflake) -> Response[None]:
r = Route('POST', '/users/@me/relationships') r = Route('POST', '/users/@me/relationships')
@ -2628,6 +2634,12 @@ class HTTPClient:
def edit_relationship(self, user_id: Snowflake, **payload) -> Response[None]: 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) 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 # Connections
def get_connections(self) -> Response[List[user.Connection]]: 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 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 .mixins import Hashable
from .object import Object from .object import Object
from .utils import MISSING, parse_time from .utils import MISSING, parse_time
@ -38,14 +38,18 @@ if TYPE_CHECKING:
from .activity import ActivityTypes from .activity import ActivityTypes
from .state import ConnectionState, Presence from .state import ConnectionState, Presence
from .types.gateway import RelationshipEvent 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 from .user import User
# fmt: off
__all__ = ( __all__ = (
'Relationship', 'Relationship',
'FriendSuggestionReason',
'FriendSuggestion',
) )
# fmt: on
class Relationship(Hashable): class Relationship(Hashable):
@ -323,3 +327,92 @@ class Relationship(Hashable):
payload['nickname'] = nick payload['nickname'] = nick
await self._state.http.edit_relationship(self.user.id, **payload) 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 .channel import _channel_factory, _private_channel_factory
from .raw_models import * from .raw_models import *
from .member import Member from .member import Member
from .relationship import Relationship from .relationship import Relationship, FriendSuggestion
from .role import Role from .role import Role
from .enums import ( from .enums import (
ChannelType, ChannelType,
@ -2625,6 +2625,16 @@ class ConnectionState:
new._update(data) new._update(data)
self.dispatch('relationship_update', old, new) 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: def parse_interaction_create(self, data) -> None:
if 'nonce' not in data: # Sometimes interactions seem to be missing the nonce if 'nonce' not in data: # Sometimes interactions seem to be missing the nonce
return return

9
discord/types/gateway.py

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

11
discord/types/user.py

@ -156,3 +156,14 @@ class Note(TypedDict):
note: str note: str
user_id: Snowflake user_id: Snowflake
note_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. :param after: The updated relationship.
:type after: :class:`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 Notes
~~~~~~ ~~~~~~
@ -6426,6 +6455,16 @@ Relationship
.. autoclass:: Relationship() .. autoclass:: Relationship()
:members: :members:
.. attributetable:: FriendSuggestion
.. autoclass:: FriendSuggestion()
:members:
.. attributetable:: FriendSuggestionReason
.. autoclass:: FriendSuggestionReason()
:members:
Settings Settings
~~~~~~~~ ~~~~~~~~

Loading…
Cancel
Save