From 8d91197a4611ba7002f140d931d447aa294644ca Mon Sep 17 00:00:00 2001 From: matevip Date: Wed, 20 May 2026 10:13:53 +0800 Subject: [PATCH] refactor(feishu): drop dead helper, allow configurable card header, tidy regex --- .../channel/feishu/FeishuCardFormatter.java | 29 +++++++++++++------ .../channel/feishu/FeishuChannelAdapter.java | 4 ++- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuCardFormatter.java b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuCardFormatter.java index 37fc7f27..c785ee95 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuCardFormatter.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuCardFormatter.java @@ -19,7 +19,7 @@ final class FeishuCardFormatter { private static final Pattern HEADER = Pattern.compile("(?m)^#{1,6}\\s"); private static final Pattern TABLE_SEP = Pattern.compile("(?m)^\\|[\\s|:-]+\\|\\s*$"); private static final Pattern JSON_CODE_BLOCK = - Pattern.compile("(?s)```(?:json)?\\s*([\\[{][\\s\\S]*?[\\]\\}])\\s*```"); + Pattern.compile("(?s)```(?:json)?\\s*([\\[{][\\s\\S]*?[\\]}])\\s*```"); private FeishuCardFormatter() {} @@ -68,24 +68,35 @@ final class FeishuCardFormatter { .count(); } - private static String extractJsonCodeBlock(String s) { - Matcher m = JSON_CODE_BLOCK.matcher(s); - return m.find() ? m.group(1).strip() : null; - } - // ==================== 渲染层 ==================== + /** Default markdown card header used when the channel doesn't override it. */ + static final String DEFAULT_MARKDOWN_HEADER = "AI 助手"; + static Map render(String content, ContentFormat format) { + return render(content, format, DEFAULT_MARKDOWN_HEADER); + } + + /** + * Renders the card for the given content/format pair. {@code markdownHeader} + * controls the title shown above markdown cards — pass null or blank to + * suppress the header entirely. JSON and long-text/plain-text layouts ignore + * the header (they have never carried one). + */ + static Map render(String content, ContentFormat format, String markdownHeader) { return switch (format) { case JSON -> renderJson(content); - case MARKDOWN -> renderMarkdown(content); + case MARKDOWN -> renderMarkdown(content, markdownHeader); case LONG_TEXT, PLAIN_TEXT -> renderLongText(content); }; } - private static Map renderMarkdown(String content) { + private static Map renderMarkdown(String content, String headerText) { + Map header = (headerText == null || headerText.isBlank()) + ? null + : Map.of("title", Map.of("tag", "plain_text", "content", headerText)); return cardOf( - Map.of("title", Map.of("tag", "plain_text", "content", "AI 助手")), + header, List.of(Map.of( "tag", "div", "text", Map.of("tag", "lark_md", "content", content) diff --git a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java index 3ce0c688..8a67011d 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/feishu/FeishuChannelAdapter.java @@ -54,6 +54,7 @@ import java.util.concurrent.TimeUnit; * - stale_event_threshold_seconds: 过滤旧事件阈值(默认 30,0 禁用) * - card_format: 卡片格式化模式 "auto"(默认)| "always" | "never" * auto: 根据内容自动检测;always: 全部包卡片;never: 全部纯文本(降级/调试用) + * - card_header: Markdown 卡片 header 文案,默认 "AI 助手";设为空串可隐藏 header * - require_mention: 群聊中是否需要 @机器人 才响应(默认 false) * true: 仅当消息中 @了机器人才处理;通过飞书 mentions 字段精确判断,无需配置 botPrefix * @@ -1413,7 +1414,8 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter { boolean preferCard = "always".equals(cardFormat) || fmt != FeishuCardFormatter.ContentFormat.PLAIN_TEXT; if (preferCard) { - boolean sent = sendCard(targetId, FeishuCardFormatter.render(content, fmt)); + String cardHeader = getConfigString("card_header", FeishuCardFormatter.DEFAULT_MARKDOWN_HEADER); + boolean sent = sendCard(targetId, FeishuCardFormatter.render(content, fmt, cardHeader)); if (sent) return; // Card path bailed (oversized payload, null guard, or network error) — // fall through to text so the user doesn't get nothing.