From 6bc65b0a5d9e61d69ae1b08e704de3a88890ef0d Mon Sep 17 00:00:00 2001 From: Instinct Agent Date: Tue, 22 Sep 2026 12:42:25 +0300 Subject: [PATCH] fix(chat): hydrate group delete keys from stored messages --- .../whatsapp/whatsapp.baileys.service.ts | 24 +++++++++++++++---- src/api/services/message-key.service.ts | 13 ++++++++++ test/message-key.test.ts | 18 +++++++++++++- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts index a80ddac157..d34f1a5732 100644 --- a/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts +++ b/src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts @@ -67,7 +67,7 @@ import { messageArchiveStateFromStatus, messageUpdateIdentity, } from '@api/services/message-archive.service'; -import { preserveMessageKey } from '@api/services/message-key.service'; +import { hydrateMessageKey, preserveMessageKey } from '@api/services/message-key.service'; import { applyMistakesToMessage } from '@api/services/mistakes-generator.service'; import { OutboundSafetyService } from '@api/services/outbound-safety.service'; import { SettingsTemplateService } from '@api/services/settings-template.service'; @@ -4180,15 +4180,29 @@ export class BaileysStartupService extends ChannelStartupService { remoteJid: del.remoteJid, }); } - const deleteKey = preserveMessageKey(del); + const storedMessage = await this.prismaRepository.message.findFirst({ + where: { + instanceId: this.instanceId, + key: { path: ['id'], equals: del.id }, + OR: [ + { key: { path: ['remoteJid'], equals: del.remoteJid } }, + { key: { path: ['remoteJidAlt'], equals: del.remoteJid } }, + { remoteJid: del.remoteJid }, + { remoteJidAlt: del.remoteJid }, + ], + }, + }); + const storedKey = + storedMessage?.key && typeof storedMessage.key === 'object' && !Array.isArray(storedMessage.key) + ? storedMessage.key + : null; + const deleteKey = hydrateMessageKey(del, storedKey); const response = await this.client.sendMessage(deleteKey.remoteJid, { delete: deleteKey }); if (response) { const messageId = deleteKey.id; if (messageId) { const isLogicalDeleted = configService.get('DATABASE').DELETE_DATA.LOGICAL_MESSAGE_DELETE; - let message = await this.prismaRepository.message.findFirst({ - where: { instanceId: this.instanceId, key: { path: ['id'], equals: messageId } }, - }); + let message = storedMessage; if (isLogicalDeleted) { if (!message) return response; const existingKey = typeof message?.key === 'object' && message.key !== null ? message.key : {}; diff --git a/src/api/services/message-key.service.ts b/src/api/services/message-key.service.ts index 23a17cc4df..4b5c6b20bd 100644 --- a/src/api/services/message-key.service.ts +++ b/src/api/services/message-key.service.ts @@ -24,3 +24,16 @@ export function preserveMessageKey(key: T): T { ...(key.addressingMode ? { addressingMode: key.addressingMode } : {}), }; } + +/** + * Rebuild a protocol key from the authoritative key stored with the original message. + * + * API clients often retain only id/fromMe/remoteJid/participant. Group deletes also + * need the original participant alternate and addressing mode when WhatsApp used LID + * addressing. The stored key wins for protocol fields; the requested id is retained + * so a mismatched database record can never redirect the operation. + */ +export function hydrateMessageKey(requested: T, stored?: MessageKeyLike | null): T { + if (!stored || typeof stored !== 'object') return preserveMessageKey(requested); + return preserveMessageKey({ ...requested, ...stored, id: requested.id } as T); +} diff --git a/test/message-key.test.ts b/test/message-key.test.ts index bce4d0f3a5..5be25840bc 100644 --- a/test/message-key.test.ts +++ b/test/message-key.test.ts @@ -1,6 +1,6 @@ import assert from 'node:assert/strict'; -import { preserveMessageKey } from '../src/api/services/message-key.service'; +import { hydrateMessageKey, preserveMessageKey } from '../src/api/services/message-key.service'; const groupLidKey = { id: '3EB0A_MESSAGE', @@ -28,3 +28,19 @@ const directLidKey = { assert.deepEqual(preserveMessageKey(directLidKey), directLidKey); console.log('message key tests passed'); + + +const partialGroupDelete = { + id: '3EB0A_MESSAGE', + fromMe: false, + remoteJid: '120363000000000000@g.us', + participant: '230687726690306@lid', +}; +const hydrated = hydrateMessageKey(partialGroupDelete, groupLidKey); +assert.equal(hydrated.id, partialGroupDelete.id); +assert.equal(hydrated.remoteJid, groupLidKey.remoteJid); +assert.equal(hydrated.participant, groupLidKey.participant); +assert.equal(hydrated.participantAlt, groupLidKey.participantAlt); +assert.equal(hydrated.addressingMode, groupLidKey.addressingMode); +assert.equal(hydrated.fromMe, groupLidKey.fromMe); +assert.deepEqual(hydrateMessageKey(partialGroupDelete), partialGroupDelete);