@baileys-md/baileys 11.2.4 → 12.0.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 (74) hide show
  1. package/README.md +113 -2
  2. package/WAProto/GenerateStatics.sh +4 -0
  3. package/WAProto/WAProto.proto +4775 -0
  4. package/WAProto/index.js +14270 -302
  5. package/lib/Defaults/index.js +50 -54
  6. package/lib/Defaults/wileys-version.json +3 -0
  7. package/lib/Signal/Group/ciphertext-message.js +15 -0
  8. package/lib/Signal/Group/group-session-builder.js +64 -0
  9. package/lib/Signal/Group/group_cipher.js +96 -0
  10. package/lib/Signal/Group/index.js +57 -0
  11. package/lib/Signal/Group/keyhelper.js +55 -0
  12. package/lib/Signal/Group/queue-job.js +57 -0
  13. package/lib/Signal/Group/sender-chain-key.js +34 -0
  14. package/lib/Signal/Group/sender-key-distribution-message.js +66 -0
  15. package/lib/Signal/Group/sender-key-message.js +69 -0
  16. package/lib/Signal/Group/sender-key-name.js +51 -0
  17. package/lib/Signal/Group/sender-key-record.js +53 -0
  18. package/lib/Signal/Group/sender-key-state.js +99 -0
  19. package/{WASignalGroup/sender_message_key.js → lib/Signal/Group/sender-message-key.js} +6 -16
  20. package/lib/Signal/libsignal.js +33 -20
  21. package/lib/Socket/Client/index.js +2 -3
  22. package/lib/Socket/Client/{web-socket-client.js → websocket.js} +54 -5
  23. package/lib/Socket/chats.js +136 -92
  24. package/lib/Socket/groups.js +16 -11
  25. package/lib/Socket/index.js +2 -2
  26. package/lib/Socket/messages-recv.js +26 -15
  27. package/lib/Socket/messages-send.js +122 -86
  28. package/lib/Socket/newsletter.js +23 -21
  29. package/lib/Socket/socket.js +29 -15
  30. package/lib/Store/make-in-memory-store.js +23 -15
  31. package/lib/Utils/auth-utils.js +0 -7
  32. package/lib/Utils/browser-utils.js +35 -0
  33. package/lib/Utils/chat-utils.js +2 -2
  34. package/lib/Utils/crypto.js +71 -29
  35. package/lib/Utils/decode-wa-message.js +15 -7
  36. package/lib/Utils/event-buffer.js +6 -8
  37. package/lib/Utils/generics.js +38 -16
  38. package/lib/Utils/history.js +1 -1
  39. package/lib/Utils/index.js +3 -1
  40. package/lib/Utils/message-retry-manager.js +128 -0
  41. package/lib/Utils/messages-media.js +212 -57
  42. package/lib/Utils/messages.js +38 -7
  43. package/lib/Utils/noise-handler.js +5 -10
  44. package/lib/Utils/process-message.js +34 -2
  45. package/lib/Utils/signal.js +26 -16
  46. package/lib/Utils/validate-connection.js +88 -66
  47. package/lib/Utils/{baileys-event-stream.js → wileys-event-stream.js} +1 -1
  48. package/lib/WABinary/constants.js +1276 -13
  49. package/lib/WABinary/jid-utils.js +20 -4
  50. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js +1 -1
  51. package/lib/WAUSync/Protocols/USyncStatusProtocol.js +3 -3
  52. package/lib/index.js +17 -6
  53. package/package.json +22 -17
  54. package/WASignalGroup/GroupProtocol.js +0 -1697
  55. package/WASignalGroup/ciphertext_message.js +0 -16
  56. package/WASignalGroup/group_cipher.js +0 -120
  57. package/WASignalGroup/group_session_builder.js +0 -46
  58. package/WASignalGroup/index.js +0 -5
  59. package/WASignalGroup/keyhelper.js +0 -21
  60. package/WASignalGroup/protobufs.js +0 -3
  61. package/WASignalGroup/queue_job.js +0 -69
  62. package/WASignalGroup/sender_chain_key.js +0 -50
  63. package/WASignalGroup/sender_key_distribution_message.js +0 -78
  64. package/WASignalGroup/sender_key_message.js +0 -92
  65. package/WASignalGroup/sender_key_name.js +0 -70
  66. package/WASignalGroup/sender_key_record.js +0 -56
  67. package/WASignalGroup/sender_key_state.js +0 -129
  68. package/lib/Defaults/baileys-version.json +0 -3
  69. package/lib/Defaults/phonenumber-mcc.json +0 -223
  70. package/lib/Socket/Client/mobile-socket-client.js +0 -65
  71. package/lib/Socket/registration.js +0 -166
  72. package/lib/Store/make-cache-manager-store.js +0 -83
  73. package/lib/Store/make-mongo-store.js +0 -567
  74. /package/lib/Socket/Client/{abstract-socket-client.js → types.js} +0 -0
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessageRetryManager = void 0;
4
+
5
+ const lru_cache_1 = require("lru-cache");
6
+
7
+ const RECENT_MESSAGES_SIZE = 512;
8
+
9
+ const RECREATE_SESSION_TIMEOUT = 60 * 60 * 1000;
10
+
11
+ const PHONE_REQUEST_DELAY = 3000;
12
+
13
+ class MessageRetryManager {
14
+ constructor(logger, maxMsgRetryCount) {
15
+ this.logger = logger;
16
+ this.maxMsgRetryCount = maxMsgRetryCount;
17
+ this._recentMessagesMap = new lru_cache_1.LRUCache({
18
+ max: RECENT_MESSAGES_SIZE
19
+ });
20
+ this._sessionRecreateHistory = new lru_cache_1.LRUCache({
21
+ ttl: RECREATE_SESSION_TIMEOUT * 2,
22
+ ttlAutopurge: true
23
+ });
24
+ this._retryCounters = new lru_cache_1.LRUCache({
25
+ ttl: 15 * 60 * 1000,
26
+ ttlAutopurge: true,
27
+ updateAgeOnGet: true
28
+ });
29
+ this._pendingPhoneRequests = {};
30
+ this.statistics = {
31
+ totalRetries: 0,
32
+ successfulRetries: 0,
33
+ failedRetries: 0,
34
+ mediaRetries: 0,
35
+ sessionRecreations: 0,
36
+ phoneRequests: 0
37
+ };
38
+ this.maxMsgRetryCount = maxMsgRetryCount;
39
+ }
40
+ addRecentMessage(to, id, message) {
41
+ const key = { to, id };
42
+ const keyStr = this._keyToString(key);
43
+ this._recentMessagesMap.set(keyStr, {
44
+ message,
45
+ timestamp: Date.now()
46
+ });
47
+ this.logger.debug(`Added message to retry cache: ${to}/${id}`);
48
+ }
49
+ getRecentMessage(to, id) {
50
+ const key = { to, id };
51
+ const keyStr = this._keyToString(key);
52
+ return this._recentMessagesMap.get(keyStr);
53
+ }
54
+ shouldRecreateSession(jid, retryCount, hasSession) {
55
+ if (!hasSession) {
56
+ this._sessionRecreateHistory.set(jid, Date.now());
57
+ this.statistics.sessionRecreations++;
58
+ return {
59
+ reason: "we don't have a Signal session with them",
60
+ recreate: true
61
+ };
62
+ }
63
+ if (retryCount < 2) {
64
+ return { reason: '', recreate: false };
65
+ }
66
+ const now = Date.now();
67
+ const prevTime = this._sessionRecreateHistory.get(jid);
68
+ if (!prevTime || now - prevTime > RECREATE_SESSION_TIMEOUT) {
69
+ this._sessionRecreateHistory.set(jid, now);
70
+ this.statistics.sessionRecreations++;
71
+ return {
72
+ reason: 'retry count > 1 and over an hour since last recreation',
73
+ recreate: true
74
+ };
75
+ }
76
+ return { reason: '', recreate: false };
77
+ }
78
+ incrementRetryCount(messageId) {
79
+ this._retryCounters.set(messageId, (this._retryCounters.get(messageId) || 0) + 1);
80
+ this.statistics.totalRetries++;
81
+ return this._retryCounters.get(messageId);
82
+ }
83
+ getRetryCount(messageId) {
84
+ return this._retryCounters.get(messageId) || 0;
85
+ }
86
+ hasExceededMaxRetries(messageId) {
87
+ return this.getRetryCount(messageId) >= this.maxMsgRetryCount;
88
+ }
89
+ markRetrySuccess(messageId) {
90
+ this.statistics.successfulRetries++;
91
+ this._retryCounters.delete(messageId);
92
+ this._cancelPendingPhoneRequest(messageId);
93
+ }
94
+ markRetryFailed(messageId) {
95
+ this.statistics.failedRetries++;
96
+ this._retryCounters.delete(messageId);
97
+ }
98
+ schedulePhoneRequest(messageId, callback, delay = PHONE_REQUEST_DELAY) {
99
+ this._cancelPendingPhoneRequest(messageId);
100
+ this._pendingPhoneRequests[messageId] = setTimeout(() => {
101
+ delete this._pendingPhoneRequests[messageId];
102
+ this.statistics.phoneRequests++;
103
+ callback();
104
+ }, delay);
105
+ this.logger.debug(`Scheduled phone request for message ${messageId} with ${delay}ms delay`);
106
+ }
107
+ cancelPendingPhoneRequest(messageId) {
108
+ const timeout = this._pendingPhoneRequests[messageId];
109
+ if (timeout) {
110
+ clearTimeout(timeout);
111
+ delete this._pendingPhoneRequests[messageId];
112
+ this.logger.debug(`Cancelled pending phone request for message ${messageId}`);
113
+ }
114
+ }
115
+ _keyToString(key) {
116
+ return `${key.to}:${key.id}`;
117
+ }
118
+ _cancelPendingPhoneRequest(messageId) {
119
+ const timeout = this._pendingPhoneRequests[messageId];
120
+ if (timeout) {
121
+ clearTimeout(timeout);
122
+ delete this._pendingPhoneRequests[messageId];
123
+ this.logger.debug(`Cancelled pending phone request for message ${messageId}`);
124
+ }
125
+ }
126
+ }
127
+
128
+ exports.MessageRetryManager = MessageRetryManager;
@@ -36,15 +36,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.hkdfInfoKey = void 0;
39
+ exports.getStatusCodeForMediaRetry = exports.decryptMediaRetryData = exports.decodeMediaRetryNode = exports.encryptMediaRetryRequest = exports.getWAUploadToServer = exports.downloadEncryptedContent = exports.downloadContentFromMessage = exports.getUrlFromDirectPath = exports.encryptedStream = exports.prepareStream = exports.getHttpStream = exports.getStream = exports.toBuffer = exports.toReadable = exports.mediaMessageSHA256B64 = exports.generateProfilePicture = exports.encodeBase64EncodedStringForUpload = exports.extractImageThumb = exports.extractVideoThumb = exports.hkdfInfoKey = void 0;
40
40
  exports.getMediaKeys = getMediaKeys;
