discord_radio: presence follows voice; name why a voice join fails #38

Merged
logan merged 2 commits from fix/discord-voice-join into main 2026-10-04 22:52:31 -04:00
2 changed files with 21 additions and 3 deletions
Showing only changes of commit 2065446393 - Show all commits
+10 -3
View File
@@ -100,7 +100,11 @@ def _join_refusal(guild, channel) -> Optional[tuple[str, str]]:
f"The radio bot lacks {', '.join(missing)} on that voice channel — "
"allow it in the channel's permission settings in Discord.")
limit = getattr(channel, "user_limit", 0) or 0
members = getattr(channel, "members", None) or []
# The bot's own lingering voice state (a dropped connection) is not a
# slot it needs; counting it would block the rejoin of a full channel.
me_id = getattr(me, "id", None)
members = [m for m in (getattr(channel, "members", None) or [])
if me_id is None or getattr(m, "id", None) != me_id]
if limit and len(members) >= limit and not getattr(perms, "move_members", False):
return ("channel_full", f"That voice channel is full ({len(members)}/{limit}) — "
"raise its user limit or free a slot.")
@@ -334,8 +338,11 @@ class RadioManager:
# "(TimeoutError: )" — useless on the admin page.
v.vc = None
v.state, v.code = "error", "connect_timeout"
v.error = ("Discord never completed the voice join (timed out). Check the channel's "
"permission overrides for the radio bot and that Discord voice is healthy.")
# Permissions were checked above, so this is usually the voice
# handshake itself (e.g. a pre-2.7 discord.py refused with 4017 for
# lacking DAVE E2EE, then retried until the 30s timeout) or UDP egress.
v.error = ("Discord never completed the voice join (timed out). Permissions look fine, so "
"check the node's discord.py voice log (DAVE close 4017, UDP blocked).")
logger.error(f"Stream {v.stream_id}: voice connect timed out")
return
except Exception as e:
+11
View File
@@ -296,6 +296,17 @@ async def test_full_channel_is_reported(env):
assert st["code"] == "channel_full" and "2/2" in st["error"]
async def test_bots_own_lingering_voice_state_does_not_fill_the_channel(env):
"""A dropped connection can leave the bot listed in the channel; that is
not a slot it needs to rejoin."""
guild = env.guilds[1]
guild.me = type("Me", (), {"id": 999})()
ch = guild.channels[11]
ch.user_limit, ch.members = 2, [object(), guild.me]
await env.mgr.apply_desired(_desired(1, (1, 11, "t1")))
assert env.published[-1]["streams"]["1:11"]["state"] == "connected"
async def test_connect_timeout_has_a_readable_error(env):
env.guilds[1].channels[11].connect_error = asyncio.TimeoutError()
await env.mgr.apply_desired(_desired(1, (1, 11, "t1")))