diff --git a/discord/http.py b/discord/http.py index 29d52a65a..e9a616116 100644 --- a/discord/http.py +++ b/discord/http.py @@ -2167,9 +2167,7 @@ class HTTPClient: return self.request(r, json=payload, context_properties=props) - def change_friend_nickname(self, user_id, nickname): - payload = {'nickname': nickname} - + def edit_relationship(self, user_id, **payload): # TODO: return type return self.request(Route('PATCH', '/users/@me/relationships/{user_id}', user_id=user_id), json=payload) # Connections diff --git a/discord/relationship.py b/discord/relationship.py index a0296fcb0..db20f7b76 100644 --- a/discord/relationship.py +++ b/discord/relationship.py @@ -24,9 +24,12 @@ DEALINGS IN THE SOFTWARE. from __future__ import annotations +import copy from typing import Optional, TYPE_CHECKING from .enums import RelationshipAction, RelationshipType, try_enum +from .object import Object +from .utils import MISSING if TYPE_CHECKING: from .state import ConnectionState @@ -60,24 +63,38 @@ class Relationship: Attributes ----------- - nickname: Optional[:class:`str`] + nick: Optional[:class:`str`] The user's friend nickname (if applicable). + + .. versionchanged:: 2.0 + Renamed ``nickname`` to :attr:`nick`. user: :class:`User` The user you have the relationship with. type: :class:`RelationshipType` The type of relationship you have. """ - __slots__ = ('nickname', 'type', 'user', '_state') + __slots__ = ('nick', 'type', 'user', '_state') def __init__(self, *, state: ConnectionState, data) -> None: # TODO: type data self._state = state + self._update(data) + + def _update(self, data: dict) -> None: self.type: RelationshipType = try_enum(RelationshipType, data['type']) - self.user: User = state.store_user(data['user']) - self.nickname: Optional[str] = data.get('nickname', None) + self.nick: Optional[str] = data.get('nickname') + + self.user: User + if (user := data.get('user')) is not None: + self.user = self._state.store_user(user) + elif self.user: + return + else: + user_id = int(data['id']) + self.user = self._state.get_user(user_id) or Object(id=user_id) # type: ignore # Lying for better developer UX def __repr__(self) -> str: - return f'' + return f'' def __eq__(self, other: object) -> bool: return isinstance(other, Relationship) and other.user.id == self.user.id @@ -95,50 +112,81 @@ class Relationship: Deletes the relationship. + Depending on the type, this could mean unfriending or unblocking the user, + denying an incoming friend request, or discarding an outgoing friend request. + Raises ------ HTTPException Deleting the relationship failed. """ + action = RelationshipAction.deny_request if self.type is RelationshipType.friend: - await self._state.http.remove_relationship(self.user.id, action=RelationshipAction.unfriend) + action = RelationshipAction.unfriend elif self.type is RelationshipType.blocked: - await self._state.http.remove_relationship(self.user.id, action=RelationshipAction.unblock) + action = RelationshipAction.unblock elif self.type is RelationshipType.incoming_request: - await self._state.http.remove_relationship(self.user.id, action=RelationshipAction.deny_request) + action = RelationshipAction.deny_request elif self.type is RelationshipType.outgoing_request: - await self._state.http.remove_relationship(self.user.id, action=RelationshipAction.remove_pending_request) + action = RelationshipAction.remove_pending_request - async def accept(self) -> None: + await self._state.http.remove_relationship(self.user.id, action=action) + + async def accept(self) -> Relationship: """|coro| Accepts the relationship request. Only applicable for type :class:`RelationshipType.incoming_request`. + .. versionchanged:: 2.0 + Changed the return type to :class:`Relationship`. + Raises ------- HTTPException Accepting the relationship failed. + + Returns + ------- + :class:`Relationship` + The new relationship. """ - await self._state.http.add_relationship(self.user.id, action=RelationshipAction.accept_request) + data = await self._state.http.add_relationship(self.user.id, action=RelationshipAction.accept_request) + return Relationship(state=self._state, data=data) - async def change_nickname(self, nick: Optional[str]) -> None: + async def edit(self, nick: Optional[str] = MISSING) -> Relationship: """|coro| - Changes a relationship's nickname. Only applicable for - type :class:`RelationshipType.friend`. + Edits the relationship. .. versionadded:: 1.9 + .. versionchanged:: 2.0 + Changed the name of the method to :meth:`edit`. + The edit is no longer in-place. + Parameters ---------- nick: Optional[:class:`str`] - The nickname to change to. + The nickname to change to. Can be ``None`` to denote no nickname. Raises ------- HTTPException Changing the nickname failed. + + Returns + ------- + :class:`Relationship` + The new relationship. """ - await self._state.http.change_friend_nickname(self.user.id, nick) - self.nickname = nick + payload = {} + if nick is not MISSING: + payload['nick'] = nick + + await self._state.http.edit_relationship(self.user.id, **payload) + + # Emulate the return for consistency + new = copy.copy(self) + new.nick = nick if nick is not MISSING else self.nick + return new diff --git a/discord/state.py b/discord/state.py index c3780714e..39e76d014 100644 --- a/discord/state.py +++ b/discord/state.py @@ -2167,23 +2167,36 @@ class ConnectionState: def parse_relationship_add(self, data) -> None: key = int(data['id']) - old = self.user.get_relationship(key) # type: ignore # self.user is always present here - new = Relationship(state=self, data=data) - self._relationships[key] = new - if old is not None: - self.dispatch('relationship_update', old, new) + new = self._relationships.get(key) + if new is None: + relationship = Relationship(state=self, data=data) + self._relationships[key] = relationship + self.dispatch('relationship_add', relationship) else: - self.dispatch('relationship_add', new) + old = copy.copy(new) + new._update(data) + self.dispatch('relationship_update', old, new) def parse_relationship_remove(self, data) -> None: key = int(data['id']) try: old = self._relationships.pop(key) except KeyError: - pass + _log.warning('Relationship_remove referencing unknown relationship ID: %s. Discarding.', key) else: self.dispatch('relationship_remove', old) + def parse_relationship_update(self, data) -> None: + key = int(data['id']) + new = self._relationships.get(key) + if new is None: + relationship = Relationship(state=self, data=data) + self._relationships[key] = relationship + else: + old = copy.copy(new) + new._update(data) + self.dispatch('relationship_update', old, new) + def parse_interaction_create(self, data) -> None: type, name, channel = self._interaction_cache.pop(data['nonce'], (0, None, None)) i = Interaction._from_self(channel, type=type, user=self.user, name=name, **data) # type: ignore # self.user is always present here