@pontalabs/baileys 1.0.10 → 1.1.0

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 (119) hide show
  1. package/README.md +96 -85
  2. package/lib/Defaults/connection.js +39 -0
  3. package/lib/Defaults/constants.js +50 -0
  4. package/lib/Defaults/history.js +13 -0
  5. package/lib/Defaults/index.js +36 -151
  6. package/lib/Defaults/media.js +43 -0
  7. package/lib/Defaults/phonenumber-mcc.json +221 -221
  8. package/lib/Defaults/prefix.js +12 -0
  9. package/lib/Defaults/vialeys-version.json +7 -0
  10. package/lib/Signal/Group/ciphertext-message.js +10 -15
  11. package/lib/Signal/Group/group-session-builder.js +34 -60
  12. package/lib/Signal/Group/group_cipher.js +69 -76
  13. package/lib/Signal/Group/index.js +41 -60
  14. package/lib/Signal/Group/keyhelper.js +12 -57
  15. package/lib/Signal/Group/sender-chain-key.js +20 -33
  16. package/lib/Signal/Group/sender-key-distribution-message.js +43 -48
  17. package/lib/Signal/Group/sender-key-message.js +58 -53
  18. package/lib/Signal/Group/sender-key-name.js +21 -31
  19. package/lib/Signal/Group/sender-key-record.js +21 -34
  20. package/lib/Signal/Group/sender-key-state.js +59 -106
  21. package/lib/Signal/Group/sender-message-key.js +16 -21
  22. package/lib/Signal/libsignal.js +282 -203
  23. package/lib/Signal/lid-mapping.js +164 -184
  24. package/lib/Socket/Client/index.js +31 -22
  25. package/lib/Socket/Client/types.js +9 -15
  26. package/lib/Socket/Client/websocket.js +42 -37
  27. package/lib/Socket/business.js +212 -336
  28. package/lib/Socket/chats.js +696 -940
  29. package/lib/Socket/community.js +454 -369
  30. package/lib/Socket/groups.js +278 -280
  31. package/lib/Socket/index.js +9 -21
  32. package/lib/Socket/messages-recv.js +1027 -1570
  33. package/lib/Socket/messages-send.js +1180 -1136
  34. package/lib/Socket/mex.js +59 -0
  35. package/lib/Socket/newsletter.js +193 -251
  36. package/lib/Socket/socket.js +758 -898
  37. package/lib/Store/index.js +35 -24
  38. package/lib/Store/make-cache-manager-store.js +53 -66
  39. package/lib/Store/make-in-memory-store.js +319 -325
  40. package/lib/Store/make-ordered-dictionary.js +49 -57
  41. package/lib/Store/object-repository.js +10 -15
  42. package/lib/Types/Auth.js +2 -3
  43. package/lib/Types/Bussines.js +2 -3
  44. package/lib/Types/Call.js +2 -3
  45. package/lib/Types/Chat.js +10 -9
  46. package/lib/Types/Contact.js +2 -3
  47. package/lib/Types/Events.js +2 -3
  48. package/lib/Types/GroupMetadata.js +2 -3
  49. package/lib/Types/Label.js +6 -12
  50. package/lib/Types/LabelAssociation.js +4 -13
  51. package/lib/Types/Message.js +10 -18
  52. package/lib/Types/MexUpdates.js +15 -18
  53. package/lib/Types/Newsletter.js +26 -34
  54. package/lib/Types/Product.js +2 -3
  55. package/lib/Types/Signal.js +2 -3
  56. package/lib/Types/Socket.js +2 -3
  57. package/lib/Types/State.js +8 -13
  58. package/lib/Types/USync.js +2 -3
  59. package/lib/Types/index.js +49 -40
  60. package/lib/Utils/auth-utils.js +190 -495
  61. package/lib/Utils/browser-utils.js +29 -0
  62. package/lib/Utils/business.js +177 -199
  63. package/lib/Utils/chat-utils.js +711 -624
  64. package/lib/Utils/crypto.js +116 -161
  65. package/lib/Utils/decode-wa-message.js +225 -321
  66. package/lib/Utils/event-buffer.js +363 -405
  67. package/lib/Utils/generics.js +318 -472
  68. package/lib/Utils/history.js +72 -76
  69. package/lib/Utils/index.js +49 -41
  70. package/lib/Utils/link-preview.js +59 -103
  71. package/lib/Utils/logger.js +4 -7
  72. package/lib/Utils/lt-hash.js +29 -42
  73. package/lib/Utils/make-mutex.js +26 -42
  74. package/lib/Utils/message-retry-manager.js +134 -160
  75. package/lib/Utils/messages-media.js +665 -731
  76. package/lib/Utils/messages.js +518 -1
  77. package/lib/Utils/noise-handler.js +127 -125
  78. package/lib/Utils/pre-key-manager.js +86 -0
  79. package/lib/Utils/process-message.js +739 -351
  80. package/lib/Utils/signal.js +146 -129
  81. package/lib/Utils/use-multi-file-auth-state.js +68 -135
  82. package/lib/Utils/validate-connection.js +183 -142
  83. package/lib/Utils/vialeys-event-stream.js +41 -0
  84. package/lib/WABinary/constants.js +1281 -1289
  85. package/lib/WABinary/decode.js +145 -200
  86. package/lib/WABinary/encode.js +149 -202
  87. package/lib/WABinary/generic-utils.js +79 -97
  88. package/lib/WABinary/index.js +36 -25
  89. package/lib/WABinary/jid-utils.js +91 -123
  90. package/lib/WABinary/types.js +2 -3
  91. package/lib/WAM/BinaryInfo.js +8 -13
  92. package/lib/WAM/constants.js +16584 -11464
  93. package/lib/WAM/encode.js +117 -133
  94. package/lib/WAM/index.js +34 -23
  95. package/lib/WAUSync/Protocols/USyncBotProfileProtocol.js +62 -69
  96. package/lib/WAUSync/Protocols/USyncContactProtocol.js +11 -25
  97. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js +32 -44
  98. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +13 -23
  99. package/lib/WAUSync/Protocols/USyncLIDProtocol.js +24 -38
  100. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +17 -31
  101. package/lib/WAUSync/Protocols/index.js +37 -26
  102. package/lib/WAUSync/USyncQuery.js +66 -66
  103. package/lib/WAUSync/USyncUser.js +13 -18
  104. package/lib/WAUSync/index.js +34 -23
  105. package/lib/index.js +95 -28
  106. package/package.json +4 -3
  107. package/lib/Defaults/baileys-version.json +0 -3
  108. package/lib/Signal/Group/queue-job.d.ts +0 -1
  109. package/lib/Signal/Group/queue-job.js +0 -57
  110. package/lib/Socket/usync.d.ts +0 -37
  111. package/lib/Socket/usync.js +0 -83
  112. package/lib/Utils/baileys-event-stream.d.ts +0 -18
  113. package/lib/Utils/baileys-event-stream.js +0 -70
  114. package/lib/Utils/signal-housekeeping.d.ts +0 -19
  115. package/lib/Utils/signal-housekeeping.js +0 -73
  116. package/lib/Utils/use-mongo-file-auth-state.d.ts +0 -6
  117. package/lib/Utils/use-mongo-file-auth-state.js +0 -84
  118. package/lib/Utils/use-single-file-auth-state.d.ts +0 -13
  119. package/lib/Utils/use-single-file-auth-state.js +0 -80
