openclaw/src/line/accounts.ts
2026-01-27 12:21:02 +00:00

180 lines
4.6 KiB
TypeScript

import fs from "node:fs";
import type { MoltbotConfig } from "../config/config.js";
import type {
LineConfig,
LineAccountConfig,
ResolvedLineAccount,
LineTokenSource,
} from "./types.js";
export const DEFAULT_ACCOUNT_ID = "default";
function readFileIfExists(filePath: string | undefined): string | undefined {
if (!filePath) return undefined;
try {
return fs.readFileSync(filePath, "utf-8").trim();
} catch {
return undefined;
}
}
function resolveToken(params: {
accountId: string;
baseConfig?: LineConfig;
accountConfig?: LineAccountConfig;
}): { token: string; tokenSource: LineTokenSource } {
const { accountId, baseConfig, accountConfig } = params;
// Check account-level config first
if (accountConfig?.channelAccessToken?.trim()) {
return { token: accountConfig.channelAccessToken.trim(), tokenSource: "config" };
}
// Check account-level token file
const accountFileToken = readFileIfExists(accountConfig?.tokenFile);
if (accountFileToken) {
return { token: accountFileToken, tokenSource: "file" };
}
// For default account, check base config and env
if (accountId === DEFAULT_ACCOUNT_ID) {
if (baseConfig?.channelAccessToken?.trim()) {
return { token: baseConfig.channelAccessToken.trim(), tokenSource: "config" };
}
const baseFileToken = readFileIfExists(baseConfig?.tokenFile);
if (baseFileToken) {
return { token: baseFileToken, tokenSource: "file" };
}
const envToken = process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim();
if (envToken) {
return { token: envToken, tokenSource: "env" };
}
}
return { token: "", tokenSource: "none" };
}
function resolveSecret(params: {
accountId: string;
baseConfig?: LineConfig;
accountConfig?: LineAccountConfig;
}): string {
const { accountId, baseConfig, accountConfig } = params;
// Check account-level config first
if (accountConfig?.channelSecret?.trim()) {
return accountConfig.channelSecret.trim();
}
// Check account-level secret file
const accountFileSecret = readFileIfExists(accountConfig?.secretFile);
if (accountFileSecret) {
return accountFileSecret;
}
// For default account, check base config and env
if (accountId === DEFAULT_ACCOUNT_ID) {
if (baseConfig?.channelSecret?.trim()) {
return baseConfig.channelSecret.trim();
}
const baseFileSecret = readFileIfExists(baseConfig?.secretFile);
if (baseFileSecret) {
return baseFileSecret;
}
const envSecret = process.env.LINE_CHANNEL_SECRET?.trim();
if (envSecret) {
return envSecret;
}
}
return "";
}
export function resolveLineAccount(params: {
cfg: MoltbotConfig;
accountId?: string;
}): ResolvedLineAccount {
const { cfg, accountId = DEFAULT_ACCOUNT_ID } = params;
const lineConfig = cfg.channels?.line as LineConfig | undefined;
const accounts = lineConfig?.accounts;
const accountConfig = accountId !== DEFAULT_ACCOUNT_ID ? accounts?.[accountId] : undefined;
const { token, tokenSource } = resolveToken({
accountId,
baseConfig: lineConfig,
accountConfig,
});
const secret = resolveSecret({
accountId,
baseConfig: lineConfig,
accountConfig,
});
const mergedConfig: LineConfig & LineAccountConfig = {
...lineConfig,
...accountConfig,
};
const enabled =
accountConfig?.enabled ??
(accountId === DEFAULT_ACCOUNT_ID ? (lineConfig?.enabled ?? true) : false);
const name =
accountConfig?.name ?? (accountId === DEFAULT_ACCOUNT_ID ? lineConfig?.name : undefined);
return {
accountId,
name,
enabled,
channelAccessToken: token,
channelSecret: secret,
tokenSource,
config: mergedConfig,
};
}
export function listLineAccountIds(cfg: MoltbotConfig): string[] {
const lineConfig = cfg.channels?.line as LineConfig | undefined;
const accounts = lineConfig?.accounts;
const ids = new Set<string>();
// Add default account if configured at base level
if (
lineConfig?.channelAccessToken?.trim() ||
lineConfig?.tokenFile ||
process.env.LINE_CHANNEL_ACCESS_TOKEN?.trim()
) {
ids.add(DEFAULT_ACCOUNT_ID);
}
// Add named accounts
if (accounts) {
for (const id of Object.keys(accounts)) {
ids.add(id);
}
}
return Array.from(ids);
}
export function resolveDefaultLineAccountId(cfg: MoltbotConfig): string {
const ids = listLineAccountIds(cfg);
if (ids.includes(DEFAULT_ACCOUNT_ID)) {
return DEFAULT_ACCOUNT_ID;
}
return ids[0] ?? DEFAULT_ACCOUNT_ID;
}
export function normalizeAccountId(accountId: string | undefined): string {
const trimmed = accountId?.trim().toLowerCase();
if (!trimmed || trimmed === "default") {
return DEFAULT_ACCOUNT_ID;
}
return trimmed;
}