Files
InfiniteAPI/src/Types/Events.ts
T
Renato Alcara 6c52b01ea7 fix: add pastParticipants to HistorySync with LID normalization and deduplication (#303)
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
2026-03-19 16:44:14 -03:00

221 lines
8.4 KiB
TypeScript

import type { Boom } from '@hapi/boom'
import { proto } from '../../WAProto/index.js'
import type { AuthenticationCreds, LIDMapping } from './Auth'
import type { WACallEvent } from './Call'
import type { Chat, ChatUpdate, PresenceData } from './Chat'
import type { Contact } from './Contact'
import type {
GroupMetadata,
GroupParticipant,
ParticipantAction,
RequestJoinAction,
RequestJoinMethod
} from './GroupMetadata'
import type { Label } from './Label'
import type { LabelAssociation } from './LabelAssociation'
import type { MessageUpsertType, MessageUserReceiptUpdate, WAMessage, WAMessageKey, WAMessageUpdate } from './Message'
import type { ConnectionState } from './State'
// TODO: refactor this mess
export type BaileysEventMap = {
/** connection state has been updated -- WS closed, opened, connecting etc. */
'connection.update': Partial<ConnectionState>
/** credentials updated -- some metadata, keys or something */
'creds.update': Partial<AuthenticationCreds>
/** set chats (history sync), everything is reverse chronologically sorted */
'messaging-history.set': {
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
chunkOrder?: number | null
peerDataRequestSessionId?: string | null
}
/** signals history sync milestones (completion or stall) per sync type */
'messaging-history.status': {
/** which sync phase this status refers to */
syncType: proto.HistorySync.HistorySyncType
/** the status of this sync phase */
status: 'complete' | 'paused'
/**
* progress === 100 was received from the server.
* when false, completion was inferred via timeout (no more chunks arriving).
*/
explicit: boolean
}
/** upsert chats */
'chats.upsert': Chat[]
/** update the given chats */
'chats.update': ChatUpdate[]
'lid-mapping.update': LIDMapping[]
/** delete chats with given ID */
'chats.delete': string[]
/** presence of contact in a chat updated */
'presence.update': { id: string; presences: { [participant: string]: PresenceData } }
'contacts.upsert': Contact[]
'contacts.update': Partial<Contact>[]
'messages.delete': { keys: WAMessageKey[] } | { jid: string; all: true }
'messages.update': WAMessageUpdate[]
'messages.media-update': { key: WAMessageKey; media?: { ciphertext: Uint8Array; iv: Uint8Array }; error?: Boom }[]
/**
* add/update the given messages. If they were received while the connection was online,
* the update will have type: "notify"
* if requestId is provided, then the messages was received from the phone due to it being unavailable
* */
'messages.upsert': { messages: WAMessage[]; type: MessageUpsertType; requestId?: string }
/** message was reacted to. If reaction was removed -- then "reaction.text" will be falsey */
'messages.reaction': { key: WAMessageKey; reaction: proto.IReaction }[]
'message-receipt.update': MessageUserReceiptUpdate[]
'groups.upsert': GroupMetadata[]
'groups.update': Partial<GroupMetadata>[]
/** apply an action to participants in a group */
'group-participants.update': {
id: string
author: string
authorPn?: string
participants: GroupParticipant[]
action: ParticipantAction
}
'group.join-request': {
id: string
author: string
authorPn?: string
participant: string
participantPn?: string
action: RequestJoinAction
method: RequestJoinMethod
}
/* update the labels assigned to a group participant */
'group.member-tag.update': {
groupId: string
participant: string
participantAlt?: string
label: string
messageTimestamp?: number
}
'blocklist.set': { blocklist: string[] }
'blocklist.update': { blocklist: string[]; type: 'add' | 'remove' }
/** Receive an update on a call, including when the call was received, rejected, accepted */
call: WACallEvent[]
'labels.edit': Label
'labels.association': { association: LabelAssociation; type: 'add' | 'remove' }
/** Newsletter-related events */
'newsletter.reaction': {
id: string
server_id: string
reaction: { code?: string; count?: number; removed?: boolean }
}
'newsletter.view': { id: string; server_id: string; count: number }
'newsletter-participants.update': { id: string; author: string; user: string; new_role: string; action: string }
'newsletter-settings.update': { id: string; update: any }
/** Settings and actions sync events */
'chats.lock': { id: string; locked: boolean }
/**
* Emitted when a new WhatsApp Web version is detected.
* The new version will be used on the next reconnection (soft update).
*/
'version.update': {
/** Previous version */
currentVersion: [number, number, number]
/** New version detected */
newVersion: [number, number, number]
/** Whether the update is critical (major version change) */
isCritical: boolean
}
'settings.update':
| { setting: 'unarchiveChats'; value: boolean }
| { setting: 'locale'; value: string }
| { setting: 'disableLinkPreviews'; value: proto.SyncActionValue.IPrivacySettingDisableLinkPreviewsAction }
| { setting: 'timeFormat'; value: proto.SyncActionValue.ITimeFormatAction }
| { setting: 'privacySettingRelayAllCalls'; value: proto.SyncActionValue.IPrivacySettingRelayAllCalls }
| { setting: 'statusPrivacy'; value: proto.SyncActionValue.IStatusPrivacyAction }
| {
setting: 'notificationActivitySetting'
value: proto.SyncActionValue.NotificationActivitySettingAction.NotificationActivitySetting
}
| {
setting: 'channelsPersonalisedRecommendation'
value: proto.SyncActionValue.IPrivacySettingChannelsPersonalisedRecommendationAction
}
/**
* Emitted when a contact's Signal identity key changes.
* This typically indicates the contact reinstalled WhatsApp or switched devices.
* Applications can use this to notify users about the security code change,
* similar to the "Security code changed" notification in the official WhatsApp client.
*/
'identity.changed': {
/** JID of the contact whose identity key changed */
jid: string
/** SHA-256 fingerprint of the previous identity key (hex string) */
previousKeyFingerprint: string | null
/** SHA-256 fingerprint of the new identity key (hex string) */
newKeyFingerprint: string
/** Timestamp when the change was detected */
timestamp: number
/** Whether this is a new contact (true) or an existing contact with changed key (false) */
isNewContact: boolean
}
/**
* Emitted when the session TTL (Time-To-Live) expires after 7 days.
* Applications can listen to this event to perform graceful cleanup,
* flush pending operations, or rotate credentials before the socket closes.
*/
'session.ttl-expired': {
/** Timestamp when the session started (Date.now()) */
startTime: number | undefined
/** Duration of the session in milliseconds */
duration: number
}
}
export type BufferedEventData = {
historySets: {
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
syncType?: proto.HistorySync.HistorySyncType
chunkOrder?: number | null
peerDataRequestSessionId?: string
}
chatUpserts: { [jid: string]: Chat }
chatUpdates: { [jid: string]: ChatUpdate }
chatDeletes: Set<string>
contactUpserts: { [jid: string]: Contact }
contactUpdates: { [jid: string]: Partial<Contact> }
messageUpserts: { [key: string]: { type: MessageUpsertType; message: WAMessage } }
messageUpdates: { [key: string]: WAMessageUpdate }
messageDeletes: { [key: string]: WAMessageKey }
messageReactions: { [key: string]: { key: WAMessageKey; reactions: proto.IReaction[] } }
messageReceipts: { [key: string]: { key: WAMessageKey; userReceipt: proto.IUserReceipt[] } }
groupUpdates: { [jid: string]: Partial<GroupMetadata> }
lidMappings: { [key: string]: LIDMapping }
}
export type BaileysEvent = keyof BaileysEventMap
export interface BaileysEventEmitter {
on<T extends keyof BaileysEventMap>(event: T, listener: (arg: BaileysEventMap[T]) => void): void
off<T extends keyof BaileysEventMap>(event: T, listener: (arg: BaileysEventMap[T]) => void): void
removeAllListeners<T extends keyof BaileysEventMap>(event: T): void
emit<T extends keyof BaileysEventMap>(event: T, arg: BaileysEventMap[T]): boolean
}