fix(feishu): extract table card rows
Maintainer edit: parse Feishu card table columns and rows so forwarded table cards do not fall back to [interactive], and add parser regression coverage.
This commit is contained in:
@@ -166,6 +166,28 @@ def _extract_interactive_content(content: dict) -> list[str]:
|
|||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
|
||||||
|
def _stringify_table_cell(value: Any) -> str:
|
||||||
|
if value is None:
|
||||||
|
return ""
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value.strip()
|
||||||
|
if isinstance(value, int | float | bool):
|
||||||
|
return str(value)
|
||||||
|
if isinstance(value, list):
|
||||||
|
return " ".join(filter(None, (_stringify_table_cell(item) for item in value)))
|
||||||
|
if isinstance(value, dict):
|
||||||
|
nested = _extract_element_content(value)
|
||||||
|
if nested:
|
||||||
|
return " ".join(nested)
|
||||||
|
for key in ("content", "text", "value", "name"):
|
||||||
|
text = value.get(key)
|
||||||
|
if isinstance(text, str):
|
||||||
|
return text.strip()
|
||||||
|
if isinstance(text, int | float | bool):
|
||||||
|
return str(text)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def _extract_element_content(element: dict) -> list[str]:
|
def _extract_element_content(element: dict) -> list[str]:
|
||||||
"""Extract content from a single card element."""
|
"""Extract content from a single card element."""
|
||||||
parts = []
|
parts = []
|
||||||
@@ -237,6 +259,28 @@ def _extract_element_content(element: dict) -> list[str]:
|
|||||||
if content:
|
if content:
|
||||||
parts.append(content)
|
parts.append(content)
|
||||||
|
|
||||||
|
elif tag == "table":
|
||||||
|
columns = element.get("columns", [])
|
||||||
|
rows = element.get("rows", [])
|
||||||
|
if isinstance(columns, list):
|
||||||
|
column_names = []
|
||||||
|
headers = []
|
||||||
|
for column in columns:
|
||||||
|
if not isinstance(column, dict) or not column.get("name"):
|
||||||
|
continue
|
||||||
|
column_names.append(column["name"])
|
||||||
|
headers.append(str(column.get("display_name") or column["name"]))
|
||||||
|
if headers:
|
||||||
|
parts.append(" | ".join(headers))
|
||||||
|
if isinstance(rows, list):
|
||||||
|
for row in rows:
|
||||||
|
if not isinstance(row, dict):
|
||||||
|
continue
|
||||||
|
values = [_stringify_table_cell(row.get(name)) for name in column_names]
|
||||||
|
row_text = " | ".join(values).strip()
|
||||||
|
if row_text:
|
||||||
|
parts.append(row_text)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for ne in element.get("elements", []):
|
for ne in element.get("elements", []):
|
||||||
parts.extend(_extract_element_content(ne))
|
parts.extend(_extract_element_content(ne))
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from nanobot.channels.feishu import _extract_share_card_content
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_interactive_card_reads_user_dsl_body_elements() -> None:
|
||||||
|
content = {
|
||||||
|
"user_dsl": json.dumps(
|
||||||
|
{
|
||||||
|
"schema": "2.0",
|
||||||
|
"body": {"elements": [{"tag": "markdown", "content": "**hello**"}]},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert _extract_share_card_content(content, "interactive") == "**hello**"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_interactive_card_reads_nested_text_elements() -> None:
|
||||||
|
content = {"elements": [[{"tag": "text", "text": "hello"}]]}
|
||||||
|
|
||||||
|
assert _extract_share_card_content(content, "interactive") == "hello"
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_interactive_card_reads_table_rows() -> None:
|
||||||
|
content = {
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"tag": "table",
|
||||||
|
"columns": [
|
||||||
|
{"name": "c0", "display_name": "Name"},
|
||||||
|
{"name": "c1", "display_name": "Score"},
|
||||||
|
],
|
||||||
|
"rows": [{"c0": "Alice", "c1": 98}],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
assert _extract_share_card_content(content, "interactive") == "Name | Score\nAlice | 98"
|
||||||
Reference in New Issue
Block a user