From f099d4a648b14322429cc091b6bb24ae25780ab6 Mon Sep 17 00:00:00 2001 From: dolfies Date: Sat, 25 Mar 2023 20:34:10 -0400 Subject: [PATCH] Implement affinities --- discord/__init__.py | 1 + discord/affinity.py | 139 +++++++++++++++++++++++++++++++++ discord/client.py | 47 +++++++++++ discord/http.py | 8 +- discord/settings.py | 4 +- discord/types/subscriptions.py | 5 +- discord/types/user.py | 19 +++++ docs/api.rst | 13 +++ 8 files changed, 233 insertions(+), 3 deletions(-) create mode 100644 discord/affinity.py diff --git a/discord/__init__.py b/discord/__init__.py index c484655c3..be511f555 100644 --- a/discord/__init__.py +++ b/discord/__init__.py @@ -21,6 +21,7 @@ from typing import Literal, NamedTuple from . import abc as abc, opus as opus, utils as utils from .activity import * +from .affinity import * from .appinfo import * from .asset import * from .audit_logs import * diff --git a/discord/affinity.py b/discord/affinity.py new file mode 100644 index 000000000..58ee15034 --- /dev/null +++ b/discord/affinity.py @@ -0,0 +1,139 @@ +""" +The MIT License (MIT) + +Copyright (c) 2021-present Dolfies + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +""" +from __future__ import annotations + +from typing import TYPE_CHECKING + +from .mixins import Hashable + +if TYPE_CHECKING: + from .state import ConnectionState + from .types.user import UserAffinity as UserAffinityPayload, GuildAffinity as GuildAffinityPayload + +__all__ = ( + 'UserAffinity', + 'GuildAffinity', +) + + +class UserAffinity(Hashable): + """Represents a user's affinity with another user. + + User affinities that the current user has a mutual guild with are treated as implicit relationships, + meaning that you get their user presence synced like a friend. + + These implicit relationships are not *real* relationships, and are therefore not returned by the API. + However, they are lazily added to cache by the library when detected. + + .. container:: operations + + .. describe:: x == y + + Checks if two affinities are equal. + + .. describe:: x != y + + Checks if two affinities are not equal. + + .. describe:: hash(x) + + Return the affinity's hash. + + .. versionadded:: 2.0 + + Attributes + ---------- + user_id: :class:`int` + The ID of the user being compared. + affinity: :class:`float` + The affinity score. + """ + + __slots__ = ('_state', 'user_id', 'affinity') + + def __init__(self, *, state: ConnectionState, data: UserAffinityPayload): + self._state = state + self.user_id = int(data['user_id']) + self.affinity = data['affinity'] + + def __repr__(self) -> str: + return f'' + + @property + def id(self) -> int: + """:class:`int`: The ID of the user being compared.""" + return self.user_id + + @property + def user(self): + """Optional[:class:`User`]: The user being compared.""" + return self._state.get_user(self.user_id) + + +class GuildAffinity(Hashable): + """Represents a user's affinity with a guild. + + .. container:: operations + + .. describe:: x == y + + Checks if two affinities are equal. + + .. describe:: x != y + + Checks if two affinities are not equal. + + .. describe:: hash(x) + + Return the affinity's hash. + + .. versionadded:: 2.0 + + Attributes + ---------- + guild_id: :class:`int` + The ID of the guild being compared. + affinity: :class:`float` + The affinity score. + """ + + __slots__ = ('_state', 'guild_id', 'affinity') + + def __init__(self, *, state: ConnectionState, data: GuildAffinityPayload): + self._state = state + self.guild_id = int(data['guild_id']) + self.affinity = data['affinity'] + + def __repr__(self) -> str: + return f'' + + @property + def id(self) -> int: + """:class:`int`: The ID of the guild being compared.""" + return self.guild_id + + @property + def guild(self): + """Optional[:class:`Guild`]: The guild being compared.""" + return self._state._get_guild(self.guild_id) diff --git a/discord/client.py b/discord/client.py index 55d6818a0..969019451 100644 --- a/discord/client.py +++ b/discord/client.py @@ -89,6 +89,7 @@ from .guild_premium import * from .library import LibraryApplication from .relationship import Relationship from .settings import UserSettings, LegacyUserSettings, TrackingSettings, EmailSettings +from .affinity import * if TYPE_CHECKING: from typing_extensions import Self @@ -4365,6 +4366,52 @@ class Client: data = await self._connection.http.get_premium_usage() return PremiumUsage(data=data) + async def user_affinities(self) -> List[UserAffinity]: + """|coro| + + Retrieves the user affinities for the current user. + + User affinities are the users you interact with most frecently. + + .. versionadded:: 2.0 + + Raises + ------ + HTTPException + Retrieving the user affinities failed. + + Returns + ------- + List[:class:`.UserAffinity`] + The user affinities. + """ + state = self._connection + data = await state.http.get_user_affinities() + return [UserAffinity(data=d, state=state) for d in data['user_affinities']] + + async def guild_affinities(self) -> List[GuildAffinity]: + """|coro| + + Retrieves the guild affinities for the current user. + + Guild affinities are the guilds you interact with most frecently. + + .. versionadded:: 2.0 + + Raises + ------ + HTTPException + Retrieving the guild affinities failed. + + Returns + ------- + List[:class:`.GuildAffinity`] + The guild affinities. + """ + state = self._connection + data = await state.http.get_guild_affinities() + return [GuildAffinity(data=d, state=state) for d in data['guild_affinities']] + async def join_active_developer_program(self, *, application: Snowflake, channel: Snowflake) -> int: """|coro| diff --git a/discord/http.py b/discord/http.py index 811c26cd2..4b0dd700a 100644 --- a/discord/http.py +++ b/discord/http.py @@ -3822,7 +3822,13 @@ class HTTPClient: return self.request(Route('POST', '/interactions'), json=payload, form=form, files=files) - def get_country_code(self) -> Response[dict]: + def get_user_affinities(self) -> Response[user.UserAffinities]: + return self.request(Route('GET', '/users/@me/affinities/users')) + + def get_guild_affinities(self) -> Response[user.GuildAffinities]: + return self.request(Route('GET', '/users/@me/affinities/guilds')) + + def get_country_code(self) -> Response[subscriptions.CountryCode]: return self.request(Route('GET', '/users/@me/billing/country-code')) def get_library_entries( diff --git a/discord/settings.py b/discord/settings.py index 452201c1c..8afd7d7e3 100644 --- a/discord/settings.py +++ b/discord/settings.py @@ -1539,7 +1539,9 @@ class AudioContext: self.modified_at = utcnow() def __repr__(self) -> str: - return f'' + return ( + f'' + ) @classmethod def _from_settings(cls, user_id: int, *, data: Any, state: ConnectionState) -> Self: diff --git a/discord/types/subscriptions.py b/discord/types/subscriptions.py index 792abb02f..7556dead8 100644 --- a/discord/types/subscriptions.py +++ b/discord/types/subscriptions.py @@ -131,8 +131,11 @@ class SubscriptionPrice(TypedDict): exponent: int -class SubscriptionCountryPrice(TypedDict): +class CountryCode(TypedDict): country_code: str + + +class SubscriptionCountryPrice(CountryCode): prices: List[SubscriptionPrice] diff --git a/discord/types/user.py b/discord/types/user.py index c10b9c265..a3a0a73e9 100644 --- a/discord/types/user.py +++ b/discord/types/user.py @@ -128,3 +128,22 @@ class ProtoSettings(TypedDict): ProtoSettingsType = Literal[1, 2, 3] + + +class UserAffinity(TypedDict): + user_id: Snowflake + affinity: float + + +class UserAffinities(TypedDict): + user_affinities: List[UserAffinity] + inverse_user_affinities: List[UserAffinity] + + +class GuildAffinity(TypedDict): + guild_id: Snowflake + affinity: float + + +class GuildAffinities(TypedDict): + guild_affinities: List[GuildAffinity] diff --git a/docs/api.rst b/docs/api.rst index c445ac505..96834d32d 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5853,6 +5853,19 @@ User .. autoclass:: Note() :members: +Affinity +~~~~~~~~~ + +.. attributetable:: UserAffinity + +.. autoclass:: UserAffinity() + :members: + +.. attributetable:: GuildAffinity + +.. autoclass:: GuildAffinity() + :members: + Billing ~~~~~~~