Add support for querying information about group calls.
This commit is contained in:
		| @@ -25,7 +25,7 @@ from .server import Server | ||||
| from .member import Member | ||||
| from .message import Message | ||||
| from .errors import * | ||||
| from .calls import CallMessage | ||||
| from .calls import CallMessage, GroupCall | ||||
| from .permissions import Permissions, PermissionOverwrite | ||||
| from .role import Role | ||||
| from .colour import Color, Colour | ||||
|   | ||||
| @@ -25,9 +25,11 @@ DEALINGS IN THE SOFTWARE. | ||||
| """ | ||||
|  | ||||
| from . import utils | ||||
| from .enums import ServerRegion, try_enum | ||||
| from .member import VoiceState | ||||
|  | ||||
| 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 | ||||
|     :attr:`MessageType.call`. | ||||
| @@ -46,3 +48,78 @@ class CallMessage: | ||||
|         self.channel = channel | ||||
|         self.ended_timestamp = utils.parse_time(kwargs.get('ended_timestamp')) | ||||
|         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) | ||||
|  | ||||
|   | ||||
| @@ -2651,6 +2651,22 @@ class Client: | ||||
|         """ | ||||
|         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 | ||||
|  | ||||
|   | ||||
| @@ -27,9 +27,55 @@ DEALINGS IN THE SOFTWARE. | ||||
| from .user import User | ||||
| from .game import Game | ||||
| from . import utils | ||||
| from .enums import Status | ||||
| from .enums import Status, ChannelType | ||||
| 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): | ||||
|     """Represents a Discord member to a :class:`Server`. | ||||
|  | ||||
| @@ -38,19 +84,9 @@ class Member(User): | ||||
|  | ||||
|     Attributes | ||||
|     ---------- | ||||
|     deaf : bool | ||||
|         Indicates if the member is currently deafened by the server. | ||||
|     mute : bool | ||||
|         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. | ||||
|     voice: :class:`VoiceState` | ||||
|         The member's voice state. Properties are defined to mirror access of the attributes. | ||||
|         e.g. ``Member.is_afk`` is equivalent to `Member.voice.is_afk``. | ||||
|     roles | ||||
|         A list of :class:`Role` that the member belongs to. Note that the first element of this | ||||
|         list is always the default '@everyone' role. | ||||
| @@ -68,14 +104,11 @@ class Member(User): | ||||
|         The server specific nickname of the user. | ||||
|     """ | ||||
|  | ||||
|     __slots__ = [ 'deaf', 'mute', 'self_mute', 'self_deaf', 'is_afk', | ||||
|                   'voice_channel', 'roles', 'joined_at', 'status', 'game', | ||||
|                   'server', 'nick' ] | ||||
|     __slots__ = [ 'roles', 'joined_at', 'status', 'game', 'server', 'nick', 'voice' ] | ||||
|  | ||||
|     def __init__(self, **kwargs): | ||||
|         super().__init__(**kwargs.get('user')) | ||||
|         self.deaf = kwargs.get('deaf') | ||||
|         self.mute = kwargs.get('mute') | ||||
|         self.voice = VoiceState(**kwargs) | ||||
|         self.joined_at = utils.parse_time(kwargs.get('joined_at')) | ||||
|         self.roles = kwargs.get('roles', []) | ||||
|         self.status = Status.offline | ||||
| @@ -83,14 +116,33 @@ class Member(User): | ||||
|         self.game = Game(**game) if game else None | ||||
|         self.server = kwargs.get('server', None) | ||||
|         self.nick = kwargs.get('nick', None) | ||||
|         self._update_voice_state(mute=self.mute, deaf=self.deaf) | ||||
|  | ||||
|     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.voice.self_mute = kwargs.get('self_mute', False) | ||||
|         self.voice.self_deaf = kwargs.get('self_deaf', False) | ||||
|         self.voice.is_afk = kwargs.get('suppress', False) | ||||
|         self.voice.mute = kwargs.get('mute', 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) | ||||
|         self.voice_channel = kwargs.get('voice_channel') | ||||
|  | ||||
|   | ||||
| @@ -33,7 +33,7 @@ from .member import Member | ||||
| from .role import Role | ||||
| from . import utils, compat | ||||
| from .enums import Status, ChannelType, try_enum | ||||
|  | ||||
| from .calls import GroupCall | ||||
|  | ||||
| from collections import deque, namedtuple | ||||
| import copy, enum, math | ||||
| @@ -63,6 +63,7 @@ class ConnectionState: | ||||
|         self.user = None | ||||
|         self.sequence = None | ||||
|         self.session_id = None | ||||
|         self._calls = {} | ||||
|         self._servers = {} | ||||
|         self._voice_clients = {} | ||||
|         self._private_channels = {} | ||||
| @@ -563,16 +564,21 @@ class ConnectionState: | ||||
|  | ||||
|     def parse_voice_state_update(self, data): | ||||
|         server = self._get_server(data.get('guild_id')) | ||||
|         user_id = data.get('user_id') | ||||
|         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) | ||||
|                 if voice is not None: | ||||
|                     voice.channel = server.get_channel(data.get('channel_id')) | ||||
|                     voice.channel = channel | ||||
|  | ||||
|             before, after = server._update_voice_state(data) | ||||
|             if after is not None: | ||||
|                 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): | ||||
|         channel = self.get_channel(data.get('channel_id')) | ||||
| @@ -592,6 +598,25 @@ class ConnectionState: | ||||
|                 timestamp = datetime.datetime.utcfromtimestamp(data.get('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): | ||||
|         if id is None: | ||||
|             return None | ||||
|   | ||||
		Reference in New Issue
	
	Block a user