@@ -1,872 +1,806 @@
1
- "use strict"
2
-
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k
5
- var desc = Object.getOwnPropertyDescriptor(m, k)
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k] } }
8
- }
9
- Object.defineProperty(o, k2, desc)
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k
12
- o[k2] = m[k]
13
- }))
14
-
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v })
17
- }) : function(o, v) {
18
- o["default"] = v
19
- })
20
-
21
- var __importStar = (this && this.__importStar) || function (mod) {
22
- if (mod && mod.__esModule) return mod
23
- var result = {}
24
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k)
25
- __setModuleDefault(result, mod)
26
- return result
27
- }
28
-
29
- var __importDefault = (this && this.__importDefault) || function (mod) {
30
- return (mod && mod.__esModule) ? mod : { "default": mod }
31
- }
32
-
33
- Object.defineProperty(exports, "__esModule", { value: true })
34
-
35
- const boom_1 = require("@hapi/boom")
36
- const axios_1 = __importDefault(require("axios"))
37
- const child_process_1 = require("child_process")
38
- const Crypto = __importStar(require("crypto"))
39
- const events_1 = require("events")
40
- const fs_1 = require("fs")
41
- const os_1 = require("os")
42
- const path_1 = require("path")
43
- const stream_1 = require("stream")
44
- const WAProto_1 = require("../../WAProto")
45
- const Defaults_1 = require("../Defaults")
46
- const WABinary_1 = require("../WABinary")
47
- const crypto_1 = require("./crypto")
48
- const generics_1 = require("./generics")
49
-
50
- const getImageProcessingLibrary = async () => {
51
- const [_jimp, sharp] = await Promise.all([
52
- (async () => {
53
- const jimp = await (Promise.resolve().then(() => __importStar(require('jimp'))).catch(() => { }))
54
- return jimp
55
- })(),
56
- (async () => {
57
- const sharp = await (Promise.resolve().then(() => __importStar(require('sharp'))).catch(() => { }))
58
- return sharp
59
- })()
60
- ])
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const { Boom: Boom } = require("@hapi/boom");
4
+ const { exec: exec } = require("child_process");
5
+ const { once: once } = require("events");
6
+ const {
7
+ createHash: createHash,
8
+ randomBytes: randomBytes,
9
+ createHmac: createHmac,
10
+ createCipheriv: createCipheriv,
11
+ createDecipheriv: createDecipheriv,
12
+ } = require("crypto");
13
+ const {
14
+ promises: promises,
15
+ createReadStream: createReadStream,
16
+ createWriteStream: createWriteStream,
17
+ } = require("fs");
18
+ const {
19
+ parseBuffer: parseBuffer,
20
+ parseFile: parseFile,
21
+ parseStream: parseStream,
22
+ } = require("music-metadata");
23
+ const { tmpdir: tmpdir } = require("os");
24
+ const { join: join } = require("path");
25
+ const { Readable: Readable, Transform: Transform } = require("stream");
26
+ const { proto: proto } = require("../../WAProto");
27
+ const {
28
+ MEDIA_PATH_MAP: MEDIA_PATH_MAP,
29
+ MEDIA_HKDF_KEY_MAPPING: MEDIA_HKDF_KEY_MAPPING,
30
+ } = require("../Defaults/media");
31
+ const { DEFAULT_ORIGIN: DEFAULT_ORIGIN } = require("../Defaults/constants");
32
+ const {
33
+ getBinaryNodeChild: getBinaryNodeChild,
34
+ getBinaryNodeChildBuffer: getBinaryNodeChildBuffer,
35
+ jidNormalizedUser: jidNormalizedUser,
36
+ } = require("../WABinary");
37
+ const {
38
+ aesDecryptGCM: aesDecryptGCM,
39
+ aesEncryptGCM: aesEncryptGCM,
40
+ hkdf: hkdf,
41
+ } = require("./crypto");
42
+ const { generateMessageID: generateMessageID } = require("./generics");
43
+ const getTmpFilesDirectory = () => tmpdir();
44
+ const getImageProcessingLibrary = () => {
45
+ let sharp, jimp;
46
+ try {
47
+ sharp = require("sharp");
48
+ } catch {}
61
49
  if (sharp) {
62
- return { sharp }
50
+ return { sharp: sharp };
63
51
  }
64
- // Jimp v1 pakai named export (export { Jimp }), beda sama v0 yang default
65
- // export. Coba named export dulu, fallback ke .default buat jaga-jaga versi lama.
66
- const jimp = _jimp?.Jimp || _jimp?.default || _jimp
52
+ try {
53
+ jimp = require("jimp");
54
+ } catch {}
67
55
  if (jimp) {
68
- return { jimp, jimpMime: _jimp?.JimpMime }
56
+ return { jimp: jimp };
69
57
  }
70
- throw new boom_1.Boom('No image processing library available')
71
- }
72
-
58
+ throw new Boom("No image processing library available");
59
+ };
73
60
  const hkdfInfoKey = (type) => {
74
- const hkdfInfo = Defaults_1.MEDIA_HKDF_KEY_MAPPING[type]
75
- return `WhatsApp ${hkdfInfo} Keys`
76
- }
77
-
61
+ const hkdfInfo = MEDIA_HKDF_KEY_MAPPING[type];
62
+ return `WhatsApp ${hkdfInfo} Keys`;
63
+ };
78
64
  const getRawMediaUploadData = async (media, mediaType, logger) => {
79
- const { stream } = await getStream(media)
80
-
81
- logger?.debug('got stream for raw upload')
82
-
83
- const hasher = Crypto.createHash('sha256')
84
- const filePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID())
85
- const fileWriteStream = fs_1.createWriteStream(filePath)
86
-
87
- let fileLength = 0
88
-
65
+ const { stream: stream } = await getStream(media);
66
+ logger?.debug("got stream for raw upload");
67
+ const hasher = createHash("sha256");
68
+ const filePath = join(getTmpFilesDirectory(), mediaType + generateMessageID());
69
+ const fileWriteStream = createWriteStream(filePath);
70
+ let fileLength = 0;
89
71
  try {
90
72
  for await (const data of stream) {
91
- fileLength += data.length
92
- hasher.update(data)
93
-
73
+ fileLength += data.length;
74
+ hasher.update(data);
94
75
  if (!fileWriteStream.write(data)) {
95
- await events_1.once(fileWriteStream, 'drain')
76
+ await once(fileWriteStream, "drain");
96
77
  }
97
78
  }
98
-
99
- fileWriteStream.end()
100
- await events_1.once(fileWriteStream, 'finish')
101
- stream.destroy()
102
-
103
- const fileSha256 = hasher.digest()
104
-
105
- logger?.debug('hashed data for raw upload')
106
-
79
+ fileWriteStream.end();
80
+ await once(fileWriteStream, "finish");
81
+ stream.destroy();
82
+ const fileSha256 = hasher.digest();
83
+ logger?.debug("hashed data for raw upload");
107
84
  return {
108
85
  filePath: filePath,
109
- fileSha256,
110
- fileLength
111
- }
112
- }
113
- catch (error) {
114
- fileWriteStream.destroy()
115
- stream.destroy()
116
-
86
+ fileSha256: fileSha256,
87
+ fileLength: fileLength,
88
+ };
89
+ } catch (error) {
90
+ fileWriteStream.destroy();
91
+ stream.destroy();
117
92
  try {
118
- await fs_1.promises.unlink(filePath)
119
- }
120
- catch {
121
- //
122
- }
123
- throw error
93
+ await promises.unlink(filePath);
94
+ } catch {}
95
+ throw error;
124
96
  }
125
- }
126
-
127
- /** generates all the keys required to encrypt/decrypt & sign a media message */
97
+ };
128
98
  async function getMediaKeys(buffer, mediaType) {
129
99
  if (!buffer) {
130
- throw new boom_1.Boom('Cannot derive from empty media key')
100
+ throw new Boom("Cannot derive from empty media key");
131
101
  }
132
- if (typeof buffer === 'string') {
133
- buffer = Buffer.from(buffer.replace('data:base64,', ''), 'base64')
102
+ if (typeof buffer === "string") {
103
+ buffer = Buffer.from(buffer.replace("data:base64,", ""), "base64");
134
104
  }
135
- // expand using HKDF to 112 bytes, also pass in the relevant app info
136
- const expandedMediaKey = await crypto_1.hkdf(buffer, 112, { info: hkdfInfoKey(mediaType) })
105
+ const expandedMediaKey = await hkdf(buffer, 112, {
106
+ info: hkdfInfoKey(mediaType),
107
+ });
137
108
  return {
138
109
  iv: expandedMediaKey.slice(0, 16),
139
110
  cipherKey: expandedMediaKey.slice(16, 48),
140
111
  macKey: expandedMediaKey.slice(48, 80),
141
- }
142
- }
143
-
144
- /** Extracts video thumb using FFMPEG */
145
- const extractVideoThumb = (videoPath, time = '00:00:00', size = { width: 320 }) => {
146
- return new Promise((resolve, reject) => {
147
- const args = [
148
- '-ss', time,
149
- '-i', videoPath,
150
- '-y',
151
- '-vf', `scale=${size.width}:-1`,
152
- '-vframes', '1',
153
- '-f', 'image2',
154
- '-vcodec', 'mjpeg',
155
- 'pipe:1'
156
- ]
157
-
158
- const ffmpeg = child_process_1.spawn('ffmpeg', args)
159
- const chunks = []
160
- let errorOutput = ''
161
-
162
- ffmpeg.stdout.on('data', chunk => chunks.push(chunk))
163
- ffmpeg.stderr.on('data', data => {
164
- errorOutput += data.toString()
165
- })
166
- ffmpeg.on('error', reject)
167
- ffmpeg.on('close', code => {
168
- if (code === 0) return resolve(Buffer.concat(chunks))
169
- reject(new Error(`ffmpeg exited with code ${code}\n${errorOutput}`))
170
- })
171
- })
112
+ };
172
113
  }
