6c52b01ea7
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
221 lines
8.4 KiB
TypeScript
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
|
|
}
|