From a448e7a04d22babb9a9c6c9bfc67ee6306cde2c7 Mon Sep 17 00:00:00 2001 From: LeonSGP43 <154585401+LeonSGP43@users.noreply.github.com> Date: Fri, 17 Apr 2026 11:00:21 +0800 Subject: [PATCH] fix(discord): drop invalid reply references --- gateway/platforms/discord.py | 17 ++++++++--- tests/gateway/test_discord_send.py | 45 ++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/gateway/platforms/discord.py b/gateway/platforms/discord.py index 45e37432de3..e87b47aee31 100644 --- a/gateway/platforms/discord.py +++ b/gateway/platforms/discord.py @@ -873,7 +873,10 @@ class DiscordAdapter(BasePlatformAdapter): if reply_to and self._reply_to_mode != "off": try: ref_msg = await channel.fetch_message(int(reply_to)) - reference = ref_msg + if hasattr(ref_msg, "to_reference"): + reference = ref_msg.to_reference(fail_if_not_exists=False) + else: + reference = ref_msg except Exception as e: logger.debug("Could not fetch reply-to message: %s", e) @@ -891,14 +894,20 @@ class DiscordAdapter(BasePlatformAdapter): err_text = str(e) if ( chunk_reference is not None - and "error code: 50035" in err_text - and "Cannot reply to a system message" in err_text + and ( + ( + "error code: 50035" in err_text + and "Cannot reply to a system message" in err_text + ) + or "error code: 10008" in err_text + ) ): logger.warning( - "[%s] Reply target %s is a Discord system message; retrying send without reply reference", + "[%s] Reply target %s rejected the reply reference; retrying send without reply reference", self.name, reply_to, ) + reference = None msg = await channel.send( content=chunk, reference=None, diff --git a/tests/gateway/test_discord_send.py b/tests/gateway/test_discord_send.py index 8883d46efc2..b39f6d873fa 100644 --- a/tests/gateway/test_discord_send.py +++ b/tests/gateway/test_discord_send.py @@ -48,7 +48,8 @@ from gateway.platforms.discord import DiscordAdapter # noqa: E402 async def test_send_retries_without_reference_when_reply_target_is_system_message(): adapter = DiscordAdapter(PlatformConfig(enabled=True, token="***")) - ref_msg = SimpleNamespace(id=99) + reference_obj = object() + ref_msg = SimpleNamespace(id=99, to_reference=MagicMock(return_value=reference_obj)) sent_msg = SimpleNamespace(id=1234) send_calls = [] @@ -76,5 +77,45 @@ async def test_send_retries_without_reference_when_reply_target_is_system_messag assert result.message_id == "1234" assert channel.fetch_message.await_count == 1 assert channel.send.await_count == 2 - assert send_calls[0]["reference"] is ref_msg + ref_msg.to_reference.assert_called_once_with(fail_if_not_exists=False) + assert send_calls[0]["reference"] is reference_obj assert send_calls[1]["reference"] is None + + +@pytest.mark.asyncio +async def test_send_retries_without_reference_when_reply_target_is_deleted(): + adapter = DiscordAdapter(PlatformConfig(enabled=True, token="***")) + + reference_obj = object() + ref_msg = SimpleNamespace(id=99, to_reference=MagicMock(return_value=reference_obj)) + sent_msgs = [SimpleNamespace(id=1001), SimpleNamespace(id=1002)] + send_calls = [] + + async def fake_send(*, content, reference=None): + send_calls.append({"content": content, "reference": reference}) + if len(send_calls) == 1: + raise RuntimeError( + "400 Bad Request (error code: 10008): Unknown Message" + ) + return sent_msgs[len(send_calls) - 2] + + channel = SimpleNamespace( + fetch_message=AsyncMock(return_value=ref_msg), + send=AsyncMock(side_effect=fake_send), + ) + adapter._client = SimpleNamespace( + get_channel=lambda _chat_id: channel, + fetch_channel=AsyncMock(), + ) + + long_text = "A" * (adapter.MAX_MESSAGE_LENGTH + 50) + result = await adapter.send("555", long_text, reply_to="99") + + assert result.success is True + assert result.message_id == "1001" + assert channel.fetch_message.await_count == 1 + assert channel.send.await_count == 3 + ref_msg.to_reference.assert_called_once_with(fail_if_not_exists=False) + assert send_calls[0]["reference"] is reference_obj + assert send_calls[1]["reference"] is None + assert send_calls[2]["reference"] is None