@alannxd/baileys 6.0.3 → 6.0.5

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