update markdown support to feishu
This commit is contained in:
parent
4abc18cec9
commit
3e36da2b84
@ -6,6 +6,7 @@ import {
|
||||
|
||||
// Import feishu functions directly from source
|
||||
import {
|
||||
getStartupChatIds,
|
||||
listFeishuAccountIds,
|
||||
resolveFeishuAccount,
|
||||
resolveDefaultFeishuAccountId,
|
||||
@ -157,19 +158,19 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
onMessage,
|
||||
});
|
||||
|
||||
// Send startup message if configured
|
||||
const startupChatId = account.config.startupChatId;
|
||||
if (startupChatId) {
|
||||
// Send startup message to all configured startup chat IDs
|
||||
const startupChatIds = getStartupChatIds(account.config);
|
||||
const timestamp = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
||||
for (const chatId of startupChatIds) {
|
||||
try {
|
||||
const timestamp = new Date().toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
|
||||
await client.sendTextMessage(
|
||||
startupChatId,
|
||||
chatId,
|
||||
`🚀 Clawdbot 飞书网关已启动 (${timestamp})`,
|
||||
"chat_id",
|
||||
);
|
||||
ctx.log?.info(`[${account.accountId}] sent startup message to ${startupChatId}`);
|
||||
ctx.log?.info(`[${account.accountId}] sent startup message to ${chatId}`);
|
||||
} catch (err) {
|
||||
ctx.log?.warn(`[${account.accountId}] failed to send startup message: ${err}`);
|
||||
ctx.log?.warn(`[${account.accountId}] failed to send startup message to ${chatId}: ${err}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -42,8 +42,17 @@ export type FeishuAccountConfig = {
|
||||
/** Encrypt key for event decryption (optional). */
|
||||
encryptKey?: string;
|
||||
|
||||
/** Chat ID to send a startup message to when gateway starts. */
|
||||
startupChatId?: string;
|
||||
/**
|
||||
* Chat IDs to send a startup message to when gateway starts.
|
||||
* Supports one or multiple group chat IDs (array or legacy single string).
|
||||
*/
|
||||
startupChatId?: string | string[];
|
||||
|
||||
/**
|
||||
* When true, only allow messages from groups listed in startupChatId;
|
||||
* private chats (DMs) are not allowed. Ignored if startupChatId is empty.
|
||||
*/
|
||||
allowOnlyStartupChats?: boolean;
|
||||
|
||||
/**
|
||||
* Event subscription mode:
|
||||
|
||||
@ -11,6 +11,18 @@ const debugAccounts = (...args: unknown[]) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** Normalize startupChatId config (string or string[]) to a non-empty string array. */
|
||||
export function getStartupChatIds(config: FeishuAccountConfig): string[] {
|
||||
const raw = config.startupChatId;
|
||||
if (Array.isArray(raw)) {
|
||||
return raw.map((s) => String(s).trim()).filter(Boolean);
|
||||
}
|
||||
if (raw != null && String(raw).trim()) {
|
||||
return [String(raw).trim()];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
export type ResolvedFeishuAccount = {
|
||||
accountId: string;
|
||||
enabled: boolean;
|
||||
|
||||
@ -23,6 +23,7 @@ export {
|
||||
type FeishuInteractiveCard,
|
||||
} from "./send.js";
|
||||
export {
|
||||
getStartupChatIds,
|
||||
resolveFeishuAccount,
|
||||
listFeishuAccountIds,
|
||||
listEnabledFeishuAccounts,
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
import type { ClawdbotConfig } from "../config/config.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import type { ResolvedFeishuAccount } from "./accounts.js";
|
||||
import { getStartupChatIds, type ResolvedFeishuAccount } from "./accounts.js";
|
||||
import type { FeishuMessageContext } from "./monitor.js";
|
||||
import { resolveAgentRoute } from "../routing/resolve-route.js";
|
||||
import { dispatchInboundMessageWithBufferedDispatcher } from "../auto-reply/dispatch.js";
|
||||
@ -55,6 +55,21 @@ function isFeishuSenderAllowed(
|
||||
const isGroup = ctx.chatType === "group";
|
||||
const senderId = ctx.senderId;
|
||||
|
||||
// When allowOnlyStartupChats is true: only groups in startupChatId may send; no DMs
|
||||
if (account.config.allowOnlyStartupChats) {
|
||||
const allowedChatIds = getStartupChatIds(account.config);
|
||||
if (allowedChatIds.length === 0) {
|
||||
return { allowed: false, reason: "allow_only_startup_chats_no_list" };
|
||||
}
|
||||
if (!isGroup) {
|
||||
return { allowed: false, reason: "allow_only_startup_chats_no_dm" };
|
||||
}
|
||||
if (!allowedChatIds.includes(ctx.chatId)) {
|
||||
return { allowed: false, reason: "allow_only_startup_chats_group_not_allowed" };
|
||||
}
|
||||
return { allowed: true };
|
||||
}
|
||||
|
||||
if (isGroup) {
|
||||
// Check group policy
|
||||
const groupPolicySetting = account.config.groupPolicy ?? "open";
|
||||
@ -247,13 +262,18 @@ export async function dispatchFeishuMessage(params: DispatchFeishuMessageParams)
|
||||
log(
|
||||
`feishu: deliver callback called - hasText=${!!payload.text}, textLength=${payload.text?.length ?? 0}`,
|
||||
);
|
||||
// Send response back to Feishu
|
||||
// Send response back to Feishu; in groups, @mention the user who asked
|
||||
if (payload.text) {
|
||||
let replyText = payload.text;
|
||||
if (ctx.chatType === "group" && ctx.senderId) {
|
||||
// Feishu text/interactive: <at id="open_id"></at> mentions the user
|
||||
replyText = `<at id="${ctx.senderId}"></at> ${replyText}`;
|
||||
}
|
||||
try {
|
||||
log(`feishu: sending reply to ${ctx.chatId}...`);
|
||||
await sendMessageFeishu({
|
||||
to: ctx.chatId,
|
||||
text: payload.text,
|
||||
text: replyText,
|
||||
accountId: account.accountId,
|
||||
config: cfg,
|
||||
receiveIdType: "chat_id",
|
||||
|
||||
@ -15,7 +15,7 @@ import { computeBackoff, sleepWithAbort } from "../infra/backoff.js";
|
||||
import { formatErrorMessage } from "../infra/errors.js";
|
||||
import { formatDurationMs } from "../infra/format-duration.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { resolveFeishuAccount, type ResolvedFeishuAccount } from "./accounts.js";
|
||||
import { getStartupChatIds, resolveFeishuAccount, type ResolvedFeishuAccount } from "./accounts.js";
|
||||
import { createFeishuClient, type FeishuClient } from "./client.js";
|
||||
import {
|
||||
parseFeishuEvent,
|
||||
@ -693,9 +693,9 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
||||
throw new Error(`Feishu authentication failed: ${formatErrorMessage(err)}`);
|
||||
}
|
||||
|
||||
// Send startup message if startupChatId is configured
|
||||
const startupChatId = account.config.startupChatId?.trim();
|
||||
if (startupChatId) {
|
||||
// Send startup message to all configured startup chat IDs
|
||||
const startupChatIds = getStartupChatIds(account.config);
|
||||
if (startupChatIds.length > 0) {
|
||||
const localIp = getLocalIPv4();
|
||||
const hostname = os.hostname();
|
||||
const version = process.env.CLAWDBOT_VERSION ?? process.env.npm_package_version ?? "unknown";
|
||||
@ -712,11 +712,13 @@ export async function monitorFeishuProvider(opts: MonitorFeishuOpts = {}): Promi
|
||||
`启动时间: ${timestamp}`,
|
||||
].join("\n");
|
||||
|
||||
try {
|
||||
await client.sendTextMessage(startupChatId, startupMessage);
|
||||
log(`feishu: sent startup message to chat ${startupChatId}`);
|
||||
} catch (err) {
|
||||
log(`feishu: failed to send startup message: ${formatErrorMessage(err)}`);
|
||||
for (const chatId of startupChatIds) {
|
||||
try {
|
||||
await client.sendTextMessage(chatId, startupMessage);
|
||||
log(`feishu: sent startup message to chat ${chatId}`);
|
||||
} catch (err) {
|
||||
log(`feishu: failed to send startup message to ${chatId}: ${formatErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -237,15 +237,20 @@ function parseMarkdownLine(line: string): FeishuPostElement[] {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if text contains markdown that would benefit from rich text rendering
|
||||
* Check if text contains markdown that would benefit from rich text rendering.
|
||||
* When true, sendMessageFeishu uses interactive card for full markdown support.
|
||||
*/
|
||||
export function hasMarkdown(text: string): boolean {
|
||||
return (
|
||||
/\*\*.+?\*\*/.test(text) || // bold
|
||||
/\*.+?\*/.test(text) || // italic
|
||||
/~~.+?~~/.test(text) || // strikethrough
|
||||
/`.+?`/.test(text) || // code
|
||||
/\[.+?\]\(.+?\)/.test(text) // links
|
||||
/`.+?`/.test(text) || // inline code
|
||||
/\[.+?\]\(.+?\)/.test(text) || // links
|
||||
/^#{1,6}\s+/m.test(text) || // headings (# to ######)
|
||||
/^[-*]\s+/m.test(text) || // unordered list
|
||||
/^\d+\.\s+/m.test(text) || // ordered list
|
||||
/```[\s\S]*?```/.test(text) // fenced code blocks
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user