async def _websocket_closed_handler( self, guild: discord.Guild, player: lavalink.Player, extra: Dict, deafen: bool, disconnect: bool, ) -> None: guild_id = guild.id node = player.node voice_ws: DiscordWebSocket = node.get_voice_ws(guild_id) code = extra.get("code") by_remote = extra.get("byRemote", "") reason = extra.get("reason", "").strip() if self._ws_resume[guild_id].is_set(): ws_audio_log.debug( f"WS EVENT | Discarding WS Closed event for guild {guild_id} -> " f"Socket Closed {voice_ws.socket._closing or voice_ws.socket.closed}. " f"Code: {code} -- Remote: {by_remote} -- {reason}") return self._ws_resume[guild_id].set() if player.channel: current_perms = player.channel.permissions_for( player.channel.guild.me) has_perm = current_perms.speak and current_perms.connect else: has_perm = False channel_id = player.channel.id if voice_ws.socket._closing or voice_ws.socket.closed or not voice_ws.open: if player._con_delay: delay = player._con_delay.delay() else: player._con_delay = ExponentialBackoff(base=1) delay = player._con_delay.delay() ws_audio_log.warning( "YOU CAN IGNORE THIS UNLESS IT'S CONSISTENTLY REPEATING FOR THE SAME GUILD - " f"Voice websocket closed for guild {guild_id} -> " f"Socket Closed {voice_ws.socket._closing or voice_ws.socket.closed}. " f"Code: {code} -- Remote: {by_remote} -- {reason}") ws_audio_log.debug( f"Reconnecting to channel {channel_id} in guild: {guild_id} | {delay:.2f}s" ) await asyncio.sleep(delay) while voice_ws.socket._closing or voice_ws.socket.closed or not voice_ws.open: voice_ws = node.get_voice_ws(guild_id) await asyncio.sleep(0.1) if has_perm and player.current and player.is_playing: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.resume(player.current, start=player.position) ws_audio_log.info( "Voice websocket reconnected " f"to channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Currently playing.") elif has_perm and player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.pause(pause=True) ws_audio_log.info( "Voice websocket reconnected " f"to channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Currently Paused.") elif has_perm and (not disconnect) and (not player.is_playing): player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) ws_audio_log.info( "Voice websocket reconnected " f"to channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Not playing, but auto disconnect disabled." ) self._ll_guild_updates.discard(guild_id) elif not has_perm: self.bot.dispatch("red_audio_audio_disconnect", guild) ws_audio_log.info( "Voice websocket disconnected " f"from channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Missing permissions.") self._ll_guild_updates.discard(guild_id) player.store("autoplay_notified", False) await player.stop() await player.disconnect() await self.config.guild_from_id( guild_id=guild_id).currently_auto_playing_in.set([]) else: self.bot.dispatch("red_audio_audio_disconnect", guild) ws_audio_log.info( "Voice websocket disconnected " f"from channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Unknown.") self._ll_guild_updates.discard(guild_id) player.store("autoplay_notified", False) await player.stop() await player.disconnect() await self.config.guild_from_id( guild_id=guild_id).currently_auto_playing_in.set([]) elif code in (42069, ): player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.resume(player.current, start=player.position) ws_audio_log.info( f"Player resumed in channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & {reason}.") elif code in (4015, 4014, 4009, 4006, 1006): if (code == 4006 and has_perm and player._last_resume and player._last_resume + datetime.timedelta(seconds=5) > datetime.datetime.now(tz=datetime.timezone.utc)): attempts = player.fetch("resume_attempts", 0) player.store("resume_attempts", attempts + 1) channel = self.bot.get_channel(player.channel.id) should_skip = not channel.members or all( m.bot for m in channel.members) if should_skip: ws_audio_log.info( "Voice websocket reconnected skipped " f"for channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & " "Player resumed too recently and no human members connected." ) return if player._con_delay: delay = player._con_delay.delay() else: player._con_delay = ExponentialBackoff(base=1) delay = player._con_delay.delay() ws_audio_log.debug( f"Reconnecting to channel {channel_id} in guild: {guild_id} | {delay:.2f}s" ) await asyncio.sleep(delay) if has_perm and player.current and player.is_playing: await player.connect(deafen=deafen) await player.resume(player.current, start=player.position) ws_audio_log.info( "Voice websocket reconnected " f"to channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Player is active.") elif has_perm and player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.pause(pause=True) ws_audio_log.info( "Voice websocket reconnected " f"to channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Player is paused.") elif has_perm and (not disconnect) and (not player.is_playing): player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) ws_audio_log.info( "Voice websocket reconnected " f"to channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Not playing.") self._ll_guild_updates.discard(guild_id) elif not has_perm: self.bot.dispatch("red_audio_audio_disconnect", guild) ws_audio_log.info( "Voice websocket disconnected " f"from channel {channel_id} in guild: {guild_id} | " f"Reason: Error code {code} & Missing permissions.") self._ll_guild_updates.discard(guild_id) player.store("autoplay_notified", False) await player.stop() await player.disconnect() await self.config.guild_from_id( guild_id=guild_id).currently_auto_playing_in.set([]) else: ws_audio_log.info( "WS EVENT - IGNORED (Healthy Socket) | " f"Voice websocket closed event for guild {guild_id} -> " f"Code: {code} -- Remote: {by_remote} -- {reason}") self._ws_resume[guild_id].clear()
async def _websocket_closed_handler( self, guild: discord.Guild, player: lavalink.Player, extra: Dict, deafen: bool, disconnect: bool, ) -> None: guild_id = guild.id event_channel_id = extra.get("channelID") try: if not self._ws_resume[guild_id].is_set(): await self._ws_resume[guild_id].wait() else: self._ws_resume[guild_id].clear() node = player.node voice_ws: DiscordWebSocket = node.get_voice_ws(guild_id) code = extra.get("code") by_remote = extra.get("byRemote", "") reason = extra.get("reason", "No Specified Reason").strip() channel_id = player.channel.id try: event_channel_id, to_handle_code = await self._ws_op_codes[ guild_id].get() except asyncio.QueueEmpty: log.debug("Empty queue - Resuming Processor - Early exit") return if code != to_handle_code: code = to_handle_code if player.channel.id != event_channel_id: code = 4014 if event_channel_id != channel_id: ws_audio_log.info( "Received an op code for a channel that is no longer valid; %d " "Reason: Error code %d & %s, %r", event_channel_id, code, reason, player, ) self._ws_op_codes[guild_id]._init( self._ws_op_codes[guild_id]._maxsize) return if player.channel: has_perm = self.can_join_and_speak(player.channel) else: has_perm = False if code in ( 1000, ) and has_perm and player.current and player.is_playing: player.store("resumes", player.fetch("resumes", 0) + 1) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "Player resumed | Reason: Error code %d & %s, %r", code, reason, player) self._ws_op_codes[guild_id]._init( self._ws_op_codes[guild_id]._maxsize) return if voice_ws.socket._closing or voice_ws.socket.closed or not voice_ws.open: if player._con_delay: delay = player._con_delay.delay() else: player._con_delay = ExponentialBackoff(base=1) delay = player._con_delay.delay() ws_audio_log.warning( "YOU CAN IGNORE THIS UNLESS IT'S CONSISTENTLY REPEATING FOR THE SAME GUILD - " "Voice websocket closed for guild %d -> " "Socket Closed %s. " "Code: %d -- Remote: %s -- %s, %r", guild_id, voice_ws.socket._closing or voice_ws.socket.closed, code, by_remote, reason, player, ) ws_audio_log.debug( "Reconnecting to channel %d in guild: %d | %.2fs", channel_id, guild_id, delay, ) await asyncio.sleep(delay) while voice_ws.socket._closing or voice_ws.socket.closed or not voice_ws.open: voice_ws = node.get_voice_ws(guild_id) await asyncio.sleep(0.1) if has_perm and player.current and player.is_playing: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "Voice websocket reconnected " "Reason: Error code %d & Currently playing, %r", code, player, ) elif has_perm and player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.resume(player.current, start=player.position, replace=True, pause=True) ws_audio_log.info( "Voice websocket reconnected " "Reason: Error code %d & Currently Paused, %r", code, player, ) elif has_perm and (not disconnect) and (not player.is_playing): player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) ws_audio_log.info( "Voice websocket reconnected " "Reason: Error code %d & Not playing, but auto disconnect disabled, %r", code, player, ) self._ll_guild_updates.discard(guild_id) elif not has_perm: self.bot.dispatch("red_audio_audio_disconnect", guild) ws_audio_log.info( "Voice websocket disconnected " "Reason: Error code %d & Missing permissions, %r", code, player, ) self._ll_guild_updates.discard(guild_id) player.store("autoplay_notified", False) await player.stop() await player.disconnect() await self.config.guild_from_id( guild_id=guild_id).currently_auto_playing_in.set([]) else: self.bot.dispatch("red_audio_audio_disconnect", guild) ws_audio_log.info( "Voice websocket disconnected Reason: Error code %d & Unknown, %r", code, player, ) self._ll_guild_updates.discard(guild_id) player.store("autoplay_notified", False) await player.stop() await player.disconnect() await self.config.guild_from_id( guild_id=guild_id).currently_auto_playing_in.set([]) elif code in ( 42069, ) and has_perm and player.current and player.is_playing: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "Player resumed - Reason: Error code %d & %s, %r", code, reason, player) elif code in (4015, 4009, 4006, 4000, 1006): if player._con_delay: delay = player._con_delay.delay() else: player._con_delay = ExponentialBackoff(base=1) delay = player._con_delay.delay() ws_audio_log.debug( "Reconnecting to channel %d in guild: %d | %.2fs", channel_id, guild_id, delay) await asyncio.sleep(delay) if has_perm and player.current and player.is_playing: await player.connect(deafen=deafen) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "Voice websocket reconnected " "Reason: Error code %d & Player is active, %r", code, player, ) elif has_perm and player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) await player.resume(player.current, start=player.position, replace=True, pause=True) ws_audio_log.info( "Voice websocket reconnected " "Reason: Error code %d & Player is paused, %r", code, player, ) elif has_perm and (not disconnect) and (not player.is_playing): player.store("resumes", player.fetch("resumes", 0) + 1) await player.connect(deafen=deafen) ws_audio_log.info( "Voice websocket reconnected " "to channel %d in guild: %d | " "Reason: Error code %d & Not playing, %r", channel_id, guild_id, code, player, ) self._ll_guild_updates.discard(guild_id) elif not has_perm: self.bot.dispatch("red_audio_audio_disconnect", guild) ws_audio_log.info( "Voice websocket disconnected " "Reason: Error code %d & Missing permissions, %r", code, player, ) self._ll_guild_updates.discard(guild_id) player.store("autoplay_notified", False) await player.stop() await player.disconnect() await self.config.guild_from_id( guild_id=guild_id).currently_auto_playing_in.set([]) else: if not player.paused and player.current: player.store("resumes", player.fetch("resumes", 0) + 1) await player.resume(player.current, start=player.position, replace=True) ws_audio_log.info( "WS EVENT - SIMPLE RESUME (Healthy Socket) | " "Voice websocket closed event " "Code: %d -- Remote: %s -- %s, %r", code, by_remote, reason, player, ) else: ws_audio_log.info( "WS EVENT - IGNORED (Healthy Socket) | " "Voice websocket closed event " "Code: %d -- Remote: %s -- %s, %r", code, by_remote, reason, player, ) except Exception: log.exception("Error in task") finally: self._ws_op_codes[guild_id]._init( self._ws_op_codes[guild_id]._maxsize) self._ws_resume[guild_id].set()