Browse Source

Add support for querying information about group calls.

pull/276/head
Rapptz 9 years ago
parent
commit
1c8ab25917
  1. 2
      discord/__init__.py
  2. 79
      discord/calls.py
  3. 16
      discord/client.py
  4. 102
      discord/member.py
  5. 33
      discord/state.py
  6. 6
      docs/api.rst

2
discord/__init__.py

@ -25,7 +25,7 @@ from .server import Server
from .member import Member from .member import Member
from .message import Message from .message import Message
from .errors import * from .errors import *
from .calls import CallMessage from .calls import CallMessage, GroupCall
from .permissions import Permissions, PermissionOverwrite from .permissions import Permissions, PermissionOverwrite
from .role import Role from .role import Role
from .colour import Color, Colour from .colour import Color, Colour

79
discord/calls.py

@ -25,9 +25,11 @@ DEALINGS IN THE SOFTWARE.
""" """
from . import utils from . import utils
from .enums import ServerRegion, try_enum
from .member import VoiceState
class CallMessage: class CallMessage:
"""Represents a group call from Discord. """Represents a group call message from Discord.
This is only received in cases where the message type is equivalent to This is only received in cases where the message type is equivalent to
:attr:`MessageType.call`. :attr:`MessageType.call`.
@ -46,3 +48,78 @@ class CallMessage:
self.channel = channel self.channel = channel
self.ended_timestamp = utils.parse_time(kwargs.get('ended_timestamp')) self.ended_timestamp = utils.parse_time(kwargs.get('ended_timestamp'))
self.participants = kwargs.get('participants') self.participants = kwargs.get('participants')
class GroupCall:
"""Represents the actual group call from Discord.
This is accompanied with a :class:`CallMessage` denoting the information.
Attributes
-----------
message: :class:`CallMessage`
The message associated with this group call.
unavailable: bool
Denotes if this group call is unavailable.
ringing: List[:class:`User`]
A list of users that are currently being rung to join the call.
region: :class:`ServerRegion`
The server region the group call is being hosted on.
"""
def __init__(self, **kwargs):
self.message = kwargs.get('message')
self.unavailable = kwargs.get('unavailable')
self._voice_states = {}
for state in kwargs.get('voice_states', []):
self._update_voice_state(state)
self._update(**kwargs)
def _update(self, **kwargs):
self.region = try_enum(ServerRegion, kwargs.get('region'))
lookup = {u.id: u for u in self.message.channel.recipients}
self.ringing = list(filter(None, map(lambda i: lookup.get(i), kwargs.get('ringing', []))))
def _update_voice_state(self, data):
user_id = data['user_id']
# left the voice channel?
if data['channel_id'] is None:
self._voice_states.pop(user_id, None)
else:
self._voice_states[user_id] = VoiceState(**data, voice_channel=self.channel)
@property
def connected(self):
"""A property that returns the list of :class:`User` that are currently in this call."""
ret = [u for u in self.channel.recipients if self.voice_state_for(u) is not None]
me = self.channel.me
if self.voice_state_for(me) is not None:
ret.append(me)
return ret
@property
def channel(self):
""":class:`PrivateChannel`\: Returns the channel the group call is in."""
return self.message.channel
def voice_state_for(self, user):
"""Retrieves the :class:`VoiceState` for a specified :class:`User`.
If the :class:`User` has no voice state then this function returns
``None``.
Parameters
------------
user: :class:`User`
The user to retrieve the voice state for.
Returns
--------
Optiona[:class:`VoiceState`]
The voice state associated with this user.
"""
return self._voice_states.get(user.id)

16
discord/client.py

@ -2651,6 +2651,22 @@ class Client:
""" """
return self.connection._get_voice_client(server.id) return self.connection._get_voice_client(server.id)
def group_call_in(self, channel):
"""Returns the :class:`GroupCall` associated with a private channel.
If no group call is found then ``None`` is returned.
Parameters
-----------
channel: :class:`PrivateChannel`
The group private channel to query the group call for.
Returns
--------
Optional[:class:`GroupCall`]
The group call.
"""
return self.connection._calls.get(channel.id)
# Miscellaneous stuff # Miscellaneous stuff

102
discord/member.py

