From 44abe852fb98352b8184ec3dcff146010443ebcf Mon Sep 17 00:00:00 2001 From: 0xbyt4 <35742124+0xbyt4@users.noreply.github.com> Date: Fri, 13 Mar 2026 16:59:03 +0300 Subject: [PATCH] fix: add macOS Homebrew Opus fallback and fix shutdown dict iteration - Add Homebrew library path fallback when ctypes.util.find_library fails on macOS (Apple Silicon + Intel paths, guarded by platform check) - Fix RuntimeError in gateway stop() by iterating over dict copy - Update Opus tests to verify find_library-first + conditional fallback --- gateway/platforms/discord.py | 13 +++++++++++++ gateway/run.py | 2 +- tests/gateway/test_discord_opus.py | 29 +++++++++++++++++++---------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 5e5e0bfda..601ce52a9 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -409,6 +409,19 @@ class DiscordAdapter(BasePlatformAdapter): if not discord.opus.is_loaded(): import ctypes.util opus_path = ctypes.util.find_library("opus") + # ctypes.util.find_library fails on macOS with Homebrew-installed libs, + # so fall back to known Homebrew paths if needed. + if not opus_path: + import sys + _homebrew_paths = ( + "/opt/homebrew/lib/libopus.dylib", # Apple Silicon + "/usr/local/lib/libopus.dylib", # Intel Mac + ) + if sys.platform == "darwin": + for _hp in _homebrew_paths: + if os.path.isfile(_hp): + opus_path = _hp + break if opus_path: try: discord.opus.load_opus(opus_path) diff --git a/gateway/run.py b/gateway/run.py index 992b83239..5ea408280 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -754,7 +754,7 @@ class GatewayRunner: logger.info("Stopping gateway...") self._running = False - for platform, adapter in self.adapters.items(): + for platform, adapter in list(self.adapters.items()): try: await adapter.disconnect() logger.info("✓ %s disconnected", platform.value) diff --git a/tests/gateway/test_discord_opus.py b/tests/gateway/test_discord_opus.py index 6c2e1e6c1..ef66cde00 100644 --- a/tests/gateway/test_discord_opus.py +++ b/tests/gateway/test_discord_opus.py @@ -4,22 +4,31 @@ import inspect class TestOpusFindLibrary: - """Opus loading must use ctypes.util.find_library, not hardcoded paths.""" + """Opus loading must try ctypes.util.find_library first, with platform fallback.""" - def test_no_hardcoded_opus_path(self): - from gateway.platforms.discord import DiscordAdapter - source = inspect.getsource(DiscordAdapter.connect) - assert "/opt/homebrew" not in source, \ - "Opus loading must not use hardcoded /opt/homebrew path" - assert "libopus.so.0" not in source, \ - "Opus loading must not use hardcoded libopus.so.0 path" - - def test_uses_find_library(self): + def test_uses_find_library_first(self): + """find_library must be the primary lookup strategy.""" from gateway.platforms.discord import DiscordAdapter source = inspect.getsource(DiscordAdapter.connect) assert "find_library" in source, \ "Opus loading must use ctypes.util.find_library" + def test_homebrew_fallback_is_conditional(self): + """Homebrew paths must only be tried when find_library returns None.""" + from gateway.platforms.discord import DiscordAdapter + source = inspect.getsource(DiscordAdapter.connect) + # Homebrew fallback must exist + assert "/opt/homebrew" in source or "homebrew" in source, \ + "Opus loading should have macOS Homebrew fallback" + # find_library must appear BEFORE any Homebrew path + fl_idx = source.index("find_library") + hb_idx = source.index("/opt/homebrew") + assert fl_idx < hb_idx, \ + "find_library must be tried before Homebrew fallback paths" + # Fallback must be guarded by platform check + assert "sys.platform" in source or "darwin" in source, \ + "Homebrew fallback must be guarded by macOS platform check" + def test_opus_decode_error_logged(self): """Opus decode failure must log the error, not silently return.""" from gateway.platforms.discord import VoiceReceiver