From f829b6d7a848f46eb6f52f111309f59050c85ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Lucas=20de=20Oliveira=20Lopes?= <55464917+jlucaso1@users.noreply.github.com> Date: Thu, 22 Jan 2026 10:00:41 -0300 Subject: [PATCH] fix: extract LID-PN mappings from conversation objects in history sync (#2282) * fix: extract LID-PN mappings from conversation objects in history sync * fix: extract PN from userReceipt when pnJid is missing for LID chats --- src/Utils/history.ts | 34 ++++ src/__tests__/Utils/history.test.ts | 268 ++++++++++++++++++++++++++++ 2 files changed, 302 insertions(+) diff --git a/src/Utils/history.ts b/src/Utils/history.ts index 5e94daa0..7edb9c81 100644 --- a/src/Utils/history.ts +++ b/src/Utils/history.ts @@ -3,6 +3,7 @@ import { inflate } from 'zlib' import { proto } from '../../WAProto/index.js' import type { Chat, Contact, LIDMapping, WAMessage } from '../Types' import { WAMessageStubType } from '../Types' +import { isHostedLidUser, isHostedPnUser, isLidUser, isPnUser } from '../WABinary' import { toNumber } from './generics' import type { ILogger } from './logger.js' import { normalizeMessageContent } from './messages' @@ -10,6 +11,24 @@ import { downloadContentFromMessage } from './messages-media' const inflatePromise = promisify(inflate) +const extractPnFromMessages = (messages: proto.IHistorySyncMsg[]): string | undefined => { + for (const msgItem of messages) { + const message = msgItem.message + // Only extract from outgoing messages (fromMe: true) in 1:1 chats + // because userReceipt.userJid is the recipient's JID + if (!message?.key?.fromMe || !message.userReceipt?.length) { + continue + } + + const userJid = message.userReceipt[0]?.userJid + if (userJid && (isPnUser(userJid) || isHostedPnUser(userJid))) { + return userJid + } + } + + return undefined +} + export const downloadHistory = async (msg: proto.Message.IHistorySyncNotification, options: RequestInit) => { const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options }) const bufferArray: Buffer[] = [] @@ -54,6 +73,21 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger phoneNumber: chat.pnJid || undefined }) + const chatId = chat.id! + const isLid = isLidUser(chatId) || isHostedLidUser(chatId) + const isPn = isPnUser(chatId) || isHostedPnUser(chatId) + if (isLid && chat.pnJid) { + lidPnMappings.push({ lid: chatId, pn: chat.pnJid }) + } else if (isPn && chat.lidJid) { + lidPnMappings.push({ lid: chat.lidJid, pn: chatId }) + } else if (isLid && !chat.pnJid) { + // Fallback: extract PN from userReceipt in messages when pnJid is missing + const pnFromReceipt = extractPnFromMessages(chat.messages || []) + if (pnFromReceipt) { + lidPnMappings.push({ lid: chatId, pn: pnFromReceipt }) + } + } + const msgs = chat.messages || [] delete chat.messages diff --git a/src/__tests__/Utils/history.test.ts b/src/__tests__/Utils/history.test.ts index 2676fcb6..c6477da0 100644 --- a/src/__tests__/Utils/history.test.ts +++ b/src/__tests__/Utils/history.test.ts @@ -91,4 +91,272 @@ describe('processHistoryMessage', () => { }) }) }) + + describe('LID-PN mapping extraction from conversations', () => { + it('should extract mapping when chat.id is LID and pnJid exists', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '11111111111111@lid', + pnJid: '1234567890123@s.whatsapp.net' + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toContainEqual({ + lid: '11111111111111@lid', + pn: '1234567890123@s.whatsapp.net' + }) + }) + + it('should extract mapping when chat.id is PN and lidJid exists', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '1234567890123@s.whatsapp.net', + lidJid: '11111111111111@lid' + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toContainEqual({ + lid: '11111111111111@lid', + pn: '1234567890123@s.whatsapp.net' + }) + }) + + it('should not extract mapping for group chats', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '123456789012345678@g.us', + lidJid: '11111111111111@lid', + pnJid: '1234567890123@s.whatsapp.net' + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toEqual([]) + }) + + it('should combine mappings from phoneNumberToLidMappings and conversations', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + phoneNumberToLidMappings: [{ lidJid: '11111111111111@lid', pnJid: '1111111111111@s.whatsapp.net' }], + conversations: [ + { + id: '22222222222222@lid', + pnJid: '2222222222222@s.whatsapp.net' + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toHaveLength(2) + expect(result.lidPnMappings).toContainEqual({ + lid: '11111111111111@lid', + pn: '1111111111111@s.whatsapp.net' + }) + expect(result.lidPnMappings).toContainEqual({ + lid: '22222222222222@lid', + pn: '2222222222222@s.whatsapp.net' + }) + }) + + it('should extract mapping for hosted LID users', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '11111111111111@hosted.lid', + pnJid: '1234567890123@hosted' + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toContainEqual({ + lid: '11111111111111@hosted.lid', + pn: '1234567890123@hosted' + }) + }) + + it('should extract mapping for hosted PN users', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '1234567890123@hosted', + lidJid: '11111111111111@hosted.lid' + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toContainEqual({ + lid: '11111111111111@hosted.lid', + pn: '1234567890123@hosted' + }) + }) + + it('should extract mapping from userReceipt when pnJid is missing and chat.id is LID', () => { + // Based on real-world case: LID chat without pnJid but userReceipt contains PN + // See: https://github.com/WhiskeySockets/Baileys/pull/2282#issuecomment-3777941679 + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '211071956705386@lid', + // pnJid is intentionally missing + messages: [ + { + message: { + key: { + remoteJid: '211071956705386@lid', + fromMe: true, + id: '3EB052FF8D9D00646C9994' + }, + messageTimestamp: 1768320044, + userReceipt: [ + { + userJid: '5518999991234@s.whatsapp.net', + receiptTimestamp: 1768320045, + readTimestamp: 1768327083 + } + ] + } + } + ] + } + ] + } + + const result = processHistoryMessage(historySync) + + expect(result.lidPnMappings).toContainEqual({ + lid: '211071956705386@lid', + pn: '5518999991234@s.whatsapp.net' + }) + }) + + it('should not extract mapping from userReceipt when pnJid already exists', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '211071956705386@lid', + pnJid: '5518888881234@s.whatsapp.net', // pnJid exists + messages: [ + { + message: { + key: { + remoteJid: '211071956705386@lid', + fromMe: true, + id: '3EB052FF8D9D00646C9994' + }, + userReceipt: [ + { + userJid: '5518999991234@s.whatsapp.net' // different PN + } + ] + } + } + ] + } + ] + } + + const result = processHistoryMessage(historySync) + + // Should use pnJid, not userReceipt + expect(result.lidPnMappings).toContainEqual({ + lid: '211071956705386@lid', + pn: '5518888881234@s.whatsapp.net' + }) + // Should NOT contain the userReceipt PN + expect(result.lidPnMappings).not.toContainEqual({ + lid: '211071956705386@lid', + pn: '5518999991234@s.whatsapp.net' + }) + }) + + it('should not extract mapping from userReceipt when fromMe is false', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '211071956705386@lid', + messages: [ + { + message: { + key: { + remoteJid: '211071956705386@lid', + fromMe: false, // Not from me + id: '3EB052FF8D9D00646C9994' + }, + userReceipt: [ + { + userJid: '5518999991234@s.whatsapp.net' + } + ] + } + } + ] + } + ] + } + + const result = processHistoryMessage(historySync) + + // Should not extract mapping when fromMe is false + expect(result.lidPnMappings).not.toContainEqual({ + lid: '211071956705386@lid', + pn: '5518999991234@s.whatsapp.net' + }) + }) + + it('should not extract mapping from userReceipt when userJid is also a LID', () => { + const historySync: proto.IHistorySync = { + syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP, + conversations: [ + { + id: '211071956705386@lid', + messages: [ + { + message: { + key: { + remoteJid: '211071956705386@lid', + fromMe: true, + id: '3EB052FF8D9D00646C9994' + }, + userReceipt: [ + { + userJid: '152230971891797@lid' // Also a LID, not a PN + } + ] + } + } + ] + } + ] + } + + const result = processHistoryMessage(historySync) + + // Should not create a LID->LID mapping + expect(result.lidPnMappings).toHaveLength(0) + }) + }) })