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