diff --git a/extensions/whatsapp/src/channel.ts b/extensions/whatsapp/src/channel.ts index 95406ba92e..f0248823ca 100644 --- a/extensions/whatsapp/src/channel.ts +++ b/extensions/whatsapp/src/channel.ts @@ -7,19 +7,18 @@ import { escapeRegExp, formatPairingApproveHint, getChatChannelMeta, - isWhatsAppGroupJid, listWhatsAppAccountIds, listWhatsAppDirectoryGroupsFromConfig, listWhatsAppDirectoryPeersFromConfig, looksLikeWhatsAppTargetId, migrateBaseNameToDefaultAccount, - missingTargetError, normalizeAccountId, normalizeE164, normalizeWhatsAppMessagingTarget, normalizeWhatsAppTarget, readStringParam, resolveDefaultWhatsAppAccountId, + resolveWhatsAppOutboundTarget, resolveWhatsAppAccount, resolveWhatsAppGroupRequireMention, resolveWhatsAppGroupToolPolicy, @@ -289,45 +288,8 @@ export const whatsappPlugin: ChannelPlugin = { chunkerMode: "text", textChunkLimit: 4000, pollMaxOptions: 12, - resolveTarget: ({ to, allowFrom, mode }) => { - const trimmed = to?.trim() ?? ""; - const allowListRaw = (allowFrom ?? []).map((entry) => String(entry).trim()).filter(Boolean); - const hasWildcard = allowListRaw.includes("*"); - const allowList = allowListRaw - .filter((entry) => entry !== "*") - .map((entry) => normalizeWhatsAppTarget(entry)) - .filter((entry): entry is string => Boolean(entry)); - - if (trimmed) { - const normalizedTo = normalizeWhatsAppTarget(trimmed); - if (!normalizedTo) { - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - } - if (isWhatsAppGroupJid(normalizedTo)) { - return { ok: true, to: normalizedTo }; - } - if (mode === "implicit" || mode === "heartbeat") { - if (hasWildcard || allowList.length === 0) { - return { ok: true, to: normalizedTo }; - } - if (allowList.includes(normalizedTo)) { - return { ok: true, to: normalizedTo }; - } - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - } - return { ok: true, to: normalizedTo }; - } - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - }, + resolveTarget: ({ to, allowFrom, mode }) => + resolveWhatsAppOutboundTarget({ to, allowFrom, mode }), sendText: async ({ to, text, accountId, deps, gifPlayback }) => { const send = deps?.sendWhatsApp ?? getWhatsAppRuntime().channel.whatsapp.sendMessageWhatsApp; const result = await send(to, text, { diff --git a/src/channels/plugins/outbound/whatsapp.ts b/src/channels/plugins/outbound/whatsapp.ts index cbb66935a1..625051d58d 100644 --- a/src/channels/plugins/outbound/whatsapp.ts +++ b/src/channels/plugins/outbound/whatsapp.ts @@ -1,9 +1,8 @@ import type { ChannelOutboundAdapter } from "../types.js"; import { chunkText } from "../../../auto-reply/chunk.js"; import { shouldLogVerbose } from "../../../globals.js"; -import { missingTargetError } from "../../../infra/outbound/target-errors.js"; import { sendPollWhatsApp } from "../../../web/outbound.js"; -import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../../../whatsapp/normalize.js"; +import { resolveWhatsAppOutboundTarget } from "../../../whatsapp/resolve-outbound-target.js"; export const whatsappOutbound: ChannelOutboundAdapter = { deliveryMode: "gateway", @@ -11,46 +10,8 @@ export const whatsappOutbound: ChannelOutboundAdapter = { chunkerMode: "text", textChunkLimit: 4000, pollMaxOptions: 12, - resolveTarget: ({ to, allowFrom, mode }) => { - const trimmed = to?.trim() ?? ""; - const allowListRaw = (allowFrom ?? []).map((entry) => String(entry).trim()).filter(Boolean); - const hasWildcard = allowListRaw.includes("*"); - const allowList = allowListRaw - .filter((entry) => entry !== "*") - .map((entry) => normalizeWhatsAppTarget(entry)) - .filter((entry): entry is string => Boolean(entry)); - - if (trimmed) { - const normalizedTo = normalizeWhatsAppTarget(trimmed); - if (!normalizedTo) { - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - } - if (isWhatsAppGroupJid(normalizedTo)) { - return { ok: true, to: normalizedTo }; - } - if (mode === "implicit" || mode === "heartbeat") { - if (hasWildcard || allowList.length === 0) { - return { ok: true, to: normalizedTo }; - } - if (allowList.includes(normalizedTo)) { - return { ok: true, to: normalizedTo }; - } - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - } - return { ok: true, to: normalizedTo }; - } - - return { - ok: false, - error: missingTargetError("WhatsApp", ""), - }; - }, + resolveTarget: ({ to, allowFrom, mode }) => + resolveWhatsAppOutboundTarget({ to, allowFrom, mode }), sendText: async ({ to, text, accountId, deps, gifPlayback }) => { const send = deps?.sendWhatsApp ?? (await import("../../../web/outbound.js")).sendMessageWhatsApp; diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 4b60ac0473..eb2d6ae408 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -366,6 +366,7 @@ export { type ResolvedWhatsAppAccount, } from "../web/accounts.js"; export { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "../whatsapp/normalize.js"; +export { resolveWhatsAppOutboundTarget } from "../whatsapp/resolve-outbound-target.js"; export { whatsappOnboardingAdapter } from "../channels/plugins/onboarding/whatsapp.js"; export { resolveWhatsAppHeartbeatRecipients } from "../channels/plugins/whatsapp-heartbeat.js"; export { diff --git a/src/whatsapp/resolve-outbound-target.ts b/src/whatsapp/resolve-outbound-target.ts new file mode 100644 index 0000000000..05e68e834b --- /dev/null +++ b/src/whatsapp/resolve-outbound-target.ts @@ -0,0 +1,53 @@ +import { missingTargetError } from "../infra/outbound/target-errors.js"; +import { isWhatsAppGroupJid, normalizeWhatsAppTarget } from "./normalize.js"; + +export type WhatsAppOutboundTargetResolution = + | { ok: true; to: string } + | { ok: false; error: Error }; + +export function resolveWhatsAppOutboundTarget(params: { + to: string | null | undefined; + allowFrom: Array | null | undefined; + mode: string | null | undefined; +}): WhatsAppOutboundTargetResolution { + const trimmed = params.to?.trim() ?? ""; + const allowListRaw = (params.allowFrom ?? []) + .map((entry) => String(entry).trim()) + .filter(Boolean); + const hasWildcard = allowListRaw.includes("*"); + const allowList = allowListRaw + .filter((entry) => entry !== "*") + .map((entry) => normalizeWhatsAppTarget(entry)) + .filter((entry): entry is string => Boolean(entry)); + + if (trimmed) { + const normalizedTo = normalizeWhatsAppTarget(trimmed); + if (!normalizedTo) { + return { + ok: false, + error: missingTargetError("WhatsApp", ""), + }; + } + if (isWhatsAppGroupJid(normalizedTo)) { + return { ok: true, to: normalizedTo }; + } + if (params.mode === "implicit" || params.mode === "heartbeat") { + if (hasWildcard || allowList.length === 0) { + return { ok: true, to: normalizedTo }; + } + if (allowList.includes(normalizedTo)) { + return { ok: true, to: normalizedTo }; + } + return { + ok: false, + error: missingTargetError("WhatsApp", ""), + }; + } + return { ok: true, to: normalizedTo }; + } + + return { + ok: false, + error: missingTargetError("WhatsApp", ""), + }; +}