From 98aef0f53064c343fd48516fd44b0cfb9ddf1d3c Mon Sep 17 00:00:00 2001 From: wzr <2668940489@qq.com> Date: Fri, 27 Mar 2026 04:38:22 +0800 Subject: [PATCH] feat(bot): use interactive card with markdown for Feishu messages (v2) Change Feishu message format from post (plain text) to interactive card with markdown rendering. Fixes review feedback from #981. v2 fixes: - P0: Remove extra {"card": ...} wrapper from card JSON - P1: Mention prefix on its own line to avoid breaking heading parsing - P1: Only add mention prefix when reply_to_message_id is present Closes #960 --- bot/vikingbot/channels/feishu.py | 61 ++++++++++++++------------------ 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/bot/vikingbot/channels/feishu.py b/bot/vikingbot/channels/feishu.py index d5286c2f1a..0eb49f3aac 100644 --- a/bot/vikingbot/channels/feishu.py +++ b/bot/vikingbot/channels/feishu.py @@ -535,52 +535,44 @@ async def send(self, msg: OutboundMessage) -> None: content_with_mentions = cleaned_content - # Check if we need to reply to a specific message - # Get reply message ID from metadata (original incoming message ID) + # --- Build interactive card with markdown rendering --- reply_to_message_id = None + original_sender_id = None + chat_type = "group" if msg.metadata: reply_to_message_id = msg.metadata.get("reply_to_message_id") or msg.metadata.get( "message_id" ) - - # Build post message content - content_elements = [] - - # Add @mention for the original sender when replying - original_sender_id = None - chat_type = "group" - if reply_to_message_id and msg.metadata: original_sender_id = msg.metadata.get("sender_id") chat_type = msg.metadata.get("chat_type", "group") - # Build content line: [@mention, text content] - content_line = [] + # Build card elements using markdown for proper formatting + card_elements: list[dict] = [] - # Add @mention element for original sender when replying (only in group chats) - if original_sender_id and chat_type == "group": - content_line.append({"tag": "at", "user_id": original_sender_id}) + # @mention prefix only when replying in group chats + mention_prefix = "" + if reply_to_message_id and original_sender_id and chat_type == "group": + mention_prefix = f"" - # Add text content if content_with_mentions.strip(): - content_line.append({"tag": "text", "text": content_with_mentions}) - - # Add content line if not empty - if content_line: - content_elements.append(content_line) + md_content = ( + f"{mention_prefix}\n{content_with_mentions}" + if mention_prefix + else content_with_mentions + ) + card_elements.extend(self._build_card_elements(md_content)) + elif mention_prefix: + card_elements.append({"tag": "markdown", "content": mention_prefix}) # Add images for img in images: - content_elements.append([{"tag": "img", "image_key": img["image_key"]}]) - - # Ensure we have content - if not content_elements: - content_elements.append([{"tag": "text", "text": " "}]) - - post_content = {"zh_cn": {"title": "", "content": content_elements}} + card_elements.append({"tag": "img", "img_key": img["image_key"]}) - import json + if not card_elements: + card_elements.append({"tag": "markdown", "content": " "}) - content = json.dumps(post_content, ensure_ascii=False) + # Build interactive card message (no extra {"card": ...} wrapper) + card_content = json.dumps({"elements": card_elements}, ensure_ascii=False) if reply_to_message_id: # Reply to existing message (quotes the original) @@ -598,9 +590,8 @@ async def send(self, msg: OutboundMessage) -> None: .message_id(reply_to_message_id) .request_body( ReplyMessageRequestBody.builder() - .content(content) - .msg_type("post") - # Only reply in topic thread if it's actually a topic thread (not regular group) + .content(card_content) + .msg_type("interactive") .reply_in_thread(should_reply_in_thread) .build() ) @@ -615,8 +606,8 @@ async def send(self, msg: OutboundMessage) -> None: .request_body( CreateMessageRequestBody.builder() .receive_id(reply_to) - .msg_type("post") - .content(content) + .msg_type("interactive") + .content(card_content) .build() ) .build()