185 lines
6.0 KiB
TypeScript

import {
createScopedChannelConfigAdapter,
createScopedDmSecurityResolver,
} from "openclaw/plugin-sdk/channel-config-helpers";
import { createAllowlistProviderRouteAllowlistWarningCollector } from "openclaw/plugin-sdk/channel-policy";
import { createChannelPluginBase } from "openclaw/plugin-sdk/core";
import { createDelegatedSetupWizardProxy } from "openclaw/plugin-sdk/setup";
import {
buildChannelConfigSchema,
formatWhatsAppConfigAllowFromEntries,
getChatChannelMeta,
normalizeE164,
resolveWhatsAppGroupIntroHint,
resolveWhatsAppGroupRequireMention,
resolveWhatsAppGroupToolPolicy,
WhatsAppConfigSchema,
type ChannelPlugin,
} from "openclaw/plugin-sdk/whatsapp-core";
import {
listWhatsAppAccountIds,
resolveDefaultWhatsAppAccountId,
resolveWhatsAppAccount,
type ResolvedWhatsAppAccount,
} from "./accounts.js";
export const WHATSAPP_CHANNEL = "whatsapp" as const;
export async function loadWhatsAppChannelRuntime() {
return await import("./channel.runtime.js");
}
export const whatsappSetupWizardProxy = createWhatsAppSetupWizardProxy(
async () => (await loadWhatsAppChannelRuntime()).whatsappSetupWizard,
);
const whatsappConfigAdapter = createScopedChannelConfigAdapter<ResolvedWhatsAppAccount>({
sectionKey: WHATSAPP_CHANNEL,
listAccountIds: listWhatsAppAccountIds,
resolveAccount: (cfg, accountId) => resolveWhatsAppAccount({ cfg, accountId }),
defaultAccountId: resolveDefaultWhatsAppAccountId,
clearBaseFields: [],
allowTopLevel: false,
resolveAllowFrom: (account) => account.allowFrom,
formatAllowFrom: (allowFrom) => formatWhatsAppConfigAllowFromEntries(allowFrom),
resolveDefaultTo: (account) => account.defaultTo,
});
const whatsappResolveDmPolicy = createScopedDmSecurityResolver<ResolvedWhatsAppAccount>({
channelKey: WHATSAPP_CHANNEL,
resolvePolicy: (account) => account.dmPolicy,
resolveAllowFrom: (account) => account.allowFrom,
policyPathSuffix: "dmPolicy",
normalizeEntry: (raw) => normalizeE164(raw),
});
export function createWhatsAppSetupWizardProxy(
loadWizard: () => Promise<NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["setupWizard"]>>,
): NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["setupWizard"]> {
return createDelegatedSetupWizardProxy({
channel: WHATSAPP_CHANNEL,
loadWizard,
status: {
configuredLabel: "linked",
unconfiguredLabel: "not linked",
configuredHint: "linked",
unconfiguredHint: "not linked",
configuredScore: 5,
unconfiguredScore: 4,
},
resolveShouldPromptAccountIds: (params) =>
(params.shouldPromptAccountIds || params.options?.promptWhatsAppAccountId) ?? false,
credentials: [],
delegateFinalize: true,
disable: (cfg) => ({
...cfg,
channels: {
...cfg.channels,
whatsapp: {
...cfg.channels?.whatsapp,
enabled: false,
},
},
}),
onAccountRecorded: (accountId, options) => {
options?.onWhatsAppAccountId?.(accountId);
},
});
}
export function createWhatsAppPluginBase(params: {
groups: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["groups"]>;
setupWizard: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["setupWizard"]>;
setup: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["setup"]>;
isConfigured: NonNullable<ChannelPlugin<ResolvedWhatsAppAccount>["config"]>["isConfigured"];
}): Pick<
ChannelPlugin<ResolvedWhatsAppAccount>,
| "id"
| "meta"
| "setupWizard"
| "capabilities"
| "reload"
| "gatewayMethods"
| "configSchema"
| "config"
| "security"
| "setup"
| "groups"
> {
const collectWhatsAppSecurityWarnings =
createAllowlistProviderRouteAllowlistWarningCollector<ResolvedWhatsAppAccount>({
providerConfigPresent: (cfg) => cfg.channels?.whatsapp !== undefined,
resolveGroupPolicy: (account) => account.groupPolicy,
resolveRouteAllowlistConfigured: (account) =>
Boolean(account.groups) && Object.keys(account.groups ?? {}).length > 0,
restrictSenders: {
surface: "WhatsApp groups",
openScope: "any member in allowed groups",
groupPolicyPath: "channels.whatsapp.groupPolicy",
groupAllowFromPath: "channels.whatsapp.groupAllowFrom",
},
noRouteAllowlist: {
surface: "WhatsApp groups",
routeAllowlistPath: "channels.whatsapp.groups",
routeScope: "group",
groupPolicyPath: "channels.whatsapp.groupPolicy",
groupAllowFromPath: "channels.whatsapp.groupAllowFrom",
},
});
return createChannelPluginBase({
id: WHATSAPP_CHANNEL,
meta: {
...getChatChannelMeta(WHATSAPP_CHANNEL),
showConfigured: false,
quickstartAllowFrom: true,
forceAccountBinding: true,
preferSessionLookupForAnnounceTarget: true,
},
setupWizard: params.setupWizard,
capabilities: {
chatTypes: ["direct", "group"],
polls: true,
reactions: true,
media: true,
},
reload: { configPrefixes: ["web"], noopPrefixes: ["channels.whatsapp"] },
gatewayMethods: ["web.login.start", "web.login.wait"],
configSchema: buildChannelConfigSchema(WhatsAppConfigSchema),
config: {
...whatsappConfigAdapter,
isEnabled: (account, cfg) => account.enabled && cfg.web?.enabled !== false,
disabledReason: () => "disabled",
isConfigured: params.isConfigured,
unconfiguredReason: () => "not linked",
describeAccount: (account) => ({
accountId: account.accountId,
name: account.name,
enabled: account.enabled,
configured: Boolean(account.authDir),
linked: Boolean(account.authDir),
dmPolicy: account.dmPolicy,
allowFrom: account.allowFrom,
}),
},
security: {
resolveDmPolicy: whatsappResolveDmPolicy,
collectWarnings: collectWhatsAppSecurityWarnings,
},
setup: params.setup,
groups: params.groups,
}) as Pick<
ChannelPlugin<ResolvedWhatsAppAccount>,
| "id"
| "meta"
| "setupWizard"
| "capabilities"
| "reload"
| "gatewayMethods"
| "configSchema"
| "config"
| "security"
| "setup"
| "groups"
>;
}