173
-
114
+ const extractVideoThumb = async (path, destPath, time, size) =>
115
+ new Promise((resolve, reject) => {
116
+ const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
117
+ exec(cmd, (err) => {
118
+ if (err) {
119
+ reject(err);
120
+ } else {
121
+ resolve();
122
+ }
123
+ });
124
+ });
174
125
  const extractImageThumb = async (bufferOrFilePath, width = 32, quality = 50) => {
175
- if (typeof bufferOrFilePath === "string" && bufferOrFilePath.startsWith("http")) {
176
- const response = await axios_1.default.get(bufferOrFilePath, { responseType: "arraybuffer" })
177
- bufferOrFilePath = Buffer.from(response.data)
178
- }
179
- if (bufferOrFilePath instanceof stream_1.Readable) {
180
- bufferOrFilePath = await toBuffer(bufferOrFilePath)
181
- }
182
- const lib = await getImageProcessingLibrary()
183
- if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
184
- const img = lib.sharp.default(bufferOrFilePath)
185
- const dimensions = await img.metadata()
186
- const buffer = await img
187
- .resize({
188
- width,
189
- height: width,
190
- fit: 'contain',
191
- background: { r: 255, g: 255, b: 255, alpha: 0 }
192
- })
193
- .jpeg({ quality })
194
- .toBuffer()
126
+ if (bufferOrFilePath instanceof Readable) {
127
+ bufferOrFilePath = await toBuffer(bufferOrFilePath);
128
+ }
129
+ const lib = await getImageProcessingLibrary();
130
+ if ("sharp" in lib && typeof lib.sharp === "function") {
131
+ const img = lib.sharp(bufferOrFilePath);
132
+ const dimensions = await img.metadata();
133
+ const buffer = await img.resize(width).jpeg({ quality: 50 }).toBuffer();
195
134
  return {
196
- buffer,
197
- original: {
198
- width: dimensions.width,
199
- height: dimensions.height,
200
- },
201
- }
202
- }
203
- else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
204
- const Jimp = lib.jimp
205
- const JimpMime = lib.jimpMime
206
- const jimp = await Jimp.read(bufferOrFilePath)
207
- const dimensions = {
208
- width: jimp.width,
209
- height: jimp.height
210
- }
135
+ buffer: buffer,
136
+ original: { width: dimensions.width, height: dimensions.height },
137
+ };
138
+ } else if ("jimp" in lib && typeof lib.jimp.read === "function") {
139
+ const {
140
+ read: read,
141
+ MIME_JPEG: MIME_JPEG,
142
+ RESIZE_BEZIER: RESIZE_BEZIER,
143
+ AUTO: AUTO,
144
+ } = lib.jimp;
145
+ const jimp = await read(bufferOrFilePath);
146
+ const dimensions = { width: jimp.getWidth(), height: jimp.getHeight() };
211
147
  const buffer = await jimp
212
- .resize({ w: width })
213
- .getBuffer(JimpMime?.jpeg || 'image/jpeg', { quality })
214
- return {
215
- buffer,
216
- original: dimensions
217
- }
218
- }
219
- else {
220
- throw new boom_1.Boom('No image processing library available')
221
- }
222
- }
223
-
224
- const encodeBase64EncodedStringForUpload = (b64) => (encodeURIComponent(b64
225
- .replace(/\+/g, '-')
226
- .replace(/\//g, '_')
227
- .replace(/\=+$/, '')))
228
-
229
- const generateProfilePicture = async (mediaUpload) => {
230
- let bufferOrFilePath
148
+ .quality(quality)
149
+ .resize(width, AUTO, RESIZE_BEZIER)
150
+ .getBufferAsync(MIME_JPEG);
151
+ return { buffer: buffer, original: dimensions };
152
+ } else {
153
+ throw new Boom("No image processing library available");
154
+ }
155
+ };
156
+ const encodeBase64EncodedStringForUpload = (b64) =>
157
+ encodeURIComponent(b64.replace(/\+/g, "-").replace(/\//g, "_").replace(/\=+$/, ""));
158
+ const generateProfilePicture = async (mediaUpload, dimensions) => {
159
+ let buffer;
160
+ const { width: w = 640, height: h = 640 } = dimensions || {};
231
161
  if (Buffer.isBuffer(mediaUpload)) {
232
- bufferOrFilePath = mediaUpload
233
- }
234
- else if ('url' in mediaUpload) {
235
- bufferOrFilePath = mediaUpload.url.toString()
236
- }
237
- else {
238
- bufferOrFilePath = await toBuffer(mediaUpload.stream)
239
- }
240
- const lib = await getImageProcessingLibrary()
241
- let img
242
- if ('sharp' in lib && typeof lib.sharp?.default === 'function') {
243
- img = await lib.sharp.default(bufferOrFilePath)
244
- .resize(720, 720, {
245
- fit: 'inside',
246
- })
247
- .jpeg({ quality: 50 })
248
- .toBuffer()
249
- }
250
- else if ('jimp' in lib && typeof lib.jimp?.read === 'function') {
251
- const Jimp = lib.jimp
252
- const JimpMime = lib.jimpMime
253
- const image = await Jimp.read(bufferOrFilePath)
254
- const min = image.width
255
- const max = image.height
256
- const cropped = image.crop({ x: 0, y: 0, w: min, h: max })
257
- img = await cropped.scaleToFit({ w: 720, h: 720 }).getBuffer(JimpMime?.jpeg || 'image/jpeg')
258
- }
259
- else {
260
- throw new boom_1.Boom('No image processing library available')
261
- }
262
- return {
263
- img: await img,
264
- }
265
- }
266
-
267
-
268
- /** gets the SHA256 of the given media message */
162
+ buffer = mediaUpload;
163
+ } else {
164
+ const { stream: stream } = await getStream(mediaUpload);
165
+ buffer = await toBuffer(stream);
166
+ }
167
+ const lib = await getImageProcessingLibrary();
168
+ let img;
169
+ if ("sharp" in lib && typeof lib.sharp?.default === "function") {
170
+ img = lib.sharp.default(buffer).resize(w, h).jpeg({ quality: 50 }).toBuffer();
171
+ } else if ("jimp" in lib && typeof lib.jimp?.read === "function") {
172
+ const jimp = await lib.jimp.read(buffer);
173
+ const min = Math.min(jimp.width, jimp.height);
174
+ const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min });
175
+ img = cropped
176
+ .resize({ w: w, h: h, mode: lib.jimp.ResizeStrategy.BILINEAR })
177
+ .getBuffer("image/jpeg", { quality: 50 });
178
+ } else {
179
+ throw new Boom("No image processing library available");
180
+ }
181
+ return { img: await img };
182
+ };
269
183
  const mediaMessageSHA256B64 = (message) => {
270
- const media = Object.values(message)[0]
271
- return (media === null || media === void 0 ? void 0 : media.fileSha256) && Buffer.from(media.fileSha256).toString('base64')
272
- }
273
-
184
+ const media = Object.values(message)[0];
185
+ return media?.fileSha256 && Buffer.from(media.fileSha256).toString("base64");
186
+ };
274
187
  async function getAudioDuration(buffer) {
275
- const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')))
276
- const options = {
277
- duration: true
278
- }
279
-
280
- let metadata
281
-
188
+ const options = { duration: true };
189
+ let metadata;
282
190
  if (Buffer.isBuffer(buffer)) {
283
- metadata = await musicMetadata.parseBuffer(buffer, undefined, options)
191
+ metadata = await parseBuffer(buffer, undefined, options);
192
+ } else if (typeof buffer === "string") {
193
+ metadata = await parseFile(buffer, options);
194
+ } else {
195
+ metadata = await parseStream(buffer, undefined, options);
284
196
  }
285
- else if (typeof buffer === 'string') {
286
- metadata = await musicMetadata.parseFile(buffer, options)
287
- }
288
- else {
289
- metadata = await musicMetadata.parseStream(buffer, undefined, options)
290
- }
291
- return metadata.format.duration
197
+ return metadata.format?.duration;
292
198
  }
293
-
294
- /**
295
- referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
296
- */
297
199
  async function getAudioWaveform(buffer, logger) {
298
200
  try {
299
- const { default: decoder } = await eval('import(\'audio-decode\')')
300
- let audioData
201
+ const { default: decoder } = await eval("import('audio-decode')");
202
+ let audioData;
301
203
  if (Buffer.isBuffer(buffer)) {
302
- audioData = buffer
303
- }
304
- else if (typeof buffer === 'string') {
305
- const rStream = fs_1.createReadStream(buffer)
306
- audioData = await toBuffer(rStream)
307
- }
308
- else {
309
- audioData = await toBuffer(buffer)
204
+ audioData = buffer;
205
+ } else if (typeof buffer === "string") {
206
+ const rStream = createReadStream(buffer);
207
+ audioData = await toBuffer(rStream);
208
+ } else {
209
+ audioData = await toBuffer(buffer);
310
210
  }
311
- const audioBuffer = await decoder(audioData)
312
- const rawData = audioBuffer.getChannelData(0) // We only need to work with one channel of data
313
- const samples = 64 // Number of samples we want to have in our final data set
314
- const blockSize = Math.floor(rawData.length / samples) // the number of samples in each subdivision
315
- const filteredData = []
211
+ const audioBuffer = await decoder(audioData);
212
+ const rawData = audioBuffer.getChannelData(0);
213
+ const samples = 64;
214
+ const blockSize = Math.floor(rawData.length / samples);
215
+ const filteredData = [];
316
216
  for (let i = 0; i < samples; i++) {
317
- const blockStart = blockSize * i // the location of the first sample in the block
318
- let sum = 0
217
+ const blockStart = blockSize * i;
218
+ let sum = 0;
319
219
  for (let j = 0; j < blockSize; j++) {
320
- sum = sum + Math.abs(rawData[blockStart + j]) // find the sum of all the samples in the block
220
+ sum = sum + Math.abs(rawData[blockStart + j]);
321
221
  }
322
- filteredData.push(sum / blockSize) // divide the sum by the block size to get the average
222
+ filteredData.push(sum / blockSize);
323
223
  }
324
- // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
325
- const multiplier = Math.pow(Math.max(...filteredData), -1)
326
- const normalizedData = filteredData.map((n) => n * multiplier)
327
- // Generate waveform like WhatsApp
328
- const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)))
329
- return waveform
330
- }
331
- catch (e) {
332
- logger?.debug('Failed to generate waveform: ' + e)
224
+ const multiplier = Math.pow(Math.max(...filteredData), -1);
225
+ const normalizedData = filteredData.map((n) => n * multiplier);
226
+ const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)));
227
+ return waveform;
228
+ } catch (e) {
229
+ logger?.debug("Failed to generate waveform: " + e);
333
230
  }
334
231
  }
335
-
336
232
  const toReadable = (buffer) => {
337
- const readable = new stream_1.Readable({ read: () => { } })
338
- readable.push(buffer)
339
- readable.push(null)
340
- return readable
341
- }
342
-
233
+ const readable = new Readable({ read: () => {} });
234
+ readable.push(buffer);
235
+ readable.push(null);
236
+ return readable;
237
+ };
343
238
  const toBuffer = async (stream) => {
344
- const chunks = []
239
+ const chunks = [];
345
240
  for await (const chunk of stream) {
346
- chunks.push(chunk)
241
+ chunks.push(chunk);
347
242
  }
348
- stream.destroy()
349
- return Buffer.concat(chunks)
350
- }
351
-
243
+ stream.destroy();
244
+ return Buffer.concat(chunks);
245
+ };
352
246
  const getStream = async (item, opts) => {
353
247
  if (Buffer.isBuffer(item)) {
354
- return { stream: toReadable(item), type: 'buffer' }
248
+ return { stream: toReadable(item), type: "buffer" };
355
249
  }
356
-
357
- if ('stream' in item) {
358
- return { stream: item.stream, type: 'readable' }
250
+ if ("stream" in item) {
251
+ return { stream: item.stream, type: "readable" };
359
252
  }
360
-
361
- const urlStr = item.url.toString()
362
-
363
- if (urlStr.startsWith('data:')) {
364
- const buffer = Buffer.from(urlStr.split(',')[1], 'base64')
365
- return { stream: await toReadable(buffer), type: 'buffer' }
253
+ const urlStr = item.url.toString();
254
+ if (urlStr.startsWith("data:")) {
255
+ const buffer = Buffer.from(urlStr.split(",")[1], "base64");
256
+ return { stream: await toReadable(buffer), type: "buffer" };
366
257
  }
367
-
368
- if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
369
- return { stream: await getHttpStream(item.url, opts), type: 'remote' }
258
+ if (urlStr.startsWith("http://") || urlStr.startsWith("https://")) {
259
+ return { stream: await getHttpStream(item.url, opts), type: "remote" };
370
260
  }
371
-
372
- return { stream: fs_1.createReadStream(item.url), type: 'file' }
373
- }
374
-
375
- /** generates a thumbnail for a given media, if required */
261
+ return { stream: createReadStream(item.url), type: "file" };
262
+ };
376
263
  async function generateThumbnail(file, mediaType, options) {
377
- let thumbnail
378
- let originalImageDimensions
379
- if (mediaType === 'image') {
380
- const { buffer, original } = await extractImageThumb(file, 256, 95)
381
- thumbnail = buffer.toString('base64')
264
+ let thumbnail;
265
+ let originalImageDimensions;
266
+ if (mediaType === "image") {
267
+ const { buffer: buffer, original: original } = await extractImageThumb(file);
268
+ thumbnail = buffer.toString("base64");
382
269
  if (original.width && original.height) {
383
270
  originalImageDimensions = {
384
271
  width: original.width,
385
272
  height: original.height,
386
- }
273
+ };
387
274
  }
388
- }
389
- else if (mediaType === 'video') {
275
+ } else if (mediaType === "video") {
276
+ const imgFilename = join(getTmpFilesDirectory(), generateMessageID() + ".jpg");
390
277
  try {
391
- const buff = await extractVideoThumb(file, '00:00:00', { width: 32, height: 32 })
392
- thumbnail = buff.toString('base64')
393
- }
394
- catch (err) {
395
- options?.logger?.debug('could not generate video thumb: ' + err)
278
+ await extractVideoThumb(file, imgFilename, "00:00:00", {
279
+ width: 32,
280
+ height: 32,
281
+ });
282
+ const buff = await promises.readFile(imgFilename);
283
+ thumbnail = buff.toString("base64");
284
+ await promises.unlink(imgFilename);
285
+ } catch (err) {
286
+ options.logger?.debug("could not generate video thumb: " + err);
396
287
  }
397
288
  }
398
289
  return {
399
- thumbnail,
400
- originalImageDimensions
401
- }
290
+ thumbnail: thumbnail,
291
+ originalImageDimensions: originalImageDimensions,
292
+ };
402
293
  }
