refactor(feishu): drop dead helper, allow configurable card header, tidy regex

This commit is contained in:
matevip 2026-05-20 10:13:53 +08:00
parent 56ab7b3095
commit 8d91197a46
2 changed files with 23 additions and 10 deletions

View File

@ -19,7 +19,7 @@ final class FeishuCardFormatter {
private static final Pattern HEADER = Pattern.compile("(?m)^#{1,6}\\s"); 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 TABLE_SEP = Pattern.compile("(?m)^\\|[\\s|:-]+\\|\\s*$");
private static final Pattern JSON_CODE_BLOCK = 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() {} private FeishuCardFormatter() {}
@ -68,24 +68,35 @@ final class FeishuCardFormatter {
.count(); .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<String, Object> render(String content, ContentFormat format) { static Map<String, Object> 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<String, Object> render(String content, ContentFormat format, String markdownHeader) {
return switch (format) { return switch (format) {
case JSON -> renderJson(content); case JSON -> renderJson(content);
case MARKDOWN -> renderMarkdown(content); case MARKDOWN -> renderMarkdown(content, markdownHeader);
case LONG_TEXT, PLAIN_TEXT -> renderLongText(content); case LONG_TEXT, PLAIN_TEXT -> renderLongText(content);
}; };
} }
private static Map<String, Object> renderMarkdown(String content) { private static Map<String, Object> renderMarkdown(String content, String headerText) {
Map<String, Object> header = (headerText == null || headerText.isBlank())
? null
: Map.of("title", Map.of("tag", "plain_text", "content", headerText));
return cardOf( return cardOf(
Map.of("title", Map.of("tag", "plain_text", "content", "AI 助手")), header,
List.of(Map.of( List.of(Map.of(
"tag", "div", "tag", "div",
"text", Map.of("tag", "lark_md", "content", content) "text", Map.of("tag", "lark_md", "content", content)

View File

@ -54,6 +54,7 @@ import java.util.concurrent.TimeUnit;
* - stale_event_threshold_seconds: 过滤旧事件阈值默认 300 禁用 * - stale_event_threshold_seconds: 过滤旧事件阈值默认 300 禁用
* - card_format: 卡片格式化模式 "auto"默认| "always" | "never" * - card_format: 卡片格式化模式 "auto"默认| "always" | "never"
* auto: 根据内容自动检测always: 全部包卡片never: 全部纯文本降级/调试用 * auto: 根据内容自动检测always: 全部包卡片never: 全部纯文本降级/调试用
* - card_header: Markdown 卡片 header 文案默认 "AI 助手"设为空串可隐藏 header
* - require_mention: 群聊中是否需要 @机器人 才响应默认 false * - require_mention: 群聊中是否需要 @机器人 才响应默认 false
* true: 仅当消息中 @了机器人才处理通过飞书 mentions 字段精确判断无需配置 botPrefix * true: 仅当消息中 @了机器人才处理通过飞书 mentions 字段精确判断无需配置 botPrefix
* *
@ -1413,7 +1414,8 @@ public class FeishuChannelAdapter extends AbstractChannelAdapter {
boolean preferCard = "always".equals(cardFormat) || fmt != FeishuCardFormatter.ContentFormat.PLAIN_TEXT; boolean preferCard = "always".equals(cardFormat) || fmt != FeishuCardFormatter.ContentFormat.PLAIN_TEXT;
if (preferCard) { 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; if (sent) return;
// Card path bailed (oversized payload, null guard, or network error) // Card path bailed (oversized payload, null guard, or network error)
// fall through to text so the user doesn't get nothing. // fall through to text so the user doesn't get nothing.