feat(history): comprehensive LID-PN mapping extraction with full JID format support
feat(history): comprehensive LID-PN mapping extraction with full JID format support
This commit is contained in:
+261
-4
@@ -6,9 +6,27 @@ import { WAMessageStubType } from '../Types'
|
||||
import { toNumber } from './generics'
|
||||
import { normalizeMessageContent } from './messages'
|
||||
import { downloadContentFromMessage } from './messages-media'
|
||||
import {
|
||||
isHostedLidUser,
|
||||
isHostedPnUser,
|
||||
isJidBroadcast,
|
||||
isJidGroup,
|
||||
isJidNewsletter,
|
||||
isLidUser,
|
||||
isPnUser,
|
||||
jidDecode,
|
||||
jidNormalizedUser
|
||||
} from '../WABinary/index.js'
|
||||
|
||||
const inflatePromise = promisify(inflate)
|
||||
|
||||
/**
|
||||
* Downloads and decompresses history sync data from WhatsApp servers.
|
||||
*
|
||||
* @param msg - The history sync notification message containing download info
|
||||
* @param options - Request options for the download
|
||||
* @returns Decoded HistorySync protocol buffer
|
||||
*/
|
||||
export const downloadHistory = async (msg: proto.Message.IHistorySyncNotification, options: RequestInit) => {
|
||||
const stream = await downloadContentFromMessage(msg, 'md-msg-hist', { options })
|
||||
const bufferArray: Buffer[] = []
|
||||
@@ -25,15 +43,213 @@ export const downloadHistory = async (msg: proto.Message.IHistorySyncNotificatio
|
||||
return syncData
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a JID represents a person (can have LID-PN mapping).
|
||||
* Excludes groups, broadcasts, newsletters, and bots.
|
||||
*
|
||||
* @param jid - The JID to check
|
||||
* @returns true if the JID can have LID-PN mapping
|
||||
*/
|
||||
export function isPersonJid(jid: string | undefined): boolean {
|
||||
if (!jid) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Groups, broadcasts, and newsletters don't have LID-PN mappings
|
||||
if (isJidGroup(jid) || isJidBroadcast(jid) || isJidNewsletter(jid)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Only person JIDs (LID or PN formats) can have mappings
|
||||
return !!(
|
||||
isLidUser(jid) ||
|
||||
isHostedLidUser(jid) ||
|
||||
isPnUser(jid) ||
|
||||
isHostedPnUser(jid)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts LID-PN mapping from a conversation object.
|
||||
*
|
||||
* WhatsApp uses two identifier systems:
|
||||
* - LID (Logical ID): Format `{number}@lid` or `{number}@hosted.lid`
|
||||
* - PN (Phone Number): Format `{number}@s.whatsapp.net` or `{number}@hosted`
|
||||
*
|
||||
* Conversations may have their ID in either format, with the alternate
|
||||
* format stored in `lidJid` or `pnJid` properties respectively.
|
||||
*
|
||||
* Skips non-person JIDs:
|
||||
* - `@g.us` (groups)
|
||||
* - `@broadcast` (broadcast lists)
|
||||
* - `@newsletter` (channels)
|
||||
*
|
||||
* @param chatId - The conversation ID (may be LID or PN format)
|
||||
* @param lidJid - The LID JID if chat ID is PN format
|
||||
* @param pnJid - The PN JID if chat ID is LID format
|
||||
* @returns LID-PN mapping if extractable, undefined otherwise
|
||||
*
|
||||
* @example
|
||||
* // Chat ID is LID, pnJid contains phone number
|
||||
* extractLidPnFromConversation('123456789@lid', undefined, '5511999999999@s.whatsapp.net')
|
||||
* // Returns: { lid: '123456789@lid', pn: '5511999999999@s.whatsapp.net' }
|
||||
*
|
||||
* @example
|
||||
* // Chat ID is PN, lidJid contains LID
|
||||
* extractLidPnFromConversation('5511999999999@s.whatsapp.net', '123456789@lid', undefined)
|
||||
* // Returns: { lid: '123456789@lid', pn: '5511999999999@s.whatsapp.net' }
|
||||
*
|
||||
* @example
|
||||
* // Newsletter - returns undefined (no mapping)
|
||||
* extractLidPnFromConversation('123456789@newsletter', undefined, undefined)
|
||||
* // Returns: undefined
|
||||
*/
|
||||
export function extractLidPnFromConversation(
|
||||
chatId: string,
|
||||
lidJid: string | undefined | null,
|
||||
pnJid: string | undefined | null
|
||||
): LIDMapping | undefined {
|
||||
// Skip non-person JIDs (groups, broadcasts, newsletters)
|
||||
if (!isPersonJid(chatId)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Check if chat ID is in LID format
|
||||
const chatIsLid = isLidUser(chatId) || isHostedLidUser(chatId)
|
||||
// Check if chat ID is in PN format
|
||||
const chatIsPn = isPnUser(chatId) || isHostedPnUser(chatId)
|
||||
|
||||
if (chatIsLid && pnJid) {
|
||||
// Chat ID is LID, pnJid contains the phone number
|
||||
return {
|
||||
lid: jidNormalizedUser(chatId),
|
||||
pn: jidNormalizedUser(pnJid)
|
||||
}
|
||||
}
|
||||
|
||||
if (chatIsPn && lidJid) {
|
||||
// Chat ID is PN, lidJid contains the LID
|
||||
return {
|
||||
lid: jidNormalizedUser(lidJid),
|
||||
pn: jidNormalizedUser(chatId)
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts LID-PN mapping from a message's alternative JID fields.
|
||||
*
|
||||
* Messages may contain alternate JID formats in:
|
||||
* - `key.remoteJidAlt` - Alternative remote JID format
|
||||
* - `key.participantAlt` - Alternative participant JID format (for groups)
|
||||
*
|
||||
* @param remoteJid - The primary remote JID
|
||||
* @param remoteJidAlt - The alternative remote JID (may be LID or PN)
|
||||
* @param participant - The primary participant JID (for group messages)
|
||||
* @param participantAlt - The alternative participant JID
|
||||
* @returns LID-PN mapping if extractable, undefined otherwise
|
||||
*/
|
||||
export function extractLidPnFromMessage(
|
||||
remoteJid: string | undefined | null,
|
||||
remoteJidAlt: string | undefined | null,
|
||||
participant: string | undefined | null,
|
||||
participantAlt: string | undefined | null
|
||||
): LIDMapping | undefined {
|
||||
// For group messages, use participant fields
|
||||
const primaryJid = participant || remoteJid
|
||||
const altJid = participantAlt || remoteJidAlt
|
||||
|
||||
if (!primaryJid || !altJid) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Skip non-person JIDs
|
||||
if (!isPersonJid(primaryJid) && !isPersonJid(altJid)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const primaryDecoded = jidDecode(primaryJid)
|
||||
const altDecoded = jidDecode(altJid)
|
||||
|
||||
if (!primaryDecoded || !altDecoded) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// Determine which is LID and which is PN
|
||||
const primaryIsLid = primaryDecoded.server === 'lid' || primaryDecoded.server === 'hosted.lid'
|
||||
const altIsLid = altDecoded.server === 'lid' || altDecoded.server === 'hosted.lid'
|
||||
|
||||
if (primaryIsLid && !altIsLid) {
|
||||
// Primary is LID, alt is PN
|
||||
return {
|
||||
lid: jidNormalizedUser(primaryJid),
|
||||
pn: jidNormalizedUser(altJid)
|
||||
}
|
||||
}
|
||||
|
||||
if (!primaryIsLid && altIsLid) {
|
||||
// Primary is PN, alt is LID
|
||||
return {
|
||||
lid: jidNormalizedUser(altJid),
|
||||
pn: jidNormalizedUser(primaryJid)
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a history sync message and extracts chats, contacts, messages,
|
||||
* and LID-PN mappings.
|
||||
*
|
||||
* LID-PN mappings are extracted from three sources:
|
||||
* 1. Top-level `phoneNumberToLidMappings` array in the history sync payload
|
||||
* 2. Individual conversation objects that contain both LID and PN identifiers
|
||||
* (via `lidJid` and `pnJid` properties)
|
||||
* 3. Message objects with alternate JID fields (`remoteJidAlt`, `participantAlt`)
|
||||
*
|
||||
* This multi-source extraction ensures maximum mapping coverage, as WhatsApp may
|
||||
* provide mappings in different locations depending on the sync type and context.
|
||||
*
|
||||
* Skipped JID types (no LID-PN mapping):
|
||||
* - `@g.us` (groups)
|
||||
* - `@broadcast` (broadcast lists)
|
||||
* - `@newsletter` (channels)
|
||||
*
|
||||
* @param item - The history sync protocol buffer to process
|
||||
* @returns Processed data including chats, contacts, messages, and LID-PN mappings
|
||||
*
|
||||
* @see https://github.com/WhiskeySockets/Baileys/issues/2263
|
||||
*/
|
||||
export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
const messages: WAMessage[] = []
|
||||
const contacts: Contact[] = []
|
||||
const chats: Chat[] = []
|
||||
// Extract LID-PN mappings for all sync types
|
||||
const lidPnMappings: LIDMapping[] = []
|
||||
|
||||
// Use Map for O(1) deduplication of LID-PN mappings
|
||||
const lidPnMap = new Map<string, LIDMapping>()
|
||||
|
||||
/**
|
||||
* Adds a LID-PN mapping to the map with deduplication.
|
||||
* Uses LID as key since each LID should map to exactly one PN.
|
||||
*/
|
||||
const addLidPnMapping = (mapping: LIDMapping): void => {
|
||||
// Normalize and validate
|
||||
if (!mapping.lid || !mapping.pn) {
|
||||
return
|
||||
}
|
||||
lidPnMap.set(mapping.lid, mapping)
|
||||
}
|
||||
|
||||
// Source 1: Extract from top-level phoneNumberToLidMappings array
|
||||
for (const m of item.phoneNumberToLidMappings || []) {
|
||||
if (m.lidJid && m.pnJid) {
|
||||
lidPnMappings.push({ lid: m.lidJid, pn: m.pnJid })
|
||||
addLidPnMapping({
|
||||
lid: jidNormalizedUser(m.lidJid),
|
||||
pn: jidNormalizedUser(m.pnJid)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,8 +259,21 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
case proto.HistorySync.HistorySyncType.FULL:
|
||||
case proto.HistorySync.HistorySyncType.ON_DEMAND:
|
||||
for (const chat of item.conversations! as Chat[]) {
|
||||
const chatId = chat.id!
|
||||
|
||||
// Source 2: Extract LID-PN mapping from conversation object
|
||||
// This handles cases where the mapping isn't in phoneNumberToLidMappings
|
||||
const conversationMapping = extractLidPnFromConversation(
|
||||
chatId,
|
||||
chat.lidJid,
|
||||
chat.pnJid
|
||||
)
|
||||
if (conversationMapping) {
|
||||
addLidPnMapping(conversationMapping)
|
||||
}
|
||||
|
||||
contacts.push({
|
||||
id: chat.id!,
|
||||
id: chatId,
|
||||
name: chat.name || undefined,
|
||||
lid: chat.lidJid || undefined,
|
||||
phoneNumber: chat.pnJid || undefined
|
||||
@@ -57,6 +286,18 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
const message = item.message! as WAMessage
|
||||
messages.push(message)
|
||||
|
||||
// Source 3: Extract LID-PN mapping from message's alternative JID fields
|
||||
// Messages may have remoteJidAlt or participantAlt with alternate format
|
||||
const messageMapping = extractLidPnFromMessage(
|
||||
message.key.remoteJid,
|
||||
message.key.remoteJidAlt,
|
||||
message.key.participant,
|
||||
message.key.participantAlt
|
||||
)
|
||||
if (messageMapping) {
|
||||
addLidPnMapping(messageMapping)
|
||||
}
|
||||
|
||||
if (!chat.messages?.length) {
|
||||
// keep only the most recent message in the chat array
|
||||
chat.messages = [{ message }]
|
||||
@@ -90,6 +331,9 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
break
|
||||
}
|
||||
|
||||
// Convert Map back to array for return
|
||||
const lidPnMappings = Array.from(lidPnMap.values())
|
||||
|
||||
return {
|
||||
chats,
|
||||
contacts,
|
||||
@@ -100,6 +344,13 @@ export const processHistoryMessage = (item: proto.IHistorySync) => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads and processes a history sync notification in one step.
|
||||
*
|
||||
* @param msg - The history sync notification message
|
||||
* @param options - Request options for the download
|
||||
* @returns Processed history data
|
||||
*/
|
||||
export const downloadAndProcessHistorySyncNotification = async (
|
||||
msg: proto.Message.IHistorySyncNotification,
|
||||
options: RequestInit
|
||||
@@ -114,6 +365,12 @@ export const downloadAndProcessHistorySyncNotification = async (
|
||||
return processHistoryMessage(historyMsg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the history sync notification from a protocol message.
|
||||
*
|
||||
* @param message - The protocol message to check
|
||||
* @returns The history sync notification if present, undefined otherwise
|
||||
*/
|
||||
export const getHistoryMsg = (message: proto.IMessage) => {
|
||||
const normalizedContent = !!message ? normalizeMessageContent(message) : undefined
|
||||
const anyHistoryMsg = normalizedContent?.protocolMessage?.historySyncNotification!
|
||||
|
||||
@@ -1,5 +1,290 @@
|
||||
import { proto } from '../../../WAProto/index.js'
|
||||
import { processHistoryMessage } from '../../Utils/history'
|
||||
import {
|
||||
processHistoryMessage,
|
||||
extractLidPnFromConversation,
|
||||
extractLidPnFromMessage,
|
||||
isPersonJid
|
||||
} from '../../Utils/history'
|
||||
|
||||
describe('isPersonJid', () => {
|
||||
it('should return true for @s.whatsapp.net format', () => {
|
||||
expect(isPersonJid('5511999999999@s.whatsapp.net')).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for @lid format', () => {
|
||||
expect(isPersonJid('123456789012345@lid')).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for @hosted format', () => {
|
||||
expect(isPersonJid('5511999999999@hosted')).toBe(true)
|
||||
})
|
||||
|
||||
it('should return true for @hosted.lid format', () => {
|
||||
expect(isPersonJid('123456789012345@hosted.lid')).toBe(true)
|
||||
})
|
||||
|
||||
it('should return false for @g.us (groups)', () => {
|
||||
expect(isPersonJid('123456789012345@g.us')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for @broadcast', () => {
|
||||
expect(isPersonJid('status@broadcast')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for @newsletter (channels)', () => {
|
||||
expect(isPersonJid('123456789012345@newsletter')).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for undefined', () => {
|
||||
expect(isPersonJid(undefined)).toBe(false)
|
||||
})
|
||||
|
||||
it('should return false for empty string', () => {
|
||||
expect(isPersonJid('')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractLidPnFromMessage', () => {
|
||||
describe('direct messages', () => {
|
||||
it('should extract mapping when remoteJid is LID and remoteJidAlt is PN', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'123456789012345@lid',
|
||||
'5511999999999@s.whatsapp.net',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should extract mapping when remoteJid is PN and remoteJidAlt is LID', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'5511999999999@s.whatsapp.net',
|
||||
'123456789012345@lid',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('group messages', () => {
|
||||
it('should extract mapping from participant fields in groups', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'123456789012345@g.us', // group JID
|
||||
undefined,
|
||||
'5511999999999@s.whatsapp.net', // participant
|
||||
'123456789012345@lid' // participantAlt
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should prefer participant over remoteJid for mapping', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'5511888888888@s.whatsapp.net', // ignored
|
||||
'987654321098765@lid', // ignored
|
||||
'5511999999999@s.whatsapp.net', // participant used
|
||||
'123456789012345@lid' // participantAlt used
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return undefined when no alt JID is present', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'5511999999999@s.whatsapp.net',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined when both are same type (LID)', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'123456789012345@lid',
|
||||
'987654321098765@lid',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined when both are same type (PN)', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'5511999999999@s.whatsapp.net',
|
||||
'5511888888888@s.whatsapp.net',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for newsletter messages', () => {
|
||||
const result = extractLidPnFromMessage(
|
||||
'123456789012345@newsletter',
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('extractLidPnFromConversation', () => {
|
||||
describe('LID chat with pnJid', () => {
|
||||
it('should extract mapping when chat ID is @lid format with pnJid', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'123456789012345@lid',
|
||||
undefined,
|
||||
'5511999999999@s.whatsapp.net'
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should extract mapping when chat ID is @hosted.lid format with pnJid', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'123456789012345@hosted.lid',
|
||||
undefined,
|
||||
'5511999999999@hosted'
|
||||
)
|
||||
|
||||
// jidNormalizedUser preserves hosted formats (they are distinct servers)
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@hosted.lid',
|
||||
pn: '5511999999999@hosted'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('PN chat with lidJid', () => {
|
||||
it('should extract mapping when chat ID is @s.whatsapp.net format with lidJid', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'5511999999999@s.whatsapp.net',
|
||||
'123456789012345@lid',
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should extract mapping when chat ID is @hosted format with lidJid', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'5511999999999@hosted',
|
||||
'123456789012345@hosted.lid',
|
||||
undefined
|
||||
)
|
||||
|
||||
// jidNormalizedUser preserves hosted formats (they are distinct servers)
|
||||
expect(result).toEqual({
|
||||
lid: '123456789012345@hosted.lid',
|
||||
pn: '5511999999999@hosted'
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('edge cases', () => {
|
||||
it('should return undefined for group chats', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'123456789012345@g.us',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for newsletters (@newsletter)', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'123456789012345@newsletter',
|
||||
'11111111111111@lid',
|
||||
'5511999999999@s.whatsapp.net'
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for broadcast lists (@broadcast)', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'status@broadcast',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined when no alternate JID is available', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'123456789012345@lid',
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined when both lidJid and pnJid are null', () => {
|
||||
const result = extractLidPnFromConversation(
|
||||
'5511999999999@s.whatsapp.net',
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for LID chat with lidJid (no pnJid)', () => {
|
||||
// Edge case: LID chat has lidJid but no pnJid
|
||||
const result = extractLidPnFromConversation(
|
||||
'123456789012345@lid',
|
||||
'987654321098765@lid',
|
||||
undefined
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for PN chat with pnJid (no lidJid)', () => {
|
||||
// Edge case: PN chat has pnJid but no lidJid
|
||||
const result = extractLidPnFromConversation(
|
||||
'5511999999999@s.whatsapp.net',
|
||||
undefined,
|
||||
'5511888888888@s.whatsapp.net'
|
||||
)
|
||||
|
||||
expect(result).toBeUndefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('processHistoryMessage', () => {
|
||||
describe('phoneNumberToLidMappings extraction', () => {
|
||||
@@ -91,4 +376,318 @@ describe('processHistoryMessage', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('LID-PN extraction from conversations', () => {
|
||||
it('should extract LID-PN mapping when chat ID is LID with pnJid', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '11111111111111@lid',
|
||||
name: 'LID User',
|
||||
pnJid: '5511999999999@s.whatsapp.net'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toEqual([
|
||||
{ lid: '11111111111111@lid', pn: '5511999999999@s.whatsapp.net' }
|
||||
])
|
||||
})
|
||||
|
||||
it('should extract LID-PN mapping when chat ID is PN with lidJid', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '5511999999999@s.whatsapp.net',
|
||||
name: 'PN User',
|
||||
lidJid: '11111111111111@lid'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toEqual([
|
||||
{ lid: '11111111111111@lid', pn: '5511999999999@s.whatsapp.net' }
|
||||
])
|
||||
})
|
||||
|
||||
it('should deduplicate mappings from both sources', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
phoneNumberToLidMappings: [
|
||||
{ lidJid: '11111111111111@lid', pnJid: '5511999999999@s.whatsapp.net' }
|
||||
],
|
||||
conversations: [
|
||||
{
|
||||
id: '11111111111111@lid',
|
||||
name: 'Same User',
|
||||
pnJid: '5511999999999@s.whatsapp.net'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
// Should have only 1 mapping (deduplicated)
|
||||
expect(result.lidPnMappings).toHaveLength(1)
|
||||
expect(result.lidPnMappings[0]).toEqual({
|
||||
lid: '11111111111111@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should combine mappings from both sources', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
phoneNumberToLidMappings: [
|
||||
{ lidJid: '11111111111111@lid', pnJid: '5511999999999@s.whatsapp.net' }
|
||||
],
|
||||
conversations: [
|
||||
{
|
||||
id: '22222222222222@lid',
|
||||
name: 'Different User',
|
||||
pnJid: '5511888888888@s.whatsapp.net'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toHaveLength(2)
|
||||
expect(result.lidPnMappings).toContainEqual({
|
||||
lid: '11111111111111@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
expect(result.lidPnMappings).toContainEqual({
|
||||
lid: '22222222222222@lid',
|
||||
pn: '5511888888888@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should not extract mapping from group chats', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '123456789012345@g.us',
|
||||
name: 'Group Chat'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toEqual([])
|
||||
})
|
||||
|
||||
it('should handle hosted LID format', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '11111111111111@hosted.lid',
|
||||
name: 'Hosted LID User',
|
||||
pnJid: '5511999999999@hosted'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
// jidNormalizedUser preserves hosted formats (they are distinct servers)
|
||||
expect(result.lidPnMappings).toEqual([
|
||||
{ lid: '11111111111111@hosted.lid', pn: '5511999999999@hosted' }
|
||||
])
|
||||
})
|
||||
|
||||
it('should extract mappings for all conversation sync types', () => {
|
||||
const syncTypes = [
|
||||
proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
proto.HistorySync.HistorySyncType.RECENT,
|
||||
proto.HistorySync.HistorySyncType.FULL,
|
||||
proto.HistorySync.HistorySyncType.ON_DEMAND
|
||||
]
|
||||
|
||||
for (const syncType of syncTypes) {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType,
|
||||
conversations: [
|
||||
{
|
||||
id: '11111111111111@lid',
|
||||
pnJid: '5511999999999@s.whatsapp.net'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toEqual([
|
||||
{ lid: '11111111111111@lid', pn: '5511999999999@s.whatsapp.net' }
|
||||
])
|
||||
}
|
||||
})
|
||||
|
||||
it('should not extract conversation mappings for PUSH_NAME sync type', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.PUSH_NAME,
|
||||
pushnames: [
|
||||
{ id: '5511999999999@s.whatsapp.net', pushname: 'User Name' }
|
||||
],
|
||||
phoneNumberToLidMappings: [
|
||||
{ lidJid: '11111111111111@lid', pnJid: '5511999999999@s.whatsapp.net' }
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
// Should still extract from phoneNumberToLidMappings
|
||||
expect(result.lidPnMappings).toEqual([
|
||||
{ lid: '11111111111111@lid', pn: '5511999999999@s.whatsapp.net' }
|
||||
])
|
||||
})
|
||||
|
||||
it('should not extract mapping from newsletter conversations', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '123456789012345@newsletter',
|
||||
name: 'News Channel'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toEqual([])
|
||||
})
|
||||
|
||||
it('should not extract mapping from broadcast conversations', () => {
|
||||
const historySync: proto.IHistorySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: 'status@broadcast',
|
||||
name: 'Status'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('LID-PN extraction from messages (remoteJidAlt)', () => {
|
||||
// Note: remoteJidAlt and participantAlt are runtime extensions to IMessageKey
|
||||
// defined in WAMessageKey type. We cast via unknown for test data.
|
||||
it('should extract mapping from message with remoteJidAlt', () => {
|
||||
const historySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '5511999999999@s.whatsapp.net',
|
||||
name: 'User',
|
||||
messages: [
|
||||
{
|
||||
message: {
|
||||
key: {
|
||||
remoteJid: '5511999999999@s.whatsapp.net',
|
||||
remoteJidAlt: '11111111111111@lid',
|
||||
id: 'MSG123',
|
||||
fromMe: false
|
||||
},
|
||||
messageTimestamp: 1234567890
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
} as unknown as proto.IHistorySync
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toContainEqual({
|
||||
lid: '11111111111111@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should extract mapping from group message with participantAlt', () => {
|
||||
const historySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
conversations: [
|
||||
{
|
||||
id: '123456789012345@g.us',
|
||||
name: 'Group Chat',
|
||||
messages: [
|
||||
{
|
||||
message: {
|
||||
key: {
|
||||
remoteJid: '123456789012345@g.us',
|
||||
participant: '5511999999999@s.whatsapp.net',
|
||||
participantAlt: '11111111111111@lid',
|
||||
id: 'MSG456',
|
||||
fromMe: false
|
||||
},
|
||||
messageTimestamp: 1234567890
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
} as unknown as proto.IHistorySync
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
expect(result.lidPnMappings).toContainEqual({
|
||||
lid: '11111111111111@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
|
||||
it('should deduplicate mappings from all three sources', () => {
|
||||
const historySync = {
|
||||
syncType: proto.HistorySync.HistorySyncType.INITIAL_BOOTSTRAP,
|
||||
phoneNumberToLidMappings: [
|
||||
{ lidJid: '11111111111111@lid', pnJid: '5511999999999@s.whatsapp.net' }
|
||||
],
|
||||
conversations: [
|
||||
{
|
||||
id: '11111111111111@lid',
|
||||
name: 'User',
|
||||
pnJid: '5511999999999@s.whatsapp.net',
|
||||
messages: [
|
||||
{
|
||||
message: {
|
||||
key: {
|
||||
remoteJid: '11111111111111@lid',
|
||||
remoteJidAlt: '5511999999999@s.whatsapp.net',
|
||||
id: 'MSG789',
|
||||
fromMe: true
|
||||
},
|
||||
messageTimestamp: 1234567890
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
} as unknown as proto.IHistorySync
|
||||
|
||||
const result = processHistoryMessage(historySync)
|
||||
|
||||
// Should have only 1 mapping (deduplicated across all 3 sources)
|
||||
expect(result.lidPnMappings).toHaveLength(1)
|
||||
expect(result.lidPnMappings[0]).toEqual({
|
||||
lid: '11111111111111@lid',
|
||||
pn: '5511999999999@s.whatsapp.net'
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user