From 5d2869b53cf68c43d26588c58d86fd42ae40f840 Mon Sep 17 00:00:00 2001 From: Luna Date: Fri, 1 Mar 2019 04:48:05 -0300 Subject: [PATCH] remove voice websocket implementation voice websockets are left to the voice server itself. --- litecord/voice/errors.py | 54 -------- litecord/voice/opcodes.py | 32 ----- litecord/voice/websocket.py | 198 ---------------------------- litecord/voice/websocket_starter.py | 42 ------ 4 files changed, 326 deletions(-) delete mode 100644 litecord/voice/errors.py delete mode 100644 litecord/voice/opcodes.py delete mode 100644 litecord/voice/websocket.py delete mode 100644 litecord/voice/websocket_starter.py diff --git a/litecord/voice/errors.py b/litecord/voice/errors.py deleted file mode 100644 index c30201f..0000000 --- a/litecord/voice/errors.py +++ /dev/null @@ -1,54 +0,0 @@ -""" - -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 litecord.errors import WebsocketClose - - -class UnknownOPCode(WebsocketClose): - close_code = 4000 - -class NotAuthenticated(WebsocketClose): - close_code = 4003 - -class AuthFailed(WebsocketClose): - close_code = 4004 - -class AlreadyAuth(WebsocketClose): - close_code = 4005 - -class InvalidSession(WebsocketClose): - close_code = 4006 - -class SessionTimeout(WebsocketClose): - close_code = 4009 - -class ServerNotFound(WebsocketClose): - close_code = 4011 - -class UnknownProtocol(WebsocketClose): - close_code = 4012 - -class Disconnected(WebsocketClose): - close_code = 4014 - -class VoiceServerCrash(WebsocketClose): - close_code = 4015 - -class UnknownEncryption(WebsocketClose): - close_code = 4016 diff --git a/litecord/voice/opcodes.py b/litecord/voice/opcodes.py deleted file mode 100644 index ca1ca27..0000000 --- a/litecord/voice/opcodes.py +++ /dev/null @@ -1,32 +0,0 @@ -""" - -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 . - -""" - -class VoiceOP: - identify = 0 - select_protocol = 1 - ready = 2 - heartbeat = 3 - session_description = 4 - speaking = 5 - heartbeat_ack = 6 - resume = 7 - hello = 8 - resumed = 9 - client_connect = 12 - client_disconnect = 13 diff --git a/litecord/voice/websocket.py b/litecord/voice/websocket.py deleted file mode 100644 index 91241c5..0000000 --- a/litecord/voice/websocket.py +++ /dev/null @@ -1,198 +0,0 @@ -""" - -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 . - -""" - -import json -from dataclasses import dataclass, asdict - -import websockets.exceptions -from logbook import Logger - -from litecord.voice.opcodes import VoiceOP - -from litecord.errors import WebsocketClose -from litecord.voice.errors import ( - UnknownOPCode, AuthFailed, UnknownProtocol, InvalidSession -) - -from litecord.enums import ChannelType, VOICE_CHANNELS - -log = Logger(__name__) - - -@dataclass -class VoiceState: - """Store a voice websocket's state.""" - server_id: int - user_id: int - - def __bool__(self): - as_dict = asdict(self) - return all(bool(v) for v in as_dict.values()) - - -class VoiceWebsocket: - """Voice websocket class. - - Implements the Discord Voice Websocket Protocol Version 4. - """ - def __init__(self, ws, app): - self.ws = ws - self.app = app - self.voice = app.voice - self.storage = app.storage - - self.state = None - - async def send_op(self, opcode: VoiceOP, data: dict): - """Send a message through the websocket.""" - encoded = json.dumps({ - 'op': opcode, - 'd': data - }) - - await self.ws.send(encoded) - - async def _handle_0(self, msg: dict): - """Handle OP 0 Identify.""" - data = msg['d'] - - # NOTE: there is a data.video, but we don't handle video. - try: - server_id = int(data['server_id']) - user_id = int(data['user_id']) - session_id = data['session_id'] - token = data['token'] - except (KeyError, ValueError): - raise AuthFailed('Invalid identify payload') - - # server_id can be a: - # - voice channel id - # - dm id - # - group dm id - - channel = await self.storage.get_channel(server_id) - ctype = ChannelType(channel['type']) - - if ctype not in VOICE_CHANNELS: - raise AuthFailed('invalid channel id') - - v_user_id = await self.voice.authenticate(token, session_id) - - if v_user_id != user_id: - raise AuthFailed('invalid user id') - - await self.send_op(VoiceOP.hello, { - 'v': 4, - 'heartbeat_interval': 10000, - }) - - # TODO: get ourselves a place on the voice server - place = await self.voice.get_place(server_id) - - if not place: - raise InvalidSession('invalid voice place') - - self.state = VoiceState(place.server_id, user_id) - - await self.send_op(VoiceOP.ready, { - 'ssrc': place.ssrc, - 'port': place.port, - 'modes': place.modes, - 'ip': place.ip, - }) - - async def _handle_1(self, msg: dict): - """Handle 1 Select Protocol.""" - data = msg['d'] - - try: - protocol = data['protocol'] - proto_data = data['data'] - except KeyError: - raise UnknownProtocol('invalid select protocol') - - if protocol != 'udp': - raise UnknownProtocol('invalid protocol') - - try: - client_addr = proto_data['address'] - client_port = proto_data['port'] - client_mode = proto_data['mode'] - except KeyError: - raise UnknownProtocol('incomplete protocol data') - - # signal the voice server about (address, port) + mode - session = await self.voice.register( - self.state.server_id, - client_addr, client_port, client_mode - ) - - await self.send_op(VoiceOP.session_description, { - 'video_codec': 'VP8', - 'secret_key': session.key, - 'mode': session.mode, - 'media_session_id': session.sess_id, - 'audio_codec': 'opus' - }) - - async def _handle_3(self, msg: dict): - """Handle 3 Heartbeat.""" - await self.send_op(VoiceOP.heartbeat_ack, { - 'd': msg['d'] - }) - - async def _handle_5(self, msg: dict): - """Handle 5 Speaking.""" - if not self.state: - return - - await self.voice.update(self.state, msg['d']) - - async def _handle_7(self, msg: dict): - """Handle 7 Resume.""" - pass - - async def _process_msg(self): - msg = await self.ws.recv() - msg = json.loads(msg) - op_code = msg['op'] - - try: - handler = getattr(self, f'_handle_{op_code}') - except AttributeError: - raise UnknownOPCode('Unknown OP code.') - - await handler(msg) - - async def _loop(self): - while True: - await self._process_msg() - - async def run(self): - """Main entry point for a voice websocket.""" - try: - await self._loop() - except websockets.exceptions.ConnectionClosed as err: - log.warning('conn close, state={}, err={}', self.state, err) - except WebsocketClose as err: - log.warning('ws close, state={} err={}', self.state, err) - await self.ws.close(code=err.code, reason=err.reason) - except Exception as err: - log.exception('An exception has occoured. state={}', self.state) - await self.ws.close(code=4000, reason=repr(err)) diff --git a/litecord/voice/websocket_starter.py b/litecord/voice/websocket_starter.py deleted file mode 100644 index 27640f5..0000000 --- a/litecord/voice/websocket_starter.py +++ /dev/null @@ -1,42 +0,0 @@ -""" - -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 . - -""" - -import urllib.parse -from litecord.voice.websocket import VoiceWebsocket - - -async def voice_websocket_handler(app, ws, url): - """Main handler to instantiate a VoiceWebsocket - with the given url.""" - args = urllib.parse.parse_qs( - urllib.parse.urlparse(url).query - ) - - try: - gw_version = args['v'][0] - except (KeyError, IndexError): - gw_version = '4' - - if gw_version not in ('1', '2', '3', '4'): - return await ws.close(1000, 'Invalid gateway version') - - # TODO: select a different VoiceWebsocket runner depending on the selected - # version. - vws = VoiceWebsocket(ws, app) - await vws.run()