403
-
404
294
  const getHttpStream = async (url, options = {}) => {
405
- const fetched = await axios_1.default.get(url.toString(), { ...options, responseType: 'stream' })
406
- return fetched.data
407
- }
408
-
409
- const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
410
- const { stream, type } = await getStream(media, opts)
411
- logger?.debug('fetched media stream')
412
-
413
- const encFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-plain')
414
- const encFileWriteStream = fs_1.createWriteStream(encFilePath)
415
-
416
- let originalFilePath
417
- let originalFileStream
418
-
419
- if (type === 'file') {
420
- originalFilePath = media.url.toString()
421
- } else if (saveOriginalFileIfRequired) {
422
- originalFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-original')
423
- originalFileStream = fs_1.createWriteStream(originalFilePath)
424
- }
425
-
426
- let fileLength = 0
427
- const sha256 = Crypto.createHash('sha256')
428
-
429
- try {
430
- for await (const data of stream) {
431
- fileLength += data.length
432
-
433
- if (type === 'remote'
434
- && opts?.maxContentLength
435
- && fileLength + data.length > opts.maxContentLength) {
436
- throw new boom_1.Boom(`content length exceeded when preparing "${type}"`, {
437
- data: { media, type }
438
- })
439
- }
440
-
441
- sha256.update(data)
442
- encFileWriteStream.write(data)
443
-
444
- if (originalFileStream && !originalFileStream.write(data)) {
445
- await events_1.once(originalFileStream, 'drain')
446
- }
447
- }
448
-
449
- const fileSha256 = sha256.digest()
450
- encFileWriteStream.end()
451
- originalFileStream?.end?.call(originalFileStream)
452
- stream.destroy()
453
-
454
- logger?.debug('prepared plain stream successfully')
455
-
456
- return {
457
- mediaKey: undefined,
458
- originalFilePath,
459
- encFilePath,
460
- mac: undefined,
461
- fileEncSha256: undefined,
462
- fileSha256,
463
- fileLength
464
- }
465
- }
466
- catch (error) {
467
- encFileWriteStream.destroy()
468
- originalFileStream?.destroy?.call(originalFileStream)
469
- sha256.destroy()
470
- stream.destroy()
471
- try {
472
- await fs_1.promises.unlink(encFilePath)
473
- if (originalFilePath && didSaveToTmpPath) {
474
- await fs_1.promises.unlink(originalFilePath)
475
- }
476
- } catch (err) {
477
- logger?.error({ err }, 'failed deleting tmp files')
478
- }
479
- throw error
480
- }
481
- }
482
-
483
- const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfRequired, opts } = {}) => {
484
- const { stream, type } = await getStream(media, opts)
485
- logger?.debug('fetched media stream')
486
- const mediaKey = Crypto.randomBytes(32)
487
- const { cipherKey, iv, macKey } = await getMediaKeys(mediaKey, mediaType)
488
- const encFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-enc')
489
- const encFileWriteStream = fs_1.createWriteStream(encFilePath)
490
- let originalFileStream
491
- let originalFilePath
295
+ const response = await fetch(url.toString(), {
296
+ dispatcher: options.dispatcher,
297
+ method: "GET",
298
+ headers: options.headers,
299
+ });
300
+ if (!response.ok) {
301
+ throw new Boom(`Failed to fetch stream from ${url}`, {
302
+ statusCode: response.status,
303
+ data: { url: url },
304
+ });
305
+ }
306
+ return response.body instanceof Readable ? response.body : Readable.fromWeb(response.body);
307
+ };
308
+ const encryptedStream = async (
309
+ media,
310
+ mediaType,
311
+ { logger: logger, saveOriginalFileIfRequired: saveOriginalFileIfRequired, opts: opts } = {}
312
+ ) => {
313
+ const { stream: stream, type: type } = await getStream(media, opts);
314
+ logger?.debug("fetched media stream");
315
+ const mediaKey = randomBytes(32);
316
+ const {
317
+ cipherKey: cipherKey,
318
+ iv: iv,
319
+ macKey: macKey,
320
+ } = await getMediaKeys(mediaKey, mediaType);
321
+ const encFilePath = join(getTmpFilesDirectory(), mediaType + generateMessageID() + "-enc");
322
+ const encFileWriteStream = createWriteStream(encFilePath);
323
+ let originalFileStream;
324
+ let originalFilePath;
492
325
  if (saveOriginalFileIfRequired) {
493
- originalFilePath = path_1.join(os_1.tmpdir(), mediaType + generics_1.generateMessageID() + '-original')
494
- originalFileStream = fs_1.createWriteStream(originalFilePath)
495
- }
496
- let fileLength = 0
497
- const aes = Crypto.createCipheriv('aes-256-cbc', cipherKey, iv)
498
- const hmac = Crypto.createHmac('sha256', macKey).update(iv)
499
- const sha256Plain = Crypto.createHash('sha256')
500
- const sha256Enc = Crypto.createHash('sha256')
501
- const onChunk = (buff) => {
502
- sha256Enc.update(buff)
503
- hmac.update(buff)
504
- encFileWriteStream.write(buff)
505
- }
326
+ originalFilePath = join(
327
+ getTmpFilesDirectory(),
328
+ mediaType + generateMessageID() + "-original"
329
+ );
330
+ originalFileStream = createWriteStream(originalFilePath);
331
+ }
332
+ let fileLength = 0;
333
+ const aes = createCipheriv("aes-256-cbc", cipherKey, iv);
334
+ const hmac = createHmac("sha256", macKey).update(iv);
335
+ const sha256Plain = createHash("sha256");
336
+ const sha256Enc = createHash("sha256");
337
+ const onChunk = async (buff) => {
338
+ sha256Enc.update(buff);
339
+ hmac.update(buff);
340
+ if (!encFileWriteStream.write(buff)) {
341
+ await once(encFileWriteStream, "drain");
342
+ }
343
+ };
506
344
  try {
507
345
  for await (const data of stream) {
508
- fileLength += data.length
509
- if (type === 'remote'
510
- && opts?.maxContentLength
511
- && fileLength + data.length > opts.maxContentLength) {
512
- throw new boom_1.Boom(`content length exceeded when encrypting "${type}"`, {
513
- data: { media, type }
514
- })
346
+ fileLength += data.length;
347
+ if (
348
+ type === "remote" &&
349
+ opts?.maxContentLength &&
350
+ fileLength + data.length > opts.maxContentLength
351
+ ) {
352
+ throw new Boom(`content length exceeded when encrypting "${type}"`, {
353
+ data: { media: media, type: type },
354
+ });
515
355
  }
516
356
  if (originalFileStream) {
517
357
  if (!originalFileStream.write(data)) {
518
- await events_1.once(originalFileStream, 'drain')
358
+ await once(originalFileStream, "drain");
519
359
  }
520
360
  }
521
- sha256Plain.update(data)
522
- onChunk(aes.update(data))
361
+ sha256Plain.update(data);
362
+ await onChunk(aes.update(data));
523
363
  }
524
- onChunk(aes.final())
525
- const mac = hmac.digest().slice(0, 10)
526
- sha256Enc.update(mac)
527
- const fileSha256 = sha256Plain.digest()
528
- const fileEncSha256 = sha256Enc.digest()
529
- encFileWriteStream.write(mac)
530
- encFileWriteStream.end()
531
- originalFileStream?.end?.call(originalFileStream)
532
- stream.destroy()
533
- logger?.debug('encrypted data successfully')
364
+ await onChunk(aes.final());
365
+ const mac = hmac.digest().slice(0, 10);
366
+ sha256Enc.update(mac);
367
+ const fileSha256 = sha256Plain.digest();
368
+ const fileEncSha256 = sha256Enc.digest();
369
+ encFileWriteStream.write(mac);
370
+ const encFinishPromise = once(encFileWriteStream, "finish");
371
+ const originalFinishPromise = originalFileStream
372
+ ? once(originalFileStream, "finish")
373
+ : Promise.resolve();
374
+ encFileWriteStream.end();
375
+ originalFileStream?.end?.();
376
+ stream.destroy();
377
+ await encFinishPromise;
378
+ await originalFinishPromise;
379
+ logger?.debug("encrypted data successfully");
534
380
  return {
535
- mediaKey,
536
- originalFilePath,
537
- encFilePath,
538
- mac,
539
- fileEncSha256,
540
- fileSha256,
541
- fileLength
542
- }
543
- }
544
- catch (error) {
545
- // destroy all streams with error
546
- encFileWriteStream.destroy()
547
- originalFileStream?.destroy?.call(originalFileStream)
548
- aes.destroy()
549
- hmac.destroy()
550
- sha256Plain.destroy()
551
- sha256Enc.destroy()
552
- stream.destroy()
381
+ mediaKey: mediaKey,
382
+ originalFilePath: originalFilePath,
383
+ encFilePath: encFilePath,
384
+ mac: mac,
385
+ fileEncSha256: fileEncSha256,
386
+ fileSha256: fileSha256,
387
+ fileLength: fileLength,
388
+ };
389
+ } catch (error) {
390
+ encFileWriteStream.destroy();
391
+ originalFileStream?.destroy?.();
392
+ aes.destroy();
393
+ hmac.destroy();
394
+ sha256Plain.destroy();
395
+ sha256Enc.destroy();
396
+ stream.destroy();
553
397
  try {
554
- await fs_1.promises.unlink(encFilePath)
398
+ await promises.unlink(encFilePath);
555
399
  if (originalFilePath) {
556
- await fs_1.promises.unlink(originalFilePath)
400
+ await promises.unlink(originalFilePath);
557
401
  }
402
+ } catch (err) {
403
+ logger?.error({ err: err }, "failed deleting tmp files");
558
404
  }
559
- catch (err) {
560
- logger?.error({ err }, 'failed deleting tmp files')
561
- }
562
- throw error
563
- }
564
- }
565
-
566
- const DEF_HOST = 'mmg.whatsapp.net'
567
-
568
- const AES_CHUNK_SIZE = 16
569
-
570
- const toSmallestChunkSize = (num) => {
571
- return Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE
572
- }
573
- const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`
574
-
575
- const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
576
- const isValidMediaUrl = url?.startsWith('https://mmg.whatsapp.net/')
577
- const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath)
578
-
405
+ throw error;
406
+ }
407
+ };
408
+ const DEF_HOST = "mmg.whatsapp.net";
409
+ const AES_CHUNK_SIZE = 16;
410
+ const toSmallestChunkSize = (num) => Math.floor(num / AES_CHUNK_SIZE) * AES_CHUNK_SIZE;
411
+ const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
412
+ const downloadContentFromMessage = async (
413
+ { mediaKey: mediaKey, directPath: directPath, url: url },
414
+ type,
415
+ opts = {}
416
+ ) => {
417
+ const isValidMediaUrl = url?.startsWith("https://mmg.whatsapp.net/");
418
+ const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath);
579
419
  if (!downloadUrl) {
580
- throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 })
581
- }
582
-
583
- const keys = await getMediaKeys(mediaKey, type)
584
- return downloadEncryptedContent(downloadUrl, keys, opts)
585
- }
586
-
587
- /**
588
- * Decrypts and downloads an AES256-CBC encrypted file given the keys.
589
- * Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
590
- * */
591
- const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
592
- let bytesFetched = 0
593
- let startChunk = 0
594
- let firstBlockIsIV = false
595
- // if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
420
+ throw new Boom("No valid media URL or directPath present in message", {
421
+ statusCode: 400,
422
+ });
423
+ }
424
+ const keys = await getMediaKeys(mediaKey, type);
425
+ return downloadEncryptedContent(downloadUrl, keys, opts);
426
+ };
427
+ const downloadEncryptedContent = async (
428
+ downloadUrl,
429
+ { cipherKey: cipherKey, iv: iv },
430
+ { startByte: startByte, endByte: endByte, options: options } = {}
431
+ ) => {
432
+ let bytesFetched = 0;
433
+ let startChunk = 0;
434
+ let firstBlockIsIV = false;
596
435
  if (startByte) {
597
- const chunk = toSmallestChunkSize(startByte || 0)
436
+ const chunk = toSmallestChunkSize(startByte || 0);
598
437
  if (chunk) {
599
- startChunk = chunk - AES_CHUNK_SIZE
600
- bytesFetched = chunk
601
- firstBlockIsIV = true
438
+ startChunk = chunk - AES_CHUNK_SIZE;
439
+ bytesFetched = chunk;
440
+ firstBlockIsIV = true;
602
441
  }
603
442
  }
604
- const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined
443
+ const endChunk = endByte ? toSmallestChunkSize(endByte || 0) + AES_CHUNK_SIZE : undefined;
444
+ const headersInit = options?.headers ? options.headers : undefined;
605
445
  const headers = {
606
- ...(options?.headers) || {},
607
- Origin: Defaults_1.DEFAULT_ORIGIN,
608
- }
446
+ ...(headersInit
447
+ ? Array.isArray(headersInit)
448
+ ? Object.fromEntries(headersInit)
449
+ : headersInit
450
+ : {}),
451
+ Origin: DEFAULT_ORIGIN,
452
+ };
609
453
  if (startChunk || endChunk) {
610
- headers.Range = `bytes=${startChunk}-`
454
+ headers.Range = `bytes=${startChunk}-`;
611
455
  if (endChunk) {
612
- headers.Range += endChunk
456
+ headers.Range += endChunk;
613
457
  }
614
458
  }
615
- // download the message
616
459
  const fetched = await getHttpStream(downloadUrl, {
617
- ...options || {},
618
- headers,
619
- maxBodyLength: Infinity,
620
- maxContentLength: Infinity,
621
- })
622
- let remainingBytes = Buffer.from([])
623
- let aes
460
+ ...(options || {}),
461
+ headers: headers,
462
+ });
463
+ let remainingBytes = Buffer.from([]);
464
+ let aes;
624
465
  const pushBytes = (bytes, push) => {
625
466
  if (startByte || endByte) {
626
- const start = bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0)
627
- const end = bytesFetched + bytes.length < endByte ? undefined : Math.max(endByte - bytesFetched, 0)
628
- push(bytes.slice(start, end))
629
- bytesFetched += bytes.length
630
- }
631
- else {
632
- push(bytes)
467
+ const start =
468
+ bytesFetched >= startByte ? undefined : Math.max(startByte - bytesFetched, 0);
469
+ const end =
470
+ bytesFetched + bytes.length < endByte
471
+ ? undefined
472
+ : Math.max(endByte - bytesFetched, 0);
473
+ push(bytes.slice(start, end));
474
+ bytesFetched += bytes.length;
475
+ } else {
476
+ push(bytes);
633
477
  }
634
- }
635
- const output = new stream_1.Transform({
478
+ };
479
+ const output = new Transform({
636
480
  transform(chunk, _, callback) {
637
- let data = Buffer.concat([remainingBytes, chunk])
638
- const decryptLength = toSmallestChunkSize(data.length)
639
- remainingBytes = data.slice(decryptLength)
640
- data = data.slice(0, decryptLength)
481
+ let data = Buffer.concat([remainingBytes, chunk]);
482
+ const decryptLength = toSmallestChunkSize(data.length);
483
+ remainingBytes = data.slice(decryptLength);
484
+ data = data.slice(0, decryptLength);
641
485
  if (!aes) {
642
- let ivValue = iv
486
+ let ivValue = iv;
643
487
  if (firstBlockIsIV) {
644
- ivValue = data.slice(0, AES_CHUNK_SIZE)
645
- data = data.slice(AES_CHUNK_SIZE)
488
+ ivValue = data.slice(0, AES_CHUNK_SIZE);
489
+ data = data.slice(AES_CHUNK_SIZE);
646
490
  }
647
- aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue)
648
- // if an end byte that is not EOF is specified
649
- // stop auto padding (PKCS7) -- otherwise throws an error for decryption
491
+ aes = createDecipheriv("aes-256-cbc", cipherKey, ivValue);
650
492
  if (endByte) {
651
- aes.setAutoPadding(false)
493
+ aes.setAutoPadding(false);
652
494
  }
653
495
  }
654
496
  try {
655
- pushBytes(aes.update(data), b => this.push(b))
656
- callback()
657
- }
658
- catch (error) {
659
- callback(error)
497
+ pushBytes(aes.update(data), (b) => this.push(b));
498
+ callback();
499
+ } catch (error) {
500
+ callback(error);
660
501
  }
661
502
  },
662
503
  final(callback) {
663
504
  try {
664
- pushBytes(aes.final(), b => this.push(b))
665
- callback()
666
- }
667
- catch (error) {
668
- callback(error)
505
+ pushBytes(aes.final(), (b) => this.push(b));
506
+ callback();
507
+ } catch (error) {
508
+ callback(error);
669
509
  }
670
510
  },
671
- })
672
- return fetched.pipe(output, { end: true })
673
- }
674
-
511
+ });
512
+ return fetched.pipe(output, { end: true });
513
+ };
675
514
  function extensionForMediaMessage(message) {
676
- const getExtension = (mimetype) => mimetype.split('')[0].split('/')[1]
677
- const type = Object.keys(message)[0]
678
- let extension
679
- if (type === 'locationMessage' ||
680
- type === 'liveLocationMessage' ||
681
- type === 'productMessage') {
682
- extension = '.jpeg'
683
- }
684
- else {
685
- const messageContent = message[type]
686
- extension = getExtension(messageContent.mimetype)
687
- }
688
- return extension
515
+ const getExtension = (mimetype) => mimetype.split("")[0].split("/")[1];
516
+ const type = Object.keys(message)[0];
517
+ let extension;
518
+ if (type === "locationMessage" || type === "liveLocationMessage" || type === "productMessage") {
519
+ extension = ".jpeg";
520
+ } else {
521
+ const messageContent = message[type];
522
+ extension = getExtension(messageContent.mimetype);
523
+ }
524
+ return extension;
689
525
  }
690
-
691
- const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
692
- return async (filePath, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
693
- // send a query JSON to obtain the url & auth token to upload our media
694
- let uploadInfo = await refreshMediaConn(false)
695
- let urls
696
- let media = Defaults_1.MEDIA_PATH_MAP[mediaType]
697
- const hosts = [...customUploadHosts, ...uploadInfo.hosts]
698
- fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64)
699
- if (newsletter) {
700
- media = media?.replace('/mms/', '/newsletter/newsletter-')
701
- }
702
- for (const { hostname } of hosts) {
703
- logger.debug(`uploading to "${hostname}"`)
704
- const auth = encodeURIComponent(uploadInfo.auth) // the auth token
705
- const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`
706
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
707
- let result
526
+ const isNodeRuntime = () =>
527
+ typeof process !== "undefined" &&
528
+ process.versions?.node !== null &&
529
+ typeof process.versions.bun === "undefined" &&
530
+ typeof globalThis.Deno === "undefined";
531
+ const uploadWithNodeHttp = async (
532
+ { url: url, filePath: filePath, headers: headers, timeoutMs: timeoutMs, agent: agent },
533
+ redirectCount = 0
534
+ ) => {
535
+ if (redirectCount > 5) {
536
+ throw new Error("Too many redirects");
537
+ }
538
+ const parsedUrl = new URL(url);
539
+ const httpModule = parsedUrl.protocol === "https:" ? require("https") : require("http");
540
+ const fileStats = await promises.stat(filePath);
541
+ const fileSize = fileStats.size;
542
+ return new Promise((resolve, reject) => {
543
+ const req = httpModule.request(
544
+ {
545
+ hostname: parsedUrl.hostname,
546
+ port: parsedUrl.port || (parsedUrl.protocol === "https:" ? 443 : 80),
547
+ path: parsedUrl.pathname + parsedUrl.search,
548
+ method: "POST",
549
+ headers: { ...headers, "Content-Length": fileSize },
550
+ agent: agent,
551
+ timeout: timeoutMs,
552
+ },
553
+ (res) => {
554
+ if (
555
+ res.statusCode &&
556
+ res.statusCode >= 300 &&
557
+ res.statusCode < 400 &&
558
+ res.headers.location
559
+ ) {
560
+ res.resume();
561
+ const newUrl = new URL(res.headers.location, url).toString();
562
+ resolve(
563
+ uploadWithNodeHttp(
564
+ {
565
+ url: newUrl,
566
+ filePath: filePath,
567
+ headers: headers,
568
+ timeoutMs: timeoutMs,
569
+ agent: agent,
570
+ },
571
+ redirectCount + 1
572
+ )
573
+ );
574
+ return;
575
+ }
576
+ let body = "";
577
+ res.on("data", (chunk) => (body += chunk));
578
+ res.on("end", () => {
579
+ try {
580
+ resolve(JSON.parse(body));
581
+ } catch {
582
+ resolve(undefined);
583
+ }
584
+ });
585
+ }
586
+ );
587
+ req.on("error", reject);
588
+ req.on("timeout", () => {
589
+ req.destroy();
590
+ reject(new Error("Upload timeout"));
591
+ });
592
+ const stream = createReadStream(filePath);
593
+ stream.pipe(req);
594
+ stream.on("error", (err) => {
595
+ req.destroy();
596
+ reject(err);
597
+ });
598
+ });
599
+ };
600
+ const uploadWithFetch = async ({
601
+ url: url,
602
+ filePath: filePath,
603
+ headers: headers,
604
+ timeoutMs: timeoutMs,
605
+ agent: agent,
606
+ }) => {
607
+ const nodeStream = createReadStream(filePath);
608
+ const webStream = Readable.toWeb(nodeStream);
609
+ const response = await fetch(url, {
610
+ dispatcher: agent,
611
+ method: "POST",
612
+ body: webStream,
613
+ headers: headers,
614
+ duplex: "half",
615
+ signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined,
616
+ });
617
+ try {
618
+ return await response.json();
619
+ } catch {
620
+ return undefined;
621
+ }
622
+ };
623
+ const uploadMedia = async (params, logger) => {
624
+ if (isNodeRuntime()) {
625
+ logger?.debug("Using Node.js https module for upload (avoids undici buffering bug)");
626
+ return uploadWithNodeHttp(params);
627
+ } else {
628
+ logger?.debug("Using web-standard Fetch API for upload");
629
+ return uploadWithFetch(params);
630
+ }
631
+ };
632
+ const getWAUploadToServer =
633
+ (
634
+ {
635
+ customUploadHosts: customUploadHosts,
636
+ fetchAgent: fetchAgent,
637
+ logger: logger,
638
+ options: options,
639
+ },
640
+ refreshMediaConn
641
+ ) =>
642
+ async (
643
+ filePath,
644
+ { mediaType: mediaType, fileEncSha256B64: fileEncSha256B64, timeoutMs: timeoutMs }
645
+ ) => {
646
+ let uploadInfo = await refreshMediaConn(false);
647
+ let urls;
648
+ const hosts = [...customUploadHosts, ...uploadInfo.hosts];
649
+ fileEncSha256B64 = encodeBase64EncodedStringForUpload(fileEncSha256B64);
650
+ const customHeaders = (() => {
651
+ const hdrs = options?.headers;
652
+ if (!hdrs) return {};
653
+ return Array.isArray(hdrs) ? Object.fromEntries(hdrs) : hdrs;
654
+ })();
655
+ const headers = {
656
+ ...customHeaders,
657
+ "Content-Type": "application/octet-stream",
658
+ Origin: DEFAULT_ORIGIN,
659
+ };
660
+ for (const { hostname: hostname } of hosts) {
661
+ logger.debug(`uploading to "${hostname}"`);
662
+ const auth = encodeURIComponent(uploadInfo.auth);
663
+ const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
664
+ let result;
708
665
  try {
709
- const body = await axios_1.default.post(url, fs_1.createReadStream(filePath), {
710
- ...options,
711
- maxRedirects: 0,
712
- headers: {
713
- ...options.headers || {},
714
- 'Content-Type': 'application/octet-stream',
715
- 'Origin': Defaults_1.DEFAULT_ORIGIN
666
+ result = await uploadMedia(
667
+ {
668
+ url: url,
669
+ filePath: filePath,
670
+ headers: headers,
671
+ timeoutMs: timeoutMs,
672
+ agent: fetchAgent,
716
673
  },
717
- httpsAgent: fetchAgent,
718
- timeout: timeoutMs,
719
- responseType: 'json',
720
- maxBodyLength: Infinity,
721
- maxContentLength: Infinity,
722
- })
723
- result = body.data
724
- if (result?.url || result?.directPath) {
674
+ logger
675
+ );
676
+ if (result?.url || result?.direct_path) {
725
677
  urls = {
726
678
  mediaUrl: result.url,
727
- directPath: result.direct_path,
679
+ directPath: result.direct_path,
728
680
  meta_hmac: result.meta_hmac,
729
681
  fbid: result.fbid,
730
- ts: result.ts
731
- }
732
- break
733
- }
734
- else {
735
- uploadInfo = await refreshMediaConn(true)
736
- throw new Error(`upload failed, reason: ${JSON.stringify(result)}`)
737
- }
738
- }
739
- catch (error) {
740
- if (axios_1.default.isAxiosError(error)) {
741
- result = error.response?.data
682
+ ts: result.ts,
683
+ };
684
+ break;
685
+ } else {
686
+ uploadInfo = await refreshMediaConn(true);
687
+ throw new Error(`upload failed, reason: ${JSON.stringify(result)}`);
742
688
  }
743
- const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname
744
- logger.warn({ trace: error.stack, uploadResult: result }, `Error in uploading to ${hostname} ${isLast ? '' : ', retrying...'}`)
689
+ } catch (error) {
690
+ const isLast = hostname === hosts[uploadInfo.hosts.length - 1]?.hostname;
691
+ logger.warn(
692
+ { trace: error?.stack, uploadResult: result },
693
+ `Error in uploading to ${hostname} ${isLast ? "" : ", retrying..."}`
694
+ );
745
695
  }
746
696
  }
747
697
  if (!urls) {
748
- throw new boom_1.Boom('Media upload failed on all hosts', { statusCode: 500 })
698
+ throw new Boom("Media upload failed on all hosts", { statusCode: 500 });
749
699
  }
750
- return urls
751
- }
752
- }
753
-
754
- const getMediaRetryKey = (mediaKey) => {
755
- return crypto_1.hkdf(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' })
756
- }
757
- /**
758
- * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
759
- */
700
+ return urls;
701
+ };
702
+ const getMediaRetryKey = (mediaKey) =>
703
+ hkdf(mediaKey, 32, { info: "WhatsApp Media Retry Notification" });
760
704
  const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
