@badzz88/baileys 8.4.9 → 8.5.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.
Files changed (154) hide show
  1. package/README.md +1 -1
  2. package/WAProto/WAProto.proto +6 -347
  3. package/WAProto/fix-imports.js +85 -86
  4. package/WAProto/index.d.ts +25 -4913
  5. package/WAProto/index.js +93 -14069
  6. package/{src → lib}/Defaults/index.js +2 -13
  7. package/{src → lib}/Signal/Group/group_cipher.js +5 -5
  8. package/{src → lib}/Signal/Group/keyhelper.js +4 -4
  9. package/{src → lib}/Signal/Group/sender-chain-key.js +2 -2
  10. package/{src → lib}/Signal/Group/sender-key-distribution-message.js +1 -1
  11. package/{src → lib}/Signal/Group/sender-key-message.js +3 -3
  12. package/{src → lib}/Signal/Group/sender-key-record.js +1 -1
  13. package/{src → lib}/Signal/Group/sender-message-key.js +2 -2
  14. package/{src → lib}/Signal/libsignal.js +26 -6
  15. package/{src → lib}/Socket/chats.js +58 -616
  16. package/{src → lib}/Socket/communities.js +28 -59
  17. package/{src → lib}/Socket/graphql.js +103 -362
  18. package/{src → lib}/Socket/groups.js +29 -77
  19. package/{src → lib}/Socket/index.js +5 -3
  20. package/{src → lib}/Socket/interactive-handler.js +60 -117
  21. package/{src → lib}/Socket/interop.js +19 -36
  22. package/lib/Socket/luxu.js +387 -0
  23. package/{src → lib}/Socket/managed-account.js +27 -58
  24. package/{src → lib}/Socket/messages-recv.js +144 -715
  25. package/{src → lib}/Socket/messages-send.js +102 -412
  26. package/lib/Socket/mex.js +60 -0
  27. package/{src → lib}/Socket/newsletter.js +23 -266
  28. package/{src → lib}/Socket/privacy.js +38 -41
  29. package/{src → lib}/Socket/registration.js +106 -113
  30. package/{src → lib}/Socket/socket.js +101 -88
  31. package/{src → lib}/Socket/text-router.js +17 -1
  32. package/lib/Socket/username.js +243 -0
  33. package/lib/Store/keyed-db.js +108 -0
  34. package/{src → lib}/Store/make-in-memory-store.js +104 -6
  35. package/lib/Types/Mex.js +112 -0
  36. package/{src → lib}/Types/Newsletter.js +41 -72
  37. package/{src → lib}/Types/index.js +1 -0
  38. package/{src → lib}/Utils/chat-utils.js +9 -73
  39. package/lib/Utils/command-loader.d.ts +31 -0
  40. package/lib/Utils/command-loader.js +100 -0
  41. package/lib/Utils/companion-reg-client-utils.js +42 -0
  42. package/lib/Utils/crypto.js +155 -0
  43. package/lib/Utils/curve25519-js.js +275 -0
  44. package/{src → lib}/Utils/decode-wa-message.js +36 -284
  45. package/{src → lib}/Utils/event-buffer.js +27 -6
  46. package/{src → lib}/Utils/generics.js +37 -114
  47. package/{src → lib}/Utils/index.js +5 -1
  48. package/lib/Utils/lt-hash.js +39 -0
  49. package/{src → lib}/Utils/message-retry-manager.js +39 -0
  50. package/{src → lib}/Utils/messages-media.js +58 -105
  51. package/{src → lib}/Utils/messages.js +529 -115
  52. package/lib/Utils/meta-ai-msmsg.js +262 -0
  53. package/lib/Utils/native-bridge.js +82 -0
  54. package/{src → lib}/Utils/noise-handler.js +15 -13
  55. package/{src → lib}/Utils/process-message.js +55 -54
  56. package/{src → lib}/Utils/reporting-utils.js +2 -2
  57. package/lib/Utils/session-pool.d.ts +16 -0
  58. package/lib/Utils/session-pool.js +94 -0
  59. package/{src → lib}/Utils/signal.js +51 -19
  60. package/{src → lib}/Utils/stanza-ack.js +2 -36
  61. package/lib/Utils/sticker.d.ts +17 -0
  62. package/lib/Utils/sticker.js +145 -0
  63. package/{src → lib}/Utils/tc-token-utils.js +1 -1
  64. package/{src → lib}/Utils/validate-connection.js +11 -40
  65. package/lib/Utils/view-once-cache.d.ts +9 -0
  66. package/lib/Utils/view-once-cache.js +79 -0
  67. package/{src → lib}/WABinary/decode.js +4 -38
  68. package/lib/WABinary/encode.js +228 -0
  69. package/{src → lib}/WABinary/generic-utils.js +93 -1
  70. package/{src → lib}/WABinary/jid-utils.js +3 -16
  71. package/{src → lib}/WAM/encode.js +1 -1
  72. package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +43 -0
  73. package/{src → lib}/WAUSync/Protocols/USyncContactProtocol.js +1 -7
  74. package/{src → lib}/WAUSync/Protocols/USyncDeviceProtocol.js +5 -13
  75. package/{src → lib}/WAUSync/Protocols/USyncDisappearingModeProtocol.js +4 -1
  76. package/{src → lib}/WAUSync/Protocols/USyncFeatureProtocol.js +2 -17
  77. package/{src → lib}/WAUSync/Protocols/USyncStatusProtocol.js +2 -7
  78. package/{src → lib}/WAUSync/Protocols/USyncUsernameProtocol.js +1 -2
  79. package/{src → lib}/WAUSync/Protocols/index.js +0 -1
  80. package/{src → lib}/WAUSync/USyncQuery.js +6 -77
  81. package/{src → lib}/WAUSync/USyncUser.js +8 -16
  82. package/{src → lib}/WAUSync/index.js +0 -1
  83. package/{src → lib}/antiban.js +266 -355
  84. package/{src → lib}/index.js +2 -2
  85. package/package.json +19 -16
  86. package/WAProto/fix-import.js +0 -38
  87. package/src/Socket/mex.js +0 -47
  88. package/src/Socket/username.js +0 -234
  89. package/src/Utils/crypto.js +0 -125
  90. package/src/Utils/lt-hash.js +0 -10
  91. package/src/Utils/message-inspect.js +0 -400
  92. package/src/Utils/meta-ai-msmsg.js +0 -133
  93. package/src/WABinary/encode.js +0 -58
  94. package/src/WAUSync/Protocols/USyncBusinessProtocol.js +0 -100
  95. package/src/WAUSync/USyncBackoff.js +0 -31
  96. /package/{src → lib}/Defaults/phonenumber-mcc.json +0 -0
  97. /package/{src → lib}/Signal/Group/ciphertext-message.js +0 -0
  98. /package/{src → lib}/Signal/Group/group-session-builder.js +0 -0
  99. /package/{src → lib}/Signal/Group/index.js +0 -0
  100. /package/{src → lib}/Signal/Group/sender-key-name.js +0 -0
  101. /package/{src → lib}/Signal/Group/sender-key-state.js +0 -0
  102. /package/{src → lib}/Signal/lid-mapping.js +0 -0
  103. /package/{src → lib}/Socket/Client/index.js +0 -0
  104. /package/{src → lib}/Socket/Client/types.js +0 -0
  105. /package/{src → lib}/Socket/Client/websocket.js +0 -0
  106. /package/{src → lib}/Socket/aigroups.js +0 -0
  107. /package/{src → lib}/Socket/business.js +0 -0
  108. /package/{src → lib}/Store/index.js +0 -0
  109. /package/{src → lib}/Store/make-cache-manager-store.js +0 -0
  110. /package/{src → lib}/Store/make-ordered-dictionary.js +0 -0
  111. /package/{src → lib}/Store/object-repository.js +0 -0
  112. /package/{src → lib}/Types/Auth.js +0 -0
  113. /package/{src → lib}/Types/Bussines.js +0 -0
  114. /package/{src → lib}/Types/Call.js +0 -0
  115. /package/{src → lib}/Types/Chat.js +0 -0
  116. /package/{src → lib}/Types/Contact.js +0 -0
  117. /package/{src → lib}/Types/Events.js +0 -0
  118. /package/{src → lib}/Types/GroupMetadata.js +0 -0
  119. /package/{src → lib}/Types/Label.js +0 -0
  120. /package/{src → lib}/Types/LabelAssociation.js +0 -0
  121. /package/{src → lib}/Types/Message.js +0 -0
  122. /package/{src → lib}/Types/Product.js +0 -0
  123. /package/{src → lib}/Types/Signal.js +0 -0
  124. /package/{src → lib}/Types/Socket.js +0 -0
  125. /package/{src → lib}/Types/State.js +0 -0
  126. /package/{src → lib}/Types/USync.js +0 -0
  127. /package/{src → lib}/Utils/auth-utils.js +0 -0
  128. /package/{src → lib}/Utils/browser-utils.js +0 -0
  129. /package/{src → lib}/Utils/business.js +0 -0
  130. /package/{src → lib}/Utils/consumer-application.js +0 -0
  131. /package/{src → lib}/Utils/group-history.js +0 -0
  132. /package/{src → lib}/Utils/history.js +0 -0
  133. /package/{src → lib}/Utils/identity-change-handler.js +0 -0
  134. /package/{src → lib}/Utils/jid-display-normalization.js +0 -0
  135. /package/{src → lib}/Utils/link-preview.js +0 -0
  136. /package/{src → lib}/Utils/logger.js +0 -0
  137. /package/{src → lib}/Utils/make-mutex.js +0 -0
  138. /package/{src → lib}/Utils/message-composer.js +0 -0
  139. /package/{src → lib}/Utils/offline-node-processor.js +0 -0
  140. /package/{src → lib}/Utils/pre-key-manager.js +0 -0
  141. /package/{src → lib}/Utils/sync-action-utils.js +0 -0
  142. /package/{src → lib}/Utils/use-multi-file-auth-state.js +0 -0
  143. /package/{src → lib}/Utils/voip-rekey.js +0 -0
  144. /package/{src → lib}/WABinary/constants.js +0 -0
  145. /package/{src → lib}/WABinary/index.js +0 -0
  146. /package/{src → lib}/WABinary/types.js +0 -0
  147. /package/{src → lib}/WAM/BinaryInfo.js +0 -0
  148. /package/{src → lib}/WAM/constants.js +0 -0
  149. /package/{src → lib}/WAM/index.js +0 -0
  150. /package/{src → lib}/WAUSync/Protocols/USyncBotProfileProtocol.js +0 -0
  151. /package/{src → lib}/WAUSync/Protocols/USyncLIDProtocol.js +0 -0
  152. /package/{src → lib}/WAUSync/Protocols/USyncPictureProtocol.js +0 -0
  153. /package/{src → lib}/WAUSync/Protocols/USyncSidelistProtocol.js +0 -0
  154. /package/{src → lib}/WAUSync/Protocols/USyncTextStatusProtocol.js +0 -0
