feat(feishu): sync with clawdbot-feishu #137 (multi-account support)
- Sync latest changes from clawdbot-feishu including multi-account support - Add eslint-disable comments for SDK-related any types - Remove unused imports - Fix no-floating-promises in monitor.ts Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,8 @@ import {
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
type HistoryEntry,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import type { FeishuConfig, FeishuMessageContext, FeishuMediaInfo } from "./types.js";
|
||||
import type { FeishuMessageContext, FeishuMediaInfo, ResolvedFeishuAccount } from "./types.js";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
import { downloadMessageResourceFeishu } from "./media.js";
|
||||
import { extractMentionTargets, extractMessageBody, isMentionForwardRequest } from "./mention.js";
|
||||
@@ -79,12 +80,13 @@ type SenderNameResult = {
|
||||
};
|
||||
|
||||
async function resolveFeishuSenderName(params: {
|
||||
feishuCfg?: FeishuConfig;
|
||||
account: ResolvedFeishuAccount;
|
||||
senderOpenId: string;
|
||||
log: (...args: unknown[]) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- generic log function
|
||||
log: (...args: any[]) => void;
|
||||
}): Promise<SenderNameResult> {
|
||||
const { feishuCfg, senderOpenId, log } = params;
|
||||
if (!feishuCfg) {
|
||||
const { account, senderOpenId, log } = params;
|
||||
if (!account.configured) {
|
||||
return {};
|
||||
}
|
||||
if (!senderOpenId) {
|
||||
@@ -98,10 +100,11 @@ async function resolveFeishuSenderName(params: {
|
||||
}
|
||||
|
||||
try {
|
||||
const client = createFeishuClient(feishuCfg);
|
||||
const client = createFeishuClient(account);
|
||||
|
||||
// contact/v3/users/:user_id?user_id_type=open_id
|
||||
const res = await client.contact.user.get({
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK response type
|
||||
const res: any = await client.contact.user.get({
|
||||
path: { user_id: senderOpenId },
|
||||
params: { user_id_type: "open_id" },
|
||||
});
|
||||
@@ -325,8 +328,9 @@ async function resolveFeishuMediaList(params: {
|
||||
content: string;
|
||||
maxBytes: number;
|
||||
log?: (msg: string) => void;
|
||||
accountId?: string;
|
||||
}): Promise<FeishuMediaInfo[]> {
|
||||
const { cfg, messageId, messageType, content, maxBytes, log } = params;
|
||||
const { cfg, messageId, messageType, content, maxBytes, log, accountId } = params;
|
||||
|
||||
// Only process media message types (including post for embedded images)
|
||||
const mediaTypes = ["image", "file", "audio", "video", "sticker", "post"];
|
||||
@@ -354,6 +358,7 @@ async function resolveFeishuMediaList(params: {
|
||||
messageId,
|
||||
fileKey: imageKey,
|
||||
type: "image",
|
||||
accountId,
|
||||
});
|
||||
|
||||
let contentType = result.contentType;
|
||||
@@ -407,6 +412,7 @@ async function resolveFeishuMediaList(params: {
|
||||
messageId,
|
||||
fileKey,
|
||||
type: resourceType,
|
||||
accountId,
|
||||
});
|
||||
buffer = result.buffer;
|
||||
contentType = result.contentType;
|
||||
@@ -506,9 +512,14 @@ export async function handleFeishuMessage(params: {
|
||||
botOpenId?: string;
|
||||
runtime?: RuntimeEnv;
|
||||
chatHistories?: Map<string, HistoryEntry[]>;
|
||||
accountId?: string;
|
||||
}): Promise<void> {
|
||||
const { cfg, event, botOpenId, runtime, chatHistories } = params;
|
||||
const feishuCfg = cfg.channels?.feishu as FeishuConfig | undefined;
|
||||
const { cfg, event, botOpenId, runtime, chatHistories, accountId } = params;
|
||||
|
||||
// Resolve account with merged config
|
||||
const account = resolveFeishuAccount({ cfg, accountId });
|
||||
const feishuCfg = account.config;
|
||||
|
||||
const log = runtime?.log ?? console.log;
|
||||
const error = runtime?.error ?? console.error;
|
||||
|
||||
@@ -517,7 +528,7 @@ export async function handleFeishuMessage(params: {
|
||||
|
||||
// Resolve sender display name (best-effort) so the agent can attribute messages correctly.
|
||||
const senderResult = await resolveFeishuSenderName({
|
||||
feishuCfg,
|
||||
account,
|
||||
senderOpenId: ctx.senderOpenId,
|
||||
log,
|
||||
});
|
||||
@@ -528,7 +539,7 @@ export async function handleFeishuMessage(params: {
|
||||
// Track permission error to inform agent later (with cooldown to avoid repetition)
|
||||
let permissionErrorForAgent: PermissionError | undefined;
|
||||
if (senderResult.permissionError) {
|
||||
const appKey = feishuCfg?.appId ?? "default";
|
||||
const appKey = account.appId ?? "default";
|
||||
const now = Date.now();
|
||||
const lastNotified = permissionErrorNotifiedAt.get(appKey) ?? 0;
|
||||
|
||||
@@ -538,12 +549,14 @@ export async function handleFeishuMessage(params: {
|
||||
}
|
||||
}
|
||||
|
||||
log(`feishu: received message from ${ctx.senderOpenId} in ${ctx.chatId} (${ctx.chatType})`);
|
||||
log(
|
||||
`feishu[${account.accountId}]: received message from ${ctx.senderOpenId} in ${ctx.chatId} (${ctx.chatType})`,
|
||||
);
|
||||
|
||||
// Log mention targets if detected
|
||||
if (ctx.mentionTargets && ctx.mentionTargets.length > 0) {
|
||||
const names = ctx.mentionTargets.map((t) => t.name).join(", ");
|
||||
log(`feishu: detected @ forward request, targets: [${names}]`);
|
||||
log(`feishu[${account.accountId}]: detected @ forward request, targets: [${names}]`);
|
||||
}
|
||||
|
||||
const historyLimit = Math.max(
|
||||
@@ -554,6 +567,7 @@ export async function handleFeishuMessage(params: {
|
||||
if (isGroup) {
|
||||
const groupPolicy = feishuCfg?.groupPolicy ?? "open";
|
||||
const groupAllowFrom = feishuCfg?.groupAllowFrom ?? [];
|
||||
// DEBUG: log(`feishu[${account.accountId}]: groupPolicy=${groupPolicy}`);
|
||||
const groupConfig = resolveFeishuGroupConfig({ cfg: feishuCfg, groupId: ctx.chatId });
|
||||
|
||||
// Check if this GROUP is allowed (groupAllowFrom contains group IDs like oc_xxx, not user IDs)
|
||||
@@ -565,7 +579,7 @@ export async function handleFeishuMessage(params: {
|
||||
});
|
||||
|
||||
if (!groupAllowed) {
|
||||
log(`feishu: group ${ctx.chatId} not in allowlist`);
|
||||
log(`feishu[${account.accountId}]: sender ${ctx.senderOpenId} not in group allowlist`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -591,7 +605,9 @@ export async function handleFeishuMessage(params: {
|
||||
});
|
||||
|
||||
if (requireMention && !ctx.mentionedBot) {
|
||||
log(`feishu: message in group ${ctx.chatId} did not mention bot, recording to history`);
|
||||
log(
|
||||
`feishu[${account.accountId}]: message in group ${ctx.chatId} did not mention bot, recording to history`,
|
||||
);
|
||||
if (chatHistories) {
|
||||
recordPendingHistoryEntryIfEnabled({
|
||||
historyMap: chatHistories,
|
||||
@@ -617,7 +633,7 @@ export async function handleFeishuMessage(params: {
|
||||
senderId: ctx.senderOpenId,
|
||||
});
|
||||
if (!match.allowed) {
|
||||
log(`feishu: sender ${ctx.senderOpenId} not in DM allowlist`);
|
||||
log(`feishu[${account.accountId}]: sender ${ctx.senderOpenId} not in DM allowlist`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -634,6 +650,7 @@ export async function handleFeishuMessage(params: {
|
||||
const route = core.channel.routing.resolveAgentRoute({
|
||||
cfg,
|
||||
channel: "feishu",
|
||||
accountId: account.accountId,
|
||||
peer: {
|
||||
kind: isGroup ? "group" : "dm",
|
||||
id: isGroup ? ctx.chatId : ctx.senderOpenId,
|
||||
@@ -642,8 +659,8 @@ export async function handleFeishuMessage(params: {
|
||||
|
||||
const preview = ctx.content.replace(/\s+/g, " ").slice(0, 160);
|
||||
const inboundLabel = isGroup
|
||||
? `Feishu message in group ${ctx.chatId}`
|
||||
: `Feishu DM from ${ctx.senderOpenId}`;
|
||||
? `Feishu[${account.accountId}] message in group ${ctx.chatId}`
|
||||
: `Feishu[${account.accountId}] DM from ${ctx.senderOpenId}`;
|
||||
|
||||
core.system.enqueueSystemEvent(`${inboundLabel}: ${preview}`, {
|
||||
sessionKey: route.sessionKey,
|
||||
@@ -659,6 +676,7 @@ export async function handleFeishuMessage(params: {
|
||||
content: event.message.content,
|
||||
maxBytes: mediaMaxBytes,
|
||||
log,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
const mediaPayload = buildFeishuMediaPayload(mediaList);
|
||||
|
||||
@@ -666,13 +684,19 @@ export async function handleFeishuMessage(params: {
|
||||
let quotedContent: string | undefined;
|
||||
if (ctx.parentId) {
|
||||
try {
|
||||
const quotedMsg = await getMessageFeishu({ cfg, messageId: ctx.parentId });
|
||||
const quotedMsg = await getMessageFeishu({
|
||||
cfg,
|
||||
messageId: ctx.parentId,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
if (quotedMsg) {
|
||||
quotedContent = quotedMsg.content;
|
||||
log(`feishu: fetched quoted message: ${quotedContent?.slice(0, 100)}`);
|
||||
log(
|
||||
`feishu[${account.accountId}]: fetched quoted message: ${quotedContent?.slice(0, 100)}`,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
log(`feishu: failed to fetch quoted message: ${String(err)}`);
|
||||
log(`feishu[${account.accountId}]: failed to fetch quoted message: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -742,9 +766,10 @@ export async function handleFeishuMessage(params: {
|
||||
runtime: runtime as RuntimeEnv,
|
||||
chatId: ctx.chatId,
|
||||
replyToMessageId: ctx.messageId,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
log(`feishu: dispatching permission error notification to agent`);
|
||||
log(`feishu[${account.accountId}]: dispatching permission error notification to agent`);
|
||||
|
||||
await core.channel.reply.dispatchReplyFromConfig({
|
||||
ctx: permissionCtx,
|
||||
@@ -815,9 +840,10 @@ export async function handleFeishuMessage(params: {
|
||||
chatId: ctx.chatId,
|
||||
replyToMessageId: ctx.messageId,
|
||||
mentionTargets: ctx.mentionTargets,
|
||||
accountId: account.accountId,
|
||||
});
|
||||
|
||||
log(`feishu: dispatching to agent (session=${route.sessionKey})`);
|
||||
log(`feishu[${account.accountId}]: dispatching to agent (session=${route.sessionKey})`);
|
||||
|
||||
const { queuedFinal, counts } = await core.channel.reply.dispatchReplyFromConfig({
|
||||
ctx: ctxPayload,
|
||||
@@ -836,8 +862,10 @@ export async function handleFeishuMessage(params: {
|
||||
});
|
||||
}
|
||||
|
||||
log(`feishu: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`);
|
||||
log(
|
||||
`feishu[${account.accountId}]: dispatch complete (queuedFinal=${queuedFinal}, replies=${counts.final})`,
|
||||
);
|
||||
} catch (err) {
|
||||
error(`feishu: failed to dispatch message: ${String(err)}`);
|
||||
error(`feishu[${account.accountId}]: failed to dispatch message: ${String(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user