mirror of
https://github.com/AstrBotDevs/AstrBot.git
synced 2026-10-16 15:06:04 +08:00
fix: preserve Slack code block and quote text (#10380)
* fix(slack): preserve code block and quote text * fix(slack): preserve quote boundaries around mentions * fix(slack): preserve leading mention wake targeting * test(slack): trim rich text regression coverage * test(slack): focus rich text tests on message conversion * fix(slack): preserve whitespace-only block content
This commit is contained in:
@@ -212,7 +212,14 @@ class SlackAdapter(Platform):
|
||||
return abm
|
||||
|
||||
def _parse_blocks(self, blocks: list) -> list:
|
||||
"""解析 Slack blocks 格式的消息内容"""
|
||||
"""Parse Slack blocks into message components.
|
||||
|
||||
Args:
|
||||
blocks: Slack message blocks.
|
||||
|
||||
Returns:
|
||||
Message components in their original order.
|
||||
"""
|
||||
message_components = []
|
||||
|
||||
for block in blocks:
|
||||
@@ -222,9 +229,14 @@ class SlackAdapter(Platform):
|
||||
# 处理富文本块
|
||||
elements = block.get("elements", [])
|
||||
for element in elements:
|
||||
if element.get("type") == "rich_text_section":
|
||||
# 处理富文本段落
|
||||
if element.get("type") in (
|
||||
"rich_text_section",
|
||||
"rich_text_preformatted",
|
||||
"rich_text_quote",
|
||||
):
|
||||
# Sections, code blocks, and quotes share inline elements.
|
||||
section_elements = element.get("elements", [])
|
||||
component_start = len(message_components)
|
||||
text_parts = []
|
||||
for section_element in section_elements:
|
||||
element_type = section_element.get("type", "")
|
||||
@@ -261,9 +273,24 @@ class SlackAdapter(Platform):
|
||||
|
||||
text_content = "".join(text_parts)
|
||||
|
||||
if text_content.strip():
|
||||
if text_content and (
|
||||
text_content.strip()
|
||||
or element.get("type") != "rich_text_section"
|
||||
):
|
||||
message_components.append(Plain(text=text_content))
|
||||
|
||||
if (
|
||||
element.get("type") != "rich_text_section"
|
||||
and len(message_components) > component_start
|
||||
):
|
||||
# Keep block boundaries outside any inline mentions.
|
||||
# Preserve a leading At for the wake target check.
|
||||
if component_start:
|
||||
message_components.insert(
|
||||
component_start, Plain(text="\n")
|
||||
)
|
||||
message_components.append(Plain(text="\n"))
|
||||
|
||||
elif element.get("type") == "rich_text_list":
|
||||
# 处理列表
|
||||
list_items = element.get("elements", [])
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import asyncio
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
import pytest
|
||||
|
||||
from astrbot.api.message_components import At, Plain
|
||||
from astrbot.core.platform.sources.slack.slack_adapter import SlackAdapter
|
||||
from tests.fixtures.helpers import make_platform_config
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"block_type",
|
||||
["rich_text_preformatted", "rich_text_quote", "rich_text_section", "inline_code"],
|
||||
)
|
||||
async def test_slack_rich_text_preserves_body_and_mention(block_type):
|
||||
adapter = SlackAdapter(
|
||||
make_platform_config("slack", bot_token="xoxb-test", app_token="xapp-test"),
|
||||
{},
|
||||
asyncio.Queue(),
|
||||
)
|
||||
adapter.bot_self_id = "UBOT"
|
||||
adapter.web_client.users_info = AsyncMock(
|
||||
return_value={"user": {"real_name": "Tester"}},
|
||||
)
|
||||
adapter.web_client.conversations_info = AsyncMock(
|
||||
return_value={"channel": {"is_im": False, "name": "test"}},
|
||||
)
|
||||
body = "BLOCK_TEST_9527\nRuntimeError: unique_test_9527"
|
||||
body_element = {"type": "text", "text": body}
|
||||
if block_type == "inline_code":
|
||||
body_element["style"] = {"code": True}
|
||||
event = {
|
||||
"type": "message",
|
||||
"user": "UTEST",
|
||||
"channel": "CTEST",
|
||||
"ts": "1700000000.000001",
|
||||
"text": f"<@UBOT> Explain this error\n{body}\nEnd",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "rich_text",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [
|
||||
{"type": "user", "user_id": "UBOT"},
|
||||
{"type": "text", "text": "Explain this error"},
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "rich_text_section"
|
||||
if block_type == "inline_code"
|
||||
else block_type,
|
||||
"elements": [body_element],
|
||||
},
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [{"type": "text", "text": "End"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
received = await adapter.convert_message(event)
|
||||
components = received.message
|
||||
assert isinstance(components[0], At)
|
||||
assert components[0].qq == "UBOT"
|
||||
expected_body = body
|
||||
if block_type in ("rich_text_preformatted", "rich_text_quote"):
|
||||
expected_body = f"\n{expected_body}\n"
|
||||
assert received.message_str == f"Explain this error{expected_body}End"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("before_mention", "after_mention"),
|
||||
[
|
||||
("quoted before ", " after"),
|
||||
("quoted text ", ""),
|
||||
],
|
||||
ids=["mention_middle", "mention_last"],
|
||||
)
|
||||
def test_slack_quote_keeps_boundaries_around_mentions(before_mention, after_mention):
|
||||
adapter = SlackAdapter(
|
||||
make_platform_config("slack", bot_token="xoxb-test", app_token="xapp-test"),
|
||||
{},
|
||||
asyncio.Queue(),
|
||||
)
|
||||
components = adapter._parse_blocks(
|
||||
[
|
||||
{
|
||||
"type": "rich_text",
|
||||
"elements": [
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [{"type": "text", "text": "Question:"}],
|
||||
},
|
||||
{
|
||||
"type": "rich_text_quote",
|
||||
"elements": [
|
||||
{"type": "text", "text": before_mention},
|
||||
{"type": "user", "user_id": "UOTHER"},
|
||||
{"type": "text", "text": after_mention},
|
||||
],
|
||||
},
|
||||
{
|
||||
"type": "rich_text_section",
|
||||
"elements": [{"type": "text", "text": "Next paragraph"}],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
assert "".join(c.text for c in components if isinstance(c, Plain)) == (
|
||||
f"Question:\n{before_mention}{after_mention}\nNext paragraph"
|
||||
)
|
||||
assert [c.qq for c in components if isinstance(c, At)] == ["UOTHER"]
|
||||
Reference in New Issue
Block a user