From eb8efeba98ca2b59edb3bd0e6723fd18b0954d15 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 18 Oct 2026 10:15:53 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=85=BC=E5=AE=B9=20richText=20?= =?UTF-8?q?=E6=B6=88=E6=81=AF=E4=B8=AD=E4=B8=8D=E5=B8=A6=20type=20?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E7=9A=84=E6=96=87=E6=9C=AC=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E8=A7=A3=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../normalize/converters/richtext.py | 9 ++- tests/test_message.py | 65 +++++++++++++++++++ tests/test_normalize_converters.py | 19 ++++++ 3 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/dingtalk_channel_sdk/normalize/converters/richtext.py b/src/dingtalk_channel_sdk/normalize/converters/richtext.py index d5bebd5..c9516fc 100644 --- a/src/dingtalk_channel_sdk/normalize/converters/richtext.py +++ b/src/dingtalk_channel_sdk/normalize/converters/richtext.py @@ -16,11 +16,14 @@ def convert_rich_text(content: Dict[str, Any]) -> Tuple[str, List[Dict[str, Any] resources: List[Dict[str, Any]] = [] seen: set = set() parts: List[str] = [] - for item in (content or {}).get("richText", []): + rich_list = (content or {}).get("richText") + if not isinstance(rich_list, list): + return "", mentions, resources + for item in rich_list: if not isinstance(item, dict): continue - item_type = item.get("type", "") - if item_type == "text": + item_type = item.get("type") or "" + if item_type in ("", "text"): text = item.get("text", "") if isinstance(text, str): parts.append(text) diff --git a/tests/test_message.py b/tests/test_message.py index b74ba96..56a9050 100644 --- a/tests/test_message.py +++ b/tests/test_message.py @@ -30,6 +30,71 @@ def test_normalize_richtext(): assert any(m.get("userId") == "staff-2" for m in msg.mentions) +def test_normalize_richtext_with_attachment_without_text_type(): + """钉钉带附件的 richText 消息:顶层无 text 字段,且 richText 文本段无 type 字段。""" + # 群聊:@机器人 + 文本 + 图片 + data_group = json.dumps({ + "conversationId": "cid-1", + "conversationType": "2", + "msgId": "rt-2", + "senderStaffId": "staff-1", + "senderNick": "John", + "sessionWebhook": "", + "isInAtList": True, + "msgtype": "richText", + "content": { + "richText": [ + {"text": "@机器人 请帮我看看这张图"}, + {"downloadCode": "dc-pic-1", "type": "picture", "pictureDownloadCode": "pdc-1"}, + ] + }, + }) + msg_group = normalize_incoming(data_group) + assert msg_group.text == "请帮我看看这张图" + assert msg_group.resources == [{"type": "image", "downloadCode": "dc-pic-1"}] + + # 单聊真实样本:图片段在前 + {"text": "\n"} + {"text": "请解释下"}(均无 type 字段) + data_dm = json.dumps({ + "senderPlatform": "Win", + "conversationId": "cidXxxxxx=", + "chatbotCorpId": "dingxxxxxx", + "chatbotUserId": "$:LWCP_v1:$xXxxxx", + "openThreadId": "cidXxxxxx=", + "msgId": "msgsXxxxxxx", + "senderNick": "Xxxxx", + "isAdmin": True, + "senderStaffId": "0000000000-1234334534", + "sessionWebhookExpiredTime": 1792294060566, + "createAt": 1792288660182, + "content": { + "richText": [ + { + "pictureDownloadCode": "EJiwxxxxx", + "downloadCode": "mIoxxxxx", + "type": "picture", + }, + { + "text": "\n", + }, + { + "text": "请解释下", + }, + ] + }, + "senderCorpId": "dingxxxxxx", + "conversationType": "1", + "senderId": "$:LWCP_v1:$Xxxxxx", + "sessionWebhook": "https://oapi.dingtalk.com/robot/sendBySession?session=xxxx", + "robotCode": "dingjgxxxxxxxxxxxxxxx", + "msgtype": "richText", + }) + msg_dm = normalize_incoming(data_dm) + assert msg_dm.text == "请解释下" + assert msg_dm.resources == [{"type": "image", "downloadCode": "mIoxxxxx"}] + + + + def test_normalize_picture(): data = json.dumps({ "conversationId": "cid-1", diff --git a/tests/test_normalize_converters.py b/tests/test_normalize_converters.py index 62da11d..31e7823 100644 --- a/tests/test_normalize_converters.py +++ b/tests/test_normalize_converters.py @@ -178,3 +178,22 @@ def test_convert_rich_text_resources_dirty_data(): text, resources, _ = parse_content("richText", content, []) assert text == "ok" assert resources == [] + + +def test_convert_rich_text_without_type_field(): + """钉钉真实 richText 回调中文本段通常不带 type 字段(仅有 {"text": "..."})。""" + content = { + "richText": [ + {"text": "请帮我看看"}, + {"downloadCode": "dc-img-1", "type": "picture", "pictureDownloadCode": "pdc-1"}, + {"text": "和这份文档"}, + {"downloadCode": "dc-file-1", "type": "file", "fileName": "spec.pdf"}, + ] + } + text, resources, _ = parse_content("richText", content, []) + assert text == "请帮我看看和这份文档" + assert resources == [ + {"type": "image", "downloadCode": "dc-img-1"}, + {"type": "file", "downloadCode": "dc-file-1", "fileName": "spec.pdf"}, + ] +