fix: add whatsapp reply context
This commit is contained in:
@@ -1751,6 +1751,47 @@ describe("web auto-reply", () => {
|
||||
expect(callArg?.Body).toContain("hello");
|
||||
});
|
||||
|
||||
it("forwards reply-to context to resolver", async () => {
|
||||
let capturedOnMessage:
|
||||
| ((msg: import("./inbound.js").WebInboundMessage) => Promise<void>)
|
||||
| undefined;
|
||||
const listenerFactory = async (opts: {
|
||||
onMessage: (
|
||||
msg: import("./inbound.js").WebInboundMessage,
|
||||
) => Promise<void>;
|
||||
}) => {
|
||||
capturedOnMessage = opts.onMessage;
|
||||
return { close: vi.fn() };
|
||||
};
|
||||
|
||||
const resolver = vi.fn().mockResolvedValue({ text: "reply" });
|
||||
|
||||
await monitorWebProvider(false, listenerFactory, false, resolver);
|
||||
expect(capturedOnMessage).toBeDefined();
|
||||
|
||||
await capturedOnMessage?.({
|
||||
body: "hello",
|
||||
from: "+1555",
|
||||
to: "+2666",
|
||||
id: "msg1",
|
||||
replyToId: "q1",
|
||||
replyToBody: "original",
|
||||
replyToSender: "+1999",
|
||||
sendComposing: vi.fn(),
|
||||
reply: vi.fn(),
|
||||
sendMedia: vi.fn(),
|
||||
});
|
||||
|
||||
const callArg = resolver.mock.calls[0]?.[0] as {
|
||||
ReplyToId?: string;
|
||||
ReplyToBody?: string;
|
||||
ReplyToSender?: string;
|
||||
};
|
||||
expect(callArg.ReplyToId).toBe("q1");
|
||||
expect(callArg.ReplyToBody).toBe("original");
|
||||
expect(callArg.ReplyToSender).toBe("+1999");
|
||||
});
|
||||
|
||||
it("applies responsePrefix to regular replies", async () => {
|
||||
setLoadConfigMock(() => ({
|
||||
inbound: {
|
||||
|
||||
@@ -1107,6 +1107,9 @@ export async function monitorWebProvider(
|
||||
From: msg.from,
|
||||
To: msg.to,
|
||||
MessageSid: msg.id,
|
||||
ReplyToId: msg.replyToId,
|
||||
ReplyToBody: msg.replyToBody,
|
||||
ReplyToSender: msg.replyToSender,
|
||||
MediaPath: msg.mediaPath,
|
||||
MediaUrl: msg.mediaUrl,
|
||||
MediaType: msg.mediaType,
|
||||
|
||||
@@ -39,6 +39,9 @@ export type WebInboundMessage = {
|
||||
senderJid?: string;
|
||||
senderE164?: string;
|
||||
senderName?: string;
|
||||
replyToId?: string;
|
||||
replyToBody?: string;
|
||||
replyToSender?: string;
|
||||
groupSubject?: string;
|
||||
groupParticipants?: string[];
|
||||
mentionedJids?: string[];
|
||||
@@ -187,6 +190,9 @@ export async function monitorWebInbox(options: {
|
||||
body = extractMediaPlaceholder(msg.message ?? undefined);
|
||||
if (!body) continue;
|
||||
}
|
||||
const replyContext = describeReplyContext(
|
||||
msg.message as proto.IMessage | undefined,
|
||||
);
|
||||
let mediaPath: string | undefined;
|
||||
let mediaType: string | undefined;
|
||||
try {
|
||||
@@ -211,10 +217,10 @@ export async function monitorWebInbox(options: {
|
||||
}
|
||||
};
|
||||
const reply = async (text: string) => {
|
||||
await sock.sendMessage(chatJid, { text });
|
||||
await sock.sendMessage(chatJid, { text }, { quoted: msg });
|
||||
};
|
||||
const sendMedia = async (payload: AnyMessageContent) => {
|
||||
await sock.sendMessage(chatJid, payload);
|
||||
await sock.sendMessage(chatJid, payload, { quoted: msg });
|
||||
};
|
||||
const timestamp = msg.messageTimestamp
|
||||
? Number(msg.messageTimestamp) * 1000
|
||||
@@ -249,6 +255,9 @@ export async function monitorWebInbox(options: {
|
||||
senderJid: participantJid,
|
||||
senderE164: senderE164 ?? undefined,
|
||||
senderName,
|
||||
replyToId: replyContext?.id,
|
||||
replyToBody: replyContext?.body,
|
||||
replyToSender: replyContext?.sender,
|
||||
groupSubject,
|
||||
groupParticipants,
|
||||
mentionedJids: mentionedJids ?? undefined,
|
||||
@@ -443,6 +452,36 @@ export function extractMediaPlaceholder(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function describeReplyContext(rawMessage: proto.IMessage | undefined): {
|
||||
id?: string;
|
||||
body: string;
|
||||
sender: string;
|
||||
} | null {
|
||||
const message = unwrapMessage(rawMessage);
|
||||
if (!message) return null;
|
||||
const contextInfo =
|
||||
message.extendedTextMessage?.contextInfo ??
|
||||
message.imageMessage?.contextInfo ??
|
||||
message.videoMessage?.contextInfo ??
|
||||
message.documentMessage?.contextInfo ??
|
||||
message.audioMessage?.contextInfo ??
|
||||
message.stickerMessage?.contextInfo ??
|
||||
message.buttonsResponseMessage?.contextInfo ??
|
||||
message.listResponseMessage?.contextInfo;
|
||||
const quoted = contextInfo?.quotedMessage as proto.IMessage | undefined;
|
||||
if (!quoted) return null;
|
||||
const body = extractText(quoted) ?? extractMediaPlaceholder(quoted);
|
||||
if (!body) return null;
|
||||
const senderJid = contextInfo?.participant ?? undefined;
|
||||
const senderE164 = senderJid ? jidToE164(senderJid) ?? senderJid : undefined;
|
||||
const sender = senderE164 ?? "unknown sender";
|
||||
return {
|
||||
id: contextInfo?.stanzaId ? String(contextInfo.stanzaId) : undefined,
|
||||
body,
|
||||
sender,
|
||||
};
|
||||
}
|
||||
|
||||
async function downloadInboundMedia(
|
||||
msg: proto.IWebMessageInfo,
|
||||
sock: Awaited<ReturnType<typeof createWaSocket>>,
|
||||
|
||||
@@ -107,9 +107,11 @@ describe("web monitor inbox", () => {
|
||||
"composing",
|
||||
"999@s.whatsapp.net",
|
||||
);
|
||||
expect(sock.sendMessage).toHaveBeenCalledWith("999@s.whatsapp.net", {
|
||||
text: "pong",
|
||||
});
|
||||
expect(sock.sendMessage).toHaveBeenCalledWith(
|
||||
"999@s.whatsapp.net",
|
||||
{ text: "pong" },
|
||||
{ quoted: expect.objectContaining({ key: { id: "abc" } }) },
|
||||
);
|
||||
|
||||
await listener.close();
|
||||
});
|
||||
@@ -151,6 +153,53 @@ describe("web monitor inbox", () => {
|
||||
await listener.close();
|
||||
});
|
||||
|
||||
it("captures reply context from quoted messages", async () => {
|
||||
const onMessage = vi.fn(async (msg) => {
|
||||
await msg.reply("pong");
|
||||
});
|
||||
|
||||
const listener = await monitorWebInbox({ verbose: false, onMessage });
|
||||
const sock = await createWaSocket();
|
||||
const upsert = {
|
||||
type: "notify",
|
||||
messages: [
|
||||
{
|
||||
key: { id: "abc", fromMe: false, remoteJid: "999@s.whatsapp.net" },
|
||||
message: {
|
||||
extendedTextMessage: {
|
||||
text: "reply",
|
||||
contextInfo: {
|
||||
stanzaId: "q1",
|
||||
participant: "111@s.whatsapp.net",
|
||||
quotedMessage: { conversation: "original" },
|
||||
},
|
||||
},
|
||||
},
|
||||
messageTimestamp: 1_700_000_000,
|
||||
pushName: "Tester",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
sock.ev.emit("messages.upsert", upsert);
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
|
||||
expect(onMessage).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
replyToId: "q1",
|
||||
replyToBody: "original",
|
||||
replyToSender: "+111",
|
||||
}),
|
||||
);
|
||||
expect(sock.sendMessage).toHaveBeenCalledWith(
|
||||
"999@s.whatsapp.net",
|
||||
{ text: "pong" },
|
||||
{ quoted: expect.objectContaining({ key: { id: "abc" } }) },
|
||||
);
|
||||
|
||||
await listener.close();
|
||||
});
|
||||
|
||||
it("captures media path for image messages", async () => {
|
||||
const onMessage = vi.fn();
|
||||
const listener = await monitorWebInbox({ verbose: false, onMessage });
|
||||
|
||||
Reference in New Issue
Block a user