Skip to content
Merged
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
2 changes: 1 addition & 1 deletion plugins/platforms/discord/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -7234,7 +7234,7 @@ async def _standalone_send(
else:
# Forum channels (type 15) reject POST /messages — create a thread post.
if await _standalone_is_forum(aiohttp, chat_id, json_headers, _sess_kw, _req_kw):
thread_name = _derive_forum_thread_name(message)
thread_name = _derive_forum_thread_name(caption or message)
thread_url = f"https://discord.com/api/v10/channels/{chat_id}/threads"
# Filter readable media first to pick JSON vs multipart before opening a session.
valid_media = []
Expand Down
50 changes: 47 additions & 3 deletions tests/tools/test_discord_send_message_caption.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from plugins.platforms.discord.adapter import _remember_channel_is_forum, _standalone_send


Expand Down Expand Up @@ -66,8 +68,8 @@ def _tmpfile(suffix):
return f.name


def _payload_json_content(form_data):
"""Extract the 'content' from a FormData's payload_json field, if any."""
def _payload_json(form_data):
"""Extract Discord's multipart payload_json object, if present."""
for field in getattr(form_data, "_fields", []):
# aiohttp FormData stores (type_options_dict, headers, value)
try:
Expand All @@ -76,10 +78,52 @@ def _payload_json_content(form_data):
except (IndexError, TypeError):
continue
if type_opts.get("name") == "payload_json":
return json.loads(value).get("content")
return json.loads(value)
return None


def _payload_json_content(form_data):
"""Extract the message content from a JSON or multipart POST payload."""
payload = _payload_json(form_data) or {}
return payload.get("message", payload).get("content")


@pytest.mark.parametrize(
("message", "caption"),
[
("", "Health check-in: Sun Nov 8\nmore"),
("Health check-in: Sun Nov 8\nmore", ""),
],
)
def test_forum_thread_name_uses_caption_or_message(message, caption):
chat_id = "999000333"
_remember_channel_is_forum(chat_id, True)
img = _tmpfile(".png")
try:
session_ctx, calls = _session_with(
[_resp(200, {"id": "thread-1", "message": {"id": "starter-1"}})]
)
with patch("aiohttp.ClientSession", return_value=session_ctx):
result = asyncio.run(
_standalone_send(
_pconfig(),
chat_id,
message,
media_files=[(img, False)],
caption=caption or None,
)
)
assert result["success"] is True
assert len(calls) == 1
url, _json, data = calls[0]
assert url.endswith("/threads")
payload = _payload_json(data)
assert payload["name"] == "Health check-in: Sun Nov 8"
assert payload["message"]["content"] == (caption or message)
finally:
os.unlink(img)


def test_caption_rides_media_non_forum():
chat_id = "999000111"
_remember_channel_is_forum(chat_id, False) # avoid the live GET probe
Expand Down
Loading