feat(whatsapp): support inbound read receipts

This commit is contained in:
Cyrus 2026-07-23 23:12:04 +01:00 • committed by kshitij
parent 560800f3cc
commit 35afa8ce06
8 changed files with 89 additions and 0 deletions

View file

@ -39,6 +39,7 @@ import {
buildTextSendPayload,
createBoundedMessageStore,
extractBridgeEvent,
inboundReadReceiptKeys,
inferMediaType,
mediaPayloadForFile,
pollCreationMessageFromPayload,
@ -75,6 +76,12 @@ const FORWARD_OWNER_MESSAGES =
typeof process.env.WHATSAPP_FORWARD_OWNER_MESSAGES === 'string' &&
['1', 'true', 'yes', 'on'].includes(process.env.WHATSAPP_FORWARD_OWNER_MESSAGES.toLowerCase());
const SEND_READ_RECEIPTS =
typeof process !== 'undefined' &&
process.env &&
typeof process.env.WHATSAPP_SEND_READ_RECEIPTS === 'string' &&
['1', 'true', 'yes', 'on'].includes(process.env.WHATSAPP_SEND_READ_RECEIPTS.toLowerCase());
const PORT = parseInt(getArg('port', '3000'), 10);
const SESSION_DIR = getArg('session', path.join(process.env.HOME || '~', '.hermes', 'whatsapp', 'session'));
// Cache directories: the Python gateway passes the profile-aware paths via
@ -647,6 +654,15 @@ async function startSocket() {
}
}
const receiptKeys = inboundReadReceiptKeys({ key: msg.key, enabled: SEND_READ_RECEIPTS });
if (receiptKeys.length > 0) {
try {
await sock.readMessages(receiptKeys);
} catch (err) {
console.warn('[bridge] failed to send read receipt:', err.message);
}
}
const messageContent = getMessageContent(msg);
if (messageContent.pollUpdateMessage) {
const pollUpdateMessage = messageContent.pollUpdateMessage;
@ -1074,6 +1090,7 @@ app.get('/health', (req, res) => {
queueLength: messageQueue.length,
uptime: process.uptime(),
scriptHash: SCRIPT_HASH,
sendReadReceipts: SEND_READ_RECEIPTS,
});
});

View file

@ -18,11 +18,33 @@ import {
createBoundedMessageStore,
appendMediaFailureNote,
extractBridgeEvent,
inboundReadReceiptKeys,
mediaPayloadForFile,
pollCreationMessageFromPayload,
pollUpdateForAggregation,
} from './bridge_helpers.js';
// -- inbound read receipts ------------------------------------------------
{
const groupKey = {
id: 'incoming-group-1',
remoteJid: '120363001234567890@g.us',
participant: '15550001111@s.whatsapp.net',
fromMe: false,
};
assert.deepEqual(inboundReadReceiptKeys({ key: groupKey, enabled: false }), []);
assert.deepEqual(
inboundReadReceiptKeys({ key: { ...groupKey, fromMe: true }, enabled: true }),
[],
);
const receiptKeys = inboundReadReceiptKeys({ key: groupKey, enabled: true });
assert.equal(receiptKeys.length, 1);
assert.equal(receiptKeys[0], groupKey);
assert.equal(receiptKeys[0].participant, groupKey.participant);
console.log(' ✓ inbound read receipts preserve the original group message key');
}
// -- quoted outbound text -------------------------------------------------
{
const store = createBoundedMessageStore(2);

View file

@ -494,6 +494,12 @@ export function inferMediaType(ext) {
return 'document';
}
export function inboundReadReceiptKeys({ key, enabled }) {
if (!enabled || !key || key.fromMe || !key.id || !key.remoteJid) return [];
// Preserve participant for group messages: Baileys needs the original key.
return [key];
}
export function mediaPayloadForFile({ buffer, filePath, mediaType, caption, fileName }) {
const ext = filePath.toLowerCase().split('.').pop();
const type = mediaType || inferMediaType(ext);