Merge PR #1361: fix(feishu): parse post wrapper payload for rich text messages

This commit is contained in:
Re-bin
2026-03-01 06:36:32 +00:00
2 changed files with 87 additions and 45 deletions

View File

@@ -181,57 +181,59 @@ def _extract_element_content(element: dict) -> list[str]:
def _extract_post_content(content_json: dict) -> tuple[str, list[str]]: def _extract_post_content(content_json: dict) -> tuple[str, list[str]]:
"""Extract text and image keys from Feishu post (rich text) message content. """Extract text and image keys from Feishu post (rich text) message.
Supports two formats: Handles three payload shapes:
1. Direct format: {"title": "...", "content": [...]} - Direct: {"title": "...", "content": [[...]]}
2. Localized format: {"zh_cn": {"title": "...", "content": [...]}} - Localized: {"zh_cn": {"title": "...", "content": [...]}}
- Wrapped: {"post": {"zh_cn": {"title": "...", "content": [...]}}}
Returns:
(text, image_keys) - extracted text and list of image keys
""" """
def extract_from_lang(lang_content: dict) -> tuple[str | None, list[str]]:
if not isinstance(lang_content, dict): def _parse_block(block: dict) -> tuple[str | None, list[str]]:
if not isinstance(block, dict) or not isinstance(block.get("content"), list):
return None, [] return None, []
title = lang_content.get("title", "") texts, images = [], []
content_blocks = lang_content.get("content", []) if title := block.get("title"):
if not isinstance(content_blocks, list): texts.append(title)
return None, [] for row in block["content"]:
text_parts = [] if not isinstance(row, list):
image_keys = []
if title:
text_parts.append(title)
for block in content_blocks:
if not isinstance(block, list):
continue continue
for element in block: for el in row:
if isinstance(element, dict): if not isinstance(el, dict):
tag = element.get("tag") continue
if tag == "text": tag = el.get("tag")
text_parts.append(element.get("text", "")) if tag in ("text", "a"):
elif tag == "a": texts.append(el.get("text", ""))
text_parts.append(element.get("text", ""))
elif tag == "at": elif tag == "at":
text_parts.append(f"@{element.get('user_name', 'user')}") texts.append(f"@{el.get('user_name', 'user')}")
elif tag == "img": elif tag == "img" and (key := el.get("image_key")):
img_key = element.get("image_key") images.append(key)
if img_key: return (" ".join(texts).strip() or None), images
image_keys.append(img_key)
text = " ".join(text_parts).strip() if text_parts else None
return text, image_keys
# Try direct format first # Unwrap optional {"post": ...} envelope
if "content" in content_json: root = content_json
text, images = extract_from_lang(content_json) if isinstance(root, dict) and isinstance(root.get("post"), dict):
if text or images: root = root["post"]
return text or "", images if not isinstance(root, dict):
return "", []
# Try localized format # Direct format
for lang_key in ("zh_cn", "en_us", "ja_jp"): if "content" in root:
lang_content = content_json.get(lang_key) text, imgs = _parse_block(root)
text, images = extract_from_lang(lang_content) if text or imgs:
if text or images: return text or "", imgs
return text or "", images
# Localized: prefer known locales, then fall back to any dict child
for key in ("zh_cn", "en_us", "ja_jp"):
if key in root:
text, imgs = _parse_block(root[key])
if text or imgs:
return text or "", imgs
for val in root.values():
if isinstance(val, dict):
text, imgs = _parse_block(val)
if text or imgs:
return text or "", imgs
return "", [] return "", []

View File

@@ -0,0 +1,40 @@
from nanobot.channels.feishu import _extract_post_content
def test_extract_post_content_supports_post_wrapper_shape() -> None:
payload = {
"post": {
"zh_cn": {
"title": "日报",
"content": [
[
{"tag": "text", "text": "完成"},
{"tag": "img", "image_key": "img_1"},
]
],
}
}
}
text, image_keys = _extract_post_content(payload)
assert text == "日报 完成"
assert image_keys == ["img_1"]
def test_extract_post_content_keeps_direct_shape_behavior() -> None:
payload = {
"title": "Daily",
"content": [
[
{"tag": "text", "text": "report"},
{"tag": "img", "image_key": "img_a"},
{"tag": "img", "image_key": "img_b"},
]
],
}
text, image_keys = _extract_post_content(payload)
assert text == "Daily report"
assert image_keys == ["img_a", "img_b"]