mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
refactor(whatsapp): reuse SDK normalization helpers (#105580)
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
// Whatsapp helper module supports normalize target behavior.
|
||||
import { normalizeE164 } from "openclaw/plugin-sdk/account-resolution";
|
||||
import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeStringEntries,
|
||||
uniqueStrings,
|
||||
} from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
|
||||
const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
|
||||
const WHATSAPP_LEGACY_USER_JID_RE = /^(\d+)@c\.us$/i;
|
||||
@@ -111,19 +115,11 @@ export function normalizeWhatsAppMessagingTarget(raw: string): string | undefine
|
||||
}
|
||||
|
||||
export function normalizeWhatsAppAllowFromEntries(allowFrom: Array<string | number>): string[] {
|
||||
const seen = new Set<string>();
|
||||
const normalized = allowFrom
|
||||
.map((entry) => String(entry).trim())
|
||||
.filter((entry): entry is string => Boolean(entry))
|
||||
.map(normalizeWhatsAppAllowFromEntry)
|
||||
.filter((entry): entry is string => Boolean(entry));
|
||||
return normalized.filter((entry) => {
|
||||
if (seen.has(entry)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(entry);
|
||||
return true;
|
||||
});
|
||||
return uniqueStrings(
|
||||
normalizeStringEntries(allowFrom)
|
||||
.map(normalizeWhatsAppAllowFromEntry)
|
||||
.filter((entry): entry is string => Boolean(entry)),
|
||||
);
|
||||
}
|
||||
|
||||
export function normalizeWhatsAppAllowFromEntry(entry: string): string | null {
|
||||
|
||||
@@ -162,6 +162,7 @@ describe("createWhatsAppOutboundBase", () => {
|
||||
cfg: {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
authDir: "/tmp/whatsapp-default",
|
||||
defaultAccount: "work",
|
||||
accounts: {
|
||||
work: {},
|
||||
@@ -185,6 +186,50 @@ describe("createWhatsAppOutboundBase", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("uses the implicit authDir default account for quote metadata lookup", async () => {
|
||||
cacheInboundMessageMeta("default", "15551234567@s.whatsapp.net", "reply-auth-dir", {
|
||||
participant: "444@s.whatsapp.net",
|
||||
body: "implicit default body",
|
||||
});
|
||||
const sendMessageWhatsApp = vi.fn(async () => ({
|
||||
messageId: "msg-auth-dir",
|
||||
toJid: "15551234567@s.whatsapp.net",
|
||||
}));
|
||||
const outbound = createWhatsAppOutboundBase({
|
||||
chunker: (text) => [text],
|
||||
sendMessageWhatsApp,
|
||||
sendPollWhatsApp: vi.fn(),
|
||||
shouldLogVerbose: () => false,
|
||||
resolveTarget: ({ to }) => ({ ok: true as const, to: to ?? "" }),
|
||||
});
|
||||
|
||||
await outbound.sendText!({
|
||||
cfg: {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
authDir: "/tmp/whatsapp-default",
|
||||
accounts: {
|
||||
work: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
to: "whatsapp:+15551234567",
|
||||
text: "reply",
|
||||
deps: { sendWhatsApp: sendMessageWhatsApp },
|
||||
replyToId: "reply-auth-dir",
|
||||
});
|
||||
|
||||
const options = sendMessageOptionsAt(sendMessageWhatsApp, 0, "whatsapp:+15551234567", "reply");
|
||||
expect(options.quotedMessageKey).toEqual({
|
||||
id: "reply-auth-dir",
|
||||
remoteJid: "15551234567@s.whatsapp.net",
|
||||
fromMe: false,
|
||||
participant: "444@s.whatsapp.net",
|
||||
messageText: "implicit default body",
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes mixed-case defaultAccount before quote metadata lookup", async () => {
|
||||
cacheInboundMessageMeta("work", "15551234567@s.whatsapp.net", "reply-case", {
|
||||
participant: "333@s.whatsapp.net",
|
||||
@@ -345,8 +390,11 @@ describe("createWhatsAppOutboundBase", () => {
|
||||
cfg: {
|
||||
channels: {
|
||||
whatsapp: {
|
||||
authDir: "/tmp/whatsapp-default",
|
||||
defaultAccount: "other",
|
||||
accounts: {
|
||||
work: {},
|
||||
other: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
// Whatsapp plugin module implements outbound base behavior.
|
||||
import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
listCombinedAccountIds,
|
||||
normalizeOptionalAccountId,
|
||||
resolveListedDefaultAccountId,
|
||||
} from "openclaw/plugin-sdk/account-core";
|
||||
import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-core";
|
||||
import { resolveOutboundSendDep } from "openclaw/plugin-sdk/channel-outbound";
|
||||
import {
|
||||
attachChannelToResult,
|
||||
@@ -13,6 +8,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/channel-send-result";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { sendTextMediaPayload } from "openclaw/plugin-sdk/reply-payload";
|
||||
import { resolveDefaultWhatsAppAccountId } from "./account-ids.js";
|
||||
import {
|
||||
normalizeWhatsAppOutboundPayload,
|
||||
normalizeWhatsAppPayloadText,
|
||||
@@ -73,18 +69,7 @@ function resolveQuoteLookupAccountId(cfg?: OpenClawConfig, accountId?: string |
|
||||
if (explicitAccountId) {
|
||||
return explicitAccountId;
|
||||
}
|
||||
const channelCfg = cfg?.channels?.whatsapp;
|
||||
const configuredIds = listCombinedAccountIds({
|
||||
configuredAccountIds:
|
||||
channelCfg?.accounts && typeof channelCfg.accounts === "object"
|
||||
? Object.keys(channelCfg.accounts).filter(Boolean)
|
||||
: [],
|
||||
fallbackAccountIdWhenEmpty: DEFAULT_ACCOUNT_ID,
|
||||
});
|
||||
return resolveListedDefaultAccountId({
|
||||
accountIds: configuredIds,
|
||||
configuredDefaultAccountId: normalizeOptionalAccountId(channelCfg?.defaultAccount),
|
||||
});
|
||||
return resolveDefaultWhatsAppAccountId(cfg ?? {});
|
||||
}
|
||||
|
||||
type WhatsAppOutboundBaseCore = Pick<
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
isWhatsAppNewsletterJid,
|
||||
looksLikeWhatsAppTargetId,
|
||||
isWhatsAppUserTarget,
|
||||
normalizeWhatsAppAllowFromEntries,
|
||||
normalizeWhatsAppMessagingTarget,
|
||||
normalizeWhatsAppTarget,
|
||||
} from "./normalize-target.js";
|
||||
@@ -120,6 +121,20 @@ describe("normalizeWhatsAppMessagingTarget", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("normalizeWhatsAppAllowFromEntries", () => {
|
||||
it("deduplicates entries after WhatsApp target normalization", () => {
|
||||
expect(
|
||||
normalizeWhatsAppAllowFromEntries([
|
||||
" +1 (555) 123-4567 ",
|
||||
"15551234567@s.whatsapp.net",
|
||||
15551234567,
|
||||
" ",
|
||||
"invalid",
|
||||
]),
|
||||
).toEqual(["15551234567"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("looksLikeWhatsAppTargetId", () => {
|
||||
it("detects common WhatsApp target forms", () => {
|
||||
expect(looksLikeWhatsAppTargetId("whatsapp:+15555550123")).toBe(true);
|
||||
|
||||
@@ -58,6 +58,27 @@ describe("resolveWhatsAppSocketTiming", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects invalid numeric timing values", () => {
|
||||
expect(
|
||||
resolveWhatsAppSocketTiming(
|
||||
{
|
||||
web: {
|
||||
whatsapp: {
|
||||
keepAliveIntervalMs: 0,
|
||||
connectTimeoutMs: Number.NaN,
|
||||
defaultQueryTimeoutMs: 1.5,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
keepAliveIntervalMs: -1,
|
||||
connectTimeoutMs: Number.POSITIVE_INFINITY,
|
||||
defaultQueryTimeoutMs: Number.MAX_SAFE_INTEGER + 1,
|
||||
},
|
||||
),
|
||||
).toEqual(DEFAULT_WHATSAPP_SOCKET_TIMING);
|
||||
});
|
||||
|
||||
it("marks operation timeout errors as unknown delivery state", () => {
|
||||
const error = new WhatsAppSocketOperationTimeoutError(
|
||||
"sendMessage",
|
||||
|
||||
@@ -6,7 +6,10 @@ import type {
|
||||
WAPresence,
|
||||
} from "baileys";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { resolveTimerTimeoutMs } from "openclaw/plugin-sdk/number-runtime";
|
||||
import {
|
||||
parseStrictPositiveInteger,
|
||||
resolveTimerTimeoutMs,
|
||||
} from "openclaw/plugin-sdk/number-runtime";
|
||||
|
||||
export type WhatsAppSocketTimingOptions = {
|
||||
keepAliveIntervalMs?: number;
|
||||
@@ -47,10 +50,6 @@ export class WhatsAppSocketOperationTimeoutError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function positiveInteger(value: number | undefined): number | undefined {
|
||||
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : undefined;
|
||||
}
|
||||
|
||||
export function resolveWhatsAppSocketTiming(
|
||||
cfg: OpenClawConfig,
|
||||
overrides?: WhatsAppSocketTimingOptions,
|
||||
@@ -58,16 +57,16 @@ export function resolveWhatsAppSocketTiming(
|
||||
const configured = cfg.web?.whatsapp;
|
||||
return {
|
||||
keepAliveIntervalMs:
|
||||
positiveInteger(overrides?.keepAliveIntervalMs) ??
|
||||
positiveInteger(configured?.keepAliveIntervalMs) ??
|
||||
parseStrictPositiveInteger(overrides?.keepAliveIntervalMs) ??
|
||||
parseStrictPositiveInteger(configured?.keepAliveIntervalMs) ??
|
||||
DEFAULT_WHATSAPP_SOCKET_TIMING.keepAliveIntervalMs,
|
||||
connectTimeoutMs:
|
||||
positiveInteger(overrides?.connectTimeoutMs) ??
|
||||
positiveInteger(configured?.connectTimeoutMs) ??
|
||||
parseStrictPositiveInteger(overrides?.connectTimeoutMs) ??
|
||||
parseStrictPositiveInteger(configured?.connectTimeoutMs) ??
|
||||
DEFAULT_WHATSAPP_SOCKET_TIMING.connectTimeoutMs,
|
||||
defaultQueryTimeoutMs:
|
||||
positiveInteger(overrides?.defaultQueryTimeoutMs) ??
|
||||
positiveInteger(configured?.defaultQueryTimeoutMs) ??
|
||||
parseStrictPositiveInteger(overrides?.defaultQueryTimeoutMs) ??
|
||||
parseStrictPositiveInteger(configured?.defaultQueryTimeoutMs) ??
|
||||
DEFAULT_WHATSAPP_SOCKET_TIMING.defaultQueryTimeoutMs,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user