@areumtecnologia/wasockets 1.1.5 → 2.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,9 @@ const {
13
13
  } = require("../Utils")
14
14
  const {
15
15
  jidDecode,
16
- jidNormalizedUser
16
+ jidNormalizedUser,
17
+ isPnUser,
18
+ isLidUser
17
19
  } = require("../WABinary")
18
20
  const { makeOrderedDictionary } = require("./make-ordered-dictionary")
19
21
  const { ObjectRepository } = require("./object-repository")
@@ -37,6 +39,7 @@ const makeInMemoryStore = (config) => {
37
39
  const chatKey = config.chatKey || waChatKey(true)
38
40
  const labelAssociationKey = config.labelAssociationKey || waLabelAssociationKey
39
41
  const logger = config.logger || DEFAULT_CONNECTION_CONFIG.logger.child({ stream: 'in-mem-store' })
42
+ const maxMessagesPerChat = config.maxMessagesPerChat || 500
40
43
  const KeyedDB = require('@adiwajshing/keyed-db').default
41
44
  const chats = new KeyedDB(chatKey, c => c.id)
42
45
  const messages = {}
@@ -46,6 +49,142 @@ const makeInMemoryStore = (config) => {
46
49
  const state = { connection: 'close' }
47
50
  const labels = new ObjectRepository()
48
51
  const labelAssociations = new KeyedDB(labelAssociationKey, labelAssociationKey.key)
52
+ const lidToId = new Map()
53
+ const pnToId = new Map()
54
+
55
+ const updateIndices = (contact) => {
56
+ if (!contact || !contact.id) return
57
+ const id = contact.id
58
+ const normId = jidNormalizedUser(id)
59
+ const decodedId = jidDecode(id)
60
+ const userId = decodedId?.user
61
+
62
+ if (isLidUser(id)) {
63
+ lidToId.set(id, id)
64
+ lidToId.set(normId, id)
65
+ if (userId) lidToId.set(userId, id)
66
+ } else if (isPnUser(id)) {
67
+ pnToId.set(id, id)
68
+ pnToId.set(normId, id)
69
+ if (userId) pnToId.set(userId, id)
70
+ }
71
+
72
+ if (contact.lid) {
73
+ const normLid = jidNormalizedUser(contact.lid)
74
+ const decodedLid = jidDecode(contact.lid)
75
+ lidToId.set(contact.lid, id)
76
+ lidToId.set(normLid, id)
77
+ if (decodedLid?.user) lidToId.set(decodedLid.user, id)
78
+ }
79
+
80
+ if (contact.phoneNumber) {
81
+ const normPn = jidNormalizedUser(contact.phoneNumber)
82
+ const decodedPn = jidDecode(contact.phoneNumber)
83
+ pnToId.set(contact.phoneNumber, id)
84
+ pnToId.set(normPn, id)
85
+ if (decodedPn?.user) pnToId.set(decodedPn.user, id)
86
+ }
87
+
88
+ // Bridge with socket signalRepository.lidMapping if available
89
+ const lid = contact.lid || (isLidUser(id) ? id : undefined)
90
+ const pn = contact.phoneNumber || (isPnUser(id) ? id : undefined)
91
+ if (lid && pn && isLidUser(lid) && isPnUser(pn)) {
92
+ if (socket?.signalRepository?.lidMapping?.storeLIDPNMappings) {
93
+ socket.signalRepository.lidMapping.storeLIDPNMappings([{ lid, pn }]).catch(err => {
94
+ logger.debug({ err, lid, pn }, 'Error storing LID-PN mapping from contact')
95
+ })
96
+ }
97
+ }
98
+ }
99
+
100
+ const learnMapping = (jidA, jidB) => {
101
+ if (!jidA || !jidB) return
102
+ const isLidA = isLidUser(jidA)
103
+ const isPnA = isPnUser(jidA)
104
+ const isLidB = isLidUser(jidB)
105
+ const isPnB = isPnUser(jidB)
106
+
107
+ const lid = isLidA ? jidA : (isLidB ? jidB : undefined)
108
+ const pn = isPnA ? jidA : (isPnB ? jidB : undefined)
109
+
110
+ if (lid && pn) {
111
+ const normLid = jidNormalizedUser(lid)
112
+ const normPn = jidNormalizedUser(pn)
113
+ const userLid = jidDecode(lid)?.user
114
+ const userPn = jidDecode(pn)?.user
115
+
116
+ lidToId.set(lid, normPn)
117
+ lidToId.set(normLid, normPn)
118
+ if (userLid) lidToId.set(userLid, normPn)
119
+
120
+ pnToId.set(pn, normPn)
121
+ pnToId.set(normPn, normPn)
122
+ if (userPn) pnToId.set(userPn, normPn)
123
+
124
+ if (contacts[normPn]) {
125
+ contacts[normPn].lid = contacts[normPn].lid || normLid
126
+ }
127
+ if (contacts[normLid]) {
128
+ contacts[normLid].phoneNumber = contacts[normLid].phoneNumber || normPn
129
+ }
130
+
131
+ if (socket?.signalRepository?.lidMapping?.storeLIDPNMappings) {
132
+ socket.signalRepository.lidMapping.storeLIDPNMappings([{ lid: normLid, pn: normPn }]).catch(() => {})
133
+ }
134
+ }
135
+ }
136
+
137
+ const getContact = (jid) => {
138
+ if (!jid) return undefined
139
+ if (contacts[jid]) return contacts[jid]
140
+
141
+ const norm = jidNormalizedUser(jid)
142
+ if (contacts[norm]) return contacts[norm]
143
+
144
+ const decoded = jidDecode(jid)
145
+ const user = decoded?.user || (typeof jid === 'string' ? jid.split('@')[0].split(':')[0] : undefined)
146
+
147
+ const idFromLid = lidToId.get(jid) || lidToId.get(norm) || (user ? lidToId.get(user) : undefined)
148
+ if (idFromLid && contacts[idFromLid]) return contacts[idFromLid]
149
+
150
+ const idFromPn = pnToId.get(jid) || pnToId.get(norm) || (user ? pnToId.get(user) : undefined)
151
+ if (idFromPn && contacts[idFromPn]) return contacts[idFromPn]
152
+
153
+ for (const id in contacts) {
154
+ const c = contacts[id]
155
+ if (c.lid === jid || c.lid === norm || (c.lid && jidDecode(c.lid)?.user === user)) {
156
+ return c
157
+ }
158
+ if (c.phoneNumber === jid || c.phoneNumber === norm || (c.phoneNumber && jidDecode(c.phoneNumber)?.user === user)) {
159
+ return c
160
+ }
161
+ if (c.id === jid || c.id === norm || (c.id && jidDecode(c.id)?.user === user)) {
162
+ return c
163
+ }
164
+ }
165
+ return undefined
166
+ }
167
+
168
+ const getChat = (jid) => {
169
+ if (!jid) return undefined
170
+ let chat = chats.get(jid)
171
+ if (chat) return chat
172
+
173
+ const norm = jidNormalizedUser(jid)
174
+ chat = chats.get(norm)
175
+ if (chat) return chat
176
+
177
+ const contact = getContact(jid)
178
+ if (contact) {
179
+ const altJids = [contact.id, contact.phoneNumber, contact.lid].filter(Boolean)
180
+ for (const alt of altJids) {
181
+ chat = chats.get(alt) || chats.get(jidNormalizedUser(alt))
182
+ if (chat) return chat
183
+ }
184
+ }
185
+ return undefined
186
+ }
187
+
49
188
  const assertMessageList = (jid) => {
50
189
  if (!messages[jid]) {
51
190
  messages[jid] = makeMessagesDictionary()
@@ -57,6 +196,7 @@ const makeInMemoryStore = (config) => {
57
196
  for (const contact of newContacts) {
58
197
  oldContacts.delete(contact.id)
59
198
  contacts[contact.id] = Object.assign(contacts[contact.id] || {}, contact)
199
+ updateIndices(contacts[contact.id])
60
200
  }
61
201
  return oldContacts
62
202
  }
@@ -112,8 +252,12 @@ const makeInMemoryStore = (config) => {
112
252
  contact = contacts[update.id]
113
253
  }
114
254
  else {
255
+ contact = getContact(update.id)
256
+ }
257
+ if (!contact) {
115
258
  const contactHashes = await Promise.all(Object.keys(contacts).map(async (contactId) => {
116
- const { user } = jidDecode(contactId)
259
+ const decoded = jidDecode(contactId)
260
+ const user = decoded?.user || ''
117
261
  return [contactId, (await md5(Buffer.from(user + 'WA_ADD_NOTIF', 'utf8'))).toString('base64').slice(0, 3)]
118
262
  }))
119
263
  contact = contacts[contactHashes.find(([, b]) => b === update.id?.[0]) || ''] // find contact by attrs.hash, when user is not saved as a contact
@@ -125,11 +269,12 @@ const makeInMemoryStore = (config) => {
125
269
  else if (update.imgUrl === 'removed') {
126
270
  delete contact.imgUrl
127
271
  }
272
+ Object.assign(contacts[contact.id], update)
273
+ updateIndices(contacts[contact.id])
128
274
  }
129
275
  else {
130
- return logger.debug({ update }, 'got update for non-existant contact')
276
+ logger.debug({ update }, 'got update for non-existant contact')
131
277
  }
132
- Object.assign(contacts[contact.id], contact)
133
278
  }
134
279
  })
135
280
  ev.on('chats.upsert', newChats => {
@@ -137,13 +282,29 @@ const makeInMemoryStore = (config) => {
137
282
  })
138
283
  ev.on('chats.update', updates => {
139
284
  for (let update of updates) {
140
- const result = chats.update(update.id, chat => {
285
+ let result = chats.update(update.id, chat => {
141
286
  if (update.unreadCount > 0) {
142
287
  update = { ...update }
143
288
  update.unreadCount = (chat.unreadCount || 0) + update.unreadCount
144
289
  }
145
290
  Object.assign(chat, update)
146
291
  })
292
+ if (!result) {
293
+ const altContact = getContact(update.id)
294
+ if (altContact) {
295
+ const candidates = [altContact.id, altContact.phoneNumber, altContact.lid].filter(id => id && id !== update.id)
296
+ for (const altId of candidates) {
297
+ result = chats.update(altId, chat => {
298
+ if (update.unreadCount > 0) {
299
+ update = { ...update }
300
+ update.unreadCount = (chat.unreadCount || 0) + update.unreadCount
301
+ }
302
+ Object.assign(chat, update)
303
+ })
304
+ if (result) break
305
+ }
306
+ }
307
+ }
147
308
  if (!result) {
148
309
  logger.debug({ update }, 'got update for non-existant chat')
149
310
  }
@@ -190,14 +351,33 @@ const makeInMemoryStore = (config) => {
190
351
  const jid = jidNormalizedUser(msg.key.remoteJid)
191
352
  const list = assertMessageList(jid)
192
353
  list.upsert(msg, 'append')
354
+
355
+ if (maxMessagesPerChat && list.array.length > maxMessagesPerChat) {
356
+ const excess = list.array.length - maxMessagesPerChat
357
+ const toRemove = list.array.slice(0, excess)
358
+ for (const oldMsg of toRemove) {
359
+ list.remove(oldMsg)
360
+ }
361
+ }
362
+
363
+ // Learn LID mapping from participantAlt / remoteJidAlt
364
+ const alt = msg.key.participantAlt || msg.key.remoteJidAlt
365
+ const primary = msg.key.participant || msg.key.remoteJid
366
+ if (alt && primary) {
367
+ learnMapping(primary, alt)
368
+ }
369
+
193
370
  if (type === 'notify' && !chats.get(jid)) {
194
- ev.emit('chats.upsert', [
195
- {
196
- id: jid,
197
- conversationTimestamp: toNumber(msg.messageTimestamp),
198
- unreadCount: 1
199
- }
200
- ])
371
+ const existingChat = getChat(jid)
372
+ if (!existingChat) {
373
+ ev.emit('chats.upsert', [
374
+ {
375
+ id: jid,
376
+ conversationTimestamp: toNumber(msg.messageTimestamp),
377
+ unreadCount: 1
378
+ }
379
+ ])
380
+ }
201
381
  }
202
382
  }
203
383
  break
@@ -314,30 +494,44 @@ const makeInMemoryStore = (config) => {
314
494
  labels,
315
495
  labelAssociations,
316
496
  bind,
497
+ getContact,
498
+ getChat,
317
499
  /** loads messages from the store, if not found -- uses the legacy connection */
318
500
  loadMessages: async (jid, count, cursor) => {
319
- const list = assertMessageList(jid)
501
+ let list = assertMessageList(jid)
502
+ if (list.array.length === 0) {
503
+ const contact = getContact(jid)
504
+ if (contact) {
505
+ const altJids = [contact.id, contact.phoneNumber, contact.lid].filter(Boolean)
506
+ for (const alt of altJids) {
507
+ if (alt !== jid && messages[alt]?.array.length > 0) {
508
+ list = messages[alt]
509
+ break
510
+ }
511
+ }
512
+ }
513
+ }
320
514
  const mode = !cursor || 'before' in cursor ? 'before' : 'after'
321
515
  const cursorKey = !!cursor ? ('before' in cursor ? cursor.before : cursor.after) : undefined
322
516
  const cursorValue = cursorKey ? list.get(cursorKey.id) : undefined
323
- let messages
517
+ let messagesResult
324
518
  if (list && mode === 'before' && (!cursorKey || cursorValue)) {
325
519
  if (cursorValue) {
326
520
  const msgIdx = list.array.findIndex(m => m.key.id === cursorKey?.id)
327
- messages = list.array.slice(0, msgIdx)
521
+ messagesResult = list.array.slice(0, msgIdx)
328
522
  }
329
523
  else {
330
- messages = list.array
524
+ messagesResult = list.array
331
525
  }
332
- const diff = count - messages.length
526
+ const diff = count - messagesResult.length
333
527
  if (diff < 0) {
334
- messages = messages.slice(-count) // get the last X messages
528
+ messagesResult = messagesResult.slice(-count) // get the last X messages
335
529
  }
336
530
  }
337
531
  else {
338
- messages = []
532
+ messagesResult = []
339
533
  }
340
- return messages
534
+ return messagesResult
341
535
  },
342
536
  /**
343
537
  * Get all available labels for profile
@@ -367,18 +561,44 @@ const makeInMemoryStore = (config) => {
367
561
  .all()
368
562
  return associations.map(({ labelId }) => labelId)
369
563
  },
370
- loadMessage: async (jid, id) => messages[jid]?.get(id),
564
+ loadMessage: async (jid, id) => {
565
+ let msg = messages[jid]?.get(id)
566
+ if (!msg) {
567
+ const contact = getContact(jid)
568
+ if (contact) {
569
+ const altJids = [contact.id, contact.phoneNumber, contact.lid].filter(Boolean)
570
+ for (const alt of altJids) {
571
+ if (alt !== jid && messages[alt]?.get(id)) {
572
+ return messages[alt].get(id)
573
+ }
574
+ }
575
+ }
576
+ }
577
+ return msg
578
+ },
371
579
  mostRecentMessage: async (jid) => {
372
- const message = messages[jid]?.array.slice(-1)[0]
580
+ let message = messages[jid]?.array.slice(-1)[0]
581
+ if (!message) {
582
+ const contact = getContact(jid)
583
+ if (contact) {
584
+ const altJids = [contact.id, contact.phoneNumber, contact.lid].filter(Boolean)
585
+ for (const alt of altJids) {
586
+ if (alt !== jid && messages[alt]?.array.length) {
587
+ return messages[alt].array.slice(-1)[0]
588
+ }
589
+ }
590
+ }
591
+ }
373
592
  return message
374
593
  },
375
594
  fetchImageUrl: async (jid, suki) => {
376
- const contact = contacts[jid]
595
+ const contact = getContact(jid) || contacts[jid]
596
+ const targetJid = contact?.id || jid
377
597
  if (!contact) {
378
- return suki?.profilePictureUrl(jid)
598
+ return suki?.profilePictureUrl(targetJid)
379
599
  }
380
600
  if (typeof contact.imgUrl === 'undefined') {
381
- contact.imgUrl = await suki?.profilePictureUrl(jid)
601
+ contact.imgUrl = await suki?.profilePictureUrl(targetJid)
382
602
  }
383
603
  return contact.imgUrl
384
604
  },
@@ -421,6 +641,55 @@ const makeInMemoryStore = (config) => {
421
641
  const json = JSON.parse(jsonStr)
422
642
  fromJSON(json)
423
643
  }
644
+ },
645
+ pruneMessages: (jid, keepLastN = 100) => {
646
+ if (jid) {
647
+ const list = messages[jid] || messages[jidNormalizedUser(jid)]
648
+ if (list && list.array.length > keepLastN) {
649
+ const excess = list.array.length - keepLastN
650
+ const toRemove = list.array.slice(0, excess)
651
+ for (const oldMsg of toRemove) {
652
+ list.remove(oldMsg)
653
+ }
654
+ return excess
655
+ }
656
+ return 0
657
+ }
658
+ let totalPruned = 0
659
+ for (const id in messages) {
660
+ const list = messages[id]
661
+ if (list && list.array.length > keepLastN) {
662
+ const excess = list.array.length - keepLastN
663
+ const toRemove = list.array.slice(0, excess)
664
+ for (const oldMsg of toRemove) {
665
+ list.remove(oldMsg)
666
+ }
667
+ totalPruned += excess
668
+ }
669
+ }
670
+ return totalPruned
671
+ },
672
+ clear: (chatId) => {
673
+ if (chatId) {
674
+ const norm = jidNormalizedUser(chatId)
675
+ delete messages[chatId]
676
+ delete messages[norm]
677
+ } else {
678
+ for (const id in messages) {
679
+ delete messages[id]
680
+ }
681
+ }
682
+ },
683
+ getMessageCount: (jid) => {
684
+ if (jid) {
685
+ const list = messages[jid] || messages[jidNormalizedUser(jid)]
686
+ return list ? list.array.length : 0
687
+ }
688
+ let total = 0
689
+ for (const id in messages) {
690
+ total += messages[id]?.array?.length || 0
691
+ }
692
+ return total
424
693
  }
425
694
  }
426
695
  }
@@ -0,0 +1,199 @@
1
+ import { proto } from '../../WAProto'
2
+
3
+ export declare const DisconnectReason: {
4
+ readonly connectionClosed: 428
5
+ readonly connectionLost: 408
6
+ readonly connectionReplaced: 440
7
+ readonly timedOut: 408
8
+ readonly loggedOut: 401
9
+ readonly badSession: 500
10
+ readonly restartRequired: 515
11
+ readonly multideviceMismatch: 411
12
+ readonly forbidden: 403
13
+ readonly unavailableService: 503
14
+ }
15
+
16
+ export type DisconnectReasonCode = typeof DisconnectReason[keyof typeof DisconnectReason]
17
+
18
+ export interface AuthenticationCreds {
19
+ noiseKey?: any
20
+ pairingEphemeralKeyPair?: any
21
+ signedIdentityKey?: any
22
+ signedPreKey?: any
23
+ registrationId?: number
24
+ advSecretKey?: string
25
+ processedHistoryMessages?: any[]
26
+ nextPreKeyId?: number
27
+ firstUnuploadedPreKeyId?: number
28
+ accountSyncCounter?: number
29
+ accountSettings?: any
30
+ me?: Contact
31
+ signalIdentities?: any[]
32
+ myAppStateKeyId?: string
33
+ registered?: boolean
34
+ [key: string]: any
35
+ }
36
+
37
+ export interface SignalKeyStore {
38
+ get(type: string, ids: string[]): Promise<Record<string, any>>
39
+ set(data: Record<string, any>): Promise<void>
40
+ clear?(): Promise<void>
41
+ }
42
+
43
+ export interface AuthenticationState {
44
+ creds: AuthenticationCreds
45
+ keys: SignalKeyStore
46
+ }
47
+
48
+ export interface Contact {
49
+ id: string
50
+ name?: string
51
+ notify?: string
52
+ verifiedName?: string
53
+ imgUrl?: string | null
54
+ status?: string
55
+ [key: string]: any
56
+ }
57
+
58
+ export interface GroupParticipant {
59
+ id: string
60
+ admin?: 'admin' | 'superadmin' | null
61
+ [key: string]: any
62
+ }
63
+
64
+ export interface GroupMetadata {
65
+ id: string
66
+ owner?: string
67
+ subject: string
68
+ subjectOwner?: string
69
+ subjectTime?: number
70
+ creation?: number
71
+ desc?: string
72
+ descOwner?: string
73
+ descId?: string
74
+ restrict?: boolean
75
+ announce?: boolean
76
+ size?: number
77
+ participants: GroupParticipant[]
78
+ ephemeralDuration?: number
79
+ inviteCode?: string
80
+ author?: string
81
+ memberAddMode?: 'admin_add' | 'all_member_add'
82
+ approvalMode?: boolean | 'on' | 'off'
83
+ [key: string]: any
84
+ }
85
+
86
+ export interface WAMessageKey {
87
+ remoteJid?: string | null
88
+ fromMe?: boolean | null
89
+ id?: string | null
90
+ participant?: string | null
91
+ }
92
+
93
+ export interface WAMessageContent {
94
+ conversation?: string | null
95
+ extendedTextMessage?: any
96
+ imageMessage?: any
97
+ videoMessage?: any
98
+ audioMessage?: any
99
+ documentMessage?: any
100
+ stickerMessage?: any
101
+ contactMessage?: any
102
+ contactsArrayMessage?: any
103
+ locationMessage?: any
104
+ liveLocationMessage?: any
105
+ pollCreationMessage?: any
106
+ pollCreationMessageV2?: any
107
+ pollCreationMessageV3?: any
108
+ pollUpdateMessage?: any
109
+ reactionMessage?: any
110
+ [key: string]: any
111
+ }
112
+
113
+ export interface WAMessage {
114
+ key: WAMessageKey
115
+ message?: WAMessageContent | null
116
+ messageTimestamp?: number | any
117
+ pushName?: string | null
118
+ status?: number | string | null
119
+ broadcast?: boolean | null
120
+ [key: string]: any
121
+ }
122
+
123
+ export type AnyMessageContent =
124
+ | { text: string; [key: string]: any }
125
+ | { image: any; caption?: string; [key: string]: any }
126
+ | { video: any; caption?: string; [key: string]: any }
127
+ | { audio: any; mimetype?: string; ptt?: boolean; [key: string]: any }
128
+ | { document: any; mimetype: string; fileName?: string; [key: string]: any }
129
+ | { sticker: any; [key: string]: any }
130
+ | { location: { degreesLatitude: number; degreesLongitude: number; [key: string]: any }; [key: string]: any }
131
+ | { contacts: { displayName?: string; contacts: Array<{ vcard: string }> }; [key: string]: any }
132
+ | { poll: { name: string; values: string[]; selectableCount?: number }; [key: string]: any }
133
+ | { react: { text: string; key: WAMessageKey }; [key: string]: any }
134
+ | { edit: WAMessageKey; text?: string; [key: string]: any }
135
+ | { pin: WAMessageKey; type: number; time?: number; [key: string]: any }
136
+ | Record<string, any>
137
+
138
+ export interface MiscMessageGenerationOptions {
139
+ quoted?: WAMessage
140
+ timestamp?: Date | number
141
+ messageId?: string
142
+ cachedGroupMetadata?: (jid: string) => Promise<GroupMetadata | undefined>
143
+ font?: number
144
+ textColor?: string
145
+ backgroundColor?: string
146
+ broadcast?: boolean
147
+ statusJidList?: string[]
148
+ [key: string]: any
149
+ }
150
+
151
+ export interface ConnectionState {
152
+ connection: 'close' | 'open' | 'connecting'
153
+ lastDisconnect?: {
154
+ error?: Error | any
155
+ date?: Date
156
+ }
157
+ isNewLogin?: boolean
158
+ qr?: string
159
+ receivedPendingNotifications?: boolean
160
+ legacy?: any
161
+ isOnline?: boolean
162
+ }
163
+
164
+ export interface WACallEvent {
165
+ id: string
166
+ from: string
167
+ timestamp: number
168
+ status: 'offer' | 'accept' | 'reject' | 'timeout' | 'terminate'
169
+ isVideo?: boolean
170
+ isGroup?: boolean
171
+ [key: string]: any
172
+ }
173
+
174
+ export interface BaileysEventMap {
175
+ 'connection.update': Partial<ConnectionState>
176
+ 'creds.update': Partial<AuthenticationCreds>
177
+ 'messages.upsert': { messages: WAMessage[]; type: 'append' | 'notify'; [key: string]: any }
178
+ 'messages.update': Array<{ key: WAMessageKey; update: Partial<WAMessage> }>
179
+ 'message-receipt.update': Array<{ key: WAMessageKey; receipt: any }>
180
+ 'messages.reaction': Array<{ key: WAMessageKey; reaction: any }>
181
+ 'messages.delete': { keys: WAMessageKey[] } | { jid: string; all: true }
182
+ 'chats.upsert': any[]
183
+ 'chats.update': any[]
184
+ 'chats.delete': string[]
185
+ 'presence.update': { id: string; presences: Record<string, any> }
186
+ 'contacts.upsert': Contact[]
187
+ 'contacts.update': Partial<Contact>[]
188
+ 'group-participants.update': { id: string; author?: string; participants: string[]; action: 'add' | 'remove' | 'demote' | 'promote' }
189
+ 'call': WACallEvent[]
190
+ [key: string]: any
191
+ }
192
+
193
+ export interface BaileysEventEmitter {
194
+ on<T extends keyof BaileysEventMap>(event: T, listener: (arg: BaileysEventMap[T]) => void): this
195
+ off<T extends keyof BaileysEventMap>(event: T, listener: (arg: BaileysEventMap[T]) => void): this
196
+ removeAllListeners<T extends keyof BaileysEventMap>(event?: T): this
197
+ emit<T extends keyof BaileysEventMap>(event: T, arg: BaileysEventMap[T]): boolean
198
+ [key: string]: any
199
+ }
@@ -527,6 +527,38 @@ const chatModificationToAppPatch = (mod, jid) => {
527
527
  }
528
528
  }
529
529
 
530
+ else if ('favorite' in mod) {
531
+ patch = {
532
+ syncAction: {
533
+ favoritesAction: {
534
+ favorites: mod.favorite ? [{ id: jid }] : []
535
+ }
536
+ },
537
+ index: ['favorites', jid],
538
+ type: 'regular_low',
539
+ apiVersion: 1,
540
+ operation: mod.favorite ? OP.SET : OP.REMOVE
541
+ }
542
+ }
543
+
544
+ else if ('note' in mod) {
545
+ patch = {
546
+ syncAction: {
547
+ noteEditAction: {
548
+ type: 1,
549
+ chatJid: jid,
550
+ createdAt: mod.note?.createdAt || Date.now(),
551
+ deleted: !!mod.note?.deleted,
552
+ unstructuredContent: mod.note?.text || ''
553
+ }
554
+ },
555
+ index: ['note_edit', jid],
556
+ type: 'regular_low',
557
+ apiVersion: 1,
558
+ operation: mod.note?.deleted ? OP.REMOVE : OP.SET
559
+ }
560
+ }
561
+
530
562
  else if ('star' in mod) {
531
563
  const key = mod.star.messages[0]
532
564
  patch = {
@@ -814,6 +846,25 @@ const processSyncAction = (syncAction, ev, me, initialSyncOpts, logger) => {
814
846
  }
815
847
  }
816
848
 
849
+ else if (action?.favoritesAction || type === 'favorites') {
850
+ const isFavorite = (action?.favoritesAction?.favorites?.length || 0) > 0
851
+ ev.emit('chats.update', [{
852
+ id,
853
+ favorite: isFavorite,
854
+ conditional: getChatUpdateConditional(id, undefined)
855
+ }])
856
+ }
857
+
858
+ else if (action?.noteEditAction || type === 'note_edit') {
859
+ ev.emit('chats.update', [{
860
+ id: action?.noteEditAction?.chatJid || id,
861
+ notes: action?.noteEditAction?.deleted ? null : {
862
+ text: action?.noteEditAction?.unstructuredContent,
863
+ createdAt: toNumber(action?.noteEditAction?.createdAt)
864
+ }
865
+ }])
866
+ }
867
+
817
868
  else if (action?.labelEditAction) {
818
869
  const { name, color, deleted, predefinedId } = action.labelEditAction
819
870
  ev.emit('labels.edit', {