Fix voice connection issues and upgrade to voice v8

This commit is contained in:
DA344 2025-06-30 09:14:23 +02:00 committed by GitHub
parent 20055e7cc1
commit 2175bd51c0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 7 deletions

View File

@ -212,6 +212,9 @@ class KeepAliveHandler(threading.Thread):
class VoiceKeepAliveHandler(KeepAliveHandler): class VoiceKeepAliveHandler(KeepAliveHandler):
if TYPE_CHECKING:
ws: DiscordVoiceWebSocket
def __init__(self, *args: Any, **kwargs: Any) -> None: def __init__(self, *args: Any, **kwargs: Any) -> None:
name: str = kwargs.pop('name', f'voice-keep-alive-handler:{id(self):#x}') name: str = kwargs.pop('name', f'voice-keep-alive-handler:{id(self):#x}')
super().__init__(*args, name=name, **kwargs) super().__init__(*args, name=name, **kwargs)
@ -223,7 +226,10 @@ class VoiceKeepAliveHandler(KeepAliveHandler):
def get_payload(self) -> Dict[str, Any]: def get_payload(self) -> Dict[str, Any]:
return { return {
'op': self.ws.HEARTBEAT, 'op': self.ws.HEARTBEAT,
'd': int(time.time() * 1000), 'd': {
't': int(time.time() * 1000),
'seq_ack': self.ws.seq_ack,
},
} }
def ack(self) -> None: def ack(self) -> None:
@ -830,6 +836,8 @@ class DiscordVoiceWebSocket:
self._keep_alive: Optional[VoiceKeepAliveHandler] = None self._keep_alive: Optional[VoiceKeepAliveHandler] = None
self._close_code: Optional[int] = None self._close_code: Optional[int] = None
self.secret_key: Optional[List[int]] = None self.secret_key: Optional[List[int]] = None
# defaulting to -1
self.seq_ack: int = -1
if hook: if hook:
self._hook = hook # type: ignore self._hook = hook # type: ignore
@ -850,6 +858,7 @@ class DiscordVoiceWebSocket:
'token': state.token, 'token': state.token,
'server_id': str(state.server_id), 'server_id': str(state.server_id),
'session_id': state.session_id, 'session_id': state.session_id,
'seq_ack': self.seq_ack,
}, },
} }
await self.send_as_json(payload) await self.send_as_json(payload)
@ -874,14 +883,16 @@ class DiscordVoiceWebSocket:
*, *,
resume: bool = False, resume: bool = False,
hook: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None, hook: Optional[Callable[..., Coroutine[Any, Any, Any]]] = None,
seq_ack: int = -1,
) -> Self: ) -> Self:
"""Creates a voice websocket for the :class:`VoiceClient`.""" """Creates a voice websocket for the :class:`VoiceClient`."""
gateway = f'wss://{state.endpoint}/?v=4' gateway = f'wss://{state.endpoint}/?v=8'
client = state.voice_client client = state.voice_client
http = client._state.http http = client._state.http
socket = await http.ws_connect(gateway, compress=15) socket = await http.ws_connect(gateway, compress=15)
ws = cls(socket, loop=client.loop, hook=hook) ws = cls(socket, loop=client.loop, hook=hook)
ws.gateway = gateway ws.gateway = gateway
ws.seq_ack = seq_ack
ws._connection = state ws._connection = state
ws._max_heartbeat_timeout = 60.0 ws._max_heartbeat_timeout = 60.0
ws.thread_id = threading.get_ident() ws.thread_id = threading.get_ident()
@ -934,6 +945,7 @@ class DiscordVoiceWebSocket:
_log.debug('Voice websocket frame received: %s', msg) _log.debug('Voice websocket frame received: %s', msg)
op = msg['op'] op = msg['op']
data = msg['d'] # According to Discord this key is always given data = msg['d'] # According to Discord this key is always given
self.seq_ack = msg.get('seq', self.seq_ack) # this key could not be given
if op == self.READY: if op == self.READY:
await self.initial_connection(data) await self.initial_connection(data)

View File

@ -321,7 +321,7 @@ class VoiceConnectionState:
) )
return return
self.endpoint, _, _ = endpoint.rpartition(':') self.endpoint = endpoint
if self.endpoint.startswith('wss://'): if self.endpoint.startswith('wss://'):
# Just in case, strip it off since we're going to add it later # Just in case, strip it off since we're going to add it later
self.endpoint = self.endpoint[6:] self.endpoint = self.endpoint[6:]
@ -574,7 +574,10 @@ class VoiceConnectionState:
self._disconnected.clear() self._disconnected.clear()
async def _connect_websocket(self, resume: bool) -> DiscordVoiceWebSocket: async def _connect_websocket(self, resume: bool) -> DiscordVoiceWebSocket:
ws = await DiscordVoiceWebSocket.from_connection_state(self, resume=resume, hook=self.hook) seq_ack = -1
if self.ws is not MISSING:
seq_ack = self.ws.seq_ack
ws = await DiscordVoiceWebSocket.from_connection_state(self, resume=resume, hook=self.hook, seq_ack=seq_ack)
self.state = ConnectionFlowState.websocket_connected self.state = ConnectionFlowState.websocket_connected
return ws return ws
@ -603,15 +606,17 @@ class VoiceConnectionState:
# The following close codes are undocumented so I will document them here. # The following close codes are undocumented so I will document them here.
# 1000 - normal closure (obviously) # 1000 - normal closure (obviously)
# 4014 - we were externally disconnected (voice channel deleted, we were moved, etc) # 4014 - we were externally disconnected (voice channel deleted, we were moved, etc)
# 4015 - voice server has crashed # 4015 - voice server has crashed, we should resume
if exc.code in (1000, 4015): # 4021 - rate limited, we should not reconnect
# 4022 - call terminated, similar to 4014
if exc.code == 1000:
# Don't call disconnect a second time if the websocket closed from a disconnect call # Don't call disconnect a second time if the websocket closed from a disconnect call
if not self._expecting_disconnect: if not self._expecting_disconnect:
_log.info('Disconnecting from voice normally, close code %d.', exc.code) _log.info('Disconnecting from voice normally, close code %d.', exc.code)
await self.disconnect() await self.disconnect()
break break
if exc.code == 4014: if exc.code in (4014, 4022):
# We were disconnected by discord # We were disconnected by discord
# This condition is a race between the main ws event and the voice ws closing # This condition is a race between the main ws event and the voice ws closing
if self._disconnected.is_set(): if self._disconnected.is_set():
@ -631,6 +636,31 @@ class VoiceConnectionState:
else: else:
continue continue
if exc.code == 4021:
_log.warning('We are being ratelimited while trying to connect to voice. Disconnecting...')
if self.state is not ConnectionFlowState.disconnected:
await self.disconnect()
break
if exc.code == 4015:
_log.info('Disconnected from voice, attempting a resume...')
try:
await self._connect(
reconnect=reconnect,
timeout=self.timeout,
self_deaf=(self.self_voice_state or self).self_deaf,
self_mute=(self.self_voice_state or self).self_mute,
resume=True,
)
except asyncio.TimeoutError:
_log.info('Could not resume the voice connection... Disconnecting...')
if self.state is not ConnectionFlowState.disconnected:
await self.disconnect()
break
else:
_log.info('Successfully resumed voice connection')
continue
_log.debug('Not handling close code %s (%s)', exc.code, exc.reason or 'no reason') _log.debug('Not handling close code %s (%s)', exc.code, exc.reason or 'no reason')
if not reconnect: if not reconnect: