@badzz88/baileys 8.5.2 → 8.5.4
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.
- package/README.md +1 -1
- package/WAProto/WAProto.proto +1256 -75
- package/WAProto/index.d.ts +36042 -19649
- package/WAProto/index.js +181990 -135645
- package/engine-requirements.js +7 -15
- package/lib/Defaults/index.js +22 -17
- package/lib/Signal/Group/group_cipher.js +5 -5
- package/lib/Signal/Group/keyhelper.js +4 -4
- package/lib/Signal/Group/sender-chain-key.js +2 -2
- package/lib/Signal/Group/sender-key-distribution-message.js +1 -1
- package/lib/Signal/Group/sender-key-message.js +3 -3
- package/lib/Signal/Group/sender-key-record.js +1 -1
- package/lib/Signal/Group/sender-message-key.js +5 -5
- package/lib/Signal/libsignal.js +39 -59
- package/lib/Signal/lid-mapping.js +28 -10
- package/lib/Socket/aigroups.js +5 -5
- package/lib/Socket/business.js +3 -11
- package/lib/Socket/chats.js +668 -319
- package/lib/Socket/communities.js +85 -89
- package/lib/Socket/graphql.js +435 -228
- package/lib/Socket/groups.js +132 -211
- package/lib/Socket/index.js +9 -7
- package/lib/Socket/interactive-handler.js +117 -60
- package/lib/Socket/interop.js +56 -175
- package/lib/Socket/managed-account.js +79 -48
- package/lib/Socket/messages-recv.js +898 -340
- package/lib/Socket/messages-send.js +462 -160
- package/lib/Socket/mex.js +40 -53
- package/lib/Socket/newsletter.js +291 -291
- package/lib/Socket/privacy.js +77 -265
- package/lib/Socket/registration.js +164 -157
- package/lib/Socket/socket.js +181 -199
- package/lib/Socket/username.js +144 -227
- package/lib/Store/make-cache-manager-store.js +2 -2
- package/lib/Store/make-in-memory-store.js +25 -141
- package/lib/Types/Auth.js +3 -3
- package/lib/Types/Label.js +1 -1
- package/lib/Types/LabelAssociation.js +1 -1
- package/lib/Types/Newsletter.js +96 -65
- package/lib/Types/State.js +4 -4
- package/lib/Types/index.js +1 -1
- package/lib/Utils/auth-utils.js +52 -57
- package/lib/Utils/browser-utils.js +1 -1
- package/lib/Utils/business.js +2 -6
- package/lib/Utils/chat-utils.js +102 -38
- package/lib/Utils/crypto.js +54 -84
- package/lib/Utils/decode-wa-message.js +316 -83
- package/lib/Utils/event-buffer.js +37 -61
- package/lib/Utils/generics.js +157 -103
- package/lib/Utils/group-history.js +3 -12
- package/lib/Utils/history.js +15 -15
- package/lib/Utils/index.js +1 -5
- package/lib/Utils/link-preview.js +3 -8
- package/lib/Utils/lt-hash.js +2 -35
- package/lib/Utils/make-mutex.js +1 -1
- package/lib/Utils/message-inspect.js +393 -0
- package/lib/Utils/message-retry-manager.js +27 -92
- package/lib/Utils/messages-media.js +159 -130
- package/lib/Utils/messages.js +144 -566
- package/lib/Utils/meta-ai-msmsg.js +62 -202
- package/lib/Utils/noise-handler.js +19 -21
- package/lib/Utils/offline-node-processor.js +3 -8
- package/lib/Utils/pre-key-manager.js +13 -23
- package/lib/Utils/process-message.js +197 -106
- package/lib/Utils/reporting-utils.js +2 -2
- package/lib/Utils/signal.js +28 -63
- package/lib/Utils/stanza-ack.js +39 -12
- package/lib/Utils/sync-action-utils.js +4 -7
- package/lib/Utils/tc-token-utils.js +20 -25
- package/lib/Utils/use-multi-file-auth-state.js +30 -14
- package/lib/Utils/validate-connection.js +59 -19
- package/lib/WABinary/decode.js +50 -16
- package/lib/WABinary/encode.js +54 -224
- package/lib/WABinary/generic-utils.js +7 -97
- package/lib/WABinary/jid-utils.js +27 -14
- package/lib/WAM/encode.js +7 -7
- package/lib/WAUSync/Protocols/USyncBusinessProtocol.js +58 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.js +7 -1
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +13 -5
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +1 -4
- package/lib/WAUSync/Protocols/USyncFeatureProtocol.js +20 -7
- package/lib/WAUSync/Protocols/USyncNewsletterProtocol.js +865 -0
- package/lib/WAUSync/Protocols/USyncPictureProtocol.js +1 -1
- package/lib/WAUSync/Protocols/USyncSidelistProtocol.js +2 -2
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js +7 -2
- package/lib/WAUSync/Protocols/USyncTextStatusProtocol.js +1 -1
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +2 -1
- package/lib/WAUSync/Protocols/index.js +1 -0
- package/lib/WAUSync/USyncBackoff.js +26 -0
- package/lib/WAUSync/USyncQuery.js +77 -6
- package/lib/WAUSync/USyncUser.js +16 -8
- package/lib/WAUSync/index.js +1 -0
- package/lib/antiban.js +608 -854
- package/lib/index.js +3 -3
- package/package.json +16 -19
- package/lib/Socket/luxu.js +0 -387
- package/lib/Socket/text-router.js +0 -83
- package/lib/Store/keyed-db.js +0 -108
- package/lib/Types/Mex.js +0 -112
- package/lib/Utils/command-loader.d.ts +0 -31
- package/lib/Utils/command-loader.js +0 -100
- package/lib/Utils/companion-reg-client-utils.js +0 -42
- package/lib/Utils/curve25519-js.js +0 -275
- package/lib/Utils/native-bridge.js +0 -82
- package/lib/Utils/session-pool.d.ts +0 -16
- package/lib/Utils/session-pool.js +0 -94
- package/lib/Utils/sticker.d.ts +0 -17
- package/lib/Utils/sticker.js +0 -145
- package/lib/Utils/view-once-cache.d.ts +0 -9
- package/lib/Utils/view-once-cache.js +0 -79
|
@@ -60,6 +60,7 @@ exports.getStatusCodeForMediaRetry =
|
|
|
60
60
|
exports.downloadEncryptedContent =
|
|
61
61
|
exports.downloadContentFromMessage =
|
|
62
62
|
exports.getUrlFromDirectPath =
|
|
63
|
+
exports.getMediaProp =
|
|
63
64
|
exports.encryptedStream =
|
|
64
65
|
exports.getHttpStream =
|
|
65
66
|
exports.getStream =
|
|
@@ -93,7 +94,7 @@ const crypto_1 = require('./crypto')
|
|
|
93
94
|
const generics_1 = require('./generics')
|
|
94
95
|
const getTmpFilesDirectory = () => (0, os_1.tmpdir)()
|
|
95
96
|
const getImageProcessingLibrary = async () => {
|
|
96
|
-
|
|
97
|
+
|
|
97
98
|
const [jimp, sharp] = await Promise.all([
|
|
98
99
|
Promise.resolve()
|
|
99
100
|
.then(() => __importStar(require('jimp')))
|
|
@@ -146,13 +147,13 @@ const getRawMediaUploadData = async (media, mediaType, logger) => {
|
|
|
146
147
|
try {
|
|
147
148
|
await fs_1.promises.unlink(filePath)
|
|
148
149
|
} catch {
|
|
149
|
-
|
|
150
|
+
|
|
150
151
|
}
|
|
151
152
|
throw error
|
|
152
153
|
}
|
|
153
154
|
}
|
|
154
155
|
exports.getRawMediaUploadData = getRawMediaUploadData
|
|
155
|
-
|
|
156
|
+
|
|
156
157
|
async function getMediaKeys(buffer, mediaType) {
|
|
157
158
|
if (!buffer) {
|
|
158
159
|
throw new boom_1.Boom('Cannot derive from empty media key')
|
|
@@ -160,7 +161,7 @@ async function getMediaKeys(buffer, mediaType) {
|
|
|
160
161
|
if (typeof buffer === 'string') {
|
|
161
162
|
buffer = Buffer.from(buffer.replace('data:;base64,', ''), 'base64')
|
|
162
163
|
}
|
|
163
|
-
|
|
164
|
+
|
|
164
165
|
const expandedMediaKey = (0, crypto_1.hkdf)(buffer, 112, { info: (0, exports.hkdfInfoKey)(mediaType) })
|
|
165
166
|
return {
|
|
166
167
|
iv: expandedMediaKey.slice(0, 16),
|
|
@@ -168,7 +169,7 @@ async function getMediaKeys(buffer, mediaType) {
|
|
|
168
169
|
macKey: expandedMediaKey.slice(48, 80)
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
|
-
|
|
172
|
+
|
|
172
173
|
const extractVideoThumb = async (path, destPath, time, size) =>
|
|
173
174
|
new Promise((resolve, reject) => {
|
|
174
175
|
const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`
|
|
@@ -181,8 +182,8 @@ const extractVideoThumb = async (path, destPath, time, size) =>
|
|
|
181
182
|
})
|
|
182
183
|
})
|
|
183
184
|
const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
|
|
186
|
+
|
|
186
187
|
if (bufferOrFilePath instanceof stream_1.Readable) {
|
|
187
188
|
bufferOrFilePath = await (0, exports.toBuffer)(bufferOrFilePath)
|
|
188
189
|
}
|
|
@@ -225,9 +226,9 @@ const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
|
225
226
|
if (Buffer.isBuffer(mediaUpload)) {
|
|
226
227
|
buffer = mediaUpload
|
|
227
228
|
} else {
|
|
228
|
-
|
|
229
|
+
|
|
229
230
|
const { stream } = await (0, exports.getStream)(mediaUpload)
|
|
230
|
-
|
|
231
|
+
|
|
231
232
|
buffer = await (0, exports.toBuffer)(stream)
|
|
232
233
|
}
|
|
233
234
|
const lib = await getImageProcessingLibrary()
|
|
@@ -253,7 +254,7 @@ const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
|
253
254
|
}
|
|
254
255
|
}
|
|
255
256
|
exports.generateProfilePicture = generateProfilePicture
|
|
256
|
-
|
|
257
|
+
|
|
257
258
|
const mediaMessageSHA256B64 = message => {
|
|
258
259
|
const media = Object.values(message)[0]
|
|
259
260
|
return media?.fileSha256 && Buffer.from(media.fileSha256).toString('base64')
|
|
@@ -274,12 +275,10 @@ async function getAudioDuration(buffer) {
|
|
|
274
275
|
}
|
|
275
276
|
return metadata.format.duration
|
|
276
277
|
}
|
|
277
|
-
|
|
278
|
-
referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
|
|
279
|
-
*/
|
|
278
|
+
|
|
280
279
|
async function getAudioWaveform(buffer, logger) {
|
|
281
280
|
try {
|
|
282
|
-
|
|
281
|
+
|
|
283
282
|
const { default: decoder } = await Promise.resolve().then(() => __importStar(require('audio-decode')))
|
|
284
283
|
let audioData
|
|
285
284
|
if (Buffer.isBuffer(buffer)) {
|
|
@@ -291,22 +290,22 @@ async function getAudioWaveform(buffer, logger) {
|
|
|
291
290
|
audioData = await (0, exports.toBuffer)(buffer)
|
|
292
291
|
}
|
|
293
292
|
const audioBuffer = await decoder(audioData)
|
|
294
|
-
const rawData = audioBuffer.getChannelData(0)
|
|
295
|
-
const samples = 64
|
|
296
|
-
const blockSize = Math.floor(rawData.length / samples)
|
|
293
|
+
const rawData = audioBuffer.getChannelData(0)
|
|
294
|
+
const samples = 64
|
|
295
|
+
const blockSize = Math.floor(rawData.length / samples)
|
|
297
296
|
const filteredData = []
|
|
298
297
|
for (let i = 0; i < samples; i++) {
|
|
299
|
-
const blockStart = blockSize * i
|
|
298
|
+
const blockStart = blockSize * i
|
|
300
299
|
let sum = 0
|
|
301
300
|
for (let j = 0; j < blockSize; j++) {
|
|
302
|
-
sum = sum + Math.abs(rawData[blockStart + j])
|
|
301
|
+
sum = sum + Math.abs(rawData[blockStart + j])
|
|
303
302
|
}
|
|
304
|
-
filteredData.push(sum / blockSize)
|
|
303
|
+
filteredData.push(sum / blockSize)
|
|
305
304
|
}
|
|
306
|
-
|
|
305
|
+
|
|
307
306
|
const multiplier = Math.pow(Math.max(...filteredData), -1)
|
|
308
307
|
const normalizedData = filteredData.map(n => n * multiplier)
|
|
309
|
-
|
|
308
|
+
|
|
310
309
|
const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)))
|
|
311
310
|
return waveform
|
|
312
311
|
} catch (e) {
|
|
@@ -347,7 +346,7 @@ const getStream = async (item, opts) => {
|
|
|
347
346
|
return { stream: (0, fs_1.createReadStream)(item.url), type: 'file' }
|
|
348
347
|
}
|
|
349
348
|
exports.getStream = getStream
|
|
350
|
-
|
|
349
|
+
|
|
351
350
|
async function generateThumbnail(file, mediaType, options) {
|
|
352
351
|
let thumbnail
|
|
353
352
|
let originalImageDimensions
|
|
@@ -385,7 +384,7 @@ const getHttpStream = async (url, options = {}) => {
|
|
|
385
384
|
if (!response.ok) {
|
|
386
385
|
throw new boom_1.Boom(`Failed to fetch stream from ${url}`, { statusCode: response.status, data: { url } })
|
|
387
386
|
}
|
|
388
|
-
|
|
387
|
+
|
|
389
388
|
return response.body instanceof stream_1.Readable ? response.body : stream_1.Readable.fromWeb(response.body)
|
|
390
389
|
}
|
|
391
390
|
exports.getHttpStream = getHttpStream
|
|
@@ -416,7 +415,7 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
416
415
|
const onChunk = async buff => {
|
|
417
416
|
sha256Enc.update(buff)
|
|
418
417
|
hmac.update(buff)
|
|
419
|
-
|
|
418
|
+
|
|
420
419
|
if (!encFileWriteStream.write(buff)) {
|
|
421
420
|
await (0, events_1.once)(encFileWriteStream, 'drain')
|
|
422
421
|
}
|
|
@@ -450,8 +449,8 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
450
449
|
encFileWriteStream.end()
|
|
451
450
|
originalFileStream?.end?.()
|
|
452
451
|
stream.destroy()
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
|
|
453
|
+
|
|
455
454
|
await encFinishPromise
|
|
456
455
|
await originalFinishPromise
|
|
457
456
|
logger?.debug('encrypted data successfully')
|
|
@@ -465,7 +464,7 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
|
|
|
465
464
|
fileLength
|
|
466
465
|
}
|
|
467
466
|
} catch (error) {
|
|
468
|
-
|
|
467
|
+
|
|
469
468
|
encFileWriteStream.destroy()
|
|
470
469
|
originalFileStream?.destroy?.()
|
|
471
470
|
aes.destroy()
|
|
@@ -492,25 +491,74 @@ const toSmallestChunkSize = num => {
|
|
|
492
491
|
}
|
|
493
492
|
const getUrlFromDirectPath = directPath => `https://${DEF_HOST}${directPath}`
|
|
494
493
|
exports.getUrlFromDirectPath = getUrlFromDirectPath
|
|
494
|
+
|
|
495
|
+
const getMediaProp = (mediaAbProps, propName) => {
|
|
496
|
+
if (!mediaAbProps || typeof mediaAbProps !== 'object') return false
|
|
497
|
+
return !!mediaAbProps[propName]
|
|
498
|
+
}
|
|
499
|
+
exports.getMediaProp = getMediaProp
|
|
495
500
|
const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
|
|
501
|
+
const directUrl = directPath ? (0, exports.getUrlFromDirectPath)(directPath) : undefined
|
|
502
|
+
const fallbackUrl = url?.startsWith('https://') ? url : undefined
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
let downloadUrl
|
|
506
|
+
if (directUrl) {
|
|
507
|
+
downloadUrl = directUrl
|
|
508
|
+
} else if (fallbackUrl) {
|
|
509
|
+
downloadUrl = fallbackUrl
|
|
510
|
+
} else {
|
|
499
511
|
throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 })
|
|
500
512
|
}
|
|
501
513
|
const keys = await getMediaKeys(mediaKey, type)
|
|
502
|
-
|
|
514
|
+
|
|
515
|
+
const mediaAbProps = opts.mediaAbProps
|
|
516
|
+
const pjpegHeaders = {}
|
|
517
|
+
if (type === 'image' && getMediaProp(mediaAbProps, 'partial_pjpeg_enabled')) {
|
|
518
|
+
|
|
519
|
+
pjpegHeaders['X-WhatsApp-PJPEG'] = '1'
|
|
520
|
+
}
|
|
521
|
+
if (type === 'image' && getMediaProp(mediaAbProps, 'multi_scan_pjpeg')) {
|
|
522
|
+
pjpegHeaders['X-WhatsApp-Multi-Scan-PJPEG'] = '1'
|
|
523
|
+
}
|
|
524
|
+
const mergedOpts = Object.keys(pjpegHeaders).length
|
|
525
|
+
? { ...opts, options: { ...(opts.options || {}), headers: { ...(opts.options?.headers || {}), ...pjpegHeaders } } }
|
|
526
|
+
: opts
|
|
527
|
+
try {
|
|
528
|
+
return await (0, exports.downloadEncryptedContent)(downloadUrl, keys, mergedOpts)
|
|
529
|
+
} catch (err) {
|
|
530
|
+
|
|
531
|
+
if (directUrl && fallbackUrl && fallbackUrl !== directUrl) {
|
|
532
|
+
return (0, exports.downloadEncryptedContent)(fallbackUrl, keys, mergedOpts)
|
|
533
|
+
}
|
|
534
|
+
throw err
|
|
535
|
+
}
|
|
503
536
|
}
|
|
504
537
|
exports.downloadContentFromMessage = downloadContentFromMessage
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
538
|
+
|
|
539
|
+
const downloadMediaChunk = (message, type, startByte, endByte, opts = {}) =>
|
|
540
|
+
downloadContentFromMessage(message, type, { ...opts, startByte, endByte })
|
|
541
|
+
exports.downloadMediaChunk = downloadMediaChunk
|
|
542
|
+
|
|
543
|
+
async function* streamMediaChunks(message, type, opts = {}) {
|
|
544
|
+
const chunkSize = opts.chunkSize || 2 * 1024 * 1024
|
|
545
|
+
const fileLength = message.fileLength
|
|
546
|
+
if (!fileLength || fileLength <= chunkSize) {
|
|
547
|
+
yield downloadContentFromMessage(message, type, opts)
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
for (let offset = 0; offset < fileLength; offset += chunkSize) {
|
|
551
|
+
const endByte = Math.min(offset + chunkSize - 1, fileLength - 1)
|
|
552
|
+
yield downloadContentFromMessage(message, type, { ...opts, startByte: offset, endByte })
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
exports.streamMediaChunks = streamMediaChunks
|
|
556
|
+
|
|
509
557
|
const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
|
|
510
558
|
let bytesFetched = 0
|
|
511
559
|
let startChunk = 0
|
|
512
560
|
let firstBlockIsIV = false
|
|
513
|
-
|
|
561
|
+
|
|
514
562
|
if (startByte) {
|
|
515
563
|
const chunk = toSmallestChunkSize(startByte || 0)
|
|
516
564
|
if (chunk) {
|
|
@@ -531,7 +579,7 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
531
579
|
headers.Range += endChunk
|
|
532
580
|
}
|
|
533
581
|
}
|
|
534
|
-
|
|
582
|
+
|
|
535
583
|
const fetched = await (0, exports.getHttpStream)(downloadUrl, {
|
|
536
584
|
...(options || {}),
|
|
537
585
|
headers
|
|
@@ -561,8 +609,8 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
561
609
|
data = data.slice(AES_CHUNK_SIZE)
|
|
562
610
|
}
|
|
563
611
|
aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue)
|
|
564
|
-
|
|
565
|
-
|
|
612
|
+
|
|
613
|
+
|
|
566
614
|
if (endByte) {
|
|
567
615
|
aes.setAutoPadding(false)
|
|
568
616
|
}
|
|
@@ -583,7 +631,15 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
|
|
|
583
631
|
}
|
|
584
632
|
}
|
|
585
633
|
})
|
|
586
|
-
|
|
634
|
+
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
;(0, stream_1.pipeline)(fetched, output, error => {
|
|
638
|
+
if (error && !output.destroyed) {
|
|
639
|
+
output.destroy(error)
|
|
640
|
+
}
|
|
641
|
+
})
|
|
642
|
+
return output
|
|
587
643
|
}
|
|
588
644
|
exports.downloadEncryptedContent = downloadEncryptedContent
|
|
589
645
|
function extensionForMediaMessage(message) {
|
|
@@ -606,6 +662,14 @@ const isNodeRuntime = () => {
|
|
|
606
662
|
typeof globalThis.Deno === 'undefined'
|
|
607
663
|
)
|
|
608
664
|
}
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
const isFetchDispatcher = agent => {
|
|
670
|
+
return !!agent && typeof agent.dispatch === 'function'
|
|
671
|
+
}
|
|
672
|
+
exports.isFetchDispatcher = isFetchDispatcher
|
|
609
673
|
const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
|
|
610
674
|
if (redirectCount > 5) {
|
|
611
675
|
throw new Error('Too many redirects')
|
|
@@ -615,7 +679,7 @@ const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent },
|
|
|
615
679
|
parsedUrl.protocol === 'https:'
|
|
616
680
|
? await Promise.resolve().then(() => __importStar(require('https')))
|
|
617
681
|
: await Promise.resolve().then(() => __importStar(require('http')))
|
|
618
|
-
|
|
682
|
+
|
|
619
683
|
const fileStats = await fs_1.promises.stat(filePath)
|
|
620
684
|
const fileSize = fileStats.size
|
|
621
685
|
return new Promise((resolve, reject) => {
|
|
@@ -629,13 +693,13 @@ const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent },
|
|
|
629
693
|
...headers,
|
|
630
694
|
'Content-Length': fileSize
|
|
631
695
|
},
|
|
632
|
-
agent,
|
|
696
|
+
agent: isFetchDispatcher(agent) ? undefined : agent,
|
|
633
697
|
timeout: timeoutMs
|
|
634
698
|
},
|
|
635
699
|
res => {
|
|
636
|
-
|
|
700
|
+
|
|
637
701
|
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
638
|
-
res.resume()
|
|
702
|
+
res.resume()
|
|
639
703
|
const newUrl = new url_1.URL(res.headers.location, url).toString()
|
|
640
704
|
resolve(
|
|
641
705
|
(0, exports.uploadWithNodeHttp)(
|
|
@@ -655,13 +719,9 @@ const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent },
|
|
|
655
719
|
res.on('data', chunk => (body += chunk))
|
|
656
720
|
res.on('end', () => {
|
|
657
721
|
try {
|
|
658
|
-
|
|
659
|
-
if (parsed && typeof parsed === 'object') {
|
|
660
|
-
Object.defineProperty(parsed, '__statusCode', { value: res.statusCode, enumerable: false })
|
|
661
|
-
}
|
|
662
|
-
resolve(parsed)
|
|
722
|
+
resolve(JSON.parse(body))
|
|
663
723
|
} catch {
|
|
664
|
-
resolve(
|
|
724
|
+
resolve(undefined)
|
|
665
725
|
}
|
|
666
726
|
})
|
|
667
727
|
}
|
|
@@ -681,11 +741,11 @@ const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent },
|
|
|
681
741
|
}
|
|
682
742
|
exports.uploadWithNodeHttp = uploadWithNodeHttp
|
|
683
743
|
const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
|
|
684
|
-
|
|
744
|
+
|
|
685
745
|
const nodeStream = (0, fs_1.createReadStream)(filePath)
|
|
686
746
|
const webStream = stream_1.Readable.toWeb(nodeStream)
|
|
687
747
|
const response = await fetch(url, {
|
|
688
|
-
dispatcher: agent,
|
|
748
|
+
dispatcher: isFetchDispatcher(agent) ? agent : undefined,
|
|
689
749
|
method: 'POST',
|
|
690
750
|
body: webStream,
|
|
691
751
|
headers,
|
|
@@ -693,49 +753,32 @@ const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) =>
|
|
|
693
753
|
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
|
|
694
754
|
})
|
|
695
755
|
try {
|
|
696
|
-
|
|
697
|
-
if (parsed && typeof parsed === 'object') {
|
|
698
|
-
Object.defineProperty(parsed, '__statusCode', { value: response.status, enumerable: false })
|
|
699
|
-
}
|
|
700
|
-
return parsed
|
|
756
|
+
return await response.json()
|
|
701
757
|
} catch {
|
|
702
|
-
return
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
* Uploads media to WhatsApp servers.
|
|
707
|
-
*
|
|
708
|
-
* ## Why we have two upload implementations:
|
|
709
|
-
*
|
|
710
|
-
* Node.js's native `fetch` (powered by undici) has a known bug where it buffers
|
|
711
|
-
* the entire request body in memory before sending, even when using streams.
|
|
712
|
-
* This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
|
|
713
|
-
* See: https://github.com/nodejs/undici/issues/4058
|
|
714
|
-
*
|
|
715
|
-
* Other runtimes (Bun, Deno, browsers) correctly stream the request body without
|
|
716
|
-
* buffering, so we can use the web-standard Fetch API there.
|
|
717
|
-
*
|
|
718
|
-
* ## Future considerations:
|
|
719
|
-
* Once the undici bug is fixed, we can simplify this to use only the Fetch API
|
|
720
|
-
* across all runtimes. Monitor the GitHub issue for updates.
|
|
721
|
-
*/
|
|
758
|
+
return undefined
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
722
762
|
const uploadMedia = async (params, logger) => {
|
|
723
|
-
if (isNodeRuntime()) {
|
|
763
|
+
if (isNodeRuntime() && !isFetchDispatcher(params.agent)) {
|
|
724
764
|
logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)')
|
|
725
765
|
return (0, exports.uploadWithNodeHttp)(params)
|
|
726
766
|
} else {
|
|
767
|
+
|
|
768
|
+
|
|
727
769
|
logger?.debug('Using web-standard Fetch API for upload')
|
|
728
770
|
return uploadWithFetch(params)
|
|
729
771
|
}
|
|
730
772
|
}
|
|
773
|
+
exports.uploadMedia = uploadMedia
|
|
731
774
|
const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
|
|
732
775
|
return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
|
|
733
|
-
|
|
776
|
+
|
|
734
777
|
let uploadInfo = await refreshMediaConn(false)
|
|
735
778
|
let urls
|
|
736
779
|
const hosts = [...customUploadHosts, ...uploadInfo.hosts]
|
|
737
780
|
fileEncSha256B64 = (0, exports.encodeBase64EncodedStringForUpload)(fileEncSha256B64)
|
|
738
|
-
|
|
781
|
+
|
|
739
782
|
const customHeaders = (() => {
|
|
740
783
|
const hdrs = options?.headers
|
|
741
784
|
if (!hdrs) return {}
|
|
@@ -751,52 +794,37 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
751
794
|
const auth = encodeURIComponent(uploadInfo.auth)
|
|
752
795
|
const url = `https://${hostname}${Defaults_1.MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
|
|
753
796
|
let result
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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)}`)
|
|
797
|
+
try {
|
|
798
|
+
result = await uploadMedia(
|
|
799
|
+
{
|
|
800
|
+
url,
|
|
801
|
+
filePath,
|
|
802
|
+
headers,
|
|
803
|
+
timeoutMs,
|
|
804
|
+
agent: fetchAgent
|
|
805
|
+
},
|
|
806
|
+
logger
|
|
807
|
+
)
|
|
808
|
+
if (result?.url || result?.direct_path) {
|
|
809
|
+
urls = {
|
|
810
|
+
mediaUrl: result.url,
|
|
811
|
+
directPath: result.direct_path,
|
|
812
|
+
meta_hmac: result.meta_hmac,
|
|
813
|
+
fbid: result.fbid,
|
|
814
|
+
ts: result.ts
|
|
788
815
|
}
|
|
789
816
|
break
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
{ trace: error?.stack, uploadResult: result },
|
|
794
|
-
`Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
|
|
795
|
-
)
|
|
796
|
-
break
|
|
817
|
+
} else {
|
|
818
|
+
uploadInfo = await refreshMediaConn(true)
|
|
819
|
+
throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
|
|
797
820
|
}
|
|
821
|
+
} catch (error) {
|
|
822
|
+
const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
|
|
823
|
+
logger.warn(
|
|
824
|
+
{ trace: error?.stack, uploadResult: result },
|
|
825
|
+
`Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`
|
|
826
|
+
)
|
|
798
827
|
}
|
|
799
|
-
if (urls) break
|
|
800
828
|
}
|
|
801
829
|
if (!urls) {
|
|
802
830
|
throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 })
|
|
@@ -806,11 +834,12 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
|
|
|
806
834
|
}
|
|
807
835
|
exports.getWAUploadToServer = getWAUploadToServer
|
|
808
836
|
const getMediaRetryKey = mediaKey => {
|
|
837
|
+
if (typeof mediaKey === 'string') {
|
|
838
|
+
mediaKey = Buffer.from(mediaKey.replace('data:;base64,', ''), 'base64')
|
|
839
|
+
}
|
|
809
840
|
return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
|
|
810
841
|
}
|
|
811
|
-
|
|
812
|
-
* Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
|
|
813
|
-
*/
|
|
842
|
+
|
|
814
843
|
const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
815
844
|
const recp = { stanzaId: key.id }
|
|
816
845
|
const recpBuffer = index_js_1.proto.ServerErrorReceipt.encode(recp).finish()
|
|
@@ -825,9 +854,9 @@ const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
|
825
854
|
type: 'server-error'
|
|
826
855
|
},
|
|
827
856
|
content: [
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
857
|
+
|
|
858
|
+
|
|
859
|
+
|
|
831
860
|
{
|
|
832
861
|
tag: 'encrypt',
|
|
833
862
|
attrs: {},
|
|
@@ -841,7 +870,7 @@ const encryptMediaRetryRequest = (key, mediaKey, meId) => {
|
|
|
841
870
|
attrs: {
|
|
842
871
|
jid: key.remoteJid,
|
|
843
872
|
from_me: (!!key.fromMe).toString(),
|
|
844
|
-
|
|
873
|
+
|
|
845
874
|
participant: key.participant || undefined
|
|
846
875
|
}
|
|
847
876
|
}
|