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(); // 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; }