fix(whatsapp): separate raw JID parsing

This commit is contained in:
joshavant
2026-08-12 01:35:03 -05:00
parent 9b320bea70
commit 75288cd4dd
5 changed files with 63 additions and 21 deletions
@@ -180,14 +180,18 @@ describe("whatsapp inbound context visibility", () => {
});
});
it("keeps a malformed native quote participant out of comparable identity facts", () => {
const malformedParticipant = "777:4:5@lid";
it.each([
["777:4:5@lid", "777@lid"],
["whatsapp:777@lid", "777@lid"],
["whatsapp:whatsapp:777@hosted.lid", "777@hosted.lid"],
[" 777@s.whatsapp.net ", "+777"],
])("keeps raw native quote participant %j out of comparable facts", (participant, allowFrom) => {
const context = describeReplyContext({
extendedTextMessage: {
text: "current",
contextInfo: {
stanzaId: "malformed-native-quote",
participant: malformedParticipant,
participant,
quotedMessage: { conversation: "Private quoted text" },
},
},
@@ -203,22 +207,22 @@ describe("whatsapp inbound context visibility", () => {
msg,
mode: "allowlist",
groupPolicy: "allowlist",
groupAllowFrom: ["777@lid"],
groupAllowFrom: [allowFrom],
}),
).toBeNull();
const visibleQuote = resolveVisibleWhatsAppReplyContext({
msg,
mode: "allowlist_quote",
groupPolicy: "allowlist",
groupAllowFrom: ["777@lid"],
groupAllowFrom: [allowFrom],
});
expect(visibleQuote?.body).toBe("Private quoted text");
expect(visibleQuote?.sender).toMatchObject({
jid: malformedParticipant,
jid: participant,
lid: null,
e164: null,
});
expect(getComparableIdentityValues(visibleQuote?.sender)).not.toContain("777@lid");
expect(getComparableIdentityValues(visibleQuote?.sender)).not.toContain(allowFrom);
});
it("renders structured quoted media only at the visible preview boundary", () => {
@@ -245,6 +245,22 @@ describe("isBotMentionedFromTargets", () => {
expectMentioned(msg, cfg, true);
});
it.each([
"whatsapp:216372600647751@lid",
"whatsapp:whatsapp:216372600647751@lid",
" 216372600647751@lid ",
])("does not treat raw native mention %j as a self mention", (mentionedJid) => {
const msg = makeMsg({
body: "hey",
mentionedJids: [mentionedJid],
selfE164: "+15551234567",
selfJid: "15551234567@s.whatsapp.net",
selfLid: "216372600647751@lid",
});
expectMentioned(msg, mentionCfg, false);
});
it("honors explicit self-chat overrides without recomputing from allowFrom", () => {
const cfg = {
mentionRegexes: [/\bopenclaw\b/i],
+15
View File
@@ -43,6 +43,15 @@ describe("normalizeWhatsAppLidJid", () => {
},
);
it.each([
"whatsapp:123@lid",
"whatsapp:whatsapp:123@hosted.lid",
" 123@s.whatsapp.net ",
" 123@hosted ",
])("keeps raw provider JID %j non-comparable", (jid) => {
expect(resolveComparableIdentity({ jid })).toEqual({ jid, lid: null, e164: null });
});
it.each([
["123:4@s.whatsapp.net", { jid: "123@s.whatsapp.net", lid: null, e164: "+123" }],
["123:4@hosted", { jid: "123@hosted", lid: null, e164: "+123" }],
@@ -51,4 +60,10 @@ describe("normalizeWhatsAppLidJid", () => {
])("canonicalizes exactly one device segment in %j", (jid, expected) => {
expect(resolveComparableIdentity({ jid })).toEqual(expected);
});
it("keeps configured LID aliases on convenience parsing", () => {
expect(normalizeWhatsAppLidJid(" whatsapp:whatsapp:123:4@HOSTED.LID ")).toBe(
"123@hosted.lid",
);
});
});
+11 -6
View File
@@ -1,6 +1,10 @@
import type { MediaPlaceholderTextFact } from "openclaw/plugin-sdk/channel-inbound";
// Whatsapp plugin module implements identity behavior.
import { normalizeWhatsAppPhoneInput, parseWhatsAppJid } from "./phone-input.js";
import {
normalizeWhatsAppPhoneInput,
parseExactWhatsAppJid,
parseWhatsAppJid,
} from "./phone-input.js";
import { jidToE164 } from "./text-runtime.js";
export type WhatsAppIdentity = {
@@ -64,14 +68,15 @@ type LegacyMentionsLike = {
};
function normalizeDeviceScopedJid(jid: string | null | undefined): string | null {
const parsed = typeof jid === "string" ? parseWhatsAppJid(jid) : null;
const parsed = typeof jid === "string" ? parseExactWhatsAppJid(jid) : null;
return parsed?.kind === "pn" || parsed?.kind === "lid"
? `${parsed.digits}@${parsed.domain}`
: (jid ?? null);
}
function isWhatsAppLidJid(jid: string | null | undefined): boolean {
return typeof jid === "string" && parseWhatsAppJid(jid)?.kind === "lid";
function normalizeExactWhatsAppLidJid(jid: string | null | undefined): string | null {
const parsed = typeof jid === "string" ? parseExactWhatsAppJid(jid) : null;
return parsed?.kind === "lid" ? `${parsed.digits}@${parsed.domain}` : null;
}
export function normalizeWhatsAppLidJid(jid: string | null | undefined): string | null {
@@ -85,8 +90,8 @@ export function resolveComparableIdentity(
): WhatsAppIdentity {
const rawJid = normalizeDeviceScopedJid(identity?.jid);
const rawLid = normalizeDeviceScopedJid(identity?.lid);
const lid = normalizeWhatsAppLidJid(rawLid ?? rawJid);
const jid = rawJid && !isWhatsAppLidJid(rawJid) ? rawJid : null;
const lid = normalizeExactWhatsAppLidJid(rawLid ?? rawJid);
const jid = rawJid && !normalizeExactWhatsAppLidJid(rawJid) ? rawJid : null;
const e164 =
identity?.e164 != null
? normalizeWhatsAppPhoneInput(identity.e164)
+10 -8
View File
@@ -36,29 +36,31 @@ export function stripWhatsAppTargetPrefixes(value: string): string {
return candidate;
}
export function parseWhatsAppJid(value: string): ParsedWhatsAppJid | null {
export function parseExactWhatsAppJid(value: string): ParsedWhatsAppJid | null {
if (hasUnsafeWhatsAppTargetCharacters(value)) {
return null;
}
const stripped = stripWhatsAppTargetPrefixes(value);
const hasGroupPrefix = /^group:/i.test(stripped);
const candidate = trimWhatsAppAsciiSpaces(stripped.replace(/^group:/i, ""));
for (const [kind, pattern] of WHATSAPP_JID_PATTERNS) {
const match = candidate.match(pattern);
const match = value.match(pattern);
const digits = match?.[1];
const domain = match?.[3]?.toLowerCase();
if (!digits || !domain) {
continue;
}
if (hasGroupPrefix && kind !== "group") {
return null;
}
const device = match?.[2] ? `:${match[2]}` : "";
return { kind, jid: `${digits}${device}@${domain}`, digits, domain };
}
return null;
}
export function parseWhatsAppJid(value: string): ParsedWhatsAppJid | null {
const stripped = stripWhatsAppTargetPrefixes(value);
const hasGroupPrefix = /^group:/i.test(stripped);
const candidate = trimWhatsAppAsciiSpaces(stripped.replace(/^group:/i, ""));
const parsed = parseExactWhatsAppJid(candidate);
return hasGroupPrefix && parsed?.kind !== "group" ? null : parsed;
}
export function normalizeWhatsAppPhoneInput(value: string): string | null {
if (hasUnsafeWhatsAppTargetCharacters(value)) {
return null;