@nexustechpro/baileys 2.0.1 → 2.0.5

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.
Files changed (102) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +924 -1299
  3. package/lib/Defaults/baileys-version.json +6 -2
  4. package/lib/Defaults/index.js +172 -172
  5. package/lib/Signal/libsignal.js +380 -292
  6. package/lib/Signal/lid-mapping.js +264 -171
  7. package/lib/Socket/Client/index.js +2 -2
  8. package/lib/Socket/Client/types.js +10 -10
  9. package/lib/Socket/Client/websocket.js +45 -310
  10. package/lib/Socket/business.js +375 -375
  11. package/lib/Socket/chats.js +909 -963
  12. package/lib/Socket/communities.js +430 -430
  13. package/lib/Socket/groups.js +342 -342
  14. package/lib/Socket/index.js +22 -22
  15. package/lib/Socket/messages-recv.js +777 -743
  16. package/lib/Socket/messages-send.js +667 -393
  17. package/lib/Socket/mex.js +50 -50
  18. package/lib/Socket/newsletter.js +148 -148
  19. package/lib/Socket/nexus-handler.js +75 -261
  20. package/lib/Socket/socket.js +709 -1201
  21. package/lib/Store/index.js +5 -5
  22. package/lib/Store/make-cache-manager-store.js +81 -81
  23. package/lib/Store/make-in-memory-store.js +416 -416
  24. package/lib/Store/make-ordered-dictionary.js +81 -81
  25. package/lib/Store/object-repository.js +30 -30
  26. package/lib/Types/Auth.js +1 -1
  27. package/lib/Types/Bussines.js +1 -1
  28. package/lib/Types/Call.js +1 -1
  29. package/lib/Types/Chat.js +7 -7
  30. package/lib/Types/Contact.js +1 -1
  31. package/lib/Types/Events.js +1 -1
  32. package/lib/Types/GroupMetadata.js +1 -1
  33. package/lib/Types/Label.js +24 -24
  34. package/lib/Types/LabelAssociation.js +6 -6
  35. package/lib/Types/Message.js +10 -10
  36. package/lib/Types/Newsletter.js +28 -28
  37. package/lib/Types/Product.js +1 -1
  38. package/lib/Types/Signal.js +1 -1
  39. package/lib/Types/Socket.js +2 -2
  40. package/lib/Types/State.js +12 -12
  41. package/lib/Types/USync.js +1 -1
  42. package/lib/Types/index.js +25 -25
  43. package/lib/Utils/auth-utils.js +264 -256
  44. package/lib/Utils/baileys-event-stream.js +55 -55
  45. package/lib/Utils/browser-utils.js +27 -27
  46. package/lib/Utils/business.js +228 -230
  47. package/lib/Utils/chat-utils.js +694 -764
  48. package/lib/Utils/crypto.js +109 -135
  49. package/lib/Utils/decode-wa-message.js +310 -314
  50. package/lib/Utils/event-buffer.js +547 -547
  51. package/lib/Utils/generics.js +297 -297
  52. package/lib/Utils/history.js +91 -83
  53. package/lib/Utils/index.js +21 -20
  54. package/lib/Utils/key-store.js +17 -0
  55. package/lib/Utils/link-preview.js +97 -88
  56. package/lib/Utils/logger.js +2 -2
  57. package/lib/Utils/lt-hash.js +47 -47
  58. package/lib/Utils/make-mutex.js +39 -39
  59. package/lib/Utils/message-retry-manager.js +148 -148
  60. package/lib/Utils/messages-media.js +534 -532
  61. package/lib/Utils/messages.js +705 -705
  62. package/lib/Utils/noise-handler.js +255 -255
  63. package/lib/Utils/pre-key-manager.js +105 -105
  64. package/lib/Utils/process-message.js +412 -412
  65. package/lib/Utils/signal.js +160 -158
  66. package/lib/Utils/use-multi-file-auth-state.js +120 -120
  67. package/lib/Utils/validate-connection.js +194 -194
  68. package/lib/WABinary/constants.js +1300 -1300
  69. package/lib/WABinary/decode.js +237 -237
  70. package/lib/WABinary/encode.js +232 -232
  71. package/lib/WABinary/generic-utils.js +252 -211
  72. package/lib/WABinary/index.js +5 -5
  73. package/lib/WABinary/jid-utils.js +279 -95
  74. package/lib/WABinary/types.js +1 -1
  75. package/lib/WAM/BinaryInfo.js +9 -9
  76. package/lib/WAM/constants.js +22852 -22852
  77. package/lib/WAM/encode.js +149 -149
  78. package/lib/WAM/index.js +3 -3
  79. package/lib/WAUSync/Protocols/USyncContactProtocol.js +28 -28
  80. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +53 -53
  81. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +26 -26
  82. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +37 -37
  83. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js +50 -50
  84. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js +28 -28
  85. package/lib/WAUSync/Protocols/index.js +4 -4
  86. package/lib/WAUSync/USyncQuery.js +93 -93
  87. package/lib/WAUSync/USyncUser.js +22 -22
  88. package/lib/WAUSync/index.js +3 -3
  89. package/lib/index.js +66 -66
  90. package/package.json +171 -144
  91. package/lib/Signal/Group/ciphertext-message.js +0 -12
  92. package/lib/Signal/Group/group-session-builder.js +0 -30
  93. package/lib/Signal/Group/group_cipher.js +0 -100
  94. package/lib/Signal/Group/index.js +0 -12
  95. package/lib/Signal/Group/keyhelper.js +0 -18
  96. package/lib/Signal/Group/sender-chain-key.js +0 -26
  97. package/lib/Signal/Group/sender-key-distribution-message.js +0 -63
  98. package/lib/Signal/Group/sender-key-message.js +0 -66
  99. package/lib/Signal/Group/sender-key-name.js +0 -48
  100. package/lib/Signal/Group/sender-key-record.js +0 -41
  101. package/lib/Signal/Group/sender-key-state.js +0 -84
  102. package/lib/Signal/Group/sender-message-key.js +0 -26