761
- const recp = { stanzaId: key.id }
762
- const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish()
763
- const iv = Crypto.randomBytes(12)
764
- const retryKey = await getMediaRetryKey(mediaKey)
765
- const ciphertext = crypto_1.aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id))
705
+ const recp = { stanzaId: key.id };
706
+ const recpBuffer = proto.ServerErrorReceipt.encode(recp).finish();
707
+ const iv = randomBytes(12);
708
+ const retryKey = await getMediaRetryKey(mediaKey);
709
+ const ciphertext = aesEncryptGCM(recpBuffer, retryKey, iv, Buffer.from(key.id));
766
710
  const req = {
767
- tag: 'receipt',
768
- attrs: {
769
- id: key.id,
770
- to: WABinary_1.jidNormalizedUser(meId),
771
- type: 'server-error'
772
- },
711
+ tag: "receipt",
712
+ attrs: { id: key.id, to: jidNormalizedUser(meId), type: "server-error" },
773
713
  content: [
774
- // this encrypt node is actually pretty useless
775
- // the media is returned even without this node
776
- // keeping it here to maintain parity with WA Web
777
714
  {
778
- tag: 'encrypt',
715
+ tag: "encrypt",
779
716
  attrs: {},
780
717
  content: [
781
- { tag: 'enc_p', attrs: {}, content: ciphertext },
782
- { tag: 'enc_iv', attrs: {}, content: iv }
783
- ]
718
+ { tag: "enc_p", attrs: {}, content: ciphertext },
719
+ { tag: "enc_iv", attrs: {}, content: iv },
720
+ ],
784
721
  },
785
722
  {
786
- tag: 'rmr',
723
+ tag: "rmr",
787
724
  attrs: {
788
725
  jid: key.remoteJid,
789
- 'from_me': (!!key.fromMe).toString(),
790
- // @ts-ignore
791
- participant: key.participant || undefined
792
- }
793
- }
794
- ]
795
- }
796
- return req
797
- }
798
-
726
+ from_me: (!!key.fromMe).toString(),
727
+ participant: key.participant || undefined,
728
+ },
729
+ },
730
+ ],
731
+ };
732
+ return req;
733
+ };
799
734
  const decodeMediaRetryNode = (node) => {
800
- const rmrNode = WABinary_1.getBinaryNodeChild(node, 'rmr')
735
+ const rmrNode = getBinaryNodeChild(node, "rmr");
801
736
  const event = {
802
737
  key: {
803
738
  id: node.attrs.id,
804
739
  remoteJid: rmrNode.attrs.jid,
805
- fromMe: rmrNode.attrs.from_me === 'true',
806
- participant: rmrNode.attrs.participant
807
- }
808
- }
809
- const errorNode = WABinary_1.getBinaryNodeChild(node, 'error')
740
+ fromMe: rmrNode.attrs.from_me === "true",
741
+ participant: rmrNode.attrs.participant,
742
+ },
743
+ };
744
+ const errorNode = getBinaryNodeChild(node, "error");
810
745
  if (errorNode) {
811
- const errorCode = +errorNode.attrs.code
812
- event.error = new boom_1.Boom(`Failed to re-upload media (${errorCode})`, { data: errorNode.attrs, statusCode: getStatusCodeForMediaRetry(errorCode) })
813
- }
814
- else {
815
- const encryptedInfoNode = WABinary_1.getBinaryNodeChild(node, 'encrypt')
816
- const ciphertext = WABinary_1.getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_p')
817
- const iv = WABinary_1.getBinaryNodeChildBuffer(encryptedInfoNode, 'enc_iv')
746
+ const errorCode = +errorNode.attrs.code;
747
+ event.error = new Boom(`Failed to re-upload media (${errorCode})`, {
748
+ data: errorNode.attrs,
749
+ statusCode: getStatusCodeForMediaRetry(errorCode),
750
+ });
751
+ } else {
752
+ const encryptedInfoNode = getBinaryNodeChild(node, "encrypt");
753
+ const ciphertext = getBinaryNodeChildBuffer(encryptedInfoNode, "enc_p");
754
+ const iv = getBinaryNodeChildBuffer(encryptedInfoNode, "enc_iv");
818
755
  if (ciphertext && iv) {
819
- event.media = { ciphertext, iv }
820
- }
821
- else {
822
- event.error = new boom_1.Boom('Failed to re-upload media (missing ciphertext)', { statusCode: 404 })
756
+ event.media = { ciphertext: ciphertext, iv: iv };
757
+ } else {
758
+ event.error = new Boom("Failed to re-upload media (missing ciphertext)", {
759
+ statusCode: 404,
760
+ });
823
761
  }
824
762
  }