@ -27,9 +27,55 @@ DEALINGS IN THE SOFTWARE.
from .user import User from .user import User
from .game import Game from .game import Game
from . import utils from . import utils
from .enums import Status from .enums import Status, ChannelType
from .colour import Colour from .colour import Colour
class VoiceState:
"""Represents a Discord user's voice state.
Attributes
------------
deaf: bool
Indicates if the user is currently deafened by the server.
mute: bool
Indicates if the user is currently muted by the server.
self_mute: bool
Indicates if the user is currently muted by their own accord.
self_deaf: bool
Indicates if the user is currently deafened by their own accord.
is_afk: bool
Indicates if the user is currently in the AFK channel in the server.
voice_channel: Optional[Union[:class:`Channel`, :class:`PrivateChannel`]]
The voice channel that the user is currently connected to. None if the user
is not currently in a voice channel.
"""
__slots__ = [ 'session_id', 'deaf', 'mute', 'self_mute',
'self_deaf', 'is_afk', 'voice_channel' ]
def __init__(self, **kwargs):
self.session_id = kwargs.get('session_id')
self._update_voice_state(**kwargs)
def _update_voice_state(self, **kwargs):
self.self_mute = kwargs.get('self_mute', False)
self.self_deaf = kwargs.get('self_deaf', False)
self.is_afk = kwargs.get('suppress', False)
self.mute = kwargs.get('mute', False)
self.deaf = kwargs.get('deaf', False)
self._handle_voice_channel(kwargs.get('voice_channel'), kwargs.get('user_id'))
def _handle_voice_channel(self, voice_channel, user_id):
self.voice_channel = voice_channel
def flatten_voice_states(cls):
for attr in VoiceState.__slots__:
def getter(self, x=attr):
return getattr(self.voice, x)
setattr(cls, attr, property(getter))
return cls
@flatten_voice_states
class Member(User): class Member(User):
"""Represents a Discord member to a :class:`Server`. """Represents a Discord member to a :class:`Server`.
@ -38,19 +84,9 @@ class Member(User):
Attributes Attributes
---------- ----------
deaf : bool voice: :class:`VoiceState`
Indicates if the member is currently deafened by the server. The member's voice state. Properties are defined to mirror access of the attributes.
mute : bool e.g. ``Member.is_afk`` is equivalent to `Member.voice.is_afk``.
Indicates if the member is currently muted by the server.
self_mute : bool
Indicates if the member is currently muted by their own accord.
self_deaf : bool
Indicates if the member is currently deafened by their own accord.
is_afk : bool
Indicates if the member is currently in the AFK channel in the server.
voice_channel : :class:`Channel`
The voice channel that the member is currently connected to. None if the member
is not currently in a voice channel.
roles roles
A list of :class:`Role` that the member belongs to. Note that the first element of this A list of :class:`Role` that the member belongs to. Note that the first element of this
list is always the default '@everyone' role. list is always the default '@everyone' role.
@ -68,14 +104,11 @@ class Member(User):
The server specific nickname of the user. The server specific nickname of the user.
""" """
__slots__ = [ 'deaf', 'mute', 'self_mute', 'self_deaf', 'is_afk', __slots__ = [ 'roles', 'joined_at', 'status', 'game', 'server', 'nick', 'voice' ]
'voice_channel', 'roles', 'joined_at', 'status', 'game',
'server', 'nick' ]
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs.get('user')) super().__init__(**kwargs.get('user'))
self.deaf = kwargs.get('deaf') self.voice = VoiceState(**kwargs)
self.mute = kwargs.get('mute')
self.joined_at = utils.parse_time(kwargs.get('joined_at')) self.joined_at = utils.parse_time(kwargs.get('joined_at'))
self.roles = kwargs.get('roles', []) self.roles = kwargs.get('roles', [])
self.status = Status.offline self.status = Status.offline
@ -83,14 +116,33 @@ class Member(User):
self.game = Game(**game) if game else None self.game = Game(**game) if game else None
self.server = kwargs.get('server', None) self.server = kwargs.get('server', None)
self.nick = kwargs.get('nick', None) self.nick = kwargs.get('nick', None)
self._update_voice_state(mute=self.mute, deaf=self.deaf)
def _update_voice_state(self, **kwargs): def _update_voice_state(self, **kwargs):
self.self_mute = kwargs.get('self_mute', False) self.voice.self_mute = kwargs.get('self_mute', False)
self.self_deaf = kwargs.get('self_deaf', False) self.voice.self_deaf = kwargs.get('self_deaf', False)
self.is_afk = kwargs.get('suppress', False) self.voice.is_afk = kwargs.get('suppress', False)
self.mute = kwargs.get('mute', False) self.voice.mute = kwargs.get('mute', False)
self.deaf = kwargs.get('deaf', False) self.voice.deaf = kwargs.get('deaf', False)
old_channel = getattr(self, 'voice_channel', None)
vc = kwargs.get('voice_channel')
if old_channel is None and vc is not None:
# we joined a channel
vc.voice_members.append(self)
elif old_channel is not None:
try:
# we either left a channel or we switched channels
old_channel.voice_members.remove(self)
except ValueError:
pass
finally:
# we switched channels
if vc is not None:
vc.voice_members.append(self)
self.voice.voice_channel = vc
def _handle_voice_channel(self, voice_channel, user_id):
old_channel = getattr(self, 'voice_channel', None) old_channel = getattr(self, 'voice_channel', None)
self.voice_channel = kwargs.get('voice_channel') self.voice_channel = kwargs.get('voice_channel')

33
discord/state.py

@ -33,7 +33,7 @@ from .member import Member
from .role import Role from .role import Role
from . import utils, compat from . import utils, compat
from .enums import Status, ChannelType, try_enum from .enums import Status, ChannelType, try_enum
from .calls import GroupCall
from collections import deque, namedtuple from collections import deque, namedtuple
import copy, enum, math import copy, enum, math
@ -63,6 +63,7 @@ class ConnectionState:
self.user = None self.user = None
self.sequence = None self.sequence = None
self.session_id = None self.session_id = None
self._calls = {}
self._servers = {} self._servers = {}
self._voice_clients = {} self._voice_clients = {}
self._private_channels = {} self._private_channels = {}
@ -563,16 +564,21 @@ class ConnectionState:
def parse_voice_state_update(self, data): def parse_voice_state_update(self, data):
server = self._get_server(data.get('guild_id')) server = self._get_server(data.get('guild_id'))
user_id = data.get('user_id')
if server is not None: if server is not None:
if user_id == self.user.id: channel = server.get_channel(data.get('channel_id'))
if data.get('user_id') == self.user.id:
voice = self._get_voice_client(server.id) voice = self._get_voice_client(server.id)
if voice is not None: if voice is not None:
voice.channel = server.get_channel(data.get('channel_id')) voice.channel = channel
before, after = server._update_voice_state(data) before, after = server._update_voice_state(data)
if after is not None: if after is not None:
self.dispatch('voice_state_update', before, after) self.dispatch('voice_state_update', before, after)
else:
# in here we're either at private or group calls
call = self._calls.get(data.get('channel_id'), None)
if call is not None:
call._update_voice_state(data)
def parse_typing_start(self, data): def parse_typing_start(self, data):
channel = self.get_channel(data.get('channel_id')) channel = self.get_channel(data.get('channel_id'))
@ -592,6 +598,25 @@ class ConnectionState:
timestamp = datetime.datetime.utcfromtimestamp(data.get('timestamp')) timestamp = datetime.datetime.utcfromtimestamp(data.get('timestamp'))
self.dispatch('typing', channel, member, timestamp) self.dispatch('typing', channel, member, timestamp)
def parse_call_create(self, data):
message = self._get_message(data.get('message_id'))
if message is not None:
call = GroupCall(message=message, **data)
self._calls[data['channel_id']] = call
self.dispatch('call', call)
def parse_call_update(self, data):
call = self._calls.get(data.get('channel_id'), None)
if call is not None:
before = copy.copy(call)
call._update(**data)
self.dispatch('call_update', before, call)
def parse_call_delete(self, data):
call = self._calls.pop(data.get('channel_id'), None)
if call is not None:
self.dispatch('call_remove', call)
def get_channel(self, id): def get_channel(self, id):
if id is None: if id is None:
return None return None

6
docs/api.rst

@ -543,6 +543,12 @@ CallMessage
.. autoclass:: CallMessage .. autoclass:: CallMessage
:members: :members:
GroupCall
~~~~~~~~~~
.. autoclass:: GroupCall
:members:
Server Server
~~~~~~ ~~~~~~

Loading…
Cancel
Save