Add support for relationships.
This commit is contained in:
parent
e2acf7b2b9
commit
4c981ee631
@ -23,6 +23,7 @@ from .game import Game
|
|||||||
from .emoji import Emoji, PartialEmoji
|
from .emoji import Emoji, PartialEmoji
|
||||||
from .channel import *
|
from .channel import *
|
||||||
from .guild import Guild
|
from .guild import Guild
|
||||||
|
from .relationship import Relationship
|
||||||
from .member import Member, VoiceState
|
from .member import Member, VoiceState
|
||||||
from .message import Message
|
from .message import Message
|
||||||
from .errors import *
|
from .errors import *
|
||||||
|
@ -96,6 +96,12 @@ class DefaultAvatar(Enum):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
class RelationshipType(Enum):
|
||||||
|
friend = 1
|
||||||
|
blocked = 2
|
||||||
|
incoming_request = 3
|
||||||
|
outgoing_request = 4
|
||||||
|
|
||||||
def try_enum(cls, val):
|
def try_enum(cls, val):
|
||||||
"""A function that tries to turn the value into enum ``cls``.
|
"""A function that tries to turn the value into enum ``cls``.
|
||||||
|
|
||||||
|
@ -618,6 +618,29 @@ class HTTPClient:
|
|||||||
def move_member(self, user_id, guild_id, channel_id):
|
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)
|
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
|
# Misc
|
||||||
|
|
||||||
def application_info(self):
|
def application_info(self):
|
||||||
|
@ -25,11 +25,12 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import itertools
|
||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .user import BaseUser
|
from .user import BaseUser, User
|
||||||
from .game import Game
|
from .game import Game
|
||||||
from .permissions import Permissions
|
from .permissions import Permissions
|
||||||
from .enums import Status, ChannelType, try_enum
|
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)
|
return '<VoiceState self_mute={0.self_mute} self_deaf={0.self_deaf} channel={0.channel!r}>'.format(self)
|
||||||
|
|
||||||
def flatten_user(cls):
|
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
|
# ignore private/special methods
|
||||||
if attr.startswith('_'):
|
if attr.startswith('_'):
|
||||||
continue
|
continue
|
||||||
|
82
discord/relationship.py
Normal file
82
discord/relationship.py
Normal file
@ -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)
|
@ -30,6 +30,7 @@ from .game import Game
|
|||||||
from .emoji import Emoji, PartialEmoji
|
from .emoji import Emoji, PartialEmoji
|
||||||
from .reaction import Reaction
|
from .reaction import Reaction
|
||||||
from .message import Message
|
from .message import Message
|
||||||
|
from .relationship import Relationship
|
||||||
from .channel import *
|
from .channel import *
|
||||||
from .member import Member
|
from .member import Member
|
||||||
from .role import Role
|
from .role import Role
|
||||||
@ -247,6 +248,14 @@ class ConnectionState:
|
|||||||
if not self.is_bot or guild.large:
|
if not self.is_bot or guild.large:
|
||||||
guilds.append(guild)
|
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', []):
|
for pm in data.get('private_channels', []):
|
||||||
factory, _ = _channel_factory(pm['type'])
|
factory, _ = _channel_factory(pm['type'])
|
||||||
self._add_private_channel(factory(me=self.user, data=pm, state=self))
|
self._add_private_channel(factory(me=self.user, data=pm, state=self))
|
||||||
@ -663,6 +672,25 @@ class ConnectionState:
|
|||||||
if call is not None:
|
if call is not None:
|
||||||
self.dispatch('call_remove', call)
|
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):
|
def _get_reaction_user(self, channel, user_id):
|
||||||
if isinstance(channel, DMChannel) and user_id == channel.recipient.id:
|
if isinstance(channel, DMChannel) and user_id == channel.recipient.id:
|
||||||
return channel.recipient
|
return channel.recipient
|
||||||
@ -761,7 +789,7 @@ class AutoShardedConnectionState(ConnectionState):
|
|||||||
if not hasattr(self, '_ready_state'):
|
if not hasattr(self, '_ready_state'):
|
||||||
self._ready_state = ReadyState(launch=asyncio.Event(), guilds=[])
|
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
|
guilds = self._ready_state.guilds
|
||||||
for guild_data in data['guilds']:
|
for guild_data in data['guilds']:
|
||||||
|
@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from .utils import snowflake_time, _bytes_to_base64_data
|
from .utils import snowflake_time, _bytes_to_base64_data
|
||||||
from .enums import DefaultAvatar
|
from .enums import DefaultAvatar, RelationshipType
|
||||||
from .errors import ClientException
|
from .errors import ClientException
|
||||||
|
|
||||||
import discord.abc
|
import discord.abc
|
||||||
@ -174,7 +174,7 @@ class ClientUser(BaseUser):
|
|||||||
premium: bool
|
premium: bool
|
||||||
Specifies if the user is a premium user (e.g. has Discord Nitro).
|
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):
|
def __init__(self, *, state, data):
|
||||||
super().__init__(state=state, data=data)
|
super().__init__(state=state, data=data)
|
||||||
@ -182,12 +182,33 @@ class ClientUser(BaseUser):
|
|||||||
self.email = data.get('email')
|
self.email = data.get('email')
|
||||||
self.mfa_enabled = data.get('mfa_enabled', False)
|
self.mfa_enabled = data.get('mfa_enabled', False)
|
||||||
self.premium = data.get('premium', False)
|
self.premium = data.get('premium', False)
|
||||||
|
self._relationships = {}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<ClientUser id={0.id} name={0.name!r} discriminator={0.discriminator!r}' \
|
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)
|
' 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
|
@asyncio.coroutine
|
||||||
def edit(self, **fields):
|
def edit(self, **fields):
|
||||||
"""|coro|
|
"""|coro|
|
||||||
@ -337,3 +358,69 @@ class User(BaseUser, discord.abc.Messageable):
|
|||||||
state = self._state
|
state = self._state
|
||||||
data = yield from state.http.start_private_message(self.id)
|
data = yield from state.http.start_private_message(self.id)
|
||||||
return state.add_dm_channel(data)
|
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)
|
||||||
|
39
docs/api.rst
39
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 channel: The group that the user joined or left.
|
||||||
:param user: The user that 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:
|
.. _discord-api-utils:
|
||||||
|
|
||||||
Utility Functions
|
Utility Functions
|
||||||
@ -607,6 +623,23 @@ All enumerations are subclasses of `enum`_.
|
|||||||
a presence a la :meth:`Client.change_presence`. When you receive a
|
a presence a la :meth:`Client.change_presence`. When you receive a
|
||||||
user's presence this will be :attr:`offline` instead.
|
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:
|
.. _discord_api_data:
|
||||||
|
|
||||||
Data Classes
|
Data Classes
|
||||||
@ -652,6 +685,12 @@ ClientUser
|
|||||||
:members:
|
:members:
|
||||||
:inherited-members:
|
:inherited-members:
|
||||||
|
|
||||||
|
Relationship
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. autoclass:: Relationship
|
||||||
|
:members:
|
||||||
|
|
||||||
User
|
User
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user