Skip to content
This repository was archived by the owner on Aug 28, 2019. It is now read-only.

Commit 21ed9f6

Browse files
committed
Fix disconnect when trying to move to another voice channel.
Not overly proud of this implementation but this allows the library to differentiate between a 4014 that means "move to another channel" or "move nowhere". Sometimes the VOICE_STATE_UPDATE comes before the actual websocket disconnect so special care had to be taken in that case. Fix Rapptz#5904
1 parent b91ddc6 commit 21ed9f6

File tree

2 files changed

+60
-22
lines changed

2 files changed

+60
-22
lines changed

discord/gateway.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -719,6 +719,7 @@ def __init__(self, socket, loop):
719719
self.loop = loop
720720
self._keep_alive = None
721721
self._close_code = None
722+
self.secret_key = None
722723

723724
async def send_as_json(self, data):
724725
log.debug('Sending voice websocket frame: %s.', data)
@@ -872,7 +873,7 @@ def average_latency(self):
872873

873874
async def load_secret_key(self, data):
874875
log.info('received secret key for voice connection')
875-
self._connection.secret_key = data.get('secret_key')
876+
self.secret_key = self._connection.secret_key = data.get('secret_key')
876877
await self.speak()
877878
await self.speak(False)
878879

discord/voice_client.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ def __init__(self, client, channel):
208208
self._connected = threading.Event()
209209

210210
self._handshaking = False
211+
self._potentially_reconnecting = False
211212
self._voice_state_complete = asyncio.Event()
212213
self._voice_server_complete = asyncio.Event()
213214

@@ -250,8 +251,10 @@ async def on_voice_state_update(self, data):
250251
self.session_id = data['session_id']
251252
channel_id = data['channel_id']
252253

253-
if not self._handshaking:
254+
if not self._handshaking or self._potentially_reconnecting:
254255
# If we're done handshaking then we just need to update ourselves
256+
# If we're potentially reconnecting due to a 4014, then we need to differentiate
257+
# a channel move and an actual force disconnect
255258
if channel_id is None:
256259
# We're being disconnected so cleanup
257260
await self.disconnect()
@@ -294,26 +297,39 @@ async def on_voice_server_update(self, data):
294297
self._voice_server_complete.set()
295298

296299
async def voice_connect(self):
297-
self._connections += 1
298300
await self.channel.guild.change_voice_state(channel=self.channel)
299301

300302
async def voice_disconnect(self):
301303
log.info('The voice handshake is being terminated for Channel ID %s (Guild ID %s)', self.channel.id, self.guild.id)
302304
await self.channel.guild.change_voice_state(channel=None)
303305

306+
def prepare_handshake(self):
307+
self._voice_state_complete.clear()
308+
self._voice_server_complete.clear()
309+
self._handshaking = True
310+
log.info('Starting voice handshake... (connection attempt %d)', self._connections + 1)
311+
self._connections += 1
312+
313+
def finish_handshake(self):
314+
log.info('Voice handshake complete. Endpoint found %s', self.endpoint)
315+
self._handshaking = False
316+
self._voice_server_complete.clear()
317+
self._voice_state_complete.clear()
318+
319+
async def connect_websocket(self):
320+
ws = await DiscordVoiceWebSocket.from_client(self)
321+
self._connected.clear()
322+
while ws.secret_key is None:
323+
await ws.poll_event()
324+
self._connected.set()
325+
return ws
326+
304327
async def connect(self, *, reconnect, timeout):
305328
log.info('Connecting to voice...')
306329
self.timeout = timeout
307-
try:
308-
del self.secret_key
309-
except AttributeError:
310-
pass
311-
312330

313331
for i in range(5):
314-
self._voice_state_complete.clear()
315-
self._voice_server_complete.clear()
316-
self._handshaking = True
332+
self.prepare_handshake()
317333

318334
# This has to be created before we start the flow.
319335
futures = [
@@ -322,7 +338,6 @@ async def connect(self, *, reconnect, timeout):
322338
]
323339

324340
# Start the connection flow
325-
log.info('Starting voice handshake... (connection attempt %d)', self._connections + 1)
326341
await self.voice_connect()
327342

328343
try:
@@ -331,17 +346,10 @@ async def connect(self, *, reconnect, timeout):
331346
await self.disconnect(force=True)
332347
raise
333348

334-
log.info('Voice handshake complete. Endpoint found %s', self.endpoint)
335-
self._handshaking = False
336-
self._voice_server_complete.clear()
337-
self._voice_state_complete.clear()
349+
self.finish_handshake()
338350

339351
try:
340-
self.ws = await DiscordVoiceWebSocket.from_client(self)
341-
self._connected.clear()
342-
while not hasattr(self, 'secret_key'):
343-
await self.ws.poll_event()
344-
self._connected.set()
352+
self.ws = await self.connect_websocket()
345353
break
346354
except (ConnectionClosed, asyncio.TimeoutError):
347355
if reconnect:
@@ -355,6 +363,26 @@ async def connect(self, *, reconnect, timeout):
355363
if self._runner is None:
356364
self._runner = self.loop.create_task(self.poll_voice_ws(reconnect))
357365

366+
async def potential_reconnect(self):
367+
self.prepare_handshake()
368+
self._potentially_reconnecting = True
369+
try:
370+
# We only care about VOICE_SERVER_UPDATE since VOICE_STATE_UPDATE can come before we get disconnected
371+
await asyncio.wait_for(self._voice_server_complete.wait(), timeout=self.timeout)
372+
except asyncio.TimeoutError:
373+
self._potentially_reconnecting = False
374+
await self.disconnect(force=True)
375+
return False
376+
377+
self.finish_handshake()
378+
self._potentially_reconnecting = False
379+
try:
380+
self.ws = await self.connect_websocket()
381+
except (ConnectionClosed, asyncio.TimeoutError):
382+
return False
383+
else:
384+
return True
385+
358386
@property
359387
def latency(self):
360388
""":class:`float`: Latency between a HEARTBEAT and a HEARTBEAT_ACK in seconds.
@@ -387,10 +415,19 @@ async def poll_voice_ws(self, reconnect):
387415
# 1000 - normal closure (obviously)
388416
# 4014 - voice channel has been deleted.
389417
# 4015 - voice server has crashed
390-
if exc.code in (1000, 4014, 4015):
418+
if exc.code in (1000, 4015):
391419
log.info('Disconnecting from voice normally, close code %d.', exc.code)
392420
await self.disconnect()
393421
break
422+
if exc.code == 4014:
423+
log.info('Disconnected from voice by force... potentially reconnecting.')
424+
successful = await self.potential_reconnect()
425+
if not successful:
426+
log.info('Reconnect was unsuccessful, disconnecting from voice normally...')
427+
await self.disconnect()
428+
break
429+
else:
430+
continue
394431

395432
if not reconnect:
396433
await self.disconnect()

0 commit comments

Comments
 (0)