mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix(whatsapp): preserve transport envelope ownership (#116989)
Co-authored-by: Peter Steinberger <steipete@macos.shared>
This commit is contained in:
committed by
GitHub
parent
629d3c2144
commit
fbb489fedf
@@ -117,6 +117,71 @@ describe("extractMentionedJids", () => {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "shared contacts",
|
||||
message: {
|
||||
contactMessage: {
|
||||
displayName: "Alice",
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "shared contact collections",
|
||||
message: {
|
||||
contactsArrayMessage: {
|
||||
contacts: [{ displayName: "Alice" }],
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "location pins",
|
||||
message: {
|
||||
locationMessage: {
|
||||
degreesLatitude: 1,
|
||||
degreesLongitude: 2,
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "live locations",
|
||||
message: {
|
||||
liveLocationMessage: {
|
||||
degreesLatitude: 1,
|
||||
degreesLongitude: 2,
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interactive button prompts",
|
||||
message: {
|
||||
buttonsMessage: {
|
||||
contentText: "Choose one",
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "interactive lists",
|
||||
message: {
|
||||
listMessage: {
|
||||
title: "Choose one",
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "native interactive prompts",
|
||||
message: {
|
||||
interactiveMessage: {
|
||||
body: { text: "Choose one" },
|
||||
contextInfo: { mentionedJid: [botJid] },
|
||||
},
|
||||
},
|
||||
},
|
||||
])("preserves direct bot mentions from $name", ({ message }) => {
|
||||
expect(extractMentionedJids(message as proto.IMessage)).toEqual([botJid]);
|
||||
});
|
||||
|
||||
@@ -113,34 +113,12 @@ export function extractContextInfo(
|
||||
}
|
||||
|
||||
export function extractMentionedJids(rawMessage: proto.IMessage | undefined): string[] | undefined {
|
||||
const message = unwrapMessage(rawMessage);
|
||||
if (!message) {
|
||||
// Context ownership already follows Baileys envelopes without entering quoted messages.
|
||||
const mentionedJids = extractContextInfo(rawMessage)?.mentionedJid?.filter(Boolean);
|
||||
if (!mentionedJids?.length) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const candidates: Array<string[] | null | undefined> = [
|
||||
message.extendedTextMessage?.contextInfo?.mentionedJid,
|
||||
message.imageMessage?.contextInfo?.mentionedJid,
|
||||
message.videoMessage?.contextInfo?.mentionedJid,
|
||||
message.ptvMessage?.contextInfo?.mentionedJid,
|
||||
message.documentMessage?.contextInfo?.mentionedJid,
|
||||
message.audioMessage?.contextInfo?.mentionedJid,
|
||||
message.stickerMessage?.contextInfo?.mentionedJid,
|
||||
message.buttonsResponseMessage?.contextInfo?.mentionedJid,
|
||||
message.listResponseMessage?.contextInfo?.mentionedJid,
|
||||
message.templateButtonReplyMessage?.contextInfo?.mentionedJid,
|
||||
message.interactiveResponseMessage?.contextInfo?.mentionedJid,
|
||||
message.pollCreationMessage?.contextInfo?.mentionedJid,
|
||||
message.pollCreationMessageV2?.contextInfo?.mentionedJid,
|
||||
message.pollCreationMessageV3?.contextInfo?.mentionedJid,
|
||||
message.pollCreationMessageV5?.contextInfo?.mentionedJid,
|
||||
];
|
||||
|
||||
const flattened = candidates.flatMap((arr) => arr ?? []).filter(Boolean);
|
||||
if (flattened.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
return uniqueStrings(flattened);
|
||||
return uniqueStrings(mentionedJids);
|
||||
}
|
||||
|
||||
function extractNativeFlowResponseText(
|
||||
|
||||
@@ -656,22 +656,25 @@ describe("web outbound", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps explicit document kind for prehydrated image payloads", async () => {
|
||||
const buf = Buffer.from("image-as-document");
|
||||
it.each([
|
||||
{ contentType: "image/png", fileName: "photo.png" },
|
||||
{ contentType: "video/mp4", fileName: "clip.mp4" },
|
||||
])("keeps explicit document delivery for prehydrated $contentType payloads", async (media) => {
|
||||
const buf = Buffer.from("visual-as-document");
|
||||
|
||||
await sendMessageWhatsApp("+1555", "doc", {
|
||||
verbose: false,
|
||||
cfg: WHATSAPP_TEST_CFG,
|
||||
mediaPayload: {
|
||||
buffer: buf,
|
||||
contentType: "image/png",
|
||||
...media,
|
||||
kind: "document",
|
||||
fileName: "photo.png",
|
||||
},
|
||||
});
|
||||
|
||||
expect(sendMessage).toHaveBeenLastCalledWith("+1555", "doc", buf, "image/png", {
|
||||
fileName: "photo.png",
|
||||
expect(sendMessage).toHaveBeenLastCalledWith("+1555", "doc", buf, media.contentType, {
|
||||
asDocument: true,
|
||||
fileName: media.fileName,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -836,6 +839,32 @@ describe("web outbound", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the actual outbound poll key when Baileys resolves a LID target", async () => {
|
||||
sendPoll.mockResolvedValueOnce({
|
||||
kind: "poll",
|
||||
messageId: "poll-lid",
|
||||
keys: [
|
||||
{
|
||||
id: "poll-lid",
|
||||
remoteJid: "123456789@lid",
|
||||
fromMe: true,
|
||||
},
|
||||
],
|
||||
providerAccepted: true,
|
||||
});
|
||||
|
||||
await expect(
|
||||
sendPollWhatsApp(
|
||||
"+1555",
|
||||
{ question: "Lunch?", options: ["Pizza", "Sushi"] },
|
||||
{ verbose: false, cfg: WHATSAPP_TEST_CFG },
|
||||
),
|
||||
).resolves.toEqual({
|
||||
messageId: "poll-lid",
|
||||
toJid: "123456789@lid",
|
||||
});
|
||||
});
|
||||
|
||||
it("checks send readiness before sending direct polls", async () => {
|
||||
const assertSendReady = vi.fn(async () => {
|
||||
throw new Error("WhatsApp reachout timelock is active");
|
||||
|
||||
@@ -49,9 +49,10 @@ function buildWhatsAppMediaSendState(params: {
|
||||
forceDocument?: boolean;
|
||||
}): WhatsAppMediaSendState {
|
||||
const { media, caption } = params;
|
||||
const forceDocumentDelivery = Boolean(
|
||||
params.forceDocument && supportsForcedDocumentDelivery(media.kind),
|
||||
);
|
||||
const forceDocumentDelivery =
|
||||
Boolean(params.forceDocument && supportsForcedDocumentDelivery(media.kind)) ||
|
||||
(media.kind === "document" &&
|
||||
(media.mimetype.startsWith("image/") || media.mimetype.startsWith("video/")));
|
||||
let text = caption ?? "";
|
||||
let documentFileName = media.kind === "document" ? media.fileName : undefined;
|
||||
let visibleTextAfterVoice: string | undefined;
|
||||
@@ -405,7 +406,7 @@ export async function sendPollWhatsApp(
|
||||
const durationMs = Date.now() - startedAt;
|
||||
outboundLog.info(`Sent poll ${messageId} -> ${redactedJid} (${durationMs}ms)`);
|
||||
logger.info({ jid: redactedJid, messageId }, "sent poll");
|
||||
return { messageId, toJid: jid };
|
||||
return { messageId, toJid: resolveActualSentRemoteJid(result, jid) };
|
||||
} catch (err) {
|
||||
logger.error({ err: String(err), to: redactedTo }, "failed to send poll via web session");
|
||||
throw err;
|
||||
|
||||
Reference in New Issue
Block a user