mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
fix(whatsapp): also recognize button/list/interactive responses as content (#73797)
Greptile review on the original PR flagged that the four extractors used in `hasInboundUserContent` (`extractText`, `extractMediaPlaceholder`, `extractContactContext`, `extractLocationData`) do not surface `buttonsResponseMessage`, `listResponseMessage`, `templateButtonReplyMessage`, or `interactiveResponseMessage`. The gate would have silently dropped real user button/list selections as if they were receipts. Add a `hasInteractiveResponseContent(message)` helper that checks for the four reply-message keys directly, and call it from `hasInboundUserContent` both at the root and after walking `buildMessageChain` (so wrapped ephemeral/viewOnce envelopes still surface their interactive responses). 5 new test cases cover all four reply types plus the wrapped-ephemeral path. Sign-Off: hclsys
This commit is contained in:
@@ -169,6 +169,65 @@ describe("hasInboundUserContent", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for buttons response (user button click)", () => {
|
||||
expect(
|
||||
hasInboundUserContent({
|
||||
buttonsResponseMessage: {
|
||||
selectedButtonId: "yes",
|
||||
selectedDisplayText: "Yes",
|
||||
},
|
||||
} as proto.IMessage),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for list response (user list selection)", () => {
|
||||
expect(
|
||||
hasInboundUserContent({
|
||||
listResponseMessage: {
|
||||
title: "Option A",
|
||||
singleSelectReply: { selectedRowId: "a" },
|
||||
} as unknown as proto.Message.IListResponseMessage,
|
||||
} as proto.IMessage),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for template button reply", () => {
|
||||
expect(
|
||||
hasInboundUserContent({
|
||||
templateButtonReplyMessage: {
|
||||
selectedId: "btn-1",
|
||||
selectedDisplayText: "Click",
|
||||
} as unknown as proto.Message.ITemplateButtonReplyMessage,
|
||||
} as proto.IMessage),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for interactive response", () => {
|
||||
expect(
|
||||
hasInboundUserContent({
|
||||
interactiveResponseMessage: {
|
||||
body: { text: "x" },
|
||||
nativeFlowResponseMessage: { name: "n", paramsJson: "{}" },
|
||||
} as unknown as proto.Message.IInteractiveResponseMessage,
|
||||
} as proto.IMessage),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns true for buttons response wrapped in ephemeralMessage (regression for #73797 + greptile review)", () => {
|
||||
expect(
|
||||
hasInboundUserContent({
|
||||
ephemeralMessage: {
|
||||
message: {
|
||||
buttonsResponseMessage: {
|
||||
selectedButtonId: "ok",
|
||||
selectedDisplayText: "OK",
|
||||
},
|
||||
},
|
||||
},
|
||||
} as proto.IMessage),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for undefined message (regression for #73797)", () => {
|
||||
expect(hasInboundUserContent(undefined)).toBe(false);
|
||||
});
|
||||
|
||||
@@ -439,11 +439,28 @@ export function describeReplyContext(
|
||||
};
|
||||
}
|
||||
|
||||
function hasInteractiveResponseContent(message: proto.IMessage | undefined): boolean {
|
||||
if (!message) {
|
||||
return false;
|
||||
}
|
||||
// Button/list/template/interactive selections that the existing four
|
||||
// extractors do not cover. Treat any presence of these keys as user
|
||||
// content — Baileys never delivers these as receipts or protocol
|
||||
// envelopes, only as explicit user choices.
|
||||
return Boolean(
|
||||
message.buttonsResponseMessage ||
|
||||
message.listResponseMessage ||
|
||||
message.templateButtonReplyMessage ||
|
||||
message.interactiveResponseMessage,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fast O(1) check that a Baileys message carries user-visible inbound content
|
||||
* (text, media, contact, location). Returns false for protocol/receipt/typing
|
||||
* notifications that arrive on the same `messages.upsert` stream as real
|
||||
* messages but should not trigger pairing access-control side effects.
|
||||
* (text, media, contact, location, button/list selection). Returns false for
|
||||
* protocol/receipt/typing notifications that arrive on the same
|
||||
* `messages.upsert` stream as real messages but should not trigger pairing
|
||||
* access-control side effects.
|
||||
*/
|
||||
export function hasInboundUserContent(rawMessage: proto.IMessage | undefined): boolean {
|
||||
if (!rawMessage) {
|
||||
@@ -461,5 +478,15 @@ export function hasInboundUserContent(rawMessage: proto.IMessage | undefined): b
|
||||
if (extractLocationData(rawMessage)) {
|
||||
return true;
|
||||
}
|
||||
if (hasInteractiveResponseContent(rawMessage)) {
|
||||
return true;
|
||||
}
|
||||
// Walk wrappers (ephemeral, viewOnce, etc.) — interactive responses
|
||||
// can arrive nested.
|
||||
for (const candidate of buildMessageChain(rawMessage)) {
|
||||
if (candidate !== rawMessage && hasInteractiveResponseContent(candidate)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user