fix(discord): a voice speaker outside the member cache still gets a name
guild.get_member() is cache-only, so a speaker who had never typed in the guild (no member intent, or joined voice straight away) rendered as the bare user id while their text turns render a display name: the session-context prompt differed again on every switch. Fall back to the cached global user (global name / username) before giving up; a guild nick still wins when the member is cached. Suggested in the first review, reproduced independently (F3 on the thread). [adapted onto main: #133463 resolves the speaker name inline in _voice_input_source, not through DiscordAdapter.voice_member_display_name, so the fallback is a small helper there. It runs after the /voice join invoker's bound name, which is the nickname their typed turns carry; a cached global name would differ from it. Tests moved to test_discord_turn_prompt_parity.py] (cherry picked from commit 295a01f3ab88f07f3bac22f8939c42bca928c398)
This commit is contained in:
@@ -237,6 +237,14 @@ class GatewayVoiceMixin:
|
||||
recent_store[key] = (recent + [(now, normalized)])[-5:]
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _cached_user_display_name(client, user_id: int) -> Optional[str]:
|
||||
"""``get_member`` is cache-only; a speaker it misses may still be a cached user, whose display
|
||||
name (global name or username, no server nickname) beats a bare id."""
|
||||
get_user = getattr(client, "get_user", None)
|
||||
name = getattr(get_user(int(user_id)) if callable(get_user) else None, "display_name", None)
|
||||
return name if isinstance(name, str) and name else None
|
||||
|
||||
@staticmethod
|
||||
def _voice_input_source(adapter, guild_id: int, user_id: int, text_ch_id) -> SessionSource:
|
||||
"""Bound text channel's own source when available (voice shares the text conversation's
|
||||
@@ -254,11 +262,13 @@ class GatewayVoiceMixin:
|
||||
# join, never another participant's.
|
||||
if user_name is None and source.user_id == str(user_id):
|
||||
user_name = source.user_name
|
||||
user_name = user_name or GatewayVoiceMixin._cached_user_display_name(client, user_id)
|
||||
source.user_id, source.user_name = str(user_id), user_name or str(user_id)
|
||||
# The bound source is the `/voice join` message's; its id is not this turn's trigger
|
||||
# (run.py exports it as HERMES_SESSION_MESSAGE_ID for reply anchoring).
|
||||
source.message_id = None
|
||||
else:
|
||||
user_name = user_name or GatewayVoiceMixin._cached_user_display_name(client, user_id)
|
||||
source = SessionSource(
|
||||
platform=Platform.DISCORD, chat_id=str(text_ch_id), user_id=str(user_id),
|
||||
user_name=user_name or str(user_id), chat_type="channel",
|
||||
|
||||
@@ -137,6 +137,9 @@ async def test_voice_channel_turn_matches_a_typed_turn(monkeypatch: pytest.Monke
|
||||
channel.name, channel.topic = "renamed", "New topic"
|
||||
if case == "speaker-uncached":
|
||||
adapter._client.get_guild = lambda _id: SimpleNamespace(get_member=lambda _uid: None)
|
||||
# Cached as users only: the joiner's bound nickname still beats their global name.
|
||||
adapter._client.get_user = {42: SimpleNamespace(display_name="alice_global"),
|
||||
43: SimpleNamespace(display_name="Bob")}.get
|
||||
typed = await _typed(adapter, channel)
|
||||
runner = object.__new__(gateway_run.GatewayRunner)
|
||||
runner.adapters = {Platform.DISCORD: adapter}
|
||||
@@ -151,6 +154,11 @@ async def test_voice_channel_turn_matches_a_typed_turn(monkeypatch: pytest.Monke
|
||||
assert spoken is not typed
|
||||
_assert_same_prompt_inputs(typed, spoken)
|
||||
assert spoken.source.message_id is None
|
||||
# The join-time name belongs to the joiner only; another uncached speaker never borrows it.
|
||||
# The join-time name belongs to the joiner only; another uncached speaker never borrows it, and
|
||||
# gets their cached user name (global name, no server nickname) rather than a bare id.
|
||||
other = runner._voice_input_source(adapter, 1, 43, channel.id).user_name
|
||||
assert other == ("43" if case == "speaker-uncached" else "Alice")
|
||||
assert other == ("Bob" if case == "speaker-uncached" else "Alice")
|
||||
if case == "speaker-uncached":
|
||||
assert runner._voice_input_source(adapter, 1, 44, channel.id).user_name == "44" # cached nowhere
|
||||
adapter._voice_sources = {}
|
||||
assert runner._voice_input_source(adapter, 1, 43, channel.id).user_name == "Bob" # no binding
|
||||
|
||||
Reference in New Issue
Block a user