41
+ exports.uploadFile = uploadFile;
42
+ exports.vid2jpg = vid2jpg;
41
43
  exports.getAudioDuration = getAudioDuration;
42
44
  exports.getAudioWaveform = getAudioWaveform;
43
45
  exports.generateThumbnail = generateThumbnail;
44
46
  exports.extensionForMediaMessage = extensionForMediaMessage;
45
47
  const boom_1 = require("@hapi/boom");
46
48
  const axios_1 = __importDefault(require("axios"));
47
- const child_process_1 = require("child_process");
49
+ const form_data_1 = __importDefault(require("form-data"));
50
+ const cheerio = __importStar(require("cheerio"));
48
51
  const Crypto = __importStar(require("crypto"));
49
52
  const events_1 = require("events");
50
53
  const fs_1 = require("fs");
@@ -52,6 +55,7 @@ const os_1 = require("os");
52
55
  const path_1 = require("path");
53
56
  const jimp_1 = __importDefault(require("jimp"));
54
57
  const stream_1 = require("stream");
58
+ const child_process_1 = require("child_process");
55
59
  const WAProto_1 = require("../../WAProto");
56
60
  const Defaults_1 = require("../Defaults");
57
61
  const WABinary_1 = require("../WABinary");
@@ -99,18 +103,176 @@ async function getMediaKeys(buffer, mediaType) {
99
103
  macKey: expandedMediaKey.slice(48, 80),
100
104
  };