@@ -41,9 +41,11 @@ exports.bindWaitForEvent = bindWaitForEvent
41
41
  exports.trimUndefined = trimUndefined
42
42
  exports.bytesToCrockford = bytesToCrockford
43
43
  exports.encodeNewsletterMessage = encodeNewsletterMessage
44
+ exports.generateIOSMessageID = void 0
45
+ exports.generateAndroMessageID = void 0
44
46
  const boom_1 = require('@hapi/boom')
45
47
  const crypto_1 = require('crypto')
46
- const rb = require('whatsapp-rust-bridge-baron')
48
+ const Crypto_1 = require('./crypto')
47
49
  const DEFAULTS_1 = require('../Defaults')
48
50
  const index_js_1 = require('../../WAProto/index.js')
49
51
  const baileysVersion = DEFAULTS_1.VERSION
@@ -196,24 +198,36 @@ async function promiseTimeout(ms, promise) {
196
198
  }
197
199
  // inspired from whatsmeow code
198
200
  // https://github.com/tulir/whatsmeow/blob/64bc969fbe78d31ae0dd443b8d4c80a5d026d07a/send.go#L42
199
- const generateMessageIDV2 = userId => {
200
- const data = Buffer.alloc(8 + 20 + 16)
201
- data.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)))
202
- if (userId) {
203
- const id = (0, WABinary_1.jidDecode)(userId)
204
- if (id?.user) {
205
- data.write(id.user, 8)
206
- data.write('@c.us', 8 + id.user.length)
207
- }
208
- }
209
- const random = (0, crypto_1.randomBytes)(16)
210
- random.copy(data, 28)
211
- const hash = Buffer.from(rb.sha256(data))
212
- return '3EB0' + hash.toString('hex').toUpperCase().substring(0, 18)
213
- }
201
+ const generateMessageIDV2 = (userId) => {
202
+ const data = Buffer.alloc(8 + 20 + 16);
203
+ data.writeBigUInt64BE(BigInt(Math.floor(Date.now() / 1000)));
204
+ if (userId) {
205
+ const id = (0, WABinary_1.jidDecode)(userId);
206
+ if (id?.user) {
207
+ data.write(id.user, 8);
208
+ data.write('@c.us', 8 + id.user.length);
209
+ }
210
+ }
211
+ const random = (0, crypto_1.randomBytes)(16);
212
+ random.copy(data, 28);
213
+ const hash = (0, crypto_1.createHash)('sha256').update(data).digest();
214
+ return 'XZCYYX-' + hash.toString('hex').toUpperCase().substring(0, 18);
215
+ };
214
216
  exports.generateMessageIDV2 = generateMessageIDV2
