From ec738cd41e4c6c8d7b2b0ee9728b3a2e0f32646a Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 1 Mar 2019 18:17:07 -0300 Subject: [PATCH] voice.manager: add some functions - add voice.state with VoiceState dataclass --- litecord/gateway/websocket.py | 7 ++-- litecord/voice/manager.py | 73 +++++++++++++++++++++++++++++++++++ litecord/voice/state.py | 52 +++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 4 deletions(-) create mode 100644 litecord/voice/state.py diff --git a/litecord/gateway/websocket.py b/litecord/gateway/websocket.py index 26fb5ef..74acee2 100644 --- a/litecord/gateway/websocket.py +++ b/litecord/gateway/websocket.py @@ -686,8 +686,8 @@ class GatewayWebsocket: return # if they can join, move the state to there. - await self.ext.voice.move_state( - self.voice_key, guild_id, channel_id) + # this will delete the old one and construct a new one. + await self.ext.voice.move_channels(self.voice_key, channel_id) async def _create_voice(self, guild_id, channel_id, _state, data): """Create a voice state.""" @@ -699,8 +699,7 @@ class GatewayWebsocket: return # if yes, create the state - await self.ext.voice.create_state( - self.voice_key, guild_id, channel_id, data) + await self.ext.voice.create_state(self.voice_key, channel_id, data) async def handle_4(self, payload: Dict[str, Any]): """Handle OP 4 Voice Status Update.""" diff --git a/litecord/voice/manager.py b/litecord/voice/manager.py index 7457623..7a5c66f 100644 --- a/litecord/voice/manager.py +++ b/litecord/voice/manager.py @@ -17,7 +17,80 @@ along with this program. If not, see . """ +from typing import Tuple +from collections import defaultdict +from dataclasses import fields + +from logbook import Logger + +from litecord.voice.state import VoiceState + + +VoiceKey = Tuple[int, int] +log = Logger(__name__) + + +def _construct_state(state_dict: dict) -> VoiceState: + """Create a VoiceState instance out of a dictionary with the + VoiceState fields as keys.""" + fields = fields(VoiceState) + args = [state_dict[field.name] for field in fields] + return VoiceState(*args) + + class VoiceManager: """Main voice manager class.""" def __init__(self, app): self.app = app + + self.states = defaultdict(dict) + + # TODO: hold voice server LVSP connections + # TODO: map channel ids to voice servers + + async def state_count(self, channel_id: int) -> int: + """Get the current amount of voice states in a channel.""" + return len(self.states[channel_id]) + + async def del_state(self, voice_key: VoiceKey): + """Delete a given voice state.""" + chan_id, user_id = voice_key + + try: + # TODO: tell that to the voice server of the channel. + self.states[chan_id].pop(user_id) + except KeyError: + pass + + async def update_state(self, voice_key: VoiceKey, prop: dict): + """Update a state in a channel""" + chan_id, user_id = voice_key + + try: + state = self.states[chan_id][user_id] + except KeyError: + return + + # construct a new state based on the old one + properties + new_state_dict = dict(state.as_json) + + for field in prop: + # NOTE: this should not happen, ever. + if field in ('channel_id', 'user_id'): + raise ValueError('properties are updating channel or user') + + new_state_dict[field] = prop[field] + + new_state = _construct_state(new_state_dict) + + # TODO: dispatch to voice server + self.states[chan_id][user_id] = new_state + + async def move_channels(self, old_voice_key: VoiceKey, channel_id: int): + """Move a user between channels.""" + await self.del_state(old_voice_key) + await self.create_state(old_voice_key, channel_id, {}) + + async def create_state(self, voice_key: VoiceKey, channel_id: int, + data: dict): + pass diff --git a/litecord/voice/state.py b/litecord/voice/state.py new file mode 100644 index 0000000..72708af --- /dev/null +++ b/litecord/voice/state.py @@ -0,0 +1,52 @@ +""" + +Litecord +Copyright (C) 2018-2019 Luna Mendes + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +""" + +from dataclasses import dataclass, asdict + + +@dataclass +class VoiceState: + """Represents a voice state.""" + channel_id: int + user_id: int + session_id: str + deaf: bool + mute: bool + self_deaf: bool + self_mute: bool + suppressed_by: int + + @property + def as_json(self): + """Return JSON-serializable dict.""" + return asdict(self) + + def as_json_for(self, user_id: int): + """Generate JSON-serializable version, given a user ID.""" + self_dict = asdict(self) + + # state.suppress is defined by the user + # that is currently viewing the state. + + # a better approach would be actually using + # the suppressed_by field for backend efficiency. + self_dict['suppress'] = user_id == self.suppressed_by + self_dict.pop('suppressed_by') + + return self_dict