825
- return event
826
- }
827
-
828
- const decryptMediaRetryData = async ({ ciphertext, iv }, mediaKey, msgId) => {
829
- const retryKey = await getMediaRetryKey(mediaKey)
830
- const plaintext = crypto_1.aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId))
831
- return WAProto_1.proto.MediaRetryNotification.decode(plaintext)
832
- }
833
-
834
- const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code]
835
-
763
+ return event;
764
+ };
765
+ const decryptMediaRetryData = async ({ ciphertext: ciphertext, iv: iv }, mediaKey, msgId) => {
766
+ const retryKey = await getMediaRetryKey(mediaKey);
767
+ const plaintext = aesDecryptGCM(ciphertext, retryKey, iv, Buffer.from(msgId));
768
+ return proto.MediaRetryNotification.decode(plaintext);
769
+ };
770
+ const getStatusCodeForMediaRetry = (code) => MEDIA_RETRY_STATUS_MAP[code];
836
771
  const MEDIA_RETRY_STATUS_MAP = {
837
- [WAProto_1.proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
838
- [WAProto_1.proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
839
- [WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
840
- [WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
841
- }
842
-
772
+ [proto.MediaRetryNotification.ResultType.SUCCESS]: 200,
773
+ [proto.MediaRetryNotification.ResultType.DECRYPTION_ERROR]: 412,
774
+ [proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
775
+ [proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
776
+ };
843
777
  module.exports = {
844
- hkdfInfoKey,
845
- getMediaKeys,
846
- extractVideoThumb,
847
- extractImageThumb,
848
- encodeBase64EncodedStringForUpload,
849
- generateProfilePicture,
850
- mediaMessageSHA256B64,
851
- getAudioDuration,
852
- getAudioWaveform,
853
- toReadable,
854
- toBuffer,
855
- getStream,
856
- generateThumbnail,
857
- getHttpStream,
858
- prepareStream,
859
- encryptedStream,
860
- getUrlFromDirectPath,
861
- downloadContentFromMessage,
862
- downloadEncryptedContent,
863
- extensionForMediaMessage,
864
- getRawMediaUploadData,
865
- getWAUploadToServer,
866
- getMediaRetryKey,
867
- encryptMediaRetryRequest,
868
- decodeMediaRetryNode,
869
- decryptMediaRetryData,
870
- getStatusCodeForMediaRetry,
871
- MEDIA_RETRY_STATUS_MAP
872
- }
778
+ hkdfInfoKey: hkdfInfoKey,
779
+ getMediaKeys: getMediaKeys,
780
+ extractVideoThumb: extractVideoThumb,
781
+ extractImageThumb: extractImageThumb,
782
+ encodeBase64EncodedStringForUpload: encodeBase64EncodedStringForUpload,
783
+ generateProfilePicture: generateProfilePicture,
784
+ mediaMessageSHA256B64: mediaMessageSHA256B64,
785
+ getAudioDuration: getAudioDuration,
786
+ getAudioWaveform: getAudioWaveform,
787
+ toReadable: toReadable,
788
+ toBuffer: toBuffer,
789
+ getStream: getStream,
790
+ generateThumbnail: generateThumbnail,
791
+ getHttpStream: getHttpStream,
792
+ encryptedStream: encryptedStream,
793
+ getUrlFromDirectPath: getUrlFromDirectPath,
794
+ downloadContentFromMessage: downloadContentFromMessage,
795
+ downloadEncryptedContent: downloadEncryptedContent,
796
+ extensionForMediaMessage: extensionForMediaMessage,
797
+ uploadWithNodeHttp: uploadWithNodeHttp,
798
+ getRawMediaUploadData: getRawMediaUploadData,
799
+ getWAUploadToServer: getWAUploadToServer,
800
+ getMediaRetryKey: getMediaRetryKey,
801
+ encryptMediaRetryRequest: encryptMediaRetryRequest,
802
+ decodeMediaRetryNode: decodeMediaRetryNode,
803
+ decryptMediaRetryData: decryptMediaRetryData,
804
+ getStatusCodeForMediaRetry: getStatusCodeForMediaRetry,
805
+ MEDIA_RETRY_STATUS_MAP: MEDIA_RETRY_STATUS_MAP,
806
+ };