Spaces:
Running
Running
| import fs from "node:fs"; | |
| import path from "node:path"; | |
| import type { OpenClawConfig } from "../config/config.js"; | |
| import type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "../config/types.js"; | |
| import { resolveOAuthDir } from "../config/paths.js"; | |
| import { DEFAULT_ACCOUNT_ID, normalizeAccountId } from "../routing/session-key.js"; | |
| import { resolveUserPath } from "../utils.js"; | |
| import { hasWebCredsSync } from "./auth-store.js"; | |
| export type ResolvedWhatsAppAccount = { | |
| accountId: string; | |
| name?: string; | |
| enabled: boolean; | |
| sendReadReceipts: boolean; | |
| messagePrefix?: string; | |
| authDir: string; | |
| isLegacyAuthDir: boolean; | |
| selfChatMode?: boolean; | |
| allowFrom?: string[]; | |
| groupAllowFrom?: string[]; | |
| groupPolicy?: GroupPolicy; | |
| dmPolicy?: DmPolicy; | |
| textChunkLimit?: number; | |
| chunkMode?: "length" | "newline"; | |
| mediaMaxMb?: number; | |
| blockStreaming?: boolean; | |
| ackReaction?: WhatsAppAccountConfig["ackReaction"]; | |
| groups?: WhatsAppAccountConfig["groups"]; | |
| debounceMs?: number; | |
| }; | |
| function listConfiguredAccountIds(cfg: OpenClawConfig): string[] { | |
| const accounts = cfg.channels?.whatsapp?.accounts; | |
| if (!accounts || typeof accounts !== "object") { | |
| return []; | |
| } | |
| return Object.keys(accounts).filter(Boolean); | |
| } | |
| export function listWhatsAppAuthDirs(cfg: OpenClawConfig): string[] { | |
| const oauthDir = resolveOAuthDir(); | |
| const whatsappDir = path.join(oauthDir, "whatsapp"); | |
| const authDirs = new Set<string>([oauthDir, path.join(whatsappDir, DEFAULT_ACCOUNT_ID)]); | |
| const accountIds = listConfiguredAccountIds(cfg); | |
| for (const accountId of accountIds) { | |
| authDirs.add(resolveWhatsAppAuthDir({ cfg, accountId }).authDir); | |
| } | |
| try { | |
| const entries = fs.readdirSync(whatsappDir, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| if (!entry.isDirectory()) { | |
| continue; | |
| } | |
| authDirs.add(path.join(whatsappDir, entry.name)); | |
| } | |
| } catch { | |
| // ignore missing dirs | |
| } | |
| return Array.from(authDirs); | |
| } | |
| export function hasAnyWhatsAppAuth(cfg: OpenClawConfig): boolean { | |
| return listWhatsAppAuthDirs(cfg).some((authDir) => hasWebCredsSync(authDir)); | |
| } | |
| export function listWhatsAppAccountIds(cfg: OpenClawConfig): string[] { | |
| const ids = listConfiguredAccountIds(cfg); | |
| if (ids.length === 0) { | |
| return [DEFAULT_ACCOUNT_ID]; | |
| } | |
| return ids.toSorted((a, b) => a.localeCompare(b)); | |
| } | |
| export function resolveDefaultWhatsAppAccountId(cfg: OpenClawConfig): string { | |
| const ids = listWhatsAppAccountIds(cfg); | |
| if (ids.includes(DEFAULT_ACCOUNT_ID)) { | |
| return DEFAULT_ACCOUNT_ID; | |
| } | |
| return ids[0] ?? DEFAULT_ACCOUNT_ID; | |
| } | |
| function resolveAccountConfig( | |
| cfg: OpenClawConfig, | |
| accountId: string, | |
| ): WhatsAppAccountConfig | undefined { | |
| const accounts = cfg.channels?.whatsapp?.accounts; | |
| if (!accounts || typeof accounts !== "object") { | |
| return undefined; | |
| } | |
| const entry = accounts[accountId] as WhatsAppAccountConfig | undefined; | |
| return entry; | |
| } | |
| function resolveDefaultAuthDir(accountId: string): string { | |
| return path.join(resolveOAuthDir(), "whatsapp", normalizeAccountId(accountId)); | |
| } | |
| function resolveLegacyAuthDir(): string { | |
| // Legacy Baileys creds lived in the same directory as OAuth tokens. | |
| return resolveOAuthDir(); | |
| } | |
| function legacyAuthExists(authDir: string): boolean { | |
| try { | |
| return fs.existsSync(path.join(authDir, "creds.json")); | |
| } catch { | |
| return false; | |
| } | |
| } | |
| export function resolveWhatsAppAuthDir(params: { cfg: OpenClawConfig; accountId: string }): { | |
| authDir: string; | |
| isLegacy: boolean; | |
| } { | |
| const accountId = params.accountId.trim() || DEFAULT_ACCOUNT_ID; | |
| const account = resolveAccountConfig(params.cfg, accountId); | |
| const configured = account?.authDir?.trim(); | |
| if (configured) { | |
| return { authDir: resolveUserPath(configured), isLegacy: false }; | |
| } | |
| const defaultDir = resolveDefaultAuthDir(accountId); | |
| if (accountId === DEFAULT_ACCOUNT_ID) { | |
| const legacyDir = resolveLegacyAuthDir(); | |
| if (legacyAuthExists(legacyDir) && !legacyAuthExists(defaultDir)) { | |
| return { authDir: legacyDir, isLegacy: true }; | |
| } | |
| } | |
| return { authDir: defaultDir, isLegacy: false }; | |
| } | |
| export function resolveWhatsAppAccount(params: { | |
| cfg: OpenClawConfig; | |
| accountId?: string | null; | |
| }): ResolvedWhatsAppAccount { | |
| const rootCfg = params.cfg.channels?.whatsapp; | |
| const accountId = params.accountId?.trim() || resolveDefaultWhatsAppAccountId(params.cfg); | |
| const accountCfg = resolveAccountConfig(params.cfg, accountId); | |
| const enabled = accountCfg?.enabled !== false; | |
| const { authDir, isLegacy } = resolveWhatsAppAuthDir({ | |
| cfg: params.cfg, | |
| accountId, | |
| }); | |
| return { | |
| accountId, | |
| name: accountCfg?.name?.trim() || undefined, | |
| enabled, | |
| sendReadReceipts: accountCfg?.sendReadReceipts ?? rootCfg?.sendReadReceipts ?? true, | |
| messagePrefix: | |
| accountCfg?.messagePrefix ?? rootCfg?.messagePrefix ?? params.cfg.messages?.messagePrefix, | |
| authDir, | |
| isLegacyAuthDir: isLegacy, | |
| selfChatMode: accountCfg?.selfChatMode ?? rootCfg?.selfChatMode, | |
| dmPolicy: accountCfg?.dmPolicy ?? rootCfg?.dmPolicy, | |
| allowFrom: accountCfg?.allowFrom ?? rootCfg?.allowFrom, | |
| groupAllowFrom: accountCfg?.groupAllowFrom ?? rootCfg?.groupAllowFrom, | |
| groupPolicy: accountCfg?.groupPolicy ?? rootCfg?.groupPolicy, | |
| textChunkLimit: accountCfg?.textChunkLimit ?? rootCfg?.textChunkLimit, | |
| chunkMode: accountCfg?.chunkMode ?? rootCfg?.chunkMode, | |
| mediaMaxMb: accountCfg?.mediaMaxMb ?? rootCfg?.mediaMaxMb, | |
| blockStreaming: accountCfg?.blockStreaming ?? rootCfg?.blockStreaming, | |
| ackReaction: accountCfg?.ackReaction ?? rootCfg?.ackReaction, | |
| groups: accountCfg?.groups ?? rootCfg?.groups, | |
| debounceMs: accountCfg?.debounceMs ?? rootCfg?.debounceMs, | |
| }; | |
| } | |
| export function listEnabledWhatsAppAccounts(cfg: OpenClawConfig): ResolvedWhatsAppAccount[] { | |
| return listWhatsAppAccountIds(cfg) | |
| .map((accountId) => resolveWhatsAppAccount({ cfg, accountId })) | |
| .filter((account) => account.enabled); | |
| } | |