aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRapptz <[email protected]>2017-01-20 19:26:56 -0500
committerRapptz <[email protected]>2017-01-20 19:28:43 -0500
commit4c981ee6315f43b28d99a84851d3f6138738a77f (patch)
tree18c252f0c09b3eade7964d5c4941f52c7dc9906a
parentAdd ClientUser.premium boolean. (diff)
downloaddiscord.py-4c981ee6315f43b28d99a84851d3f6138738a77f.tar.xz
discord.py-4c981ee6315f43b28d99a84851d3f6138738a77f.zip
Add support for relationships.
-rw-r--r--discord/__init__.py1
-rw-r--r--discord/enums.py6
-rw-r--r--discord/http.py23
-rw-r--r--discord/member.py5
-rw-r--r--discord/relationship.py82
-rw-r--r--discord/state.py30
-rw-r--r--discord/user.py91
-rw-r--r--docs/api.rst39
8 files changed, 272 insertions, 5 deletions
diff --git a/discord/__init__.py b/discord/__init__.py
index f8a4eac2..9624f2e9 100644
--- a/discord/__init__.py
+++ b/discord/__init__.py
@@ -23,6 +23,7 @@ from .game import Game
from .emoji import Emoji, PartialEmoji
from .channel import *
from .guild import Guild
+from .relationship import Relationship
from .member import Member, VoiceState
from .message import Message
from .errors import *
diff --git a/discord/enums.py b/discord/enums.py
index 8e4ebc50..f0acff11 100644
--- a/discord/enums.py
+++ b/discord/enums.py
@@ -96,6 +96,12 @@ class DefaultAvatar(Enum):
def __str__(self):
return self.name
+class RelationshipType(Enum):
+ friend = 1
+ blocked = 2
+ incoming_request = 3
+ outgoing_request = 4
+
def try_enum(cls, val):
"""A function that tries to turn the value into enum ``cls``.
diff --git a/discord/http.py b/discord/http.py
index e4d48690..db6b2969 100644
--- a/discord/http.py
+++ b/discord/http.py
@@ -618,6 +618,29 @@ class HTTPClient:
def move_member(self, user_id, guild_id, channel_id):
return self.edit_member(guild_id=guild_id, user_id=user_id, channel_id=channel_id)
+
+ # Relationship related
+
+ def remove_relationship(self, user_id):
+ r = Route('DELETE', '/users/@me/relationships/{user_id}', user_id=user_id)
+ return self.request(r)
+
+ def add_relationship(self, user_id, type=None):
+ r = Route('PUT', '/users/@me/relationships/{user_id}', user_id=user_id)
+ payload = {}
+ if type is not None:
+ payload['type'] = type
+
+ return self.request(r, json=payload)
+
+ def send_friend_request(self, username, discriminator):
+ r = Route('POST', '/users/@me/relationships')
+ payload = {
+ 'username': username,
+ 'discriminator': int(discriminator)
+ }
+ return self.request(r, json=payload)
+
# Misc
def application_info(self):
diff --git a/discord/member.py b/discord/member.py
index 2cc5a92d..4c3cf4ab 100644
--- a/discord/member.py
+++ b/discord/member.py
@@ -25,11 +25,12 @@ DEALINGS IN THE SOFTWARE.
"""
import asyncio
+import itertools
import discord.abc
from . import utils
-from .user import BaseUser
+from .user import BaseUser, User
from .game import Game
from .permissions import Permissions
from .enums import Status, ChannelType, try_enum
@@ -74,7 +75,7 @@ class VoiceState:
return '<VoiceState self_mute={0.self_mute} self_deaf={0.self_deaf} channel={0.channel!r}>'.format(self)
def flatten_user(cls):
- for attr, value in BaseUser.__dict__.items():
+ for attr, value in itertools.chain(BaseUser.__dict__.items(), User.__dict__.items()):
# ignore private/special methods
if attr.startswith('_'):
continue
diff --git a/discord/relationship.py b/discord/relationship.py
new file mode 100644
index 00000000..a9132aee
--- /dev/null
+++ b/discord/relationship.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+
+"""
+The MIT License (MIT)
+
+Copyright (c) 2015-2016 Rapptz
+
+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 .enums import RelationshipType, try_enum
+
+import asyncio
+
+class Relationship:
+ """Represents a relationship in Discord.
+
+ A relationship is like a friendship, a person who is blocked, etc.
+ Only non-bot accounts can have relationships.
+
+ Attributes
+ -----------
+ user: :class:`User`
+ The user you have the relationship with.
+ type: :class:`RelationshipType`
+ The type of relationship you have.
+ """
+
+ __slots__ = ('type', 'user', '_state')
+
+ def __init__(self, *, state, data):
+ self._state = state
+ self.type = try_enum(RelationshipType, data['type'])
+ self.user = state.store_user(data['user'])
+
+ def __repr__(self):
+ return '<Relationship user={0.user!r} type={0.type!r}>'.format(self)
+
+ @asyncio.coroutine
+ def delete(self):
+ """|coro|
+
+ Deletes the relationship.
+
+ Raises
+ ------
+ HTTPException
+ Deleting the relationship failed.
+ """
+
+ yield from self._state.http.remove_relationship(self.user.id)
+
+ @asyncio.coroutine
+ def accept(self):
+ """|coro|
+
+ Accepts the relationship request. e.g. accepting a
+ friend request.
+
+ Raises
+ -------
+ HTTPException
+ Accepting the relationship failed.
+ """
+
+ yield from self._state.http.add_relationship(self.user.id)
diff --git a/discord/state.py b/discord/state.py
index f68656cc..542d1848 100644
--- a/discord/state.py
+++ b/discord/state.py
@@ -30,6 +30,7 @@ from .game import Game
from .emoji import Emoji, PartialEmoji
from .reaction import Reaction
from .message import Message
+from .relationship import Relationship
from .channel import *
from .member import Member
from .role import Role
@@ -247,6 +248,14 @@ class ConnectionState:
if not self.is_bot or guild.large:
guilds.append(guild)
+ for relationship in data.get('relationships', []):
+ try:
+ r_id = int(relationship['id'])
+ except KeyError:
+ continue
+ else:
+ self.user._relationships[r_id] = Relationship(state=self, data=relationship)
+
for pm in data.get('private_channels', []):
factory, _ = _channel_factory(pm['type'])
self._add_private_channel(factory(me=self.user, data=pm, state=self))
@@ -663,6 +672,25 @@ class ConnectionState:
if call is not None:
self.dispatch('call_remove', call)
+ def parse_relationship_add(self, data):
+ key = int(data['id'])
+ old = self.user.get_relationship(key)
+ new = Relationship(state=self, data=data)
+ self.user._relationships[key] = new
+ if old is not None:
+ self.dispatch('relationship_update', old, new)
+ else:
+ self.dispatch('relationship_add', new)
+
+ def parse_relationship_remove(self, data):
+ key = int(data['id'])
+ try:
+ old = self.user._relationships.pop(key)
+ except KeyError:
+ pass
+ else:
+ self.dispatch('relationship_remove', old)
+
def _get_reaction_user(self, channel, user_id):
if isinstance(channel, DMChannel) and user_id == channel.recipient.id:
return channel.recipient
@@ -761,7 +789,7 @@ class AutoShardedConnectionState(ConnectionState):
if not hasattr(self, '_ready_state'):
self._ready_state = ReadyState(launch=asyncio.Event(), guilds=[])
- self.user = self.store_user(data['user'])
+ self.user = ClientUser(state=self, data=data['user'])
guilds = self._ready_state.guilds
for guild_data in data['guilds']:
diff --git a/discord/user.py b/discord/user.py
index 9bd9fb58..f097ddab 100644
--- a/discord/user.py
+++ b/discord/user.py
@@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
"""
from .utils import snowflake_time, _bytes_to_base64_data
-from .enums import DefaultAvatar
+from .enums import DefaultAvatar, RelationshipType
from .errors import ClientException
import discord.abc
@@ -174,7 +174,7 @@ class ClientUser(BaseUser):
premium: bool
Specifies if the user is a premium user (e.g. has Discord Nitro).
"""
- __slots__ = ('email', 'verified', 'mfa_enabled', 'premium')
+ __slots__ = ('email', 'verified', 'mfa_enabled', 'premium', '_relationships')
def __init__(self, *, state, data):
super().__init__(state=state, data=data)
@@ -182,12 +182,33 @@ class ClientUser(BaseUser):
self.email = data.get('email')
self.mfa_enabled = data.get('mfa_enabled', False)
self.premium = data.get('premium', False)
+ self._relationships = {}
def __repr__(self):
return '<ClientUser id={0.id} name={0.name!r} discriminator={0.discriminator!r}' \
' bot={0.bot} verified={0.verified} mfa_enabled={0.mfa_enabled}>'.format(self)
+ def get_relationship(self, user_id):
+ """Retrieves the :class:`Relationship` if applicable.
+
+ Parameters
+ -----------
+ user_id: int
+ The user ID to check if we have a relationship with them.
+
+ Returns
+ --------
+ Optional[:class:`Relationship`]
+ The relationship if available or ``None``
+ """
+ return self._relationships.get(user_id)
+
+ @property
+ def relationships(self):
+ """Returns a list of :class:`Relationship` that the user has."""
+ return list(self._relationships.values())
+
@asyncio.coroutine
def edit(self, **fields):
"""|coro|
@@ -337,3 +358,69 @@ class User(BaseUser, discord.abc.Messageable):
state = self._state
data = yield from state.http.start_private_message(self.id)
return state.add_dm_channel(data)
+
+ @property
+ def relationship(self):
+ """Returns the :class:`Relationship` with this user if applicable, ``None`` otherwise."""
+ return self._state.user.get_relationship(self.id)
+
+ @asyncio.coroutine
+ def block(self):
+ """|coro|
+
+ Blocks the user.
+
+ Raises
+ -------
+ Forbidden
+ Not allowed to block this user.
+ HTTPException
+ Blocking the user failed.
+ """
+
+ yield from self._state.http.add_relationship(self.id, type=RelationshipType.blocked.value)
+
+ @asyncio.coroutine
+ def unblock(self):
+ """|coro|
+
+ Unblocks the user.
+
+ Raises
+ -------
+ Forbidden
+ Not allowed to unblock this user.
+ HTTPException
+ Unblocking the user failed.
+ """
+ yield from self._state.http.remove_relationship(self.id)
+
+ @asyncio.coroutine
+ def remove_friend(self):
+ """|coro|
+
+ Removes the user as a friend.
+
+ Raises
+ -------
+ Forbidden
+ Not allowed to remove this user as a friend.
+ HTTPException
+ Removing the user as a friend failed.
+ """
+ yield from self._state.http.remove_relationship(self.id)
+
+ @asyncio.coroutine
+ def send_friend_request(self):
+ """|coro|
+
+ Sends the user a friend request.
+
+ Raises
+ -------
+ Forbidden
+ Not allowed to send a friend request to the user.
+ HTTPException
+ Sending the friend request failed.
+ """
+ yield from self._state.http.send_friend_request(username=self.name, discriminator=self.discriminator)
diff --git a/docs/api.rst b/docs/api.rst
index 7ab4c0b5..f8e0e8b5 100644
--- a/docs/api.rst
+++ b/docs/api.rst
@@ -409,6 +409,22 @@ to handle it, which defaults to print a traceback and ignore the exception.
:param channel: The group that the user joined or left.
:param user: The user that joined or left.
+.. function:: on_relationship_add(relationship)
+ on_relationship_remove(relationship)
+
+ Called when a :class:`Relationship` is added or removed from the
+ :class:`ClientUser`.
+
+ :param relationship: The relationship that was added or removed.
+
+.. function:: on_relationship_update(before, after)
+
+ Called when a :class:`Relationship` is updated, e.g. when you
+ block a friend or a friendship is accepted.
+
+ :param before: The previous relationship status.
+ :param after: The updated relationship status.
+
.. _discord-api-utils:
Utility Functions
@@ -607,6 +623,23 @@ All enumerations are subclasses of `enum`_.
a presence a la :meth:`Client.change_presence`. When you receive a
user's presence this will be :attr:`offline` instead.
+.. class:: RelationshipType
+
+ Specifies the type of :class:`Relationship`
+
+ .. attribute:: friend
+
+ You are friends with this user.
+ .. attribute:: blocked
+
+ You have blocked this user.
+ .. attribute:: incoming_request
+
+ The user has sent you a friend request.
+ .. attribute:: outgoing_request
+
+ You have sent a friend request to this user.
+
.. _discord_api_data:
Data Classes
@@ -652,6 +685,12 @@ ClientUser
:members:
:inherited-members:
+Relationship
+~~~~~~~~~~~~~~
+
+.. autoclass:: Relationship
+ :members:
+
User
~~~~~