Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 18 additions & 3 deletions plugins/platforms/discord/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1637,14 +1637,29 @@ async def edit_message(
content: str,
*,
finalize: bool = False,
metadata: Optional[Dict[str, Any]] = None,
) -> SendResult:
"""Edit a previously sent Discord message."""
"""Edit a previously sent Discord message.

Streaming/edit consumers pass ``metadata={'thread_id': ...}`` for
Discord thread replies. Match ``send()`` routing so progressive edits
target the thread message, not the parent channel.
"""
if not self._client:
return SendResult(success=False, error="Not connected")
try:
channel = self._client.get_channel(int(chat_id))
thread_id = None
if metadata and metadata.get("thread_id"):
thread_id = metadata["thread_id"]

target_id = thread_id or chat_id
channel = self._client.get_channel(int(target_id))
if not channel:
channel = await self._client.fetch_channel(int(chat_id))
channel = await self._client.fetch_channel(int(target_id))
if not channel:
target_kind = "Thread" if thread_id else "Channel"
return SendResult(success=False, error=f"{target_kind} {target_id} not found")

msg = await channel.fetch_message(int(message_id))
formatted = self.format_message(content)
if len(formatted) > self.MAX_MESSAGE_LENGTH:
Expand Down
99 changes: 99 additions & 0 deletions tests/gateway/test_discord_streaming.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import asyncio
import sys
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock

import pytest

from gateway.config import PlatformConfig
from gateway.stream_consumer import GatewayStreamConsumer, StreamConsumerConfig


def _ensure_discord_mock():
if "discord" in sys.modules and hasattr(sys.modules["discord"], "__file__"):
return
discord_mod = MagicMock()
discord_mod.Intents.default.return_value = MagicMock()
discord_mod.Client = MagicMock
discord_mod.File = MagicMock
for name in ("discord", "discord.ext", "discord.ext.commands"):
sys.modules.setdefault(name, discord_mod)


_ensure_discord_mock()

from plugins.platforms.discord.adapter import DiscordAdapter # noqa: E402


@pytest.fixture
def adapter():
a = DiscordAdapter(PlatformConfig(enabled=True, token="fake-token"))
a._client = MagicMock()
return a


@pytest.mark.asyncio
async def test_edit_message_uses_thread_metadata(adapter):
parent = SimpleNamespace(id=111, fetch_message=AsyncMock())
edited = SimpleNamespace(edit=AsyncMock())
thread = SimpleNamespace(id=222, fetch_message=AsyncMock(return_value=edited))

def get_channel(channel_id):
return {111: parent, 222: thread}.get(channel_id)

adapter._client.get_channel = MagicMock(side_effect=get_channel)
adapter._client.fetch_channel = AsyncMock(return_value=None)

result = await adapter.edit_message(
"111",
"555",
"stream update",
metadata={"thread_id": "222"},
)

assert result.success is True
parent.fetch_message.assert_not_awaited()
thread.fetch_message.assert_awaited_once_with(555)
edited.edit.assert_awaited_once_with(content="stream update")


@pytest.mark.asyncio
async def test_stream_consumer_edits_discord_thread_preview(adapter):
sent_message = SimpleNamespace(id=555)
edited_message = SimpleNamespace(edit=AsyncMock())
thread = SimpleNamespace(
id=222,
send=AsyncMock(return_value=sent_message),
fetch_message=AsyncMock(return_value=edited_message),
)
parent = SimpleNamespace(id=111)

def get_channel(channel_id):
return {111: parent, 222: thread}.get(channel_id)

adapter._client.get_channel = MagicMock(side_effect=get_channel)
adapter._client.fetch_channel = AsyncMock(return_value=None)
adapter._is_forum_parent = MagicMock(return_value=False)

consumer = GatewayStreamConsumer(
adapter=adapter,
chat_id="111",
config=StreamConsumerConfig(edit_interval=0.0, buffer_threshold=1, cursor=""),
metadata={"thread_id": "222"},
initial_reply_to_id="444",
)

task = asyncio.create_task(consumer.run())
consumer.on_delta("Hello")
await asyncio.sleep(0.08)
consumer.on_delta(" world")
await asyncio.sleep(0.08)
consumer.finish()
await asyncio.wait_for(task, timeout=1.0)

thread.send.assert_awaited_once()
assert thread.send.await_args.kwargs["content"] == "Hello"
assert thread.send.await_args.kwargs["reference"] is not None
thread.fetch_message.assert_awaited_with(555)
edited_message.edit.assert_any_await(content="Hello world")
assert consumer.final_response_sent is True