135 lines
3.9 KiB
TypeScript
135 lines
3.9 KiB
TypeScript
import * as Lark from "@larksuiteoapi/node-sdk";
|
|
import fs from "node:fs";
|
|
import { loadConfig } from "../config/config.js";
|
|
import { getChildLogger } from "../logging.js";
|
|
import { DEFAULT_ACCOUNT_ID } from "../routing/session-key.js";
|
|
import { normalizeFeishuDomain } from "./domain.js";
|
|
|
|
const logger = getChildLogger({ module: "feishu-client" });
|
|
|
|
function readFileIfExists(filePath?: string): string | undefined {
|
|
if (!filePath) {
|
|
return undefined;
|
|
}
|
|
try {
|
|
return fs.readFileSync(filePath, "utf-8").trim();
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
function resolveAppSecret(config?: {
|
|
appSecret?: string;
|
|
appSecretFile?: string;
|
|
}): string | undefined {
|
|
const direct = config?.appSecret?.trim();
|
|
if (direct) {
|
|
return direct;
|
|
}
|
|
return readFileIfExists(config?.appSecretFile);
|
|
}
|
|
|
|
export function getFeishuClient(accountIdOrAppId?: string, explicitAppSecret?: string) {
|
|
const cfg = loadConfig();
|
|
const feishuCfg = cfg.channels?.feishu;
|
|
|
|
let appId: string | undefined;
|
|
let appSecret: string | undefined = explicitAppSecret?.trim() || undefined;
|
|
let domain: string | undefined;
|
|
|
|
// Determine if we received an accountId or an appId
|
|
const isAppId = accountIdOrAppId?.startsWith("cli_");
|
|
const accountId = isAppId ? undefined : accountIdOrAppId || DEFAULT_ACCOUNT_ID;
|
|
|
|
if (!appSecret && feishuCfg?.accounts) {
|
|
if (isAppId) {
|
|
// When given an appId, find the account with matching appId
|
|
for (const [, acc] of Object.entries(feishuCfg.accounts)) {
|
|
if (acc.appId === accountIdOrAppId) {
|
|
appId = acc.appId;
|
|
appSecret = resolveAppSecret(acc);
|
|
domain = acc.domain ?? feishuCfg?.domain;
|
|
break;
|
|
}
|
|
}
|
|
// If not found in accounts, use the appId directly (secret from first account as fallback)
|
|
if (!appSecret) {
|
|
appId = accountIdOrAppId;
|
|
const firstKey = Object.keys(feishuCfg.accounts)[0];
|
|
if (firstKey) {
|
|
const acc = feishuCfg.accounts[firstKey];
|
|
appSecret = resolveAppSecret(acc);
|
|
domain = acc.domain ?? feishuCfg?.domain;
|
|
}
|
|
}
|
|
} else if (accountId && feishuCfg.accounts[accountId]) {
|
|
// Try to get from accounts config by accountId
|
|
const acc = feishuCfg.accounts[accountId];
|
|
appId = acc.appId;
|
|
appSecret = resolveAppSecret(acc);
|
|
domain = acc.domain ?? feishuCfg?.domain;
|
|
} else if (!accountId) {
|
|
// Fallback to first account if accountId is not specified
|
|
const firstKey = Object.keys(feishuCfg.accounts)[0];
|
|
if (firstKey) {
|
|
const acc = feishuCfg.accounts[firstKey];
|
|
appId = acc.appId;
|
|
appSecret = resolveAppSecret(acc);
|
|
domain = acc.domain ?? feishuCfg?.domain;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Fallback to top-level feishu config (for backward compatibility)
|
|
if (!appId && feishuCfg?.appId) {
|
|
appId = feishuCfg.appId.trim();
|
|
}
|
|
if (!appSecret) {
|
|
appSecret = resolveAppSecret(feishuCfg);
|
|
}
|
|
if (!domain) {
|
|
domain = feishuCfg?.domain;
|
|
}
|
|
|
|
// Environment variables fallback
|
|
if (!appId) {
|
|
appId = process.env.FEISHU_APP_ID?.trim();
|
|
}
|
|
if (!appSecret) {
|
|
appSecret = process.env.FEISHU_APP_SECRET?.trim();
|
|
}
|
|
|
|
if (!appId || !appSecret) {
|
|
throw new Error(
|
|
"Feishu app ID/secret not configured. Set channels.feishu.accounts.<id>.appId/appSecret (or appSecretFile) or FEISHU_APP_ID/FEISHU_APP_SECRET.",
|
|
);
|
|
}
|
|
|
|
const resolvedDomain = normalizeFeishuDomain(domain);
|
|
|
|
const client = new Lark.Client({
|
|
appId,
|
|
appSecret,
|
|
...(resolvedDomain ? { domain: resolvedDomain } : {}),
|
|
logger: {
|
|
debug: (msg) => {
|
|
logger.debug(msg);
|
|
},
|
|
info: (msg) => {
|
|
logger.info(msg);
|
|
},
|
|
warn: (msg) => {
|
|
logger.warn(msg);
|
|
},
|
|
error: (msg) => {
|
|
logger.error(msg);
|
|
},
|
|
trace: (msg) => {
|
|
logger.silly(msg);
|
|
},
|
|
},
|
|
});
|
|
|
|
return client;
|
|
}
|