@badzz88/baileys 8.5.3 → 8.5.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.
@@ -56,15 +56,12 @@ exports.WA_ADV_DEVICE_SIG_PREFIX = Buffer.from([6, 1])
56
56
  exports.WA_ADV_HOSTED_ACCOUNT_SIG_PREFIX = Buffer.from([6, 5])
57
57
  exports.WA_ADV_HOSTED_DEVICE_SIG_PREFIX = Buffer.from([6, 6])
58
58
  exports.WA_DEFAULT_EPHEMERAL = 7 * 24 * 60 * 60
59
- /** Status messages older than 24 hours are considered expired */
60
59
  exports.STATUS_EXPIRY_SECONDS = 24 * 60 * 60
61
- /** WA Web enforces a 14-day maximum age for placeholder resend requests */
62
60
  exports.PLACEHOLDER_MAX_AGE_SECONDS = 14 * 24 * 60 * 60
63
61
  exports.NOISE_MODE = 'Noise_XX_25519_AESGCM_SHA256\0\0\0\0'
64
62
  exports.DICT_VERSION = 3
65
63
  exports.KEY_BUNDLE_TYPE = Buffer.from([5])
66
- exports.NOISE_WA_HEADER = Buffer.from([87, 65, 6, exports.DICT_VERSION]) // last is "DICT_VERSION"
67
- /** from: https://stackoverflow.com/questions/3809401/what-is-a-good-regular-expression-to-match-a-url */
64
+ exports.NOISE_WA_HEADER = Buffer.from([87, 65, 6, exports.DICT_VERSION])
68
65
  exports.URL_REGEX = /https:\/\/(?![^:@\/\s]+:[^:@\/\s]+@)[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(:\d+)?(\/[^\s]*)?/g
69
66
  exports.WA_CERT_DETAILS = {
70
67
  SERIAL: 0,
@@ -168,17 +165,17 @@ exports.MEDIA_HKDF_KEY_MAPPING = {
168
165
  exports.MEDIA_KEYS = Object.keys(exports.MEDIA_PATH_MAP)
169
166
  exports.MIN_PREKEY_COUNT = 5
170
167
  exports.INITIAL_PREKEY_COUNT = 812
171
- exports.UPLOAD_TIMEOUT = 30000 // 30 seconds
172
- exports.MIN_UPLOAD_INTERVAL = 5000 // 5 seconds minimum between uploads
168
+ exports.UPLOAD_TIMEOUT = 30000
169
+ exports.MIN_UPLOAD_INTERVAL = 5000
173
170
  exports.DEFAULT_CACHE_TTLS = {
174
- SIGNAL_STORE: 5 * 60, // 5 minutes
175
- MSG_RETRY: 60 * 60, // 1 hour
176
- CALL_OFFER: 5 * 60, // 5 minutes
177
- USER_DEVICES: 5 * 60, // 5 minutes
178
- GROUP_METADATA: 30 * 60, // 30 minutes
179
- CALL_SESSION: 10 * 60, // 10 minutes
180
- USYNC_RESULTS: 10 * 60, // 10 minutes
181
- IDENTITY_PROOF: 60 * 60 // 1 hour
171
+ SIGNAL_STORE: 5 * 60,
172
+ MSG_RETRY: 60 * 60,
173
+ CALL_OFFER: 5 * 60,
174
+ USER_DEVICES: 5 * 60,
175
+ GROUP_METADATA: 30 * 60,
176
+ CALL_SESSION: 10 * 60,
177
+ USYNC_RESULTS: 10 * 60,
178
+ IDENTITY_PROOF: 60 * 60
182
179
  }
183
180
  exports.TimeMs = {
184
181
  Second: 1000,
@@ -60,18 +60,15 @@ const sender_key_name_1 = require('./Group/sender-key-name')
60
60
  const sender_key_record_1 = require('./Group/sender-key-record')
61
61
  const Group_1 = require('./Group')
62
62
  const lid_mapping_1 = require('./lid-mapping')
63
- /** Extract identity key from PreKeyWhisperMessage for identity change detection */
64
63
  function extractIdentityFromPkmsg(ciphertext) {
65
64
  try {
66
65
  if (!ciphertext || ciphertext.length < 2) {
67
66
  return undefined
68
67
  }
69
- // Version byte check (version 3)
70
68
  const version = ciphertext[0]
71
69
  if ((version & 0xf) !== 3) {
72
70
  return undefined
73
71
  }
74
- // Parse protobuf (skip version byte)
75
72
  const identityKeyArr = rb.getPreKeyMessageIdentityKey(ciphertext)
76
73
  if (identityKeyArr) {
77
74
  return identityKeyArr
@@ -92,7 +89,7 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
92
89
  const storage = signalStorage(auth, lidMapping)
93
90
  const parsedKeys = auth.keys
94
91
  const migratedSessionCache = new lru_cache_1.LRUCache({
95
- ttl: 3 * 24 * 60 * 60 * 1000, // 7 days
92
+ ttl: 3 * 24 * 60 * 60 * 1000,
96
93
  ttlAutopurge: true,
97
94
  updateAgeOnGet: true
98
95
  })
@@ -100,7 +97,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
100
97
  decryptGroupMessage({ group, authorJid, msg }) {
101
98
  const senderName = jidToSignalSenderKeyName(group, authorJid)
102
99
  const cipher = new Group_1.GroupCipher(storage, senderName)
103
- // Use transaction to ensure atomicity
104
100
  return parsedKeys.transaction(async () => {
105
101
  return cipher.decrypt(msg)
106
102
  }, group)
@@ -134,7 +130,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
134
130
  async decryptMessage({ jid, type, ciphertext }) {
135
131
  const addr = jidToSignalProtocolAddress(jid)
136
132
  const session = new rb.SessionCipher(storage, addr)
137
- // Extract and save sender's identity key before decryption for identity change detection
138
133
  if (type === 'pkmsg') {
139
134
  const identityKey = extractIdentityFromPkmsg(ciphertext)
140
135
  if (identityKey) {
@@ -157,8 +152,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
157
152
  }
158
153
  return result
159
154
  }
160
- // If it's not a sync message, we need to ensure atomicity
161
- // For regular messages, we use a transaction to ensure atomicity
162
155
  return parsedKeys.transaction(async () => {
163
156
  return await doDecrypt()
164
157
  }, jid)
@@ -166,7 +159,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
166
159
  async encryptMessage({ jid, data }) {
167
160
  const addr = jidToSignalProtocolAddress(jid)
168
161
  const cipher = new rb.SessionCipher(storage, addr)
169
- // Use transaction to ensure atomicity
170
162
  return parsedKeys.transaction(async () => {
171
163
  const { type: sigType, body } = await cipher.encrypt(data)
172
164
  const type = sigType === 3 ? 'pkmsg' : 'msg'
@@ -216,7 +208,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
216
208
  jidToSignalProtocolAddress(jid) {
217
209
  return jidToSignalProtocolAddress(jid).toString()
218
210
  },
219
- // Optimized direct access to LID mapping store
220
211
  lidMapping,
221
212
  async validateSession(jid) {
222
213
  try {
@@ -232,28 +223,23 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
232
223
  },
233
224
  async deleteSession(jids) {
234
225
  if (!jids.length) return
235
- // Convert JIDs to signal addresses and prepare for bulk deletion
236
226
  const sessionUpdates = {}
237
227
  jids.forEach(jid => {
238
228
  const addr = jidToSignalProtocolAddress(jid)
239
229
  sessionUpdates[addr.toString()] = null
240
230
  })
241
- // Single transaction for all deletions
242
231
  return parsedKeys.transaction(async () => {
243
232
  await auth.keys.set({ session: sessionUpdates })
244
233
  }, `delete-${jids.length}-sessions`)
245
234
  },
246
235
  async migrateSession(fromJid, toJid) {
247
- // TODO: use usync to handle this entire mess
248
236
  if (!fromJid || (!(0, WABinary_1.isLidUser)(toJid) && !(0, WABinary_1.isHostedLidUser)(toJid)))
249
237
  return { migrated: 0, skipped: 0, total: 0 }
250
- // Only support PN to LID migration
251
238
  if (!(0, WABinary_1.isPnUser)(fromJid) && !(0, WABinary_1.isHostedPnUser)(fromJid)) {
252
239
  return { migrated: 0, skipped: 0, total: 1 }
253
240
  }
254
241
  const { user } = (0, WABinary_1.jidDecode)(fromJid)
255
242
  logger.debug({ fromJid }, 'bulk device migration - loading all user devices')
256
- // Get user's device list from storage
257
243
  const { [user]: userDevices } = await parsedKeys.get('device-list', [user])
258
244
  if (!userDevices) {
259
245
  return { migrated: 0, skipped: 0, total: 0 }
@@ -263,19 +249,15 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
263
249
  if (!userDevices.includes(fromDeviceStr)) {
264
250
  userDevices.push(fromDeviceStr)
265
251
  }
266
- // Filter out cached devices before database fetch
267
252
  const uncachedDevices = userDevices.filter(device => {
268
253
  const deviceKey = `${user}.${device}`
269
254
  return !migratedSessionCache.has(deviceKey)
270
255
  })
271
- // Bulk check session existence only for uncached devices
272
256
  const deviceSessionKeys = uncachedDevices.map(device => `${user}.${device}`)
273
257
  const existingSessions = await parsedKeys.get('session', deviceSessionKeys)
274
- // Step 3: Convert existing sessions to JIDs (only migrate sessions that exist)
275
258
  const deviceJids = []
276
259
  for (const [sessionKey, sessionData] of Object.entries(existingSessions)) {
277
260
  if (sessionData) {
278
- // Session exists in storage
279
261
  const deviceStr = sessionKey.split('.')[1]
280
262
  if (!deviceStr) continue
281
263
  const deviceNum = parseInt(deviceStr)
@@ -295,7 +277,6 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
295
277
  },
296
278
  'bulk device migration complete - all user devices processed'
297
279
  )
298
- // Single transaction for all migrations
299
280
  return parsedKeys.transaction(
300
281
  async () => {
301
282
  const migrationOps = deviceJids.map(jid => {
@@ -314,31 +295,25 @@ function makeLibSignalRepository(auth, logger, pnToLIDFunc) {
314
295
  })
315
296
  const totalOps = migrationOps.length
316
297
  let migratedCount = 0
317
- // Bulk fetch PN sessions - already exist (verified during device discovery)
318
298
  const pnAddrStrings = Array.from(new Set(migrationOps.map(op => op.fromAddr.toString())))
319
299
  const pnSessions = await parsedKeys.get('session', pnAddrStrings)
320
- // Prepare bulk session updates (PN → LID migration + deletion)
321
300
  const sessionUpdates = {}
322
301
  for (const op of migrationOps) {
323
302
  const pnAddrStr = op.fromAddr.toString()
324
303
  const lidAddrStr = op.toAddr.toString()
325
304
  const pnSession = pnSessions[pnAddrStr]
326
305
  if (pnSession) {
327
- // Session exists (guaranteed from device discovery)
328
306
  const fromSession = rb.SessionRecord.deserialize(pnSession)
329
307
  if (fromSession.haveOpenSession()) {
330
- // Queue for bulk update: copy to LID, delete from PN
331
308
  sessionUpdates[lidAddrStr] = fromSession.serialize()
332
309
  sessionUpdates[pnAddrStr] = null
333
310
  migratedCount++
334
311
  }
335
312
  }
336
313
  }
337
- // Single bulk session update for all migrations
338
314
  if (Object.keys(sessionUpdates).length > 0) {
339
315
  await parsedKeys.set({ session: sessionUpdates })
340
316
  logger.debug({ migratedSessions: migratedCount }, 'bulk session migration complete')
341
- // Cache device-level migrations
342
317
  for (const op of migrationOps) {
343
318
  if (sessionUpdates[op.toAddr.toString()]) {
344
319
  const deviceKey = `${op.pnUser}.${op.deviceId}`
@@ -374,7 +349,6 @@ const jidToSignalSenderKeyName = (group, user) => {
374
349
  return new sender_key_name_1.SenderKeyName(group, jidToSignalProtocolAddress(user))
375
350
  }
376
351
  function signalStorage({ creds, keys }, lidMapping) {
377
- // Shared function to resolve PN signal address to LID if mapping exists
378
352
  const resolveLIDSignalAddress = async id => {
379
353
  if (id.includes('.')) {
380
354
  const [deviceId, device] = id.split('.')
@@ -403,22 +377,19 @@ function signalStorage({ creds, keys }, lidMapping) {
403
377
  const fetched = await keys.get('session', keysToFetch)
404
378
  const sess = fetched[wireJid] || fetched[id]
405
379
  if (!sess) return null
406
- // Detect old libsignal-node JSON format → treat as no session
407
380
  if (typeof sess === 'object' && !Buffer.isBuffer(sess) && !ArrayBuffer.isView(sess) && sess._sessions) {
408
381
  return null
409
382
  }
410
- // Return raw bytes — Rust storage adapter expects Uint8Array, not SessionRecord object
411
383
  return Buffer.isBuffer(sess) ? sess : Buffer.from(sess)
412
384
  } catch (e) {
413
385
  return null
414
386
  }
415
387
  },
416
388
  storeSession: async (id, session) => {
417
- // Store under raw ID so it's always findable regardless of LID mapping state
418
389
  await keys.set({ session: { [id]: session.serialize() } })
419
390
  },
420
391
  isTrustedIdentity: () => {
421
- return true // TOFU - Trust on First Use (same as WhatsApp Web)
392
+ return true
422
393
  },
423
394
  loadIdentityKey: async id => {
424
395
  const wireJid = await resolveLIDSignalAddress(id)
@@ -433,7 +404,6 @@ function signalStorage({ creds, keys }, lidMapping) {
433
404
  existingKey.length === identityKey.length &&
434
405
  existingKey.every((byte, i) => byte === identityKey[i])
435
406
  if (existingKey && !keysMatch) {
436
- // Identity changed - clear session and update key
437
407
  await keys.set({
438
408
  session: { [wireJid]: null },
439
409
  'identity-key': { [wireJid]: identityKey }
@@ -441,7 +411,6 @@ function signalStorage({ creds, keys }, lidMapping) {
441
411
  return true
442
412
  }
443
413
  if (!existingKey) {
444
- // New contact - Trust on First Use (TOFU)
445
414
  await keys.set({ 'identity-key': { [wireJid]: identityKey } })
446
415
  return true
447
416
  }
@@ -6,7 +6,7 @@ const WABinary_1 = require('../WABinary')
6
6
  class LIDMappingStore {
7
7
  constructor(keys, logger, pnToLIDFunc) {
8
8
  this.mappingCache = new lru_cache_1.LRUCache({
9
- ttl: 3 * 24 * 60 * 60 * 1000, // 7 days
9
+ ttl: 3 * 24 * 60 * 60 * 1000,
10
10
  ttlAutopurge: true,
11
11
  updateAgeOnGet: true
12
12
  })
@@ -77,7 +77,6 @@ class LIDMappingStore {
77
77
  await this.keys.transaction(async () => {
78
78
  await this.keys.set({ 'lid-mapping': batchData })
79
79
  }, 'lid-mapping')
80
- // Update cache after successful DB write
81
80
  for (const [pnUser, lidUser] of Object.entries(pairMap)) {
82
81
  this.mappingCache.set(`pn:${pnUser}`, lidUser)
83
82
  this.mappingCache.set(`lid:${lidUser}`, pnUser)
@@ -113,7 +112,6 @@ class LIDMappingStore {
113
112
  this.logger.warn(`Invalid or empty LID user for PN ${pn}: lidUser = "${lidUser}"`)
114
113
  return false
115
114
  }
116
- // Push the PN device ID to the LID to maintain device separation
117
115
  const pnDevice = decoded.device !== undefined ? decoded.device : 0
118
116
  const deviceSpecificLid = `${normalizedLidUser}${!!pnDevice ? `:${pnDevice}` : ``}@${decoded.server === 'hosted' ? 'hosted.lid' : 'lid'}`
119
117
  this.logger.trace(`getLIDForPN: ${pn} → ${deviceSpecificLid} (user mapping with device ${pnDevice})`)
@@ -168,7 +166,7 @@ class LIDMappingStore {
168
166
  }
169
167
  }
170
168
  if (Object.keys(usyncFetch).length > 0) {
171
- const result = await this.pnToLIDFunc?.(Object.keys(usyncFetch)) // this function already adds LIDs to mapping
169
+ const result = await this.pnToLIDFunc?.(Object.keys(usyncFetch))
172
170
  if (result && result.length > 0) {
173
171
  await this.storeLIDPNMappings(result)
174
172
  for (const pair of result) {
@@ -0,0 +1,393 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+
4
+ const boom_1 = require('@hapi/boom')
5
+ const { proto } = require('../../WAProto/index.js')
6
+ const WABinary_1 = require('../WABinary')
7
+ const crypto_2 = require('./crypto')
8
+ const generics_1 = require('./generics')
9
+ const messages_media_1 = require('./messages-media')
10
+
11
+
12
+ const getContentType = content => {
13
+ if (content) {
14
+ const keys = Object.keys(content)
15
+ const key = keys.find(k => (k === 'conversation' || k.includes('Message')) && k !== 'senderKeyDistributionMessage')
16
+ return key
17
+ }
18
+ }
19
+ exports.getContentType = getContentType
20
+
21
+
22
+ const normalizeMessageContent = content => {
23
+ if (!content) {
24
+ return undefined
25
+ }
26
+
27
+ for (let i = 0; i < 5; i++) {
28
+ const inner = getFutureProofMessage(content)
29
+ if (!inner) {
30
+ break
31
+ }
32
+ content = inner.message
33
+ }
34
+ return content
35
+ function getFutureProofMessage(message) {
36
+ return (
37
+ message.ephemeralMessage ||
38
+ message.viewOnceMessage ||
39
+ message.documentWithCaptionMessage ||
40
+ message.viewOnceMessageV2 ||
41
+ message.viewOnceMessageV2Extension ||
42
+ message.editedMessage ||
43
+ message.groupMentionedMessage ||
44
+ message.botInvokeMessage ||
45
+ message.lottieStickerMessage ||
46
+ message.eventCoverImage ||
47
+ message.statusMentionMessage ||
48
+ message.pollCreationOptionImageMessage ||
49
+ message.associatedChildMessage ||
50
+ message.groupStatusMentionMessage ||
51
+ message.pollCreationMessageV4 ||
52
+ message.pollCreationMessageV5 ||
53
+ message.statusAddYours ||
54
+ message.groupStatusMessage ||
55
+ message.limitSharingMessage ||
56
+ message.botTaskMessage ||
57
+ message.questionMessage ||
58
+ message.groupStatusMessageV2 ||
59
+ message.botForwardedMessage ||
60
+ message.questionReplyMessage ||
61
+ message.newsletterAdminProfileMessage ||
62
+ message.newsletterAdminProfileMessageV2 ||
63
+ message.newsletterAdminProfileStatusMessage ||
64
+ message.spoilerMessage ||
65
+ message.groupStatusV3Message ||
66
+ message.pollCreationMessageV6
67
+ )
68
+ }
69
+ }
70
+ exports.normalizeMessageContent = normalizeMessageContent
71
+
72
+
73
+ const extractMessageContent = content => {
74
+ const extractFromTemplateMessage = msg => {
75
+ if (msg.imageMessage) {
76
+ return { imageMessage: msg.imageMessage }
77
+ } else if (msg.documentMessage) {
78
+ return { documentMessage: msg.documentMessage }
79
+ } else if (msg.videoMessage) {
80
+ return { videoMessage: msg.videoMessage }
81
+ } else if (msg.locationMessage) {
82
+ return { locationMessage: msg.locationMessage }
83
+ } else {
84
+ return {
85
+ conversation:
86
+ 'contentText' in msg ? msg.contentText : 'hydratedContentText' in msg ? msg.hydratedContentText : ''
87
+ }
88
+ }
89
+ }
90
+ content = normalizeMessageContent(content)
91
+ if (content?.buttonsMessage) {
92
+ return extractFromTemplateMessage(content.buttonsMessage)
93
+ }
94
+ if (content?.templateMessage?.hydratedFourRowTemplate) {
95
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedFourRowTemplate)
96
+ }
97
+ if (content?.templateMessage?.hydratedTemplate) {
98
+ return extractFromTemplateMessage(content?.templateMessage?.hydratedTemplate)
99
+ }
100
+ if (content?.templateMessage?.fourRowTemplate) {
101
+ return extractFromTemplateMessage(content?.templateMessage?.fourRowTemplate)
102
+ }
103
+ return content
104
+ }
105
+ exports.extractMessageContent = extractMessageContent
106
+
107
+
108
+ const getDevice = id =>
109
+ /^3A.{18}$/.test(id)
110
+ ? 'ios'
111
+ : /^3E.{20}$/.test(id)
112
+ ? 'web'
113
+ : /^(.{21}|.{32})$/.test(id)
114
+ ? 'android'
115
+ : /^(3F|.{18}$)/.test(id)
116
+ ? 'desktop'
117
+ : 'api/baileys'
118
+ exports.getDevice = getDevice
119
+
120
+
121
+ const updateMessageWithReceipt = (msg, receipt) => {
122
+ msg.userReceipt = msg.userReceipt || []
123
+ const recp = msg.userReceipt.find(m => m.userJid === receipt.userJid)
124
+ if (recp) {
125
+ Object.assign(recp, receipt)
126
+ } else {
127
+ msg.userReceipt.push(receipt)
128
+ }
129
+ }
130
+ exports.updateMessageWithReceipt = updateMessageWithReceipt
131
+
132
+
133
+ const updateMessageWithReaction = (msg, reaction) => {
134
+ const authorID = (0, generics_1.getKeyAuthor)(reaction.key)
135
+ const reactions = (msg.reactions || []).filter(r => (0, generics_1.getKeyAuthor)(r.key) !== authorID)
136
+ reaction.text = reaction.text || ''
137
+ reactions.push(reaction)
138
+ msg.reactions = reactions
139
+ }
140
+ exports.updateMessageWithReaction = updateMessageWithReaction
141
+
142
+
143
+ const updateMessageWithPollUpdate = (msg, update) => {
144
+ const authorID = (0, generics_1.getKeyAuthor)(update.pollUpdateMessageKey)
145
+ const reactions = (msg.pollUpdates || []).filter(
146
+ r => (0, generics_1.getKeyAuthor)(r.pollUpdateMessageKey) !== authorID
147
+ )
148
+ if (update.vote?.selectedOptions?.length) {
149
+ reactions.push(update)
150
+ }
151
+ msg.pollUpdates = reactions
152
+ }
153
+ exports.updateMessageWithPollUpdate = updateMessageWithPollUpdate
154
+
155
+
156
+ const updateMessageWithEventResponse = (msg, update) => {
157
+ const authorID = (0, generics_1.getKeyAuthor)(update.eventResponseMessageKey)
158
+ const responses = (msg.eventResponses || []).filter(
159
+ r => (0, generics_1.getKeyAuthor)(r.eventResponseMessageKey) !== authorID
160
+ )
161
+ responses.push(update)
162
+ msg.eventResponses = responses
163
+ }
164
+ exports.updateMessageWithEventResponse = updateMessageWithEventResponse
165
+
166
+
167
+ function getAggregateVotesInPollMessage({ message, pollUpdates }, meId) {
168
+ const opts =
169
+ message?.pollCreationMessage?.options ||
170
+ message?.pollCreationMessageV2?.options ||
171
+ message?.pollCreationMessageV3?.options ||
172
+ message?.pollCreationMessageV5?.options ||
173
+ message?.pollCreationMessageV6?.options ||
174
+ message?.pollCreationMessageV4?.message?.pollCreationMessage?.options ||
175
+ message?.pollCreationMessageV4?.message?.pollCreationMessageV3?.options ||
176
+ []
177
+ const voteHashMap = opts.reduce((acc, opt) => {
178
+ const hash = (0, crypto_2.sha256)(Buffer.from(opt.optionName || '')).toString()
179
+ acc[hash] = {
180
+ name: opt.optionName || '',
181
+ voters: []
182
+ }
183
+ return acc
184
+ }, {})
185
+ for (const update of pollUpdates || []) {
186
+ const { vote } = update
187
+ if (!vote) {
188
+ continue
189
+ }
190
+ for (const option of vote.selectedOptions || []) {
191
+ const hash = option.toString()
192
+ let data = voteHashMap[hash]
193
+ if (!data) {
194
+ voteHashMap[hash] = {
195
+ name: 'Unknown',
196
+ voters: []
197
+ }
198
+ data = voteHashMap[hash]
199
+ }
200
+ voteHashMap[hash].voters.push((0, generics_1.getKeyAuthor)(update.pollUpdateMessageKey, meId))
201
+ }
202
+ }
203
+ return Object.values(voteHashMap)
204
+ }
205
+ exports.getAggregateVotesInPollMessage = getAggregateVotesInPollMessage
206
+
207
+
208
+ const aggregateMessageKeysNotFromMe = keys => {
209
+ const keyMap = {}
210
+ for (const { remoteJid, id, participant, fromMe, sts } of keys) {
211
+ if (!fromMe) {
212
+ const uqKey = `${remoteJid}:${participant || ''}`
213
+ if (!keyMap[uqKey]) {
214
+ keyMap[uqKey] = {
215
+ jid: remoteJid,
216
+ participant: participant,
217
+ messageIds: [],
218
+
219
+ sts: sts
220
+ }
221
+ } else if (keyMap[uqKey].sts !== sts) {
222
+
223
+ keyMap[uqKey].sts = undefined
224
+ }
225
+ keyMap[uqKey].messageIds.push(id)
226
+ }
227
+ }
228
+ return Object.values(keyMap)
229
+ }
230
+ exports.aggregateMessageKeysNotFromMe = aggregateMessageKeysNotFromMe
231
+
232
+
233
+ const isScheduledMessage = msg => !!msg?.scheduledMessageMetadata?.scheduledTime
234
+ exports.isScheduledMessage = isScheduledMessage
235
+
236
+
237
+ const getScheduledMessageTime = msg => {
238
+ const t = msg?.scheduledMessageMetadata?.scheduledTime
239
+ if (!t) return null
240
+ return new Date(Number(t) * 1000)
241
+ }
242
+ exports.getScheduledMessageTime = getScheduledMessageTime
243
+
244
+
245
+ const getMessagePaymentInfo = msg => msg?.paymentInfo || msg?.quotedPaymentInfo || null
246
+ exports.getMessagePaymentInfo = getMessagePaymentInfo
247
+
248
+
249
+ const getMessageCommentMetadata = msg => msg?.commentMetadata || null
250
+ exports.getMessageCommentMetadata = getMessageCommentMetadata
251
+
252
+
253
+ const getMessageAddOns = msg => msg?.messageAddOns || []
254
+ exports.getMessageAddOns = getMessageAddOns
255
+
256
+
257
+ const getPollCorrectAnswer = pollMsg => {
258
+ const poll =
259
+ pollMsg?.pollCreationMessage ||
260
+ pollMsg?.pollCreationMessageV2 ||
261
+ pollMsg?.pollCreationMessageV3 ||
262
+ pollMsg?.pollCreationMessageV5 ||
263
+ pollMsg?.pollCreationMessageV6
264
+ if (!poll) return null
265
+ const isQuiz = poll.pollType === proto.Message.PollType?.QUIZ || poll.pollType === 1
266
+ return isQuiz ? poll.correctAnswer?.optionName || null : null
267
+ }
268
+ exports.getPollCorrectAnswer = getPollCorrectAnswer
269
+
270
+
271
+ function getAggregateResponsesInEventMessage({ eventResponses }, meLid) {
272
+ const responseTypes = ['GOING', 'NOT_GOING', 'MAYBE']
273
+ const responseMap = {}
274
+
275
+ for (const type of responseTypes) {
276
+ responseMap[type] = {
277
+ response: type,
278
+ responders: []
279
+ }
280
+ }
281
+ for (const update of eventResponses) {
282
+ const { response } = update.response || {}
283
+ const responseType = proto.Message.EventResponseMessage.EventResponseType[response]
284
+ if (responseType !== 'UNKNOWN' && responseMap[responseType]) {
285
+ responseMap[responseType].responders.push(generics_1.getKeyAuthor(update.eventResponseMessageKey, meLid))
286
+ }
287
+ }
288
+
289
+ return Object.values(responseMap)
290
+ }
291
+ exports.getAggregateResponsesInEventMessage = getAggregateResponsesInEventMessage
292
+
293
+ const REUPLOAD_REQUIRED_STATUS = [410, 404]
294
+
295
+
296
+ const downloadMediaMessage = async (message, type, options, ctx) => {
297
+ const result = await downloadMsg().catch(async error => {
298
+
299
+
300
+
301
+ const errorStatus =
302
+ typeof error?.status === 'number'
303
+ ? error.status
304
+ : typeof error?.output?.statusCode === 'number'
305
+ ? error.output.statusCode
306
+ : undefined
307
+ if (ctx && typeof errorStatus === 'number' && REUPLOAD_REQUIRED_STATUS.includes(errorStatus)) {
308
+ ctx.logger.info({ key: message.key }, 'sending reupload media request...')
309
+ message = await ctx.reuploadRequest(message)
310
+ const result = await downloadMsg()
311
+ return result
312
+ }
313
+ throw error
314
+ })
315
+ return result
316
+ async function downloadMsg() {
317
+ const mContent = extractMessageContent(message.message)
318
+ if (!mContent) {
319
+ throw new boom_1.Boom('No message present', { statusCode: 400, data: message })
320
+ }
321
+ const contentType = getContentType(mContent)
322
+ let mediaType = contentType?.replace('Message', '')
323
+ const media = mContent[contentType]
324
+ if (!media || typeof media !== 'object' || (!('url' in media) && !('thumbnailDirectPath' in media))) {
325
+ throw new boom_1.Boom(`"${contentType}" message is not a media message`)
326
+ }
327
+ let download
328
+ if ('thumbnailDirectPath' in media && !('url' in media)) {
329
+ download = {
330
+ directPath: media.thumbnailDirectPath,
331
+ mediaKey: media.mediaKey
332
+ }
333
+ mediaType = 'thumbnail-link'
334
+ } else {
335
+ download = media
336
+ }
337
+ const stream = await (0, messages_media_1.downloadContentFromMessage)(download, mediaType, options)
338
+ if (type === 'buffer') {
339
+ const bufferArray = []
340
+ for await (const chunk of stream) {
341
+ bufferArray.push(chunk)
342
+ }
343
+ return Buffer.concat(bufferArray)
344
+ }
345
+ return stream
346
+ }
347
+ }
348
+ exports.downloadMediaMessage = downloadMediaMessage
349
+
350
+
351
+ const assertMediaContent = content => {
352
+ content = extractMessageContent(content)
353
+ const mediaContent =
354
+ content?.documentMessage ||
355
+ content?.imageMessage ||
356
+ content?.videoMessage ||
357
+ content?.audioMessage ||
358
+ content?.stickerMessage
359
+ if (!mediaContent) {
360
+ throw new boom_1.Boom('given message is not a media message', { statusCode: 400, data: content })
361
+ }
362
+ return mediaContent
363
+ }
364
+ exports.assertMediaContent = assertMediaContent
365
+
366
+
367
+ const toJid = id => {
368
+ if (!id) return ''
369
+ if (id.includes('@')) return id
370
+ return `${id}@s.whatsapp.net`
371
+ }
372
+ exports.toJid = toJid
373
+
374
+
375
+ const getSenderLid = message => {
376
+ const k = message.key
377
+ if (!k) {
378
+ return { jid: '', lid: '' }
379
+ }
380
+ const jid = k.participant || k.remoteJid || ''
381
+ if (jid.endsWith('@lid') || jid.endsWith('@hosted.lid')) {
382
+ return { jid, lid: jid }
383
+ }
384
+ if (k.lid && typeof k.lid === 'string') {
385
+ const lid = k.lid.includes('@') ? k.lid : (0, WABinary_1.jidEncode)(k.lid, 'lid')
386
+ return { jid, lid }
387
+ }
388
+ if (k.participantLid && (0, WABinary_1.isLidUser)(k.participantLid)) {
389
+ return { jid, lid: k.participantLid }
390
+ }
391
+ return { jid, lid: '' }
392
+ }
393
+ exports.getSenderLid = getSenderLid