diff --git a/nanobot/channels/feishu/runtime.py b/nanobot/channels/feishu/runtime.py index b6ddb225..51f6d8b5 100644 --- a/nanobot/channels/feishu/runtime.py +++ b/nanobot/channels/feishu/runtime.py @@ -269,7 +269,7 @@ def _extract_element_content(element: dict) -> list[str]: parts.append(text_content) elif isinstance(text, str): parts.append(text) - for field in element.get("fields", []): + for field in element.get("fields") or []: if isinstance(field, dict): field_text = field.get("text", {}) if isinstance(field_text, dict): @@ -291,7 +291,10 @@ def _extract_element_content(element: dict) -> list[str]: c = text.get("content", "") if c: parts.append(c) - url = element.get("url", "") or element.get("multi_url", {}).get("url", "") + multi_url = element.get("multi_url") or {} + url = element.get("url", "") or ( + multi_url.get("url", "") if isinstance(multi_url, dict) else "" + ) if url: parts.append(f"link: {url}") @@ -300,12 +303,14 @@ def _extract_element_content(element: dict) -> list[str]: parts.append(alt.get("content", "[image]") if isinstance(alt, dict) else "[image]") elif tag == "note": - for ne in element.get("elements", []): + for ne in element.get("elements") or []: parts.extend(_extract_element_content(ne)) elif tag == "column_set": - for col in element.get("columns", []): - for ce in col.get("elements", []): + for col in element.get("columns") or []: + if not isinstance(col, dict): + continue + for ce in col.get("elements") or []: parts.extend(_extract_element_content(ce)) elif tag == "plain_text": @@ -319,7 +324,7 @@ def _extract_element_content(element: dict) -> list[str]: for column in (element.get("columns") or []) if isinstance(column, dict) and column.get("name") ] - rows = element.get("rows", []) + rows = element.get("rows") or [] if columns: parts.append(" | ".join(header for _, header in columns)) if isinstance(rows, list): @@ -337,7 +342,7 @@ def _extract_element_content(element: dict) -> list[str]: parts.append(row_text) else: - for ne in element.get("elements", []): + for ne in element.get("elements") or []: parts.extend(_extract_element_content(ne)) return parts diff --git a/nanobot/channels/feishu/tests/test_feishu_card_extraction.py b/nanobot/channels/feishu/tests/test_feishu_card_extraction.py index b2f411d9..c403857a 100644 --- a/nanobot/channels/feishu/tests/test_feishu_card_extraction.py +++ b/nanobot/channels/feishu/tests/test_feishu_card_extraction.py @@ -1,6 +1,7 @@ import json from nanobot.channels.feishu.runtime import ( + _extract_element_content, _extract_post_content, _extract_share_card_content, ) @@ -60,3 +61,28 @@ def test_extract_post_content_tolerates_null_fields() -> None: assert "@user" in text assert "ok" in text assert images == [] + + +def test_extract_button_tolerates_null_multi_url() -> None: + element = {"tag": "button", "text": {"content": "Go"}, "multi_url": None} + assert _extract_element_content(element) == ["Go"] + + +def test_extract_column_set_tolerates_null_columns_and_elements() -> None: + assert _extract_element_content({"tag": "column_set", "columns": None}) == [] + assert _extract_element_content( + {"tag": "column_set", "columns": [{"elements": None}]} + ) == [] + + +def test_extract_div_tolerates_null_fields() -> None: + assert _extract_element_content( + {"tag": "div", "text": {"content": "hi"}, "fields": None} + ) == ["hi"] + + +def test_interactive_card_button_null_multi_url() -> None: + content = { + "elements": [{"tag": "button", "text": {"content": "Go"}, "multi_url": None}] + } + assert _extract_share_card_content(content, "interactive") == "Go"