fix(feishu): harden fenced post row splitting
This commit is contained in:
@@ -119,6 +119,8 @@ _MARKDOWN_HINT_RE = re.compile(
|
||||
re.MULTILINE,
|
||||
)
|
||||
_MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)")
|
||||
_MARKDOWN_FENCE_OPEN_RE = re.compile(r"^```([^\n`]*)\s*$")
|
||||
_MARKDOWN_FENCE_CLOSE_RE = re.compile(r"^```\s*$")
|
||||
_MENTION_RE = re.compile(r"@_user_\d+")
|
||||
_MULTISPACE_RE = re.compile(r"[ \t]{2,}")
|
||||
_POST_CONTENT_INVALID_RE = re.compile(r"content format of the post type is incorrect", re.IGNORECASE)
|
||||
@@ -445,9 +447,9 @@ def _build_markdown_post_rows(content: str) -> List[List[Dict[str, str]]]:
|
||||
"""Build Feishu post rows while isolating fenced code blocks.
|
||||
|
||||
Feishu's `md` renderer can swallow trailing content when a fenced code block
|
||||
appears inside one large markdown element. Splitting the reply at code
|
||||
fences preserves the surrounding markdown while keeping the code block in a
|
||||
dedicated row.
|
||||
appears inside one large markdown element. Split the reply at real fence
|
||||
lines so prose before/after the code block remains visible while code stays
|
||||
in a dedicated row.
|
||||
"""
|
||||
if not content:
|
||||
return [[{"tag": "md", "text": ""}]]
|
||||
@@ -458,32 +460,35 @@ def _build_markdown_post_rows(content: str) -> List[List[Dict[str, str]]]:
|
||||
current: List[str] = []
|
||||
in_code_block = False
|
||||
|
||||
def _flush_current() -> None:
|
||||
nonlocal current
|
||||
if not current:
|
||||
return
|
||||
segment = "\n".join(current)
|
||||
if segment.strip():
|
||||
rows.append([{"tag": "md", "text": segment}])
|
||||
current = []
|
||||
|
||||
for raw_line in content.splitlines():
|
||||
line = raw_line.rstrip()
|
||||
is_fence = line.strip().startswith("```")
|
||||
stripped_line = raw_line.strip()
|
||||
is_fence = bool(
|
||||
_MARKDOWN_FENCE_CLOSE_RE.match(stripped_line)
|
||||
if in_code_block
|
||||
else _MARKDOWN_FENCE_OPEN_RE.match(stripped_line)
|
||||
)
|
||||
|
||||
if is_fence:
|
||||
if not in_code_block and current:
|
||||
segment = "\n".join(current).strip()
|
||||
if segment:
|
||||
rows.append([{"tag": "md", "text": segment}])
|
||||
current = []
|
||||
current.append(line)
|
||||
if not in_code_block:
|
||||
_flush_current()
|
||||
current.append(raw_line)
|
||||
in_code_block = not in_code_block
|
||||
if not in_code_block:
|
||||
segment = "\n".join(current).strip()
|
||||
if segment:
|
||||
rows.append([{"tag": "md", "text": segment}])
|
||||
current = []
|
||||
_flush_current()
|
||||
continue
|
||||
|
||||
current.append(line)
|
||||
|
||||
if current:
|
||||
segment = "\n".join(current).strip()
|
||||
if segment:
|
||||
rows.append([{"tag": "md", "text": segment}])
|
||||
current.append(raw_line)
|
||||
|
||||
_flush_current()
|
||||
return rows or [[{"tag": "md", "text": content}]]
|
||||
|
||||
|
||||
|
||||
@@ -2433,6 +2433,48 @@ class TestAdapterBehavior(unittest.TestCase):
|
||||
],
|
||||
)
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_post_payload_keeps_fence_like_code_lines_inside_code_block(self):
|
||||
from gateway.config import PlatformConfig
|
||||
from gateway.platforms.feishu import FeishuAdapter
|
||||
|
||||
adapter = FeishuAdapter(PlatformConfig())
|
||||
payload = json.loads(
|
||||
adapter._build_post_payload(
|
||||
"before\n```python\n```oops\n```\nafter"
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
payload["zh_cn"]["content"],
|
||||
[
|
||||
[{"tag": "md", "text": "before"}],
|
||||
[{"tag": "md", "text": "```python\n```oops\n```"}],
|
||||
[{"tag": "md", "text": "after"}],
|
||||
],
|
||||
)
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_build_post_payload_preserves_trailing_spaces_in_code_block(self):
|
||||
from gateway.config import PlatformConfig
|
||||
from gateway.platforms.feishu import FeishuAdapter
|
||||
|
||||
adapter = FeishuAdapter(PlatformConfig())
|
||||
payload = json.loads(
|
||||
adapter._build_post_payload(
|
||||
"before\n```python\nline with two spaces \n```\nafter"
|
||||
)
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
payload["zh_cn"]["content"],
|
||||
[
|
||||
[{"tag": "md", "text": "before"}],
|
||||
[{"tag": "md", "text": "```python\nline with two spaces \n```"}],
|
||||
[{"tag": "md", "text": "after"}],
|
||||
],
|
||||
)
|
||||
|
||||
@patch.dict(os.environ, {}, clear=True)
|
||||
def test_send_falls_back_to_text_when_post_payload_is_rejected(self):
|
||||
from gateway.config import PlatformConfig
|
||||
|
||||
Reference in New Issue
Block a user