From a331bd5ea14c98a77e7adbaf30a23bfb955d6c74 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 25 Nov 2025 06:36:22 +0100 Subject: [PATCH] web: send read receipts for inbound messages --- src/provider-web.test.ts | 44 ++++++++++++++++++++++++++++++++++++++++ src/provider-web.ts | 14 +++++++++++++ test/mocks/baileys.ts | 2 ++ 3 files changed, 60 insertions(+) diff --git a/src/provider-web.test.ts b/src/provider-web.test.ts index effa544dc..898e739c8 100644 --- a/src/provider-web.test.ts +++ b/src/provider-web.test.ts @@ -171,6 +171,9 @@ describe("provider-web", () => { expect(onMessage).toHaveBeenCalledWith( expect.objectContaining({ body: "ping", from: "+999", to: "+123" }), ); + expect(sock.readMessages).toHaveBeenCalledWith([ + { remoteJid: "999@s.whatsapp.net", id: "abc", participant: undefined, fromMe: false }, + ]); expect(sock.sendPresenceUpdate).toHaveBeenCalledWith( "composing", "999@s.whatsapp.net", @@ -207,6 +210,47 @@ describe("provider-web", () => { mediaType: "image/jpeg", }), ); + expect(sock.readMessages).toHaveBeenCalledWith([ + { + remoteJid: "888@s.whatsapp.net", + id: "med1", + participant: undefined, + fromMe: false, + }, + ]); + await listener.close(); + }); + + it("monitorWebInbox includes participant when marking group messages read", async () => { + const onMessage = vi.fn(); + const listener = await monitorWebInbox({ verbose: false, onMessage }); + const sock = getLastSocket(); + const upsert = { + type: "notify", + messages: [ + { + key: { + id: "grp1", + fromMe: false, + remoteJid: "12345-67890@g.us", + participant: "111@s.whatsapp.net", + }, + message: { conversation: "group ping" }, + }, + ], + }; + + sock.ev.emit("messages.upsert", upsert); + await new Promise((resolve) => setImmediate(resolve)); + + expect(sock.readMessages).toHaveBeenCalledWith([ + { + remoteJid: "12345-67890@g.us", + id: "grp1", + participant: "111@s.whatsapp.net", + fromMe: false, + }, + ]); await listener.close(); }); diff --git a/src/provider-web.ts b/src/provider-web.ts index 73d1fb165..91bf8f638 100644 --- a/src/provider-web.ts +++ b/src/provider-web.ts @@ -260,6 +260,20 @@ export async function monitorWebInbox(options: { // Ignore status/broadcast traffic; we only care about direct chats. if (remoteJid.endsWith("@status") || remoteJid.endsWith("@broadcast")) continue; + if (id) { + const participant = msg.key?.participant; + try { + await sock.readMessages([ + { remoteJid, id, participant, fromMe: false }, + ]); + if (isVerbose()) { + const suffix = participant ? ` (participant ${participant})` : ""; + logVerbose(`Marked message ${id} as read for ${remoteJid}${suffix}`); + } + } catch (err) { + logVerbose(`Failed to mark message ${id} read: ${String(err)}`); + } + } const from = jidToE164(remoteJid); if (!from) continue; let body = extractText(msg.message ?? undefined); diff --git a/test/mocks/baileys.ts b/test/mocks/baileys.ts index 09b77a9a8..3c6b08591 100644 --- a/test/mocks/baileys.ts +++ b/test/mocks/baileys.ts @@ -5,6 +5,7 @@ export type MockBaileysSocket = { ws: { close: ReturnType }; sendPresenceUpdate: ReturnType; sendMessage: ReturnType; + readMessages: ReturnType; user?: { id?: string }; }; @@ -28,6 +29,7 @@ export function createMockBaileys(): { mod: MockBaileysModule; lastSocket: () => ws: { close: vi.fn() }, sendPresenceUpdate: vi.fn().mockResolvedValue(undefined), sendMessage: vi.fn().mockResolvedValue({ key: { id: "msg123" } }), + readMessages: vi.fn().mockResolvedValue(undefined), user: { id: "123@s.whatsapp.net" }, }; setImmediate(() => ev.emit("connection.update", { connection: "open" }));