fix: add pastParticipants to HistorySync with LID normalization and deduplication
Two bugs fixed compared to prior implementation:
1. history.ts — normalize userJid LID→PN
pastParticipants[].userJid may arrive as a LID identifier (e.g. "46802258641027@lid").
The consumer expects a phone number. After building the lidPnMap from conversations
and phoneNumberToLidMappings, each userJid is resolved to its PN equivalent.
If the mapping is not available the original value is preserved (no data loss).
2. event-buffer.ts — deduplicate by groupJid + userJid across chunks
Multiple HistorySync chunks can contain the same group. The previous approach
concatenated blindly, producing duplicate entries. Now a keyed map
{ [groupJid]: IPastParticipant[] } is used so each group appears once and
each participant within a group appears at most once.
Types updated (Events.ts):
- messaging-history.set event includes pastParticipants?: IPastParticipants[]
- BufferedEventData.historySets.pastParticipants typed as keyed map
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,8 @@ export type BaileysEventMap = {
|
||||
chats: Chat[]
|
||||
contacts: Contact[]
|
||||
messages: WAMessage[]
|
||||
/** Past participants for group chats (people who left/were removed). userJid is always a phone number (PN), never a LID. */
|
||||
pastParticipants?: proto.IPastParticipants[] | null
|
||||
isLatest?: boolean
|
||||
progress?: number | null
|
||||
syncType?: proto.HistorySync.HistorySyncType | null
|
||||
@@ -185,6 +187,8 @@ export type BufferedEventData = {
|
||||
chats: { [jid: string]: Chat }
|
||||
contacts: { [jid: string]: Contact }
|
||||
messages: { [uqId: string]: WAMessage }
|
||||
/** Keyed by groupJid for O(1) deduplication across chunks */
|
||||
pastParticipants: { [groupJid: string]: proto.IPastParticipant[] }
|
||||
empty: boolean
|
||||
isLatest: boolean
|
||||
progress?: number | null
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import EventEmitter from 'events'
|
||||
import { proto } from '../../WAProto/index.js'
|
||||
import type {
|
||||
BaileysEvent,
|
||||
BaileysEventEmitter,
|
||||
@@ -890,6 +891,7 @@ const makeBufferData = (): BufferedEventData => {
|
||||
chats: {},
|
||||
messages: {},
|
||||
contacts: {},
|
||||
pastParticipants: {},
|
||||
isLatest: false,
|
||||
empty: true
|
||||
},
|
||||
@@ -967,6 +969,28 @@ function append<E extends BufferableEvent>(
|
||||
}
|
||||
}
|
||||
|
||||
// Merge pastParticipants with deduplication by groupJid and by userJid within each group.
|
||||
// Multiple HistorySync chunks can carry the same group -- we merge rather than concatenate
|
||||
// to avoid duplicate entries in the final event delivered to the consumer.
|
||||
for (const group of (eventData.pastParticipants ?? []) as proto.IPastParticipants[]) {
|
||||
const groupJid = group.groupJid
|
||||
if (!groupJid) continue
|
||||
|
||||
if (!data.historySets.pastParticipants[groupJid]) {
|
||||
data.historySets.pastParticipants[groupJid] = []
|
||||
}
|
||||
|
||||
const existing = data.historySets.pastParticipants[groupJid]
|
||||
const seenJids = new Set(existing.map(p => p.userJid).filter(Boolean))
|
||||
|
||||
for (const participant of (group.pastParticipants ?? []) as proto.IPastParticipant[]) {
|
||||
if (participant.userJid && !seenJids.has(participant.userJid)) {
|
||||
existing.push(participant)
|
||||
seenJids.add(participant.userJid)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data.historySets.empty = false
|
||||
data.historySets.syncType = eventData.syncType
|
||||
data.historySets.progress = eventData.progress
|
||||
@@ -1292,6 +1316,10 @@ function consolidateEvents(data: BufferedEventData) {
|
||||
chats: Object.values(data.historySets.chats),
|
||||
messages: Object.values(data.historySets.messages),
|
||||
contacts: Object.values(data.historySets.contacts),
|
||||
// Convert dedup map back to array. Each entry has groupJid + participants (all unique userJids).
|
||||
pastParticipants: Object.entries(data.historySets.pastParticipants).map(
|
||||
([groupJid, participants]) => ({ groupJid, pastParticipants: participants })
|
||||
),
|
||||
syncType: data.historySets.syncType,
|
||||
progress: data.historySets.progress,
|
||||
isLatest: data.historySets.isLatest,
|
||||
|
||||
@@ -374,10 +374,25 @@ export const processHistoryMessage = (item: proto.IHistorySync, logger?: ILogger
|
||||
// Convert Map back to array for return
|
||||
const lidPnMappings = Array.from(lidPnMap.values())
|
||||
|
||||
// Normalize pastParticipants: resolve LID userJids → PN so the consumer always
|
||||
// receives a phone number, never an opaque LID identifier.
|
||||
// Uses the lidPnMap built above (populated from phoneNumberToLidMappings + conversations).
|
||||
// If a LID cannot be resolved, the original value is kept rather than dropping the participant.
|
||||
const pastParticipants: proto.IPastParticipants[] = (item.pastParticipants ?? []).map(group => ({
|
||||
groupJid: group.groupJid,
|
||||
pastParticipants: (group.pastParticipants ?? []).map(participant => {
|
||||
const userJid = participant.userJid
|
||||
if (!userJid || !isAnyLidUser(userJid)) return participant
|
||||
const mapping = lidPnMap.get(jidNormalizedUser(userJid))
|
||||
return mapping?.pn ? { ...participant, userJid: mapping.pn } : participant
|
||||
})
|
||||
}))
|
||||
|
||||
return {
|
||||
chats,
|
||||
contacts,
|
||||
messages,
|
||||
pastParticipants,
|
||||
lidPnMappings,
|
||||
syncType: item.syncType,
|
||||
progress: item.progress
|
||||
|
||||
Reference in New Issue
Block a user