mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-02 11:10:20 +00:00
177 lines
5.5 KiB
TypeScript
177 lines
5.5 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import {
|
|
createAccountListHelpers,
|
|
DEFAULT_ACCOUNT_ID,
|
|
normalizeAccountId,
|
|
resolveAccountEntry,
|
|
resolveMergedAccountConfig,
|
|
resolveUserPath,
|
|
type OpenClawConfig,
|
|
} from "openclaw/plugin-sdk/account-core";
|
|
import { resolveOAuthDir } from "openclaw/plugin-sdk/state-paths";
|
|
import { hasWebCredsSync } from "./creds-files.js";
|
|
import type { DmPolicy, GroupPolicy, WhatsAppAccountConfig } from "./runtime-api.js";
|
|
|
|
export type ResolvedWhatsAppAccount = {
|
|
accountId: string;
|
|
name?: string;
|
|
enabled: boolean;
|
|
sendReadReceipts: boolean;
|
|
messagePrefix?: string;
|
|
defaultTo?: 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;
|
|
};
|
|
|
|
export const DEFAULT_WHATSAPP_MEDIA_MAX_MB = 50;
|
|
|
|
const { listConfiguredAccountIds, listAccountIds, resolveDefaultAccountId } =
|
|
createAccountListHelpers("whatsapp");
|
|
export const listWhatsAppAccountIds = listAccountIds;
|
|
export const resolveDefaultWhatsAppAccountId = resolveDefaultAccountId;
|
|
|
|
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));
|
|
}
|
|
|
|
function resolveAccountConfig(
|
|
cfg: OpenClawConfig,
|
|
accountId: string,
|
|
): WhatsAppAccountConfig | undefined {
|
|
return resolveAccountEntry(cfg.channels?.whatsapp?.accounts, accountId);
|
|
}
|
|
|
|
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 merged = resolveMergedAccountConfig<WhatsAppAccountConfig>({
|
|
channelConfig: rootCfg as WhatsAppAccountConfig | undefined,
|
|
accounts: rootCfg?.accounts as Record<string, Partial<WhatsAppAccountConfig>> | undefined,
|
|
accountId,
|
|
omitKeys: ["defaultAccount"],
|
|
});
|
|
const enabled = merged.enabled !== false;
|
|
const { authDir, isLegacy } = resolveWhatsAppAuthDir({
|
|
cfg: params.cfg,
|
|
accountId,
|
|
});
|
|
return {
|
|
accountId,
|
|
name: merged.name?.trim() || undefined,
|
|
enabled,
|
|
sendReadReceipts: merged.sendReadReceipts ?? true,
|
|
messagePrefix: merged.messagePrefix ?? params.cfg.messages?.messagePrefix,
|
|
defaultTo: merged.defaultTo,
|
|
authDir,
|
|
isLegacyAuthDir: isLegacy,
|
|
selfChatMode: merged.selfChatMode,
|
|
dmPolicy: merged.dmPolicy,
|
|
allowFrom: merged.allowFrom,
|
|
groupAllowFrom: merged.groupAllowFrom,
|
|
groupPolicy: merged.groupPolicy,
|
|
textChunkLimit: merged.textChunkLimit,
|
|
chunkMode: merged.chunkMode,
|
|
mediaMaxMb: merged.mediaMaxMb,
|
|
blockStreaming: merged.blockStreaming,
|
|
ackReaction: merged.ackReaction,
|
|
groups: merged.groups,
|
|
debounceMs: merged.debounceMs,
|
|
};
|
|
}
|
|
|
|
export function resolveWhatsAppMediaMaxBytes(
|
|
account: Pick<ResolvedWhatsAppAccount, "mediaMaxMb">,
|
|
): number {
|
|
const mediaMaxMb =
|
|
typeof account.mediaMaxMb === "number" && account.mediaMaxMb > 0
|
|
? account.mediaMaxMb
|
|
: DEFAULT_WHATSAPP_MEDIA_MAX_MB;
|
|
return mediaMaxMb * 1024 * 1024;
|
|
}
|
|
|
|
export function listEnabledWhatsAppAccounts(cfg: OpenClawConfig): ResolvedWhatsAppAccount[] {
|
|
return listWhatsAppAccountIds(cfg)
|
|
.map((accountId) => resolveWhatsAppAccount({ cfg, accountId }))
|
|
.filter((account) => account.enabled);
|
|
}
|