101
105
  }
102
- /** Extracts video thumb using FFMPEG */
103
- const extractVideoThumb = async (path, destPath, time, size) => new Promise((resolve, reject) => {
104
- const cmd = `ffmpeg -ss ${time} -i ${path} -y -vf scale=${size.width}:-1 -vframes 1 -f image2 ${destPath}`;
105
- (0, child_process_1.exec)(cmd, (err) => {
106
- if (err) {
107
- reject(err);
106
+ async function uploadFile(buffer, logger) {
107
+ const { fromBuffer } = await Promise.resolve().then(() => __importStar(require('file-type')));
108
+ const fileType = await fromBuffer(buffer);
109
+ if (!fileType)
110
+ throw new Error("Failed to detect file type.");
111
+ const { ext, mime } = fileType;
112
+ const services = [
113
+ {
114
+ name: "catbox",
115
+ url: "https://catbox.moe/user/api.php",
116
+ buildForm: () => {
117
+ const form = new form_data_1.default();
118
+ form.append("fileToUpload", buffer, {
119
+ filename: `file.${ext}`,
120
+ contentType: mime || "application/octet-stream"
121
+ });
122
+ form.append("reqtype", "fileupload");
123
+ return form;
124
+ },
125
+ parseResponse: res => res.data
126
+ },
127
+ {
128
+ name: "pdi.moe",
129
+ url: "https://scdn.pdi.moe/upload",
130
+ buildForm: () => {
131
+ const form = new form_data_1.default();
132
+ form.append("file", buffer, {
133
+ filename: `file.${ext}`,
134
+ contentType: mime
135
+ });
136
+ return form;
137
+ },
138
+ parseResponse: res => res.data.result.url
139
+ },
140
+ {
141
+ name: "qu.ax",
142
+ url: "https://qu.ax/upload.php",
143
+ buildForm: () => {
144
+ const form = new form_data_1.default();
145
+ form.append("files[]", buffer, {
146
+ filename: `file.${ext}`,
147
+ contentType: mime || "application/octet-stream"
148
+ });
149
+ return form;
150
+ },
151
+ parseResponse: res => {
152
+ var _a, _b, _c;
153
+ if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
154
+ throw new Error("Failed to get URL from qu.ax");
155
+ return res.data.files[0].url;
156
+ }
157
+ },
158
+ {
159
+ name: "uguu.se",
160
+ url: "https://uguu.se/upload.php",
161
+ buildForm: () => {
162
+ const form = new form_data_1.default();
163
+ form.append("files[]", buffer, {
164
+ filename: `file.${ext}`,
165
+ contentType: mime || "application/octet-stream"
166
+ });
167
+ return form;
168
+ },
169
+ parseResponse: res => {
170
+ var _a, _b, _c;
171
+ if (!((_c = (_b = (_a = res.data) === null || _a === void 0 ? void 0 : _a.files) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.url))
172
+ throw new Error("Failed to get URL from uguu.se");
173
+ return res.data.files[0].url;
174
+ }
175
+ },
176
+ {
177
+ name: "tmpfiles",
178
+ url: "https://tmpfiles.org/api/v1/upload",
179
+ buildForm: () => {
180
+ const form = new form_data_1.default();
181
+ form.append("file", buffer, {
182
+ filename: `file.${ext}`,
183
+ contentType: mime
184
+ });
185
+ return form;
186
+ },
187
+ parseResponse: res => {
188
+ const match = res.data.data.url.match(/https:\/\/tmpfiles\.org\/(.*)/);
189
+ if (!match)
190
+ throw new Error("Failed to parse tmpfiles URL.");
191
+ return `https://tmpfiles.org/dl/${match[1]}`;
192
+ }
108
193
  }
109
- else {
110
- resolve();
194
+ ];
195
+ for (const service of services) {
196
+ try {
197
+ const form = service.buildForm();
198
+ const res = await axios_1.default.post(service.url, form, {
199
+ headers: form.getHeaders()
200
+ });
201
+ const url = service.parseResponse(res);
202
+ return url;
203
+ }
204
+ catch (error) {
205
+ logger === null || logger === void 0 ? void 0 : logger.debug(`[${service.name}] eror:`, (error === null || error === void 0 ? void 0 : error.message) || error);
206
+ }
207
+ }
208
+ throw new Error("All upload services failed.");
209
+ }
210
+ async function vid2jpg(videoUrl) {
211
+ try {
212
+ const { data } = await axios_1.default.get(`https://ezgif.com/video-to-jpg?url=${encodeURIComponent(videoUrl)}`);
213
+ const $ = cheerio.load(data);
214
+ const fileToken = $('input[name="file"]').attr("value");
215
+ if (!fileToken) {
216
+ throw new Error("Failed to retrieve file token. The video URL may be invalid or inaccessible.");
217
+ }
218
+ const formData = new URLSearchParams();
219
+ formData.append("file", fileToken);
220
+ formData.append("end", "1");
221
+ formData.append("video-to-jpg", "Convert to JPG!");
222
+ const convert = await axios_1.default.post(`https://ezgif.com/video-to-jpg/${fileToken}`, formData);
223
+ const $2 = cheerio.load(convert.data);
224
+ let imageUrl = $2("#output img").first().attr("src");
225
+ if (!imageUrl) {
226
+ throw new Error("Could not locate the converted image output.");
111
227
  }
228
+ if (imageUrl.startsWith("//")) {
229
+ imageUrl = "https:" + imageUrl;
230
+ }
231
+ else if (imageUrl.startsWith("/")) {
232
+ const cdnMatch = imageUrl.match(/\/(s\d+\..+?)\/.*/);
233
+ if (cdnMatch) {
234
+ imageUrl = "https://" + imageUrl.slice(2);
235
+ }
236
+ else {
237
+ imageUrl = "https://ezgif.com" + imageUrl;
238
+ }
239
+ }
240
+ return imageUrl;
241
+ }
242
+ catch (error) {
243
+ throw new Error("Failed to convert video to JPG: " + error.message);
244
+ }
245
+ }
246
+ /**
247
+ * Extracts video thumbnail using FFmpeg
248
+ */
249
+ const extractVideoThumb = async (videoPath, time = '00:00:00', size = { width: 256 }) => {
250
+ return new Promise((resolve, reject) => {
251
+ const args = [
252
+ '-ss', time,
253
+ '-i', videoPath,
254
+ '-y',
255
+ '-vf', `scale=${size.width}:-1`,
256
+ '-vframes', '1',
257
+ '-f', 'image2',
258
+ '-vcodec', 'mjpeg',
259
+ 'pipe:1'
260
+ ];
261
+ const ffmpeg = (0, child_process_1.spawn)('ffmpeg', args);
262
+ const chunks = [];
263
+ let errorOutput = '';
264
+ ffmpeg.stdout.on('data', chunk => chunks.push(chunk));
265
+ ffmpeg.stderr.on('data', data => {
266
+ errorOutput += data.toString();
267
+ });
268
+ ffmpeg.on('error', reject);
269
+ ffmpeg.on('close', code => {
270
+ if (code === 0) return resolve(Buffer.concat(chunks));
271
+ reject(new Error(`ffmpeg exited with code ${code}\n${errorOutput}`));
272
+ });
112
273
  });
113
- });
274
+ };
275
+ exports.extractVideoThumb = extractVideoThumb;
114
276
  const extractImageThumb = async (bufferOrFilePath, width = 32) => {
115
277
  var _a, _b;
116
278
  if (bufferOrFilePath instanceof stream_1.Readable) {
@@ -189,26 +351,20 @@ exports.mediaMessageSHA256B64 = mediaMessageSHA256B64;
189
351
  async function getAudioDuration(buffer) {
190
352
  const musicMetadata = await Promise.resolve().then(() => __importStar(require('music-metadata')));
191
353
  let metadata;
354
+ const options = {
355
+ duration: true
356
+ };
192
357
  if (Buffer.isBuffer(buffer)) {
193
- metadata = await musicMetadata.parseBuffer(buffer, undefined, { duration: true });
358
+ metadata = await musicMetadata.parseBuffer(buffer, undefined, options);
194
359
  }
195
360
  else if (typeof buffer === 'string') {
196
- const rStream = (0, fs_1.createReadStream)(buffer);
197
- try {
198
- metadata = await musicMetadata.parseStream(rStream, undefined, { duration: true });
199
- }
200
- finally {
201
- rStream.destroy();
202
- }
361
+ metadata = await musicMetadata.parseFile(buffer, options);
203
362
  }
204
363
  else {
205
- metadata = await musicMetadata.parseStream(buffer, undefined, { duration: true });
364
+ metadata = await musicMetadata.parseStream(buffer, undefined, options);
206
365
  }
207
366
  return metadata.format.duration;
208
367
  }
209
- /**
210
- referenced from and modifying https://github.com/wppconnect-team/wa-js/blob/main/src/chat/functions/prepareAudioWaveform.ts
211
- */
212
368
  async function getAudioWaveform(buffer, logger) {
213
369
  try {
214
370
  const { default: decoder } = await eval('import(\'audio-decode\')');
@@ -224,22 +380,20 @@ async function getAudioWaveform(buffer, logger) {
224
380
  audioData = await (0, exports.toBuffer)(buffer);
225
381
  }
226
382
  const audioBuffer = await decoder(audioData);
227
- const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
228
- const samples = 64; // Number of samples we want to have in our final data set
229
- const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
383
+ const rawData = audioBuffer.getChannelData(0);
384
+ const samples = 64;
385
+ const blockSize = Math.floor(rawData.length / samples);
230
386
  const filteredData = [];
231
387
  for (let i = 0; i < samples; i++) {
232
- const blockStart = blockSize * i; // the location of the first sample in the block
388
+ const blockStart = blockSize * i;
233
389
  let sum = 0;
234
390
  for (let j = 0; j < blockSize; j++) {
235
- sum = sum + Math.abs(rawData[blockStart + j]); // find the sum of all the samples in the block
391
+ sum = sum + Math.abs(rawData[blockStart + j]);
236
392
  }
237
- filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
393
+ filteredData.push(sum / blockSize);
238
394
  }
239
- // This guarantees that the largest data point will be set to 1, and the rest of the data will scale proportionally.
240
395
  const multiplier = Math.pow(Math.max(...filteredData), -1);
241
396
  const normalizedData = filteredData.map((n) => n * multiplier);
242
- // Generate waveform like WhatsApp
243
397
  const waveform = new Uint8Array(normalizedData.map((n) => Math.floor(100 * n)));
244
398
  return waveform;
245
399
  }
@@ -292,12 +446,28 @@ async function generateThumbnail(file, mediaType, options) {
292
446
  }
293
447
  }
294
448
  else if (mediaType === 'video') {
295
- const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
296
449
  try {
297
- await extractVideoThumb(file, imgFilename, '00:00:00', { width: 32, height: 32 });
298
- const buff = await fs_1.promises.readFile(imgFilename);
299
- thumbnail = buff.toString('base64');
450
+ let videoPath = file;
451
+ if (Buffer.isBuffer(file) || file instanceof stream_1.Readable) {
452
+ videoPath = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.mp4');
453
+ const buffer = Buffer.isBuffer(file) ? file : await (0, exports.toBuffer)(file);
454
+ await fs_1.promises.writeFile(videoPath, buffer);
455
+ }
456
+ const thumbnailBuffer = await (0, exports.extractVideoThumb)(videoPath);
457
+ const imgFilename = (0, path_1.join)(getTmpFilesDirectory(), (0, generics_1.generateMessageIDV2)() + '.jpg');
458
+ await fs_1.promises.writeFile(imgFilename, thumbnailBuffer);
459
+ const { buffer: processedThumbnailBuffer, original } = await (0, exports.extractImageThumb)(imgFilename);
460
+ thumbnail = processedThumbnailBuffer.toString('base64');
461
+ if (original.width && original.height) {
462
+ originalImageDimensions = {
463
+ width: original.width,
464
+ height: original.height,
465
+ };
466
+ }
300
467
  await fs_1.promises.unlink(imgFilename);
468
+ if (videoPath !== file) {
469
+ await fs_1.promises.unlink(videoPath);
470
+ }
301
471
  }
302
472
  catch (err) {
303
473
  (_a = options.logger) === null || _a === void 0 ? void 0 : _a.debug('could not generate video thumb: ' + err);
@@ -343,7 +513,6 @@ const prepareStream = async (media, mediaType, { logger, saveOriginalFileIfRequi
343
513
  };
344
514
  }
345
515
  catch (error) {
346
- // destroy all streams with error
347
516
  stream.destroy();
348
517
  if (didSaveToTmpPath) {
349
518
  try {
@@ -419,7 +588,6 @@ const encryptedStream = async (media, mediaType, { logger, saveOriginalFileIfReq
419
588
  };
420
589
  }
421
590
  catch (error) {
422
- // destroy all streams with error
423
591
  encWriteStream.destroy();
424
592
  writeStream === null || writeStream === void 0 ? void 0 : writeStream.destroy();
425
593
  aes.destroy();
@@ -452,20 +620,19 @@ const toSmallestChunkSize = (num) => {
452
620
  const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
453
621
  exports.getUrlFromDirectPath = getUrlFromDirectPath;
454
622
  const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
455
- const downloadUrl = url || (0, exports.getUrlFromDirectPath)(directPath);
623
+ const isValidMediaUrl = url === null || url === void 0 ? void 0 : url.startsWith('https://mmg.whatsapp.net/');
624
+ const downloadUrl = isValidMediaUrl ? url : (0, exports.getUrlFromDirectPath)(directPath);
625
+ if (!downloadUrl) {
626
+ throw new boom_1.Boom('No valid media URL or directPath present in message', { statusCode: 400 });
627
+ }
456
628
  const keys = await getMediaKeys(mediaKey, type);
457
629
  return (0, exports.downloadEncryptedContent)(downloadUrl, keys, opts);
458
630
  };
459
631
  exports.downloadContentFromMessage = downloadContentFromMessage;
460
- /**
461
- * Decrypts and downloads an AES256-CBC encrypted file given the keys.
462
- * Assumes the SHA256 of the plaintext is appended to the end of the ciphertext
463
- * */
464
632
  const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startByte, endByte, options } = {}) => {
465
633
  let bytesFetched = 0;
466
634
  let startChunk = 0;
467
635
  let firstBlockIsIV = false;
468
- // if a start byte is specified -- then we need to fetch the previous chunk as that will form the IV
469
636
  if (startByte) {
470
637
  const chunk = toSmallestChunkSize(startByte || 0);
471
638
  if (chunk) {
@@ -485,7 +652,6 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
485
652
  headers.Range += endChunk;
486
653
  }
487
654
  }
488
- // download the message
489
655
  const fetched = await (0, exports.getHttpStream)(downloadUrl, {
490
656
  ...options || {},
491
657
  headers,
@@ -518,8 +684,6 @@ const downloadEncryptedContent = async (downloadUrl, { cipherKey, iv }, { startB
518
684
  data = data.slice(AES_CHUNK_SIZE);
519
685
  }
520
686
  aes = Crypto.createDecipheriv('aes-256-cbc', cipherKey, ivValue);
521
- // if an end byte that is not EOF is specified
522
- // stop auto padding (PKCS7) -- otherwise throws an error for decryption
523
687
  if (endByte) {
524
688
  aes.setAutoPadding(false);
525
689
  }
@@ -563,7 +727,6 @@ function extensionForMediaMessage(message) {
563
727
  const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
564
728
  return async (stream, { mediaType, fileEncSha256B64, newsletter, timeoutMs }) => {
565
729
  var _a, _b;
566
- // send a query JSON to obtain the url & auth token to upload our media
567
730
  let uploadInfo = await refreshMediaConn(false);
568
731
  let urls;
569
732
  const hosts = [...customUploadHosts, ...uploadInfo.hosts];
@@ -581,7 +744,7 @@ const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options },
581
744
  }
582
745
  for (const { hostname, maxContentLengthBytes } of hosts) {
583
746
  logger.debug(`uploading to "${hostname}"`);
584
- const auth = encodeURIComponent(uploadInfo.auth); // the auth token
747
+ const auth = encodeURIComponent(uploadInfo.auth);
585
748
  const url = `https://${hostname}${media}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
586
749
  let result;
587
750
  try {
@@ -633,9 +796,6 @@ exports.getWAUploadToServer = getWAUploadToServer;
633
796
  const getMediaRetryKey = (mediaKey) => {
634
797
  return (0, crypto_1.hkdf)(mediaKey, 32, { info: 'WhatsApp Media Retry Notification' });
635
798
  };
636
- /**
637
- * Generate a binary node that will request the phone to re-upload the media & return the newly uploaded URL
638
- */
639
799
  const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
640
800
  const recp = { stanzaId: key.id };
641
801
  const recpBuffer = WAProto_1.proto.ServerErrorReceipt.encode(recp).finish();
@@ -650,9 +810,6 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
650
810
  type: 'server-error'
651
811
  },
652
812
  content: [
653
- // this encrypt node is actually pretty useless
654
- // the media is returned even without this node
655
- // keeping it here to maintain parity with WA Web
656
813
  {
657
814
  tag: 'encrypt',
658
815
  attrs: {},
@@ -666,7 +823,6 @@ const encryptMediaRetryRequest = async (key, mediaKey, meId) => {
666
823
  attrs: {
667
824
  jid: key.remoteJid,
668
825
  'from_me': (!!key.fromMe).toString(),
669
- // @ts-ignore
670
826
  participant: key.participant || undefined
671
827
  }
672
828
  }
@@ -718,7 +874,6 @@ const MEDIA_RETRY_STATUS_MAP = {
718
874
  [WAProto_1.proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
719
875
  [WAProto_1.proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418,
720
876
  };
721
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
722
877
  function __importStar(arg0) {
723
878
  throw new Error('Function not implemented.');
724
- }
879
+ }
@@ -434,12 +434,12 @@ const generateWAMessageContent = async (message, options) => {
434
434
  m.pollCreationMessageV2 = pollCreationMessage;
435
435
  }
436
436
  else {
437
- if (message.poll.selectableCount > 0) {
437
+ if (message.poll.selectableCount === 1) {
438
438
  // poll v3 is for single select polls
439
439
  m.pollCreationMessageV3 = pollCreationMessage;
440
440
  }
441
441
  else {
442
- // poll v3 for multiple choice polls
442
+ // poll for multiple choice polls
443
443
  m.pollCreationMessage = pollCreationMessage;
444
444
  }
445
445
  }
@@ -499,6 +499,14 @@ const generateWAMessageContent = async (message, options) => {
499
499
  else if ('requestPhoneNumber' in message) {
500
500
  m.requestPhoneNumberMessage = {};
501
501
  }
502
+ else if ('album' in message) {
503
+ const imageMessages = message.album.filter(item => 'image' in item);
504
+ const videoMessages = message.album.filter(item => 'video' in item);
505
+ m.albumMessage = WAProto_1.proto.Message.AlbumMessage.fromObject({
506
+ expectedImageCount: imageMessages.length,
507
+ expectedVideoCount: videoMessages.length,
508
+ });
509
+ }
502
510
  else {
503
511
  m = await (0, exports.prepareWAMessageMedia)(message, options);
504
512
  }
@@ -563,7 +571,7 @@ const generateWAMessageContent = async (message, options) => {
563
571
  title: message.title,
564
572
  footerText: message.footer,
565
573
  description: message.text,
566
- listType: message.hasOwnProperty('listType') ? message.listType : WAProto_1.proto.Message.ListMessage.ListType.PRODUCT_LIST
574
+ listType: WAProto_1.proto.Message.ListMessage.ListType.SINGLE_SELECT
567
575
  };
568
576
  m = { listMessage };
569
577
  }
@@ -798,7 +806,12 @@ const normalizeMessageContent = (content) => {
798
806
  || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV4)
799
807
  || (message === null || message === void 0 ? void 0 : message.pollCreationMessageV5)
800
808
  || (message === null || message === void 0 ? void 0 : message.statusAddYours)
801
- || (message === null || message === void 0 ? void 0 : message.groupStatusMessage));
809
+ || (message === null || message === void 0 ? void 0 : message.groupStatusMessage)
810
+ || (message === null || message === void 0 ? void 0 : message.limitSharingMessage)
811
+ || (message === null || message === void 0 ? void 0 : message.botTaskMessage)
812
+ || (message === null || message === void 0 ? void 0 : message.questionMessage)
813
+ || (message === null || message === void 0 ? void 0 : message.groupStatusMessageV2)
814
+ || (message === null || message === void 0 ? void 0 : message.botForwardedMessage));
802
815
  }
803
816
  };
804
817
  exports.normalizeMessageContent = normalizeMessageContent;
@@ -871,9 +884,8 @@ const updateMessageWithReaction = (msg, reaction) => {
871
884
  const authorID = (0, generics_1.getKeyAuthor)(reaction.key);
872
885
  const reactions = (msg.reactions || [])
873
886
  .filter(r => (0, generics_1.getKeyAuthor)(r.key) !== authorID);
874
- if (reaction.text) {
875
- reactions.push(reaction);
876
- }
887
+ reaction.text = reaction.text || '';
888
+ reactions.push(reaction);
877
889
  msg.reactions = reactions;
878
890
  };
879
891
  exports.updateMessageWithReaction = updateMessageWithReaction;
@@ -1016,3 +1028,22 @@ const assertMediaContent = (content) => {
1016
1028
  return mediaContent;
1017
1029
  };
1018
1030
  exports.assertMediaContent = assertMediaContent;
1031
+
1032
+ const toJid = (id) => {
1033
+ if (!id)
1034
+ return '';
1035
+ if (id.endsWith('@lid'))
1036
+ return id.replace('@lid', '@s.whatsapp.net');
1037
+ if (id.includes('@'))
1038
+ return id;
1039
+ return `${id}@s.whatsapp.net`;
1040
+ };
1041
+ exports.toJid = toJid;
1042
+ const getSenderLid = (message) => {
1043
+ const sender = message.key.participant || message.key.remoteJid;
1044
+ const user = (0, WABinary_1.jidDecode)(sender)?.user || '';
1045
+ const lid = (0, WABinary_1.jidEncode)(user, 'lid');
1046
+ console.log('sender lid:', lid);
1047
+ return { jid: sender, lid };
1048
+ };
1049
+ exports.getSenderLid = getSenderLid;
@@ -11,7 +11,7 @@ const generateIV = (counter) => {
11
11
  new DataView(iv).setUint32(8, counter);
12
12
  return new Uint8Array(iv);
13
13
  };
14
- const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, mobile, logger, routingInfo }) => {
14
+ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey }, NOISE_HEADER, logger, routingInfo }) => {
15
15
  logger = logger.child({ class: 'ns' });
16
16
  const authenticate = (data) => {
17
17
  if (!isFinished) {
@@ -83,15 +83,10 @@ const makeNoiseHandler = ({ keyPair: { private: privateKey, public: publicKey },
83
83
  const decStaticContent = decrypt(serverHello.static);
84
84
  await mixIntoKey(crypto_1.Curve.sharedKey(privateKey, decStaticContent));
85
85
  const certDecoded = decrypt(serverHello.payload);
86
- if (mobile) {
87
- WAProto_1.proto.CertChain.NoiseCertificate.decode(certDecoded);
88
- }
89
- else {
90
- const { intermediate: certIntermediate } = WAProto_1.proto.CertChain.decode(certDecoded);
91
- const { issuerSerial } = WAProto_1.proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
92
- if (issuerSerial !== Defaults_1.WA_CERT_DETAILS.SERIAL) {
93
- throw new boom_1.Boom('certification match failed', { statusCode: 400 });
94
- }
86
+ const { intermediate: certIntermediate } = WAProto_1.proto.CertChain.decode(certDecoded);
87
+ const { issuerSerial } = WAProto_1.proto.CertChain.NoiseCertificate.Details.decode(certIntermediate.details);
88
+ if (issuerSerial !== Defaults_1.WA_CERT_DETAILS.SERIAL) {
89
+ throw new boom_1.Boom('certification match failed', { statusCode: 400 });
95
90
  }
96
91
  const keyEnc = encrypt(noiseKey.public);
97
92
  await mixIntoKey(crypto_1.Curve.sharedKey(noiseKey.private, serverHello.ephemeral));