Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
PlatformMetadata,
)
from astrbot.core.platform.astr_message_event import MessageSesion
from astrbot.core.utils.quoted_message.chain_parser import OneBotPayloadParser
from astrbot.core.utils.quoted_message.settings import SETTINGS

from ...register import register_platform_adapter
from .aiocqhttp_message_event import *
Expand Down Expand Up @@ -240,6 +242,7 @@ async def _convert_handle_message_event(

# 按消息段类型类型适配
routing_params = {"self_id": event.self_id} if event.self_id else {}
forward_payload_parser = OneBotPayloadParser()
for t, m_group in itertools.groupby(event.message, key=lambda x: x["type"]):
a = None
if t == "text":
Expand Down Expand Up @@ -402,6 +405,92 @@ async def _convert_handle_message_event(
text = m["data"].get("markdown") or m["data"].get("content", "")
abm.message.append(Plain(text=text))
message_str += text
elif t == "forward":
for m in m_group:
forward_id = m.get("data", {}).get("id") or m.get("data", {}).get(
"message_id"
)
if not forward_id:
logger.warning(
f"合并转发消息段缺少 id,已忽略: data={m.get('data')}"
)
continue
Comment on lines +413 to +417

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If the message segment m does not contain the data key, m.get("data", {}) in line 410 will return {} and forward_id will be None. When entering this block, accessing m['data'] directly will raise a KeyError, crashing the message conversion loop. Using m.get('data') avoids this potential crash.

Suggested change
if not forward_id:
logger.warning(
f"合并转发消息段缺少 id,已忽略: data={m['data']}"
)
continue
if not forward_id:
logger.warning(
f"合并转发消息段缺少 id,已忽略: data={m.get('data')}"
)
continue


forward_id_str = str(forward_id).strip()
if not forward_id_str:
logger.warning(
f"合并转发消息段 id 为空,已忽略: data={m.get('data')}"
)
continue

abm.message.append(Forward(id=forward_id_str))

pending_forward_ids = [forward_id_str]
seen_forward_ids: set[str] = set()
forward_text_parts: list[str] = []
fetch_count = 0
while (
pending_forward_ids and fetch_count < SETTINGS.max_forward_fetch
):
current_id = pending_forward_ids.pop(0)
if current_id in seen_forward_ids:
continue

seen_forward_ids.add(current_id)
fetch_count += 1
params_list: list[dict[str, str | int]] = [
{"message_id": current_id},
{"id": current_id},
]
if current_id.isdigit():
int_id = int(current_id)
params_list.extend([{"message_id": int_id}, {"id": int_id}])

forward_payload = None
last_error = None
for params in params_list:
try:
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
forward_payload = await self.bot.call_action(
action="get_forward_msg",
**params,
**routing_params,
)
if isinstance(forward_payload, dict):
break
except Exception as e:
last_error = e

if not isinstance(forward_payload, dict):
if last_error:
logger.error(f"获取合并转发消息失败: {last_error}。")
continue

parsed = forward_payload_parser.parse_get_forward_payload(
forward_payload
)
if parsed["text"]:
forward_text_parts.append(parsed["text"])
for nested_id in parsed["forward_ids"]:
nested_id = str(nested_id).strip()
if nested_id and nested_id not in seen_forward_ids:
pending_forward_ids.append(nested_id)

if pending_forward_ids:
logger.warning(
"aiocqhttp: stop fetching nested forward messages "
"after %d hops",
SETTINGS.max_forward_fetch,
)

forward_text = "\n".join(forward_text_parts).strip()
if forward_text:
abm.message.append(Plain(text=forward_text))
else:
forward_text = "[Forward Message]"

if message_str and not message_str.endswith("\n"):
message_str += "\n"
message_str += forward_text
else:
for m in m_group:
try:
Expand Down
106 changes: 106 additions & 0 deletions tests/unit/test_aiocqhttp_forward.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import pytest

from astrbot.core.message.components import Forward, Plain
from astrbot.core.platform.sources.aiocqhttp.aiocqhttp_platform_adapter import (
AiocqhttpAdapter,
)


class _FakeEvent(dict):
def __getattr__(self, name):
return self[name]


class _FakeBot:
def __init__(self, responses):
self.responses = responses
self.calls = []

async def call_action(self, action, **params):
self.calls.append((action, params))
forward_id = params.get("message_id") or params.get("id")
key = (action, str(forward_id))
if key not in self.responses:
raise RuntimeError(f"no mock response for {key}")
return self.responses[key]


def _make_group_event(message):
return _FakeEvent(
{
"self_id": 10000,
"post_type": "message",
"message_type": "group",
"message_id": 123,
"group_id": 456,
"group_name": "test group",
"sender": {
"user_id": 20000,
"nickname": "Alice",
"card": "",
},
"message": message,
}
)


@pytest.mark.asyncio
async def test_aiocqhttp_forward_segment_expands_forward_text():
adapter = object.__new__(AiocqhttpAdapter)
adapter.bot = _FakeBot(
{
(
"get_forward_msg",
"fwd_1",
): {
"data": {
"messages": [
{
"sender": {"nickname": "Alice"},
"message": [{"type": "text", "data": {"text": "hello"}}],
},
{
"sender": {"nickname": "Bot", "user_id": 10000},
"message": [
{
"type": "text",
"data": {"text": "bot reply"},
}
],
},
]
}
},
}
)

abm = await adapter._convert_handle_message_event(
_make_group_event(
[
{"type": "text", "data": {"text": "context"}},
{"type": "forward", "data": {"id": "fwd_1"}},
]
)
)

assert isinstance(abm.message[0], Plain)
assert isinstance(abm.message[1], Forward)
assert isinstance(abm.message[2], Plain)
assert abm.message[1].id == "fwd_1"
assert abm.message_str == "context\nAlice: hello\nBot: bot reply"
assert abm.message[2].text == "Alice: hello\nBot: bot reply"


@pytest.mark.asyncio
async def test_aiocqhttp_forward_segment_keeps_placeholder_when_fetch_fails():
adapter = object.__new__(AiocqhttpAdapter)
adapter.bot = _FakeBot({})

abm = await adapter._convert_handle_message_event(
_make_group_event([{"type": "forward", "data": {"id": "missing"}}])
)

assert len(abm.message) == 1
assert isinstance(abm.message[0], Forward)
assert abm.message[0].id == "missing"
assert abm.message_str == "[Forward Message]"
Loading