@@ -1,547 +1,547 @@
1
- import EventEmitter from "events"
2
- import { WAMessageStatus } from "../Types/index.js"
3
- import { trimUndefined } from "./generics.js"
4
- import { updateMessageWithReaction, updateMessageWithReceipt } from "./messages.js"
5
- import { isRealMessage, shouldIncrementChatUnread } from "./process-message.js"
6
- const BUFFERABLE_EVENT = [
7
- "messaging-history.set",
8
- "chats.upsert",
9
- "chats.update",
10
- "chats.delete",
11
- "contacts.upsert",
12
- "contacts.update",
13
- "messages.upsert",
14
- "messages.update",
15
- "messages.delete",
16
- "messages.reaction",
17
- "message-receipt.update",
18
- "groups.update",
19
- ]
20
- const BUFFERABLE_EVENT_SET = new Set(BUFFERABLE_EVENT)
21
- /**
22
- * The event buffer logically consolidates different events into a single event
23
- * making the data processing more efficient.
24
- */
25
- export const makeEventBuffer = (logger) => {
26
- const ev = new EventEmitter()
27
- const historyCache = new Set()
28
- let data = makeBufferData()
29
- let isBuffering = false
30
- let bufferTimeout = null
31
- let bufferCount = 0
32
- const MAX_HISTORY_CACHE_SIZE = 10000 // Limit the history cache size to prevent memory bloat
33
- const BUFFER_TIMEOUT_MS = 30000 // 30 seconds
34
- // take the generic event and fire it as a baileys event
35
- ev.on("event", (map) => {
36
- for (const event in map) {
37
- ev.emit(event, map[event])
38
- }
39
- })
40
- function buffer() {
41
- if (!isBuffering) {
42
- logger.debug("Event buffer activated")
43
- isBuffering = true
44
- bufferCount++
45
- // Auto-flush after a timeout to prevent infinite buffering
46
- if (bufferTimeout) {
47
- clearTimeout(bufferTimeout)
48
- }
49
- bufferTimeout = setTimeout(() => {
50
- if (isBuffering) {
51
- logger.warn("Buffer timeout reached, auto-flushing")
52
- flush()
53
- }
54
- }, BUFFER_TIMEOUT_MS)
55
- } else {
56
- bufferCount++
57
- }
58
- }
59
- function flush() {
60
- if (!isBuffering) {
61
- return false
62
- }
63
- logger.debug({ bufferCount }, "Flushing event buffer")
64
- isBuffering = false
65
- bufferCount = 0
66
- // Clear timeout
67
- if (bufferTimeout) {
68
- clearTimeout(bufferTimeout)
69
- bufferTimeout = null
70
- }
71
- // Clear history cache if it exceeds the max size
72
- if (historyCache.size > MAX_HISTORY_CACHE_SIZE) {
73
- logger.debug({ cacheSize: historyCache.size }, "Clearing history cache")
74
- historyCache.clear()
75
- }
76
- const newData = makeBufferData()
77
- const chatUpdates = Object.values(data.chatUpdates)
78
- let conditionalChatUpdatesLeft = 0
79
- for (const update of chatUpdates) {
80
- if (update.conditional) {
81
- conditionalChatUpdatesLeft += 1
82
- newData.chatUpdates[update.id] = update
83
- delete data.chatUpdates[update.id]
84
- }
85
- }
86
- const consolidatedData = consolidateEvents(data)
87
- if (Object.keys(consolidatedData).length) {
88
- ev.emit("event", consolidatedData)
89
- }
90
- data = newData
91
- logger.trace({ conditionalChatUpdatesLeft }, "released buffered events")
92
- return true
93
- }
94
- return {
95
- process(handler) {
96
- const listener = (map) => {
97
- handler(map)
98
- }
99
- ev.on("event", listener)
100
- return () => {
101
- ev.off("event", listener)
102
- }
103
- },
104
- emit(event, evData) {
105
- if (isBuffering && BUFFERABLE_EVENT_SET.has(event)) {
106
- append(data, historyCache, event, evData, logger)
107
- return true
108
- }
109
- return ev.emit("event", { [event]: evData })
110
- },
111
- isBuffering() {
112
- return isBuffering
113
- },
114
- buffer,
115
- flush,
116
- createBufferedFunction(work) {
117
- return async (...args) => {
118
- buffer()
119
- try {
120
- const result = await work(...args)
121
- // If this is the only buffer, flush after a small delay
122
- if (bufferCount === 1) {
123
- setTimeout(() => {
124
- if (isBuffering && bufferCount === 1) {
125
- flush()
126
- }
127
- }, 100) // Small delay to allow nested buffers
128
- }
129
- return result
130
- } catch (error) {
131
- throw error
132
- } finally {
133
- bufferCount = Math.max(0, bufferCount - 1)
134
- if (bufferCount === 0) {
135
- // Auto-flush when no other buffers are active
136
- setTimeout(flush, 100)
137
- }
138
- }
139
- }
140
- },
141
- on: (...args) => ev.on(...args),
142
- off: (...args) => ev.off(...args),
143
- listener: (eventName) => ev.listenerCount(eventName),
144
- removeAllListeners: (...args) => ev.removeAllListeners(...args),
145
- }
146
- }
147
- const makeBufferData = () => {
148
- return {
149
- historySets: {
150
- chats: {},
151
- messages: {},
152
- contacts: {},
153
- isLatest: false,
154
- empty: true,
155
- },
156
- chatUpserts: {},
157
- chatUpdates: {},
158
- chatDeletes: new Set(),
159
- contactUpserts: {},
160
- contactUpdates: {},
161
- messageUpserts: {},
162
- messageUpdates: {},
163
- messageReactions: {},
164
- messageDeletes: {},
165
- messageReceipts: {},
166
- groupUpdates: {},
167
- }
168
- }
169
- function append(
170
- data,
171
- historyCache,
172
- event,
173
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
- eventData,
175
- logger,
176
- ) {
177
- switch (event) {
178
- case "messaging-history.set":
179
- for (const chat of eventData.chats) {
180
- const id = chat.id || ""
181
- const existingChat = data.historySets.chats[id]
182
- if (existingChat) {
183
- existingChat.endOfHistoryTransferType = chat.endOfHistoryTransferType
184
- }
185
- if (!existingChat && !historyCache.has(id)) {
186
- data.historySets.chats[id] = chat
187
- historyCache.add(id)
188
- absorbingChatUpdate(chat)
189
- }
190
- }
191
- for (const contact of eventData.contacts) {
192
- const existingContact = data.historySets.contacts[contact.id]
193
- if (existingContact) {
194
- Object.assign(existingContact, trimUndefined(contact))
195
- } else {
196
- const historyContactId = `c:${contact.id}`
197
- const hasAnyName = contact.notify || contact.name || contact.verifiedName
198
- if (!historyCache.has(historyContactId) || hasAnyName) {
199
- data.historySets.contacts[contact.id] = contact
200
- historyCache.add(historyContactId)
201
- }
202
- }
203
- }
204
- for (const message of eventData.messages) {
205
- const key = stringifyMessageKey(message.key)
206
- const existingMsg = data.historySets.messages[key]
207
- if (!existingMsg && !historyCache.has(key)) {
208
- data.historySets.messages[key] = message
209
- historyCache.add(key)
210
- }
211
- }
212
- data.historySets.empty = false
213
- data.historySets.syncType = eventData.syncType
214
- data.historySets.progress = eventData.progress
215
- data.historySets.peerDataRequestSessionId = eventData.peerDataRequestSessionId
216
- data.historySets.isLatest = eventData.isLatest || data.historySets.isLatest
217
- break
218
- case "chats.upsert":
219
- for (const chat of eventData) {
220
- const id = chat.id || ""
221
- let upsert = data.chatUpserts[id]
222
- if (id && !upsert) {
223
- upsert = data.historySets.chats[id]
224
- if (upsert) {
225
- logger.debug({ chatId: id }, "absorbed chat upsert in chat set")
226
- }
227
- }
228
- if (upsert) {
229
- upsert = concatChats(upsert, chat)
230
- } else {
231
- upsert = chat
232
- data.chatUpserts[id] = upsert
233
- }
234
- absorbingChatUpdate(upsert)
235
- if (data.chatDeletes.has(id)) {
236
- data.chatDeletes.delete(id)
237
- }
238
- }
239
- break
240
- case "chats.update":
241
- for (const update of eventData) {
242
- const chatId = update.id
243
- const conditionMatches = update.conditional ? update.conditional(data) : true
244
- if (conditionMatches) {
245
- delete update.conditional
246
- // if there is an existing upsert, merge the update into it
247
- const upsert = data.historySets.chats[chatId] || data.chatUpserts[chatId]
248
- if (upsert) {
249
- concatChats(upsert, update)
250
- } else {
251
- // merge the update into the existing update
252
- const chatUpdate = data.chatUpdates[chatId] || {}
253
- data.chatUpdates[chatId] = concatChats(chatUpdate, update)
254
- }
255
- } else if (conditionMatches === undefined) {
256
- // condition yet to be fulfilled
257
- data.chatUpdates[chatId] = update
258
- }
259
- // otherwise -- condition not met, update is invalid
260
- // if the chat has been updated
261
- // ignore any existing chat delete
262
- if (data.chatDeletes.has(chatId)) {
263
- data.chatDeletes.delete(chatId)
264
- }
265
- }
266
- break
267
- case "chats.delete":
268
- for (const chatId of eventData) {
269
- if (!data.chatDeletes.has(chatId)) {
270
- data.chatDeletes.add(chatId)
271
- }
272
- // remove any prior updates & upserts
273
- if (data.chatUpdates[chatId]) {
274
- delete data.chatUpdates[chatId]
275
- }
276
- if (data.chatUpserts[chatId]) {
277
- delete data.chatUpserts[chatId]
278
- }
279
- if (data.historySets.chats[chatId]) {
280
- delete data.historySets.chats[chatId]
281
- }
282
- }
283
- break
284
- case "contacts.upsert":
285
- for (const contact of eventData) {
286
- let upsert = data.contactUpserts[contact.id]
287
- if (!upsert) {
288
- upsert = data.historySets.contacts[contact.id]
289
- if (upsert) {
290
- logger.debug({ contactId: contact.id }, "absorbed contact upsert in contact set")
291
- }
292
- }
293
- if (upsert) {
294
- upsert = Object.assign(upsert, trimUndefined(contact))
295
- } else {
296
- upsert = contact
297
- data.contactUpserts[contact.id] = upsert
298
- }
299
- if (data.contactUpdates[contact.id]) {
300
- upsert = Object.assign(data.contactUpdates[contact.id], trimUndefined(contact))
301
- delete data.contactUpdates[contact.id]
302
- }
303
- }
304
- break
305
- case "contacts.update":
306
- const contactUpdates = eventData
307
- for (const update of contactUpdates) {
308
- const id = update.id
309
- // merge into prior upsert
310
- const upsert = data.historySets.contacts[id] || data.contactUpserts[id]
311
- if (upsert) {
312
- Object.assign(upsert, update)
313
- } else {
314
- // merge into prior update
315
- const contactUpdate = data.contactUpdates[id] || {}
316
- data.contactUpdates[id] = Object.assign(contactUpdate, update)
317
- }
318
- }
319
- break
320
- case "messages.upsert":
321
- const { messages, type } = eventData
322
- for (const message of messages) {
323
- const key = stringifyMessageKey(message.key)
324
- let existing = data.messageUpserts[key]?.message
325
- if (!existing) {
326
- existing = data.historySets.messages[key]
327
- if (existing) {
328
- logger.debug({ messageId: key }, "absorbed message upsert in message set")
329
- }
330
- }
331
- if (existing) {
332
- message.messageTimestamp = existing.messageTimestamp
333
- }
334
- if (data.messageUpdates[key]) {
335
- logger.debug("absorbed prior message update in message upsert")
336
- Object.assign(message, data.messageUpdates[key].update)
337
- delete data.messageUpdates[key]
338
- }
339
- if (data.historySets.messages[key]) {
340
- data.historySets.messages[key] = message
341
- } else {
342
- data.messageUpserts[key] = {
343
- message,
344
- type: type === "notify" || data.messageUpserts[key]?.type === "notify" ? "notify" : type,
345
- }
346
- }
347
- }
348
- break
349
- case "messages.update":
350
- const msgUpdates = eventData
351
- for (const { key, update } of msgUpdates) {
352
- const keyStr = stringifyMessageKey(key)
353
- const existing = data.historySets.messages[keyStr] || data.messageUpserts[keyStr]?.message
354
- if (existing) {
355
- Object.assign(existing, update)
356
- // if the message was received & read by us
357
- // the chat counter must have been incremented
358
- // so we need to decrement it
359
- if (update.status === WAMessageStatus.READ && !key.fromMe) {
360
- decrementChatReadCounterIfMsgDidUnread(existing)
361
- }
362
- } else {
363
- const msgUpdate = data.messageUpdates[keyStr] || { key, update: {} }
364
- Object.assign(msgUpdate.update, update)
365
- data.messageUpdates[keyStr] = msgUpdate
366
- }
367
- }
368
- break
369
- case "messages.delete":
370
- const deleteData = eventData
371
- if ("keys" in deleteData) {
372
- const { keys } = deleteData
373
- for (const key of keys) {
374
- const keyStr = stringifyMessageKey(key)
375
- if (!data.messageDeletes[keyStr]) {
376
- data.messageDeletes[keyStr] = key
377
- }
378
- if (data.messageUpserts[keyStr]) {
379
- delete data.messageUpserts[keyStr]
380
- }
381
- if (data.messageUpdates[keyStr]) {
382
- delete data.messageUpdates[keyStr]
383
- }
384
- }
385
- } else {
386
- // TODO: add support
387
- }
388
- break
389
- case "messages.reaction":
390
- const reactions = eventData
391
- for (const { key, reaction } of reactions) {
392
- const keyStr = stringifyMessageKey(key)
393
- const existing = data.messageUpserts[keyStr]
394
- if (existing) {
395
- updateMessageWithReaction(existing.message, reaction)
396
- } else {
397
- data.messageReactions[keyStr] = data.messageReactions[keyStr] || { key, reactions: [] }
398
- updateMessageWithReaction(data.messageReactions[keyStr], reaction)
399
- }
400
- }
401
- break
402
- case "message-receipt.update":
403
- const receipts = eventData
404
- for (const { key, receipt } of receipts) {
405
- const keyStr = stringifyMessageKey(key)
406
- const existing = data.messageUpserts[keyStr]
407
- if (existing) {
408
- updateMessageWithReceipt(existing.message, receipt)
409
- } else {
410
- data.messageReceipts[keyStr] = data.messageReceipts[keyStr] || { key, userReceipt: [] }
411
- updateMessageWithReceipt(data.messageReceipts[keyStr], receipt)
412
- }
413
- }
414
- break
415
- case "groups.update":
416
- const groupUpdates = eventData
417
- for (const update of groupUpdates) {
418
- const id = update.id
419
- const groupUpdate = data.groupUpdates[id] || {}
420
- if (!data.groupUpdates[id]) {
421
- data.groupUpdates[id] = Object.assign(groupUpdate, update)
422
- }
423
- }
424
- break
425
- default:
426
- throw new Error(`"${event}" cannot be buffered`)
427
- }
428
- function absorbingChatUpdate(existing) {
429
- const chatId = existing.id || ""
430
- const update = data.chatUpdates[chatId]
431
- if (update) {
432
- const conditionMatches = update.conditional ? update.conditional(data) : true
433
- if (conditionMatches) {
434
- delete update.conditional
435
- logger.debug({ chatId }, "absorbed chat update in existing chat")
436
- Object.assign(existing, concatChats(update, existing))
437
- delete data.chatUpdates[chatId]
438
- } else if (conditionMatches === false) {
439
- logger.debug({ chatId }, "chat update condition fail, removing")
440
- delete data.chatUpdates[chatId]
441
- }
442
- }
443
- }
444
- function decrementChatReadCounterIfMsgDidUnread(message) {
445
- // decrement chat unread counter
446
- // if the message has already been marked read by us
447
- const chatId = message.key.remoteJid
448
- const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId]
449
- if (
450
- isRealMessage(message) &&
451
- shouldIncrementChatUnread(message) &&
452
- typeof chat?.unreadCount === "number" &&
453
- chat.unreadCount > 0
454
- ) {
455
- logger.debug({ chatId: chat.id }, "decrementing chat counter")
456
- chat.unreadCount -= 1
457
- if (chat.unreadCount === 0) {
458
- delete chat.unreadCount
459
- }
460
- }
461
- }
462
- }
463
- function consolidateEvents(data) {
464
- const map = {}
465
- if (!data.historySets.empty) {
466
- map["messaging-history.set"] = {
467
- chats: Object.values(data.historySets.chats),
468
- messages: Object.values(data.historySets.messages),
469
- contacts: Object.values(data.historySets.contacts),
470
- syncType: data.historySets.syncType,
471
- progress: data.historySets.progress,
472
- isLatest: data.historySets.isLatest,
473
- peerDataRequestSessionId: data.historySets.peerDataRequestSessionId,
474
- }
475
- }
476
- const chatUpsertList = Object.values(data.chatUpserts)
477
- if (chatUpsertList.length) {
478
- map["chats.upsert"] = chatUpsertList
479
- }
480
- const chatUpdateList = Object.values(data.chatUpdates)
481
- if (chatUpdateList.length) {
482
- map["chats.update"] = chatUpdateList
483
- }
484
- const chatDeleteList = Array.from(data.chatDeletes)
485
- if (chatDeleteList.length) {
486
- map["chats.delete"] = chatDeleteList
487
- }
488
- const messageUpsertList = Object.values(data.messageUpserts)
489
- if (messageUpsertList.length) {
490
- const type = messageUpsertList[0].type
491
- map["messages.upsert"] = {
492
- messages: messageUpsertList.map((m) => m.message),
493
- type,
494
- }
495
- }
496
- const messageUpdateList = Object.values(data.messageUpdates)
497
- if (messageUpdateList.length) {
498
- map["messages.update"] = messageUpdateList
499
- }
500
- const messageDeleteList = Object.values(data.messageDeletes)
501
- if (messageDeleteList.length) {
502
- map["messages.delete"] = { keys: messageDeleteList }
503
- }
504
- const messageReactionList = Object.values(data.messageReactions).flatMap(({ key, reactions }) =>
505
- reactions.flatMap((reaction) => ({ key, reaction })),
506
- )
507
- if (messageReactionList.length) {
508
- map["messages.reaction"] = messageReactionList
509
- }
510
- const messageReceiptList = Object.values(data.messageReceipts).flatMap(({ key, userReceipt }) =>
511
- userReceipt.flatMap((receipt) => ({ key, receipt })),
512
- )
513
- if (messageReceiptList.length) {
514
- map["message-receipt.update"] = messageReceiptList
515
- }
516
- const contactUpsertList = Object.values(data.contactUpserts)
517
- if (contactUpsertList.length) {
518
- map["contacts.upsert"] = contactUpsertList
519
- }
520
- const contactUpdateList = Object.values(data.contactUpdates)
521
- if (contactUpdateList.length) {
522
- map["contacts.update"] = contactUpdateList
523
- }
524
- const groupUpdateList = Object.values(data.groupUpdates)
525
- if (groupUpdateList.length) {
526
- map["groups.update"] = groupUpdateList
527
- }
528
- return map
529
- }
530
- function concatChats(a, b) {
531
- if (
532
- b.unreadCount === null && // neutralize unread counter
533
- a.unreadCount < 0
534
- ) {
535
- a.unreadCount = undefined
536
- b.unreadCount = undefined
537
- }
538
- if (typeof a.unreadCount === "number" && typeof b.unreadCount === "number") {
539
- b = { ...b }
540
- if (b.unreadCount >= 0) {
541
- b.unreadCount = Math.max(b.unreadCount, 0) + Math.max(a.unreadCount, 0)
542
- }
543
- }
544
- return Object.assign(a, b)
545
- }
546
- const stringifyMessageKey = (key) => `${key.remoteJid},${key.id},${key.fromMe ? "1" : "0"}`
547
- //# sourceMappingURL=event-buffer.js.map
1
+ import EventEmitter from "events"
2
+ import { WAMessageStatus } from "../Types/index.js"
3
+ import { trimUndefined } from "./generics.js"
4
+ import { updateMessageWithReaction, updateMessageWithReceipt } from "./messages.js"
5
+ import { isRealMessage, shouldIncrementChatUnread } from "./process-message.js"
6
+ const BUFFERABLE_EVENT = [
7
+ "messaging-history.set",
8
+ "chats.upsert",
9
+ "chats.update",
10
+ "chats.delete",
11
+ "contacts.upsert",
12
+ "contacts.update",
13
+ "messages.upsert",
14
+ "messages.update",
15
+ "messages.delete",
16
+ "messages.reaction",
17
+ "message-receipt.update",
18
+ "groups.update",
19
+ ]
20
+ const BUFFERABLE_EVENT_SET = new Set(BUFFERABLE_EVENT)
21
+ /**
22
+ * The event buffer logically consolidates different events into a single event
23
+ * making the data processing more efficient.
24
+ */
25
+ export const makeEventBuffer = (logger) => {
26
+ const ev = new EventEmitter()
27
+ const historyCache = new Set()
28
+ let data = makeBufferData()
29
+ let isBuffering = false
30
+ let bufferTimeout = null
31
+ let bufferCount = 0
32
+ const MAX_HISTORY_CACHE_SIZE = 10000 // Limit the history cache size to prevent memory bloat
33
+ const BUFFER_TIMEOUT_MS = 30000 // 30 seconds
34
+ // take the generic event and fire it as a baileys event
35
+ ev.on("event", (map) => {
36
+ for (const event in map) {
37
+ ev.emit(event, map[event])
38
+ }
39
+ })
40
+ function buffer() {
41
+ if (!isBuffering) {
42
+ logger.debug("Event buffer activated")
43
+ isBuffering = true
44
+ bufferCount++
45
+ // Auto-flush after a timeout to prevent infinite buffering
46
+ if (bufferTimeout) {
47
+ clearTimeout(bufferTimeout)
48
+ }
49
+ bufferTimeout = setTimeout(() => {
50
+ if (isBuffering) {
51
+ logger.warn("Buffer timeout reached, auto-flushing")
52
+ flush()
53
+ }
54
+ }, BUFFER_TIMEOUT_MS)
55
+ } else {
56
+ bufferCount++
57
+ }
58
+ }
59
+ function flush() {
60
+ if (!isBuffering) {
61
+ return false
62
+ }
63
+ logger.debug({ bufferCount }, "Flushing event buffer")
64
+ isBuffering = false
65
+ bufferCount = 0
66
+ // Clear timeout
67
+ if (bufferTimeout) {
68
+ clearTimeout(bufferTimeout)
69
+ bufferTimeout = null
70
+ }
71
+ // Clear history cache if it exceeds the max size
72
+ if (historyCache.size > MAX_HISTORY_CACHE_SIZE) {
73
+ logger.debug({ cacheSize: historyCache.size }, "Clearing history cache")
74
+ historyCache.clear()
75
+ }
76
+ const newData = makeBufferData()
77
+ const chatUpdates = Object.values(data.chatUpdates)
78
+ let conditionalChatUpdatesLeft = 0
79
+ for (const update of chatUpdates) {
80
+ if (update.conditional) {
81
+ conditionalChatUpdatesLeft += 1
82
+ newData.chatUpdates[update.id] = update
83
+ delete data.chatUpdates[update.id]
84
+ }
85
+ }
86
+ const consolidatedData = consolidateEvents(data)
87
+ if (Object.keys(consolidatedData).length) {
88
+ ev.emit("event", consolidatedData)
89
+ }
90
+ data = newData
91
+ logger.trace({ conditionalChatUpdatesLeft }, "released buffered events")
92
+ return true
93
+ }
94
+ return {
95
+ process(handler) {
96
+ const listener = (map) => {
97
+ handler(map)
98
+ }
99
+ ev.on("event", listener)
100
+ return () => {
101
+ ev.off("event", listener)
102
+ }
103
+ },
104
+ emit(event, evData) {
105
+ if (isBuffering && BUFFERABLE_EVENT_SET.has(event)) {
106
+ append(data, historyCache, event, evData, logger)
107
+ return true
108
+ }
109
+ return ev.emit("event", { [event]: evData })
110
+ },
111
+ isBuffering() {
112
+ return isBuffering
113
+ },
114
+ buffer,
115
+ flush,
116
+ createBufferedFunction(work) {
117
+ return async (...args) => {
118
+ buffer()
119
+ try {
120
+ const result = await work(...args)
121
+ // If this is the only buffer, flush after a small delay
122
+ if (bufferCount === 1) {
123
+ setTimeout(() => {
124
+ if (isBuffering && bufferCount === 1) {
125
+ flush()
126
+ }
127
+ }, 100) // Small delay to allow nested buffers
128
+ }
129
+ return result
130
+ } catch (error) {
131
+ throw error
132
+ } finally {
133
+ bufferCount = Math.max(0, bufferCount - 1)
134
+ if (bufferCount === 0) {
135
+ // Auto-flush when no other buffers are active
136
+ setTimeout(flush, 100)
137
+ }
138
+ }
139
+ }
140
+ },
141
+ on: (...args) => ev.on(...args),
142
+ off: (...args) => ev.off(...args),
143
+ listener: (eventName) => ev.listenerCount(eventName),
144
+ removeAllListeners: (...args) => ev.removeAllListeners(...args),
145
+ }
146
+ }
147
+ const makeBufferData = () => {
148
+ return {
149
+ historySets: {
150
+ chats: {},
151
+ messages: {},
152
+ contacts: {},
153
+ isLatest: false,
154
+ empty: true,
155
+ },
156
+ chatUpserts: {},
157
+ chatUpdates: {},
158
+ chatDeletes: new Set(),
159
+ contactUpserts: {},
160
+ contactUpdates: {},
161
+ messageUpserts: {},
162
+ messageUpdates: {},
163
+ messageReactions: {},
164
+ messageDeletes: {},
165
+ messageReceipts: {},
166
+ groupUpdates: {},
167
+ }
168
+ }
169
+ function append(
170
+ data,
171
+ historyCache,
172
+ event,
173
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
+ eventData,
175
+ logger,
176
+ ) {
177
+ switch (event) {
178
+ case "messaging-history.set":
179
+ for (const chat of eventData.chats) {
180
+ const id = chat.id || ""
181
+ const existingChat = data.historySets.chats[id]
182
+ if (existingChat) {
183
+ existingChat.endOfHistoryTransferType = chat.endOfHistoryTransferType
184
+ }
185
+ if (!existingChat && !historyCache.has(id)) {
186
+ data.historySets.chats[id] = chat
187
+ historyCache.add(id)
188
+ absorbingChatUpdate(chat)
189
+ }
190
+ }
191
+ for (const contact of eventData.contacts) {
192
+ const existingContact = data.historySets.contacts[contact.id]
193
+ if (existingContact) {
194
+ Object.assign(existingContact, trimUndefined(contact))
195
+ } else {
196
+ const historyContactId = `c:${contact.id}`
197
+ const hasAnyName = contact.notify || contact.name || contact.verifiedName
198
+ if (!historyCache.has(historyContactId) || hasAnyName) {
199
+ data.historySets.contacts[contact.id] = contact
200
+ historyCache.add(historyContactId)
201
+ }
202
+ }
203
+ }
204
+ for (const message of eventData.messages) {
205
+ const key = stringifyMessageKey(message.key)
206
+ const existingMsg = data.historySets.messages[key]
207
+ if (!existingMsg && !historyCache.has(key)) {
208
+ data.historySets.messages[key] = message
209
+ historyCache.add(key)
210
+ }
211
+ }
212
+ data.historySets.empty = false
213
+ data.historySets.syncType = eventData.syncType
214
+ data.historySets.progress = eventData.progress
215
+ data.historySets.peerDataRequestSessionId = eventData.peerDataRequestSessionId
216
+ data.historySets.isLatest = eventData.isLatest || data.historySets.isLatest
217
+ break
218
+ case "chats.upsert":
219
+ for (const chat of eventData) {
220
+ const id = chat.id || ""
221
+ let upsert = data.chatUpserts[id]
222
+ if (id && !upsert) {
223
+ upsert = data.historySets.chats[id]
224
+ if (upsert) {
225
+ logger.debug({ chatId: id }, "absorbed chat upsert in chat set")
226
+ }
227
+ }
228
+ if (upsert) {
229
+ upsert = concatChats(upsert, chat)
230
+ } else {
231
+ upsert = chat
232
+ data.chatUpserts[id] = upsert
233
+ }
234
+ absorbingChatUpdate(upsert)
235
+ if (data.chatDeletes.has(id)) {
236
+ data.chatDeletes.delete(id)
237
+ }
238
+ }
239
+ break
240
+ case "chats.update":
241
+ for (const update of eventData) {
242
+ const chatId = update.id
243
+ const conditionMatches = update.conditional ? update.conditional(data) : true
244
+ if (conditionMatches) {
245
+ delete update.conditional
246
+ // if there is an existing upsert, merge the update into it
247
+ const upsert = data.historySets.chats[chatId] || data.chatUpserts[chatId]
248
+ if (upsert) {
249
+ concatChats(upsert, update)
250
+ } else {
251
+ // merge the update into the existing update
252
+ const chatUpdate = data.chatUpdates[chatId] || {}
253
+ data.chatUpdates[chatId] = concatChats(chatUpdate, update)
254
+ }
255
+ } else if (conditionMatches === undefined) {
256
+ // condition yet to be fulfilled
257
+ data.chatUpdates[chatId] = update
258
+ }
259
+ // otherwise -- condition not met, update is invalid
260
+ // if the chat has been updated
261
+ // ignore any existing chat delete
262
+ if (data.chatDeletes.has(chatId)) {
263
+ data.chatDeletes.delete(chatId)
264
+ }
265
+ }
266
+ break
267
+ case "chats.delete":
268
+ for (const chatId of eventData) {
269
+ if (!data.chatDeletes.has(chatId)) {
270
+ data.chatDeletes.add(chatId)
271
+ }
272
+ // remove any prior updates & upserts
273
+ if (data.chatUpdates[chatId]) {
274
+ delete data.chatUpdates[chatId]
275
+ }
276
+ if (data.chatUpserts[chatId]) {
277
+ delete data.chatUpserts[chatId]
278
+ }
279
+ if (data.historySets.chats[chatId]) {
280
+ delete data.historySets.chats[chatId]
281
+ }
282
+ }
283
+ break
284
+ case "contacts.upsert":
285
+ for (const contact of eventData) {
286
+ let upsert = data.contactUpserts[contact.id]
287
+ if (!upsert) {
288
+ upsert = data.historySets.contacts[contact.id]
289
+ if (upsert) {
290
+ logger.debug({ contactId: contact.id }, "absorbed contact upsert in contact set")
291
+ }
292
+ }
293
+ if (upsert) {
294
+ upsert = Object.assign(upsert, trimUndefined(contact))
295
+ } else {
296
+ upsert = contact
297
+ data.contactUpserts[contact.id] = upsert
298
+ }
299
+ if (data.contactUpdates[contact.id]) {
300
+ upsert = Object.assign(data.contactUpdates[contact.id], trimUndefined(contact))
301
+ delete data.contactUpdates[contact.id]
302
+ }
303
+ }
304
+ break
305
+ case "contacts.update":
306
+ const contactUpdates = eventData
307
+ for (const update of contactUpdates) {
308
+ const id = update.id
309
+ // merge into prior upsert
310
+ const upsert = data.historySets.contacts[id] || data.contactUpserts[id]
311
+ if (upsert) {
312
+ Object.assign(upsert, update)
313
+ } else {
314
+ // merge into prior update
315
+ const contactUpdate = data.contactUpdates[id] || {}
316
+ data.contactUpdates[id] = Object.assign(contactUpdate, update)
317
+ }
318
+ }
319
+ break
320
+ case "messages.upsert":
321
+ const { messages, type } = eventData
322
+ for (const message of messages) {
323
+ const key = stringifyMessageKey(message.key)
324
+ let existing = data.messageUpserts[key]?.message
325
+ if (!existing) {
326
+ existing = data.historySets.messages[key]
327
+ if (existing) {
328
+ logger.debug({ messageId: key }, "absorbed message upsert in message set")
329
+ }
330
+ }
331
+ if (existing) {
332
+ message.messageTimestamp = existing.messageTimestamp
333
+ }
334
+ if (data.messageUpdates[key]) {
335
+ logger.debug("absorbed prior message update in message upsert")
336
+ Object.assign(message, data.messageUpdates[key].update)
337
+ delete data.messageUpdates[key]
338
+ }
339
+ if (data.historySets.messages[key]) {
340
+ data.historySets.messages[key] = message
341
+ } else {
342
+ data.messageUpserts[key] = {
343
+ message,
344
+ type: type === "notify" || data.messageUpserts[key]?.type === "notify" ? "notify" : type,
345
+ }
346
+ }
347
+ }
348
+ break
349
+ case "messages.update":
350
+ const msgUpdates = eventData
351
+ for (const { key, update } of msgUpdates) {
352
+ const keyStr = stringifyMessageKey(key)
353
+ const existing = data.historySets.messages[keyStr] || data.messageUpserts[keyStr]?.message
354
+ if (existing) {
355
+ Object.assign(existing, update)
356
+ // if the message was received & read by us
357
+ // the chat counter must have been incremented
358
+ // so we need to decrement it
359
+ if (update.status === WAMessageStatus.READ && !key.fromMe) {
360
+ decrementChatReadCounterIfMsgDidUnread(existing)
361
+ }
362
+ } else {
363
+ const msgUpdate = data.messageUpdates[keyStr] || { key, update: {} }
364
+ Object.assign(msgUpdate.update, update)
365
+ data.messageUpdates[keyStr] = msgUpdate
366
+ }
367
+ }
368
+ break
369
+ case "messages.delete":
370
+ const deleteData = eventData
371
+ if ("keys" in deleteData) {
372
+ const { keys } = deleteData
373
+ for (const key of keys) {
374
+ const keyStr = stringifyMessageKey(key)
375
+ if (!data.messageDeletes[keyStr]) {
376
+ data.messageDeletes[keyStr] = key
377
+ }
378
+ if (data.messageUpserts[keyStr]) {
379
+ delete data.messageUpserts[keyStr]
380
+ }
381
+ if (data.messageUpdates[keyStr]) {
382
+ delete data.messageUpdates[keyStr]
383
+ }
384
+ }
385
+ } else {
386
+ // TODO: add support
387
+ }
388
+ break
389
+ case "messages.reaction":
390
+ const reactions = eventData
391
+ for (const { key, reaction } of reactions) {
392
+ const keyStr = stringifyMessageKey(key)
393
+ const existing = data.messageUpserts[keyStr]
394
+ if (existing) {
395
+ updateMessageWithReaction(existing.message, reaction)
396
+ } else {
397
+ data.messageReactions[keyStr] = data.messageReactions[keyStr] || { key, reactions: [] }
398
+ updateMessageWithReaction(data.messageReactions[keyStr], reaction)
399
+ }
400
+ }
401
+ break
402
+ case "message-receipt.update":
403
+ const receipts = eventData
404
+ for (const { key, receipt } of receipts) {
405
+ const keyStr = stringifyMessageKey(key)
406
+ const existing = data.messageUpserts[keyStr]
407
+ if (existing) {
408
+ updateMessageWithReceipt(existing.message, receipt)
409
+ } else {
410
+ data.messageReceipts[keyStr] = data.messageReceipts[keyStr] || { key, userReceipt: [] }
411
+ updateMessageWithReceipt(data.messageReceipts[keyStr], receipt)
412
+ }
413
+ }
414
+ break
415
+ case "groups.update":
416
+ const groupUpdates = eventData
417
+ for (const update of groupUpdates) {
418
+ const id = update.id
419
+ const groupUpdate = data.groupUpdates[id] || {}
420
+ if (!data.groupUpdates[id]) {
421
+ data.groupUpdates[id] = Object.assign(groupUpdate, update)
422
+ }
423
+ }
424
+ break
425
+ default:
426
+ throw new Error(`"${event}" cannot be buffered`)
427
+ }
428
+ function absorbingChatUpdate(existing) {
429
+ const chatId = existing.id || ""
430
+ const update = data.chatUpdates[chatId]
431
+ if (update) {
432
+ const conditionMatches = update.conditional ? update.conditional(data) : true
433
+ if (conditionMatches) {
434
+ delete update.conditional
435
+ logger.debug({ chatId }, "absorbed chat update in existing chat")
436
+ Object.assign(existing, concatChats(update, existing))
437
+ delete data.chatUpdates[chatId]
438
+ } else if (conditionMatches === false) {
439
+ logger.debug({ chatId }, "chat update condition fail, removing")
440
+ delete data.chatUpdates[chatId]
441
+ }
442
+ }
443
+ }
444
+ function decrementChatReadCounterIfMsgDidUnread(message) {
445
+ // decrement chat unread counter
446
+ // if the message has already been marked read by us
447
+ const chatId = message.key.remoteJid
448
+ const chat = data.chatUpdates[chatId] || data.chatUpserts[chatId]
449
+ if (
450
+ isRealMessage(message) &&
451
+ shouldIncrementChatUnread(message) &&
452
+ typeof chat?.unreadCount === "number" &&
453
+ chat.unreadCount > 0
454
+ ) {
455
+ logger.debug({ chatId: chat.id }, "decrementing chat counter")
456
+ chat.unreadCount -= 1
457
+ if (chat.unreadCount === 0) {
458
+ delete chat.unreadCount
459
+ }
460
+ }
461
+ }
462
+ }
463
+ function consolidateEvents(data) {
464
+ const map = {}
465
+ if (!data.historySets.empty) {
466
+ map["messaging-history.set"] = {
467
+ chats: Object.values(data.historySets.chats),
468
+ messages: Object.values(data.historySets.messages),
469
+ contacts: Object.values(data.historySets.contacts),
470
+ syncType: data.historySets.syncType,
471
+ progress: data.historySets.progress,
472
+ isLatest: data.historySets.isLatest,
473
+ peerDataRequestSessionId: data.historySets.peerDataRequestSessionId,
474
+ }
475
+ }
476
+ const chatUpsertList = Object.values(data.chatUpserts)
477
+ if (chatUpsertList.length) {
478
+ map["chats.upsert"] = chatUpsertList
479
+ }
480
+ const chatUpdateList = Object.values(data.chatUpdates)
481
+ if (chatUpdateList.length) {
482
+ map["chats.update"] = chatUpdateList
483
+ }
484
+ const chatDeleteList = Array.from(data.chatDeletes)
485
+ if (chatDeleteList.length) {
486
+ map["chats.delete"] = chatDeleteList
487
+ }
488
+ const messageUpsertList = Object.values(data.messageUpserts)
489
+ if (messageUpsertList.length) {
490
+ const type = messageUpsertList[0].type
491
+ map["messages.upsert"] = {
492
+ messages: messageUpsertList.map((m) => m.message),
493
+ type,
494
+ }
495
+ }
496
+ const messageUpdateList = Object.values(data.messageUpdates)
497
+ if (messageUpdateList.length) {
498
+ map["messages.update"] = messageUpdateList
499
+ }
500
+ const messageDeleteList = Object.values(data.messageDeletes)
501
+ if (messageDeleteList.length) {
502
+ map["messages.delete"] = { keys: messageDeleteList }
503
+ }
504
+ const messageReactionList = Object.values(data.messageReactions).flatMap(({ key, reactions }) =>
505
+ reactions.flatMap((reaction) => ({ key, reaction })),
506
+ )
507
+ if (messageReactionList.length) {
508
+ map["messages.reaction"] = messageReactionList
509
+ }
510
+ const messageReceiptList = Object.values(data.messageReceipts).flatMap(({ key, userReceipt }) =>
511
+ userReceipt.flatMap((receipt) => ({ key, receipt })),
512
+ )
513
+ if (messageReceiptList.length) {
514
+ map["message-receipt.update"] = messageReceiptList
515
+ }
516
+ const contactUpsertList = Object.values(data.contactUpserts)
517
+ if (contactUpsertList.length) {
518
+ map["contacts.upsert"] = contactUpsertList
519
+ }
520
+ const contactUpdateList = Object.values(data.contactUpdates)
521
+ if (contactUpdateList.length) {
522
+ map["contacts.update"] = contactUpdateList
523
+ }
524
+ const groupUpdateList = Object.values(data.groupUpdates)
525
+ if (groupUpdateList.length) {
526
+ map["groups.update"] = groupUpdateList
527
+ }
528
+ return map
529
+ }
530
+ function concatChats(a, b) {
531
+ if (
532
+ b.unreadCount === null && // neutralize unread counter
533
+ a.unreadCount < 0
534
+ ) {
535
+ a.unreadCount = undefined
536
+ b.unreadCount = undefined
537
+ }
538
+ if (typeof a.unreadCount === "number" && typeof b.unreadCount === "number") {
539
+ b = { ...b }
540
+ if (b.unreadCount >= 0) {
541
+ b.unreadCount = Math.max(b.unreadCount, 0) + Math.max(a.unreadCount, 0)
542
+ }
543
+ }
544
+ return Object.assign(a, b)
545
+ }
546
+ const stringifyMessageKey = (key) => `${key.remoteJid},${key.id},${key.fromMe ? "1" : "0"}`
547
+ //# sourceMappingURL=event-buffer.js.map