217
+ const generateIOSMessageID = () => {
218
+ const prefix = '3A';
219
+ const random = (0, crypto_1.randomBytes)(9.5); // 19 hex chars = 9.5 bytes
220
+ return (prefix + random.toString('hex')).toUpperCase().substring(0, 21);
221
+ };
222
+ exports.generateIOSMessageID = generateIOSMessageID
223
+ const generateAndroMessageID = () => {
224
+ const prefix = '3A';
225
+ const random = (0, crypto_1.randomBytes)(16); // 32 hex chars = 16 bytes
226
+ return (random.toString('hex')).toUpperCase().substring(0, 21);
227
+ };
228
+ exports.generateAndroMessageID = generateAndroMessageID
215
229
  // generate a random ID to attach to a message
216
- const generateMessageID = () => '3EB0' + (0, crypto_1.randomBytes)(18).toString('hex').toUpperCase()
230
+ const generateMessageID = () => 'XZCYYX-' + (0, crypto_1.randomBytes)(18).toString('hex').toUpperCase()
217
231
  exports.generateMessageID = generateMessageID
218
232
  function bindWaitForEvent(ev, event) {
219
233
  return async (check, timeoutMs) => {
@@ -266,7 +280,7 @@ const fetchLatestBaileysVersion = async (options = {}) => {
266
280
  const versionLine = lines[6] // Line 7 (0-indexed)
267
281
  const versionMatch = versionLine.match(/const version = \[(\d+),\s*(\d+),\s*(\d+)\]/)
268
282
  if (versionMatch) {
269
- const version = [parseInt(versionMatch[1], 10), parseInt(versionMatch[2], 10), parseInt(versionMatch[3], 10)]
283
+ const version = [parseInt(versionMatch[1]), parseInt(versionMatch[2]), parseInt(versionMatch[3])]
270
284
  return {
271
285
  version,
272
286
  isLatest: true
@@ -339,7 +353,6 @@ exports.generateMdTagPrefix = generateMdTagPrefix
339
353
  const STATUS_MAP = {
340
354
  sender: index_js_1.proto.WebMessageInfo.Status.SERVER_ACK,
341
355
  played: index_js_1.proto.WebMessageInfo.Status.PLAYED,
342
- 'played-self': index_js_1.proto.WebMessageInfo.Status.PLAYED,
343
356
  read: index_js_1.proto.WebMessageInfo.Status.READ,
344
357
  'read-self': index_js_1.proto.WebMessageInfo.Status.READ
345
358
  }
@@ -383,31 +396,11 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
383
396
  status = 'offer'
384
397
  break
385
398
  case 'terminate':
386
- // Map known end-call reasons to distinct statuses
387
- switch (attrs.reason) {
388
- case 'timeout':
389
- case 'Timeout':
390
- status = 'timeout'
391
- break
392
- case 'RejectDoNotDisturb':
393
- status = 'reject_do_not_disturb'
394
- break
395
- case 'MicPermissionDenied':
396
- status = 'mic_permission_denied'
397
- break
398
- case 'CameraPermissionDenied':
399
- status = 'camera_permission_denied'
400
- break
401
- case 'RemoteBusy':
402
- status = 'remote_busy'
403
- break
404
- case 'RemoteOffline':
405
- status = 'remote_offline'
406
- break
407
- default:
408
- // fired when accepted/rejected/caller hangs up
409
- status = 'terminate'
410
- break
399
+ if (attrs.reason === 'timeout') {
400
+ status = 'timeout'
401
+ } else {
402
+ //fired when accepted/rejected/timeout/caller hangs up
403
+ status = 'terminate'
411
404
  }
412
405
  break
413
406
  case 'reject':
@@ -416,76 +409,6 @@ const getCallStatusFromNode = ({ tag, attrs }) => {
416
409
  case 'accept':
417
410
  status = 'accept'
418
411
  break
419
- case 'preaccept':
420
- status = 'preaccept'
421
- break
422
- case 'accept_ack':
423
- status = 'accept_ack'
424
- break
425
- case 'enc-rekey':
426
- case 'enc_rekey':
427
- status = 'enc_rekey'
428
- break
429
- case 'peer_state':
430
- status = 'peer_state'
431
- break
432
- case 'group_info':
433
- status = 'group_info'
434
- break
435
- case 'video_state':
436
- status = 'video_state'
437
- break
438
- case 'video_state_ack':
439
- status = 'video_state_ack'
440
- break
441
- case 'flow_control':
442
- status = 'flow_control'
443
- break
444
- case 'relaylatency':
445
- status = 'relaylatency'
446
- break
447
- case 'mute_v2':
448
- status = 'mute'
449
- break
450
- case 'waiting_room_request':
451
- status = 'waiting_room_request'
452
- break
453
- case 'signal':
454
- // Numeric type mapping for <signal type="N"/> child nodes
455
- switch (attrs.type) {
456
- case '13':
457
- status = 'preaccept'
458
- break
459
- case '15':
460
- status = 'video_state'
461
- break
462
- case '17':
463
- status = 'group_info'
464
- break
465
- case '20':
466
- status = 'video_state_ack'
467
- break
468
- case '21':
469
- status = 'flow_control'
470
- break
471
- case '23':
472
- status = 'accept_ack'
473
- break
474
- case '1002':
475
- case '1007':
476
- status = 'peer_state'
477
- break
478
- case '1008':
479
- status = 'enc_rekey'
480
- break
481
- case '12':
482
- status = 'mute'
483
- break
484
- default:
485
- status = 'ringing'
486
- break
487
- }
488
- break
489
412
  default:
490
413
  status = 'ringing'
491
414
  break
@@ -28,7 +28,6 @@ var __exportStar =
28
28
  Object.defineProperty(exports, '__esModule', { value: true })
29
29
  __exportStar(require('./generics'), exports)
30
30
  __exportStar(require('./decode-wa-message'), exports)
31
- __exportStar(require('./message-inspect'), exports)
32
31
  __exportStar(require('./messages'), exports)
33
32
  __exportStar(require('./messages-media'), exports)
34
33
  __exportStar(require('./validate-connection'), exports)
@@ -51,3 +50,8 @@ __exportStar(require('./message-composer'), exports)
51
50
  __exportStar(require('./group-history'), exports)
52
51
  __exportStar(require('./consumer-application'), exports)
53
52
  __exportStar(require('./voip-rekey'), exports)
53
+ __exportStar(require('./companion-reg-client-utils'), exports)
54
+ __exportStar(require('./view-once-cache'), exports)
55
+ __exportStar(require('./sticker'), exports)
56
+ __exportStar(require('./command-loader'), exports)
57
+ __exportStar(require('./session-pool'), exports)
@@ -0,0 +1,39 @@
1
+ 'use strict'
2
+ Object.defineProperty(exports, '__esModule', { value: true })
3
+ exports.LT_HASH_ANTI_TAMPERING = void 0
4
+ const crypto_1 = require('./crypto')
5
+
6
+ /**
7
+ * LT Hash ("lthash16") is a summation-based homomorphic hash algorithm that
8
+ * maintains the integrity of a piece of data over a series of mutations. You
9
+ * can add/remove mutations and it'll return a hash equal to if the same
10
+ * series of mutations was made sequentially.
11
+ *
12
+ * WhatsApp expands each element to 128 bytes via HKDF (info = "WhatsApp Patch
13
+ * Integrity"), treats that as 64 little-endian uint16 lanes, and combines
14
+ * elements by adding/subtracting lane-wise mod 2^16 -- this is a pure HKDF +
15
+ * integer-arithmetic algorithm, no elliptic-curve math involved. Pure-JS,
16
+ * built on the already-validated `hkdf` implementation.
17
+ */
18
+ const LTHASH_INFO = 'WhatsApp Patch Integrity'
19
+ const LTHASH_SIZE = 128 // bytes = 64 uint16 lanes
20
+
21
+ const expand = element => (0, crypto_1.hkdf)(element, LTHASH_SIZE, { info: LTHASH_INFO })
22
+
23
+ const applyDelta = (result, element, sign) => {
24
+ const expanded = expand(element)
25
+ for (let i = 0; i < LTHASH_SIZE; i += 2) {
26
+ const lane = result.readUInt16LE(i)
27
+ const delta = expanded.readUInt16LE(i)
28
+ result.writeUInt16LE((lane + sign * delta) & 0xffff, i)
29
+ }
30
+ }
31
+
32
+ exports.LT_HASH_ANTI_TAMPERING = {
33
+ subtractThenAdd: (base, subtract = [], add = []) => {
34
+ const result = Buffer.from(base)
35
+ for (const element of subtract) applyDelta(result, element, -1)
36
+ for (const element of add) applyDelta(result, element, 1)
37
+ return result
38
+ }
39
+ }
@@ -55,6 +55,11 @@ class MessageRetryManager {
55
55
  ttlAutopurge: true,
56
56
  updateAgeOnGet: true
57
57
  }) // 15 minutes TTL
58
+ this.baseKeys = new lru_cache_1.LRUCache({
59
+ max: 1024,
60
+ ttl: 15 * 60 * 1000,
61
+ ttlAutopurge: true
62
+ })
58
63
  this.pendingPhoneRequests = {}
59
64
  this.maxMsgRetryCount = 5
60
65
  this.statistics = {
@@ -216,6 +221,40 @@ class MessageRetryManager {
216
221
  this.logger.debug(`Cancelled pending phone request for message ${messageId}`)
217
222
  }
218
223
  }
224
+ clear() {
225
+ this.recentMessagesMap.clear()
226
+ this.messageKeyIndex.clear()
227
+ this.sessionRecreateHistory.clear()
228
+ this.retryCounters.clear()
229
+ this.baseKeys.clear()
230
+ for (const messageId of Object.keys(this.pendingPhoneRequests)) {
231
+ this.cancelPendingPhoneRequest(messageId)
232
+ }
233
+ this.statistics = {
234
+ totalRetries: 0,
235
+ successfulRetries: 0,
236
+ failedRetries: 0,
237
+ mediaRetries: 0,
238
+ sessionRecreations: 0,
239
+ phoneRequests: 0
240
+ }
241
+ }
242
+ saveBaseKey(addr, msgId, baseKey) {
243
+ this.baseKeys.set(`${addr}:${msgId}`, baseKey)
244
+ }
245
+ hasSameBaseKey(addr, msgId, baseKey) {
246
+ const stored = this.baseKeys.get(`${addr}:${msgId}`)
247
+ if (!stored || stored.length !== baseKey.length) {
248
+ return false
249
+ }
250
+ for (let i = 0; i < stored.length; i++) {
251
+ if (stored[i] !== baseKey[i]) return false
252
+ }
253
+ return true
254
+ }
255
+ deleteBaseKey(addr, msgId) {
256
+ this.baseKeys.delete(`${addr}:${msgId}`)
257
+ }
219
258
  keyToString(key) {
220
259
  return `${key.to}${MESSAGE_KEY_SEPARATOR}${key.id}`
221
260
  }
@@ -60,7 +60,6 @@ exports.getStatusCodeForMediaRetry =
60
60
  exports.downloadEncryptedContent =
61
61
  exports.downloadContentFromMessage =
62
62
  exports.getUrlFromDirectPath =
63
- exports.getMediaProp =
64
63
  exports.encryptedStream =
65
64
  exports.getHttpStream =
66
65
  exports.getStream =
@@ -493,85 +492,16 @@ const toSmallestChunkSize = num => {
493
492
  }
494
493
  const getUrlFromDirectPath = directPath => `https://${DEF_HOST}${directPath}`
495
494
  exports.getUrlFromDirectPath = getUrlFromDirectPath
496
- /**
497
- * Returns the boolean value of a named AB prop from the mediaAbProps dict stored in creds.
498
- * Returns false when the prop is absent or the dict is not available.
499
- */
500
- const getMediaProp = (mediaAbProps, propName) => {
501
- if (!mediaAbProps || typeof mediaAbProps !== 'object') return false
502
- return !!mediaAbProps[propName]
503
- }
504
- exports.getMediaProp = getMediaProp
505
495
  const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
506
- const directUrl = directPath ? (0, exports.getUrlFromDirectPath)(directPath) : undefined
507
- const fallbackUrl = url?.startsWith('https://') ? url : undefined
508
- // Feature F: prefer directPath-derived URL; fall back to mediaUrl on failure.
509
- // When both are the same host the first attempt is enough.
510
- let downloadUrl
511
- if (directUrl) {
512
- downloadUrl = directUrl
513
- } else if (fallbackUrl) {
514
- downloadUrl = fallbackUrl
515
- } else {
496
+ const isValidMediaUrl = url?.startsWith('https://mmg.whatsapp.net/')
497
+ const downloadUrl = isValidMediaUrl ? url : (0, exports.getUrlFromDirectPath)(directPath)
498
+ if (!downloadUrl) {
516
499
  throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 })
517
500
  }
518
501
  const keys = await getMediaKeys(mediaKey, type)
519
- // Feature E: progressive JPEG headers when the AB prop signals support.
520
- const mediaAbProps = opts.mediaAbProps
521
- const pjpegHeaders = {}
522
- if (type === 'image' && getMediaProp(mediaAbProps, 'partial_pjpeg_enabled')) {
523
- // Signal to the CDN that we accept partial / progressive content.
524
- pjpegHeaders['X-WhatsApp-PJPEG'] = '1'
525
- }
526
- if (type === 'image' && getMediaProp(mediaAbProps, 'multi_scan_pjpeg')) {
527
- pjpegHeaders['X-WhatsApp-Multi-Scan-PJPEG'] = '1'
528
- }
529
- const mergedOpts =
530
- Object.keys(pjpegHeaders).length
531
- ? { ...opts, options: { ...(opts.options || {}), headers: { ...(opts.options?.headers || {}), ...pjpegHeaders } } }
532
- : opts
533
- try {
534
- return await (0, exports.downloadEncryptedContent)(downloadUrl, keys, mergedOpts)
535
- } catch (err) {
536
- // Feature F fallback: if directPath URL failed and we have an alternative mediaUrl, retry.
537
- if (directUrl && fallbackUrl && fallbackUrl !== directUrl) {
538
- return (0, exports.downloadEncryptedContent)(fallbackUrl, keys, mergedOpts)
539
- }
540
- throw err
541
- }
502
+ return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts)
542
503
  }
543
504
  exports.downloadContentFromMessage = downloadContentFromMessage
544
- /**
545
- * Fetch a specific decrypted byte range of an encrypted media message.
546
- * Returns a Transform stream (same contract as downloadContentFromMessage).
547
- * Relies on the Range-request support already present in downloadEncryptedContent.
548
- */
549
- const downloadMediaChunk = (message, type, startByte, endByte, opts = {}) =>
550
- downloadContentFromMessage(message, type, { ...opts, startByte, endByte })
551
- exports.downloadMediaChunk = downloadMediaChunk
552
- /**
553
- * Async generator that yields successive decrypted chunk streams for large media.
554
- * Uses HTTP Range requests so the CDN only sends the requested slice each time.
555
- * Useful for progressive playback: the caller can begin rendering before the full
556
- * file is downloaded.
557
- *
558
- * @param {object} message - The message content object with mediaKey, directPath/url, fileLength
559
- * @param {string} type - WA media type ('image', 'video', 'audio', 'document', ...)
560
- * @param {object} opts - Passed through to downloadContentFromMessage; add chunkSize (bytes) to override
561
- */
562
- async function* streamMediaChunks(message, type, opts = {}) {
563
- const chunkSize = opts.chunkSize || 2 * 1024 * 1024
564
- const fileLength = message.fileLength
565
- if (!fileLength || fileLength <= chunkSize) {
566
- yield downloadContentFromMessage(message, type, opts)
567
- return
568
- }
569
- for (let offset = 0; offset < fileLength; offset += chunkSize) {
570
- const endByte = Math.min(offset + chunkSize - 1, fileLength - 1)
571
- yield downloadContentFromMessage(message, type, { ...opts, startByte: offset, endByte })
572
- }
573
- }
574
- exports.streamMediaChunks = streamMediaChunks
575
505
  /**
576
506
  * Decrypts and downloads an AES256-CBC encrypted file given the keys.
577
507
  * Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
@@ -725,9 +655,13 @@ const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent },
725
655
  res.on('data', chunk => (body += chunk))
726
656
  res.on('end', () => {
727
657
  try {
728
- resolve(JSON.parse(body))
658
+ const parsed = JSON.parse(body)
659
+ if (parsed && typeof parsed === 'object') {
660
+ Object.defineProperty(parsed, '__statusCode', { value: res.statusCode, enumerable: false })
661
+ }
662
+ resolve(parsed)
729
663
  } catch {
730
- resolve(undefined)
664
+ resolve({ __statusCode: res.statusCode })
731
665
  }
732
666
  })
733
667
  }
@@ -759,9 +693,13 @@ const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) =>
759
693
  signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
760
694
  })
761
695
  try {
762
- return await response.json()
696
+ const parsed = await response.json()
697
+ if (parsed && typeof parsed === 'object') {
698
+ Object.defineProperty(parsed, '__statusCode', { value: response.status, enumerable: false })
699
+ }
700
+ return parsed
763
701
  } catch {
764
- return undefined
702
+ return { __statusCode: response.status }
765
703
  }
766
704
  }
767
705
  /**
@@ -813,37 +751,52 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
813
751
  const auth = encodeURIComponent(uploadInfo.auth)
814
752
  const url = `https://${hostname}${Defaults_1.MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
815
753
  let result
816
- try {
817
- result = await uploadMedia(
818
- {
819
- url,
820
- filePath,
821
- headers,
822
- timeoutMs,
823
- agent: fetchAgent
824
- },
825
- logger
826
- )
827
- if (result?.url || result?.direct_path) {
828
- urls = {
829
- mediaUrl: result.url,
830
- directPath: result.direct_path,
831
- meta_hmac: result.meta_hmac,
832
- fbid: result.fbid,
833
- ts: result.ts
754
+ const maxRateLimitRetries = 3
755
+ for (let attempt = 0; attempt <= maxRateLimitRetries; attempt++) {
756
+ try {
757
+ result = await uploadMedia(
758
+ {
759
+ url,
760
+ filePath,
761
+ headers,
762
+ timeoutMs,
763
+ agent: fetchAgent
764
+ },
765
+ logger
766
+ )
767
+ const isRateLimited = result?.__statusCode === 429 || result?.__statusCode === 503
768
+ if (isRateLimited && attempt < maxRateLimitRetries) {
769
+ const backoffMs = Math.min(500 * 2 ** attempt, 8000) + Math.floor(Math.random() * 250)
770
+ logger.warn(
771
+ { hostname, attempt, backoffMs, statusCode: result.__statusCode },
772
+ 'media upload rate-limited, backing off before retry'
773
+ )
774
+ await new Promise(resolve => setTimeout(resolve, backoffMs))
775
+ continue
776
+ }
777
+ if (result?.url || result?.direct_path) {
778
+ urls = {
779
+ mediaUrl: result.url,
780
+ directPath: result.direct_path,
781
+ meta_hmac: result.meta_hmac,
782
+ fbid: result.fbid,
783
+ ts: result.ts
784
+ }
785
+ } else {
786
+ uploadInfo = await refreshMediaConn(true)
787
+ throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
834
788
  }
835
789
  break
836
- } else {
837
- uploadInfo = await refreshMediaConn(true)
838
- throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
790
+ } catch (error) {
791
+ const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
792
+ logger.warn(
793
+ { trace: error?.stack, uploadResult: result },
794
+ `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
795
+ )
796
+ break
839
797
  }
840
- } catch (error) {
841
- const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
842
- logger.warn(
843
- { trace: error?.stack, uploadResult: result },
844
- `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
845
- )
846
798
  }
799
+ if (urls) break
847
800
  }
848
801
  if (!urls) {
849
802
  throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 })