@crysnovax/baileys 2.5.5 → 2.6.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.
- package/lib/Defaults/index.js +4 -5
- package/lib/Signal/libsignal.js +0 -2
- package/lib/Socket/business.js +2 -1
- package/lib/Socket/chats.js +1 -2
- package/lib/Socket/communities.js +2 -1
- package/lib/Socket/groups.js +24 -5
- package/lib/Socket/messages-recv.js +0 -1
- package/lib/Socket/messages-send.js +14 -15
- package/lib/Socket/newsletter.js +6 -6
- package/lib/Socket/socket.js +44 -7
- package/lib/Types/Mex.js +1 -1
- package/lib/Types/State.js +37 -1
- package/lib/Types/index.js +1 -2
- package/lib/Utils/auth-utils.js +13 -1
- package/lib/Utils/chat-utils.js +81 -21
- package/lib/Utils/decode-wa-message.js +35 -1
- package/lib/Utils/messages-media.js +28 -16
- package/lib/Utils/messages.js +257 -318
- package/lib/Utils/signal.js +43 -1
- package/lib/Utils/tc-token-utils.js +150 -5
- package/lib/Utils/use-sqlite-auth-state.js +0 -1
- package/lib/WABinary/constants.js +5 -8
- package/package.json +12 -11
- package/lib/Socket/Client//342/234/230 +0 -1
- package/lib/Socket//342/230/201/357/270/216 +0 -1
- package/lib/Types/Newsletter.js +0 -33
- package/lib/Types//342/234/206 +0 -1
- package/lib/Utils//342/232/211 +0 -1
- package/lib/WABinary//342/216/231 +0 -1
package/lib/Utils/messages.js
CHANGED
|
@@ -5,7 +5,7 @@ import { promises as fs } from 'fs';
|
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
6
|
import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, DONATE_URL, LIBRARY_NAME, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
7
7
|
import { AssociationType, ButtonHeaderType, ButtonType, CarouselCardType, ListType, ProtocolType, WAMessageStatus, WAProto } from '../Types/index.js';
|
|
8
|
-
import {
|
|
8
|
+
import { isLidUser, isPnUser, isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
|
|
9
9
|
import { sha256 } from './crypto.js';
|
|
10
10
|
import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
|
|
11
11
|
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getImageProcessingLibrary, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
|
|
@@ -36,7 +36,7 @@ const mediaAnnotation = [
|
|
|
36
36
|
{ x: 20.840980529785156, y: -47.80188751220703 }
|
|
37
37
|
],
|
|
38
38
|
newsletter: {
|
|
39
|
-
//
|
|
39
|
+
// Lia@Note 03-02-26 --- You can change jid, message id, and name via .env (≧▽≦)
|
|
40
40
|
newsletterJid: process.env.NEWSLETTER_ID ||
|
|
41
41
|
'120363402922206865@newsletter',
|
|
42
42
|
serverMessageId: process.env.NEWSLETTER_MESSAGE_ID ||
|
|
@@ -124,54 +124,18 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
126
|
const isNewsletter = !!options.jid && isJidNewsletter(options.jid);
|
|
127
|
-
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
|
|
128
|
-
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData.jpegThumbnail === 'undefined';
|
|
129
|
-
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
|
|
130
|
-
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
|
|
131
|
-
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
|
|
132
|
-
// crysnovax@Changes 06-02-26 --- Add few support for sending media to newsletter (≧▽≦)
|
|
133
127
|
if (isNewsletter) {
|
|
134
128
|
logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
|
|
135
129
|
const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
|
|
136
130
|
const fileSha256B64 = fileSha256.toString('base64');
|
|
137
|
-
const
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
timeoutMs: options.mediaUploadTimeoutMs,
|
|
143
|
-
newsletter: isNewsletter
|
|
144
|
-
});
|
|
145
|
-
logger?.debug({ mediaType, cacheableKey }, 'uploaded media');
|
|
146
|
-
return result;
|
|
147
|
-
})(),
|
|
148
|
-
(async () => {
|
|
149
|
-
try {
|
|
150
|
-
if (requiresThumbnailComputation) {
|
|
151
|
-
const { thumbnail } = await generateThumbnail(filePath, mediaType, options);
|
|
152
|
-
uploadData.jpegThumbnail = thumbnail;
|
|
153
|
-
logger?.debug('generated thumbnail');
|
|
154
|
-
}
|
|
155
|
-
if (requiresDurationComputation) {
|
|
156
|
-
uploadData.seconds = await getAudioDuration(filePath);
|
|
157
|
-
logger?.debug('computed audio duration');
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
catch (error) {
|
|
161
|
-
logger?.warn({ trace: error.stack }, 'failed to obtain extra info');
|
|
162
|
-
}
|
|
163
|
-
})()
|
|
164
|
-
]).finally(async () => {
|
|
165
|
-
try {
|
|
166
|
-
await fs.unlink(filePath);
|
|
167
|
-
logger?.debug('removed tmp files');
|
|
168
|
-
}
|
|
169
|
-
catch (error) {
|
|
170
|
-
logger?.warn('failed to remove tmp file');
|
|
171
|
-
}
|
|
131
|
+
const { mediaUrl, directPath, thumbnailDirectPath, thumbnailSha256 } = await options.upload(filePath, {
|
|
132
|
+
fileEncSha256B64: fileSha256B64,
|
|
133
|
+
mediaType: mediaType,
|
|
134
|
+
timeoutMs: options.mediaUploadTimeoutMs,
|
|
135
|
+
newsletter: isNewsletter
|
|
172
136
|
});
|
|
173
|
-
|
|
174
|
-
const obj =
|
|
137
|
+
await fs.unlink(filePath);
|
|
138
|
+
const obj = WAProto.Message.fromObject({
|
|
175
139
|
// todo: add more support here
|
|
176
140
|
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
|
|
177
141
|
url: mediaUrl,
|
|
@@ -180,7 +144,8 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
180
144
|
fileLength,
|
|
181
145
|
thumbnailDirectPath,
|
|
182
146
|
thumbnailSha256,
|
|
183
|
-
...uploadData
|
|
147
|
+
...uploadData,
|
|
148
|
+
media: undefined
|
|
184
149
|
})
|
|
185
150
|
});
|
|
186
151
|
if (uploadData.ptv) {
|
|
@@ -196,6 +161,11 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
196
161
|
}
|
|
197
162
|
return obj;
|
|
198
163
|
}
|
|
164
|
+
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
|
|
165
|
+
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
|
|
166
|
+
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
|
|
167
|
+
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
|
|
168
|
+
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
|
|
199
169
|
const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
|
|
200
170
|
logger,
|
|
201
171
|
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
|
|
@@ -253,8 +223,7 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
253
223
|
logger?.warn('failed to remove tmp file');
|
|
254
224
|
}
|
|
255
225
|
});
|
|
256
|
-
|
|
257
|
-
const obj = proto.Message.create({
|
|
226
|
+
const obj = WAProto.Message.fromObject({
|
|
258
227
|
[`${mediaType}Message`]: MessageTypeProto[mediaType].fromObject({
|
|
259
228
|
url: mediaUrl,
|
|
260
229
|
directPath,
|
|
@@ -263,43 +232,27 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
263
232
|
fileSha256,
|
|
264
233
|
fileLength,
|
|
265
234
|
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
266
|
-
...uploadData
|
|
235
|
+
...uploadData,
|
|
236
|
+
media: undefined
|
|
267
237
|
})
|
|
268
238
|
});
|
|
269
239
|
if (uploadData.ptv) {
|
|
270
240
|
obj.ptvMessage = obj.videoMessage;
|
|
271
241
|
delete obj.videoMessage;
|
|
272
242
|
}
|
|
273
|
-
if (obj.stickerMessage) {
|
|
274
|
-
obj.stickerMessage.stickerSentTs = Date.now();
|
|
275
|
-
}
|
|
276
243
|
if (cacheableKey) {
|
|
277
244
|
logger?.debug({ cacheableKey }, 'set cache');
|
|
278
245
|
await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
|
|
279
246
|
}
|
|
280
247
|
return obj;
|
|
281
248
|
};
|
|
282
|
-
|
|
283
|
-
ephemeralExpiration = ephemeralExpiration || 0;
|
|
284
|
-
const content = {
|
|
285
|
-
ephemeralMessage: {
|
|
286
|
-
message: {
|
|
287
|
-
protocolMessage: {
|
|
288
|
-
type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
289
|
-
ephemeralExpiration
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
};
|
|
294
|
-
return WAProto.Message.fromObject(content);
|
|
295
|
-
};
|
|
296
|
-
// crysnovax@Changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
|
|
249
|
+
// Lia@Changes 31-01-26 --- Extract product message into a standalone function so it can also be reused as the header for interactive messages
|
|
297
250
|
const prepareProductMessage = async (message, options) => {
|
|
298
251
|
if (!message.businessOwnerJid) {
|
|
299
252
|
throw new Boom('"businessOwnerJid" is missing from the content', { statusCode: 400 });
|
|
300
253
|
}
|
|
301
254
|
const { imageMessage } = await prepareWAMessageMedia({ image: message.image || message.product.productImage }, options);
|
|
302
|
-
//
|
|
255
|
+
// Lia@Changes 01-02-26 --- Add product message default value
|
|
303
256
|
const { image, ...content } = message;
|
|
304
257
|
content.product = {
|
|
305
258
|
currencyCode: 'IDR',
|
|
@@ -311,11 +264,12 @@ const prepareProductMessage = async (message, options) => {
|
|
|
311
264
|
return content;
|
|
312
265
|
};
|
|
313
266
|
/**
|
|
314
|
-
*
|
|
267
|
+
* Lia@Note 30-01-26
|
|
315
268
|
* ---
|
|
316
269
|
* Credits: Work on ensuring stickerPackMessage fields are valid by @jlucaso1 (https://github.com/jlucaso1).
|
|
317
270
|
* based on https://github.com/WhiskeySockets/Baileys/pull/1561
|
|
318
271
|
*/
|
|
272
|
+
// Lia@Changes 21-04-26 --- Enhanced prepareStickerPackMessage
|
|
319
273
|
const prepareStickerPackMessage = async (message, options) => {
|
|
320
274
|
const { cover, stickers = [], name = '📦 Sticker Pack', publisher = 'Website: web.crysnovax.link', description = '🏷️ crysnovax/bailey' } = message;
|
|
321
275
|
if (stickers.length > 60) {
|
|
@@ -327,136 +281,124 @@ const prepareStickerPackMessage = async (message, options) => {
|
|
|
327
281
|
if (!cover) {
|
|
328
282
|
throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
|
|
329
283
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
284
|
+
const logger = options.logger;
|
|
285
|
+
// Lia@Changes 01-02-26 --- Add caching for sticker pack
|
|
286
|
+
let cacheableKey = false;
|
|
287
|
+
if (Array.isArray(stickers) && stickers.length && options.mediaCache) {
|
|
288
|
+
const urls = [];
|
|
289
|
+
for (let i = 0; i < stickers.length; i++) {
|
|
290
|
+
const data = stickers[i].data;
|
|
291
|
+
if (typeof data === 'object' && data?.url) {
|
|
292
|
+
urls.push(data.url);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (urls.length > 0) {
|
|
296
|
+
cacheableKey = 'sticker:' + urls.join('@');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
344
299
|
if (cacheableKey) {
|
|
345
300
|
const mediaBuff = await options.mediaCache.get(cacheableKey);
|
|
346
301
|
if (mediaBuff) {
|
|
347
|
-
|
|
302
|
+
logger?.debug({ cacheableKey }, 'got media cache hit');
|
|
348
303
|
return proto.Message.StickerPackMessage.decode(mediaBuff);
|
|
349
304
|
}
|
|
350
305
|
}
|
|
351
306
|
const lib = await getImageProcessingLibrary();
|
|
307
|
+
const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
|
|
308
|
+
const hasImage = 'image' in lib && !!lib.image?.Transformer;
|
|
309
|
+
const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
|
|
310
|
+
if (!hasSharp && !hasImage) {
|
|
311
|
+
throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting sticker to WebP.');
|
|
312
|
+
}
|
|
352
313
|
const stickerPackIdValue = generateMessageIDV2();
|
|
353
314
|
const stickerData = {};
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
315
|
+
const stickerMetadata = new Array(stickers.length);
|
|
316
|
+
for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
|
|
317
|
+
const promises = [];
|
|
318
|
+
const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
|
|
319
|
+
for (let j = i; j < chunkEnd; j++) {
|
|
320
|
+
promises.push((async (index) => {
|
|
321
|
+
const sticker = stickers[index];
|
|
322
|
+
const { stream } = await getStream(sticker.data);
|
|
323
|
+
const buffer = await toBuffer(stream);
|
|
324
|
+
let webpBuffer;
|
|
325
|
+
let isAnimated = false;
|
|
326
|
+
if (isWebPBuffer(buffer)) {
|
|
327
|
+
webpBuffer = buffer;
|
|
328
|
+
isAnimated = isAnimatedWebP(buffer);
|
|
329
|
+
}
|
|
330
|
+
else if (hasSharp) {
|
|
331
|
+
webpBuffer = await lib.sharp.default(buffer)
|
|
332
|
+
.resize(512, 512, { fit: 'inside' })
|
|
333
|
+
.webp({ quality: 80 })
|
|
334
|
+
.toBuffer();
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
webpBuffer = await new lib.image.Transformer(buffer)
|
|
338
|
+
.resize(512, 512)
|
|
339
|
+
.webp(80);
|
|
340
|
+
}
|
|
341
|
+
if (webpBuffer.length > 1024 * 1024) {
|
|
342
|
+
throw new Boom(`Sticker at index ${index} exceeds the 1MB size limit`, { statusCode: 400 });
|
|
343
|
+
}
|
|
344
|
+
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
|
|
345
|
+
const fileName = `${hash}.webp`;
|
|
346
|
+
stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
|
|
347
|
+
stickerMetadata[index] = {
|
|
348
|
+
fileName,
|
|
349
|
+
mimetype: 'image/webp',
|
|
350
|
+
isAnimated,
|
|
351
|
+
emojis: sticker.emojis || ['✨'],
|
|
352
|
+
accessibilityLabel: sticker.accessibilityLabel || ''
|
|
353
|
+
};
|
|
354
|
+
})(j));
|
|
384
355
|
}
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if (webpBuffer.length > 1024 * 1024) {
|
|
389
|
-
throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
|
|
390
|
-
}
|
|
391
|
-
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
|
|
392
|
-
const fileName = hash + '.webp';
|
|
393
|
-
stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
|
|
394
|
-
return {
|
|
395
|
-
fileName,
|
|
396
|
-
mimetype: 'image/webp',
|
|
397
|
-
isAnimated,
|
|
398
|
-
emojis: sticker.emojis || ['✨'],
|
|
399
|
-
accessibilityLabel: sticker.accessibilityLabel || ''
|
|
400
|
-
};
|
|
401
|
-
});
|
|
402
|
-
const stickerMetadata = await Promise.all(stickerPromises);
|
|
403
|
-
// Process and add cover/tray icon to the ZIP
|
|
404
|
-
const trayIconFileName = stickerPackIdValue + '.webp';
|
|
356
|
+
await Promise.all(promises);
|
|
357
|
+
}
|
|
358
|
+
const trayIconFileName = `${stickerPackIdValue}.webp`;
|
|
405
359
|
const { stream: coverStream } = await getStream(cover);
|
|
406
360
|
const coverBuffer = await toBuffer(coverStream);
|
|
407
361
|
let coverWebpBuffer;
|
|
408
|
-
|
|
409
|
-
if (isCoverWebP) {
|
|
410
|
-
// Already WebP - preserve original to keep exif metadata
|
|
362
|
+
if (isWebPBuffer(coverBuffer)) {
|
|
411
363
|
coverWebpBuffer = coverBuffer;
|
|
412
364
|
}
|
|
413
|
-
else if (
|
|
414
|
-
coverWebpBuffer = await lib
|
|
415
|
-
.sharp
|
|
416
|
-
.default(coverBuffer)
|
|
365
|
+
else if (hasSharp) {
|
|
366
|
+
coverWebpBuffer = await lib.sharp.default(coverBuffer)
|
|
417
367
|
.resize(512, 512, { fit: 'inside' })
|
|
418
368
|
.webp({ quality: 80 })
|
|
419
369
|
.toBuffer();
|
|
420
370
|
}
|
|
421
|
-
else
|
|
422
|
-
coverWebpBuffer = await new lib
|
|
423
|
-
.image
|
|
424
|
-
.Transformer(coverBuffer)
|
|
371
|
+
else {
|
|
372
|
+
coverWebpBuffer = await new lib.image.Transformer(coverBuffer)
|
|
425
373
|
.resize(512, 512)
|
|
426
374
|
.webp(80);
|
|
427
375
|
}
|
|
428
|
-
else {
|
|
429
|
-
throw new Boom('No image processing library (sharp or @napi-rs/image) available for converting cover to WebP. Either install sharp or @napi-rs/image or provide cover in WebP format.');
|
|
430
|
-
}
|
|
431
|
-
// Add cover to ZIP data
|
|
432
376
|
stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
|
|
433
377
|
const zipBuffer = await new Promise((resolve, reject) => {
|
|
434
|
-
zip(stickerData, (error, data) =>
|
|
435
|
-
if (error) {
|
|
436
|
-
reject(error);
|
|
437
|
-
} else {
|
|
438
|
-
resolve(Buffer.from(data));
|
|
439
|
-
}
|
|
440
|
-
});
|
|
378
|
+
zip(stickerData, (error, data) => error ? reject(error) : resolve(Buffer.from(data)));
|
|
441
379
|
});
|
|
442
|
-
const stickerPackSize = zipBuffer.length;
|
|
443
380
|
const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
|
|
444
|
-
logger
|
|
381
|
+
logger,
|
|
445
382
|
opts: options.options
|
|
446
383
|
});
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
384
|
+
let stickerPackUploadResult;
|
|
385
|
+
try {
|
|
386
|
+
stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
|
|
387
|
+
fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
|
|
388
|
+
mediaType: 'sticker-pack',
|
|
389
|
+
timeoutMs: options.mediaUploadTimeoutMs
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
finally {
|
|
393
|
+
fs.unlink(stickerPackUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
|
|
394
|
+
}
|
|
453
395
|
const obj = {
|
|
454
|
-
name
|
|
455
|
-
publisher
|
|
396
|
+
name,
|
|
397
|
+
publisher,
|
|
456
398
|
stickerPackId: stickerPackIdValue,
|
|
457
399
|
packDescription: description,
|
|
458
400
|
stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
|
|
459
|
-
stickerPackSize:
|
|
401
|
+
stickerPackSize: zipBuffer.length,
|
|
460
402
|
stickers: stickerMetadata,
|
|
461
403
|
fileSha256: stickerPackUpload.fileSha256,
|
|
462
404
|
fileEncSha256: stickerPackUpload.fileEncSha256,
|
|
@@ -464,31 +406,19 @@ const prepareStickerPackMessage = async (message, options) => {
|
|
|
464
406
|
directPath: stickerPackUploadResult.directPath,
|
|
465
407
|
fileLength: stickerPackUpload.fileLength,
|
|
466
408
|
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
467
|
-
trayIconFileName
|
|
409
|
+
trayIconFileName
|
|
468
410
|
};
|
|
469
411
|
try {
|
|
470
|
-
// Reuse the cover buffer we already processed for thumbnail generation
|
|
471
412
|
let thumbnailBuffer;
|
|
472
|
-
if (
|
|
473
|
-
thumbnailBuffer = await lib
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
if ('image' in lib && lib.image?.Transformer) {
|
|
481
|
-
thumbnailBuffer = await new lib
|
|
482
|
-
.image
|
|
483
|
-
.Transformer(coverBuffer)
|
|
484
|
-
.resize(252, 252)
|
|
485
|
-
.jpeg();
|
|
486
|
-
}
|
|
487
|
-
else if ('jimp' in lib && lib.jimp?.Jimp) {
|
|
413
|
+
if (hasSharp) {
|
|
414
|
+
thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
|
|
415
|
+
}
|
|
416
|
+
else if (hasImage) {
|
|
417
|
+
thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
|
|
418
|
+
}
|
|
419
|
+
else if (hasJimp) {
|
|
488
420
|
const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
|
|
489
|
-
thumbnailBuffer = await jimpImage
|
|
490
|
-
.resize({ w: 252, h: 252 })
|
|
491
|
-
.getBuffer('image/jpeg');
|
|
421
|
+
thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
|
|
492
422
|
}
|
|
493
423
|
else {
|
|
494
424
|
throw new Error('No image processing library available for thumbnail generation');
|
|
@@ -497,16 +427,21 @@ const prepareStickerPackMessage = async (message, options) => {
|
|
|
497
427
|
throw new Error('Failed to generate thumbnail buffer');
|
|
498
428
|
}
|
|
499
429
|
const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
|
|
500
|
-
logger
|
|
430
|
+
logger,
|
|
501
431
|
opts: options.options,
|
|
502
|
-
mediaKey: stickerPackUpload.mediaKey
|
|
503
|
-
});
|
|
504
|
-
const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
|
|
505
|
-
fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
|
|
506
|
-
mediaType: 'thumbnail-sticker-pack',
|
|
507
|
-
timeoutMs: options.mediaUploadTimeoutMs
|
|
432
|
+
mediaKey: stickerPackUpload.mediaKey
|
|
508
433
|
});
|
|
509
|
-
|
|
434
|
+
let thumbUploadResult;
|
|
435
|
+
try {
|
|
436
|
+
thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
|
|
437
|
+
fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
|
|
438
|
+
mediaType: 'thumbnail-sticker-pack',
|
|
439
|
+
timeoutMs: options.mediaUploadTimeoutMs
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
finally {
|
|
443
|
+
fs.unlink(thumbUpload.encFilePath).catch(() => logger?.warn('failed to remove tmp file'));
|
|
444
|
+
}
|
|
510
445
|
Object.assign(obj, {
|
|
511
446
|
thumbnailDirectPath: thumbUploadResult.directPath,
|
|
512
447
|
thumbnailSha256: thumbUpload.fileSha256,
|
|
@@ -517,27 +452,26 @@ const prepareStickerPackMessage = async (message, options) => {
|
|
|
517
452
|
});
|
|
518
453
|
}
|
|
519
454
|
catch (error) {
|
|
520
|
-
|
|
455
|
+
logger?.warn(`Thumbnail generation failed: ${error}`);
|
|
521
456
|
}
|
|
522
|
-
const content = obj;
|
|
523
457
|
if (cacheableKey) {
|
|
524
|
-
|
|
525
|
-
|
|
458
|
+
logger?.debug({ cacheableKey }, 'set cache (background)');
|
|
459
|
+
options.mediaCache.set(cacheableKey, WAProto.Message.StickerPackMessage.encode(obj).finish());
|
|
526
460
|
}
|
|
527
|
-
return WAProto.Message.StickerPackMessage.fromObject(
|
|
461
|
+
return WAProto.Message.StickerPackMessage.fromObject(obj);
|
|
528
462
|
};
|
|
529
|
-
//
|
|
463
|
+
// Lia@Changes 30-01-26 --- Add native flow button helper for interactive message
|
|
530
464
|
const prepareNativeFlowButtons = (message) => {
|
|
531
465
|
const buttons = message.nativeFlow;
|
|
532
466
|
const isButtonsFieldArray = Array.isArray(buttons);
|
|
533
467
|
const correctedField = isButtonsFieldArray ? buttons : buttons.buttons;
|
|
534
468
|
const messageParamsJson = {};
|
|
535
|
-
//
|
|
469
|
+
// Lia@Changes 31-01-26 --- Add offer and options inside interactive message
|
|
536
470
|
if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
|
|
537
471
|
Object.assign(messageParamsJson, {
|
|
538
472
|
limited_time_offer: {
|
|
539
473
|
text: message.offerText || LIBRARY_NAME,
|
|
540
|
-
url: message.offerUrl || DONATE_URL, //
|
|
474
|
+
url: message.offerUrl || DONATE_URL, // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
|
|
541
475
|
copy_code: message.offerCode,
|
|
542
476
|
expiration_time: message.offerExpiration
|
|
543
477
|
}
|
|
@@ -547,10 +481,7 @@ const prepareNativeFlowButtons = (message) => {
|
|
|
547
481
|
Object.assign(messageParamsJson, {
|
|
548
482
|
bottom_sheet: {
|
|
549
483
|
in_thread_buttons_limit: 1,
|
|
550
|
-
divider_indices: Array.from(
|
|
551
|
-
{ length: correctedField.length },
|
|
552
|
-
(_, index) => index
|
|
553
|
-
),
|
|
484
|
+
divider_indices: Array.from({ length: correctedField.length }, (_, index) => index),
|
|
554
485
|
list_title: message.optionTitle || '📄 Select Options',
|
|
555
486
|
button_title: message.optionText
|
|
556
487
|
}
|
|
@@ -602,7 +533,7 @@ const prepareNativeFlowButtons = (message) => {
|
|
|
602
533
|
})
|
|
603
534
|
};
|
|
604
535
|
}
|
|
605
|
-
//
|
|
536
|
+
// Lia@Changes 12-03-26 --- Add "single_select" shortcut \(°o°)/
|
|
606
537
|
else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
|
|
607
538
|
return {
|
|
608
539
|
name: 'single_select',
|
|
@@ -615,10 +546,23 @@ const prepareNativeFlowButtons = (message) => {
|
|
|
615
546
|
}
|
|
616
547
|
return button;
|
|
617
548
|
}),
|
|
618
|
-
messageParamsJson: JSON.stringify(messageParamsJson)
|
|
619
|
-
messageVersion: 1
|
|
549
|
+
messageParamsJson: JSON.stringify(messageParamsJson)
|
|
620
550
|
};
|
|
621
551
|
};
|
|
552
|
+
export const prepareDisappearingMessageSettingContent = (ephemeralExpiration) => {
|
|
553
|
+
ephemeralExpiration = ephemeralExpiration || 0;
|
|
554
|
+
const content = {
|
|
555
|
+
ephemeralMessage: {
|
|
556
|
+
message: {
|
|
557
|
+
protocolMessage: {
|
|
558
|
+
type: WAProto.Message.ProtocolMessage.Type.EPHEMERAL_SETTING,
|
|
559
|
+
ephemeralExpiration
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
};
|
|
564
|
+
return WAProto.Message.fromObject(content);
|
|
565
|
+
};
|
|
622
566
|
/**
|
|
623
567
|
* Generate forwarded message content like WA does
|
|
624
568
|
* @param message the message to forward
|
|
@@ -641,24 +585,12 @@ export const generateForwardMessageContent = (message, forceForward) => {
|
|
|
641
585
|
key = 'extendedTextMessage';
|
|
642
586
|
}
|
|
643
587
|
const key_ = content?.[key];
|
|
644
|
-
const contextInfo = {};
|
|
645
588
|
if (score > 0) {
|
|
646
|
-
contextInfo
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
// so the server knows where to find the original media
|
|
651
|
-
const remoteJid = message.key?.remoteJid;
|
|
652
|
-
if (remoteJid && isJidNewsletter(remoteJid)) {
|
|
653
|
-
contextInfo.forwardedNewsletterMessageInfo = {
|
|
654
|
-
newsletterJid: remoteJid,
|
|
655
|
-
serverMessageId: message.key?.server_id ? parseInt(message.key.server_id) : null,
|
|
656
|
-
newsletterName: null
|
|
657
|
-
};
|
|
658
|
-
// strip messageContextInfo (contains messageSecret etc.) as WA Web does
|
|
659
|
-
delete content.messageContextInfo;
|
|
589
|
+
key_.contextInfo = { forwardingScore: score, isForwarded: true };
|
|
590
|
+
}
|
|
591
|
+
else {
|
|
592
|
+
key_.contextInfo = {};
|
|
660
593
|
}
|
|
661
|
-
key_.contextInfo = contextInfo;
|
|
662
594
|
return content;
|
|
663
595
|
};
|
|
664
596
|
export const hasNonNullishProperty = (message, key) => {
|
|
@@ -673,7 +605,7 @@ export const hasOptionalProperty = (obj, key) => {
|
|
|
673
605
|
key in obj &&
|
|
674
606
|
obj[key] != null;
|
|
675
607
|
};
|
|
676
|
-
//
|
|
608
|
+
// Lia@Changes 06-02-26 --- Validate album message media to avoid bug 👀
|
|
677
609
|
export const hasValidAlbumMedia = (message) => {
|
|
678
610
|
return !!(message.imageMessage ||
|
|
679
611
|
message.videoMessage);
|
|
@@ -685,7 +617,7 @@ export const hasValidInteractiveHeader = (message) => {
|
|
|
685
617
|
message.productMessage ||
|
|
686
618
|
message.locationMessage);
|
|
687
619
|
};
|
|
688
|
-
//
|
|
620
|
+
// Lia@Changes 30-01-26 --- Validate carousel cards header to avoid bug 👀
|
|
689
621
|
export const hasValidCarouselHeader = (message) => {
|
|
690
622
|
return !!(message.imageMessage ||
|
|
691
623
|
message.videoMessage ||
|
|
@@ -694,12 +626,12 @@ export const hasValidCarouselHeader = (message) => {
|
|
|
694
626
|
export const generateWAMessageContent = async (message, options) => {
|
|
695
627
|
var _a, _b;
|
|
696
628
|
let m = {};
|
|
697
|
-
//
|
|
629
|
+
// Lia@Changes 30-01-26 --- Add "raw" boolean to send raw messages directly via generateWAMessage()
|
|
698
630
|
if (hasNonNullishProperty(message, 'raw')) {
|
|
699
631
|
delete message.raw;
|
|
700
632
|
return message;
|
|
701
633
|
}
|
|
702
|
-
//
|
|
634
|
+
// Lia@Changes 09-04-26 --- Add support for code block and table with richResponseMessage
|
|
703
635
|
else if (hasNonNullishProperty(message, 'code') ||
|
|
704
636
|
hasNonNullishProperty(message, 'links') ||
|
|
705
637
|
hasNonNullishProperty(message, 'table') ||
|
|
@@ -920,18 +852,18 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
920
852
|
m.pollCreationMessageV2 = pollCreationMessage;
|
|
921
853
|
}
|
|
922
854
|
else {
|
|
923
|
-
//
|
|
855
|
+
// Lia@Changes 08-02-26 --- Add quiz message support
|
|
924
856
|
if (message.poll.pollType === 1) {
|
|
925
857
|
if (!message.poll.correctAnswer) {
|
|
926
858
|
throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
|
|
927
859
|
}
|
|
928
860
|
m.pollCreationMessageV5 = {
|
|
929
|
-
//
|
|
861
|
+
// Lia@Note 08-02-26 --- quiz for newsletter only
|
|
930
862
|
...pollCreationMessage,
|
|
931
863
|
correctAnswer: {
|
|
932
864
|
optionName: message.poll.correctAnswer.toString()
|
|
933
865
|
},
|
|
934
|
-
pollType:
|
|
866
|
+
pollType: 1,
|
|
935
867
|
selectableOptionsCount: 1
|
|
936
868
|
};
|
|
937
869
|
}
|
|
@@ -949,7 +881,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
949
881
|
messageSecret: message.poll.messageSecret || randomBytes(32)
|
|
950
882
|
};
|
|
951
883
|
}
|
|
952
|
-
//
|
|
884
|
+
// Lia@Changes 08-02-26 --- Add poll result snapshot message
|
|
953
885
|
else if (hasNonNullishProperty(message, 'pollResult')) {
|
|
954
886
|
const pollResultSnapshotMessage = {
|
|
955
887
|
name: message.pollResult.name,
|
|
@@ -967,7 +899,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
967
899
|
m.pollResultSnapshotMessage = pollResultSnapshotMessage;
|
|
968
900
|
}
|
|
969
901
|
}
|
|
970
|
-
//
|
|
902
|
+
// Lia@Changes 08-02-26 --- Add poll update message
|
|
971
903
|
else if (hasNonNullishProperty(message, 'pollUpdate')) {
|
|
972
904
|
if (!message.pollUpdate.key) {
|
|
973
905
|
throw new Boom('Message key is required', { statusCode: 400 });
|
|
@@ -982,33 +914,14 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
982
914
|
vote: message.pollUpdate.vote
|
|
983
915
|
};
|
|
984
916
|
}
|
|
985
|
-
|
|
986
|
-
m.protocolMessage = {
|
|
987
|
-
type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
|
|
988
|
-
};
|
|
989
|
-
}
|
|
990
|
-
else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
|
|
991
|
-
m.requestPhoneNumberMessage = {};
|
|
992
|
-
}
|
|
993
|
-
else if (hasNonNullishProperty(message, 'limitSharing')) {
|
|
994
|
-
m.protocolMessage = {
|
|
995
|
-
type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
|
|
996
|
-
limitSharing: {
|
|
997
|
-
sharingLimited: message.limitSharing === true,
|
|
998
|
-
trigger: 1,
|
|
999
|
-
limitSharingSettingTimestamp: Date.now(),
|
|
1000
|
-
initiatedByMe: true
|
|
1001
|
-
}
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
// crysnovax@Changes 01-02-26 --- Add payment invite message
|
|
917
|
+
// Lia@Changes 01-02-26 --- Add payment invite message
|
|
1005
918
|
else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
|
|
1006
919
|
m.paymentInviteMessage = {
|
|
1007
920
|
expiryTimestamp: Date.now(),
|
|
1008
921
|
serviceType: message.paymentInviteServiceType
|
|
1009
922
|
};
|
|
1010
923
|
}
|
|
1011
|
-
//
|
|
924
|
+
// Lia@Changes 01-02-26 --- Add order message
|
|
1012
925
|
else if (hasNonNullishProperty(message, 'orderText')) {
|
|
1013
926
|
if (!Buffer.isBuffer(message.thumbnail)) {
|
|
1014
927
|
throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
|
|
@@ -1027,7 +940,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1027
940
|
};
|
|
1028
941
|
delete m.orderMessage.orderText;
|
|
1029
942
|
}
|
|
1030
|
-
//
|
|
943
|
+
// Lia@Changes 31-01-26 --- Add support for album messages
|
|
1031
944
|
else if (hasNonNullishProperty(message, 'album')) {
|
|
1032
945
|
if (!Array.isArray(message.album)) {
|
|
1033
946
|
throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
|
|
@@ -1037,11 +950,13 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1037
950
|
if (message.album[i].video)
|
|
1038
951
|
videoCount++;
|
|
1039
952
|
}
|
|
953
|
+
;
|
|
1040
954
|
let imageCount = 0;
|
|
1041
955
|
for (let i = 0; i < message.album.length; i++) {
|
|
1042
956
|
if (message.album[i].image)
|
|
1043
957
|
imageCount++;
|
|
1044
958
|
}
|
|
959
|
+
;
|
|
1045
960
|
if ((videoCount + imageCount) < 2) {
|
|
1046
961
|
throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
|
|
1047
962
|
}
|
|
@@ -1050,23 +965,42 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1050
965
|
expectedVideoCount: videoCount
|
|
1051
966
|
};
|
|
1052
967
|
}
|
|
968
|
+
else if (hasNonNullishProperty(message, 'sharePhoneNumber')) {
|
|
969
|
+
m.protocolMessage = {
|
|
970
|
+
type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
|
|
971
|
+
};
|
|
972
|
+
}
|
|
973
|
+
else if (hasNonNullishProperty(message, 'requestPhoneNumber')) {
|
|
974
|
+
m.requestPhoneNumberMessage = {};
|
|
975
|
+
}
|
|
976
|
+
else if (hasNonNullishProperty(message, 'limitSharing')) {
|
|
977
|
+
m.protocolMessage = {
|
|
978
|
+
type: proto.Message.ProtocolMessage.Type.LIMIT_SHARING,
|
|
979
|
+
limitSharing: {
|
|
980
|
+
sharingLimited: message.limitSharing === true,
|
|
981
|
+
trigger: 1,
|
|
982
|
+
limitSharingSettingTimestamp: Date.now(),
|
|
983
|
+
initiatedByMe: true
|
|
984
|
+
}
|
|
985
|
+
};
|
|
986
|
+
}
|
|
1053
987
|
else {
|
|
1054
988
|
m = await prepareWAMessageMedia(message, options);
|
|
1055
989
|
}
|
|
1056
|
-
//
|
|
990
|
+
// Lia@Changes 30-01-26 --- Add interactive messages (buttonsMessage, listMessage, interactiveMessage, templateMessage, and carouselMessage)
|
|
1057
991
|
if (hasNonNullishProperty(message, 'buttons')) {
|
|
1058
992
|
const buttonsMessage = {
|
|
1059
993
|
buttons: message.buttons.map(button => {
|
|
1060
|
-
//
|
|
994
|
+
// Lia@Changes 12-03-26 --- Add "single_select" shortcut!
|
|
1061
995
|
const buttonText = button.text || button.buttonText;
|
|
1062
996
|
if (hasOptionalProperty(button, 'sections')) {
|
|
1063
997
|
return {
|
|
1064
998
|
nativeFlowInfo: {
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
999
|
+
name: 'single_select',
|
|
1000
|
+
paramsJson: JSON.stringify({
|
|
1001
|
+
title: buttonText,
|
|
1002
|
+
sections: button.sections
|
|
1003
|
+
})
|
|
1070
1004
|
},
|
|
1071
1005
|
type: ButtonType.NATIVE_FLOW
|
|
1072
1006
|
};
|
|
@@ -1074,8 +1008,8 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1074
1008
|
else if (hasOptionalProperty(button, 'name')) {
|
|
1075
1009
|
return {
|
|
1076
1010
|
nativeFlowInfo: {
|
|
1077
|
-
|
|
1078
|
-
|
|
1011
|
+
name: button.name,
|
|
1012
|
+
paramsJson: button.paramsJson
|
|
1079
1013
|
},
|
|
1080
1014
|
type: ButtonType.NATIVE_FLOW
|
|
1081
1015
|
};
|
|
@@ -1115,7 +1049,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1115
1049
|
};
|
|
1116
1050
|
m = { listMessage };
|
|
1117
1051
|
}
|
|
1118
|
-
//
|
|
1052
|
+
// Lia@Note 03-02-26 --- This message type is shown on WhatsApp Web/Desktop and iOS (I guess 。◕‿◕。). On Android, it only appears in newsletter (so far ಥ‿ಥ)
|
|
1119
1053
|
else if (hasNonNullishProperty(message, 'templateButtons')) {
|
|
1120
1054
|
const hydratedTemplate = {
|
|
1121
1055
|
hydratedButtons: message.templateButtons.map((button, i) => {
|
|
@@ -1158,19 +1092,20 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1158
1092
|
if (hasOptionalProperty(message, 'caption')) {
|
|
1159
1093
|
hydratedTemplate.hydratedTitleText = message.title;
|
|
1160
1094
|
hydratedTemplate.hydratedContentText = message.caption;
|
|
1161
|
-
}
|
|
1095
|
+
}
|
|
1096
|
+
;
|
|
1162
1097
|
Object.assign(hydratedTemplate, m);
|
|
1163
1098
|
}
|
|
1164
1099
|
if (hasOptionalProperty(message, 'footer')) {
|
|
1165
1100
|
hydratedTemplate.hydratedFooterText = message.footer;
|
|
1166
1101
|
}
|
|
1167
|
-
hydratedTemplate.templateId = message.id || 'template-' + Date.now(); //
|
|
1102
|
+
hydratedTemplate.templateId = message.id || 'template-' + Date.now(); // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp ( ꈍᴗꈍ)
|
|
1168
1103
|
m = {
|
|
1169
1104
|
templateMessage: {
|
|
1170
1105
|
hydratedFourRowTemplate: hydratedTemplate,
|
|
1171
1106
|
hydratedTemplate: hydratedTemplate
|
|
1172
1107
|
}
|
|
1173
|
-
}
|
|
1108
|
+
};
|
|
1174
1109
|
}
|
|
1175
1110
|
else if (hasNonNullishProperty(message, 'nativeFlow')) {
|
|
1176
1111
|
const interactiveMessage = {
|
|
@@ -1195,9 +1130,9 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1195
1130
|
}
|
|
1196
1131
|
else {
|
|
1197
1132
|
if (hasOptionalProperty(message, 'caption')) {
|
|
1198
|
-
const isValidHeader = hasValidInteractiveHeader(m)
|
|
1133
|
+
const isValidHeader = hasValidInteractiveHeader(m);
|
|
1199
1134
|
if (!isValidHeader) {
|
|
1200
|
-
|
|
1135
|
+
throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
|
|
1201
1136
|
}
|
|
1202
1137
|
interactiveMessage.header = {
|
|
1203
1138
|
title: message.title || '',
|
|
@@ -1212,11 +1147,11 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1212
1147
|
Object.assign(interactiveMessage.header, m);
|
|
1213
1148
|
}
|
|
1214
1149
|
if (hasOptionalProperty(message, 'audioFooter')) {
|
|
1215
|
-
const
|
|
1150
|
+
const { audioMessage } = await prepareWAMessageMedia({
|
|
1216
1151
|
audio: message.audioFooter
|
|
1217
1152
|
}, options);
|
|
1218
1153
|
interactiveMessage.footer = {
|
|
1219
|
-
audioMessage
|
|
1154
|
+
audioMessage,
|
|
1220
1155
|
hasMediaAttachment: true
|
|
1221
1156
|
};
|
|
1222
1157
|
}
|
|
@@ -1228,13 +1163,13 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1228
1163
|
else if (hasNonNullishProperty(message, 'cards')) {
|
|
1229
1164
|
const interactiveMessage = {
|
|
1230
1165
|
carouselMessage: {
|
|
1231
|
-
cards: await Promise.all(message.cards.map(async card => {
|
|
1166
|
+
cards: await Promise.all(message.cards.map(async (card) => {
|
|
1232
1167
|
let carouselHeader = {};
|
|
1233
1168
|
if (hasNonNullishProperty(card, 'product')) {
|
|
1234
1169
|
carouselHeader.productMessage = await prepareProductMessage(card, options);
|
|
1235
1170
|
}
|
|
1236
1171
|
else {
|
|
1237
|
-
carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({
|
|
1172
|
+
carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
|
|
1238
1173
|
}
|
|
1239
1174
|
const isValidHeader = hasValidCarouselHeader(carouselHeader);
|
|
1240
1175
|
if (!isValidHeader) {
|
|
@@ -1272,7 +1207,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1272
1207
|
else if (hasOptionalProperty(card, 'footer')) {
|
|
1273
1208
|
carouselCard.footer = { text: card.footer };
|
|
1274
1209
|
}
|
|
1275
|
-
return carouselCard
|
|
1210
|
+
return carouselCard;
|
|
1276
1211
|
})),
|
|
1277
1212
|
carouselCardType: CarouselCardType.UNKNOWN,
|
|
1278
1213
|
messageVersion: 1
|
|
@@ -1286,13 +1221,13 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1286
1221
|
}
|
|
1287
1222
|
m = { interactiveMessage };
|
|
1288
1223
|
}
|
|
1289
|
-
//
|
|
1224
|
+
// Lia@Changes 01-02-26 --- Add request payment message
|
|
1290
1225
|
else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
|
|
1291
1226
|
const requestPaymentMessage = {
|
|
1292
1227
|
amount: {
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1228
|
+
currencyCode: 'IDR',
|
|
1229
|
+
offset: 1000,
|
|
1230
|
+
value: 1000
|
|
1296
1231
|
},
|
|
1297
1232
|
amount1000: 1000,
|
|
1298
1233
|
currencyCodeIso4217: 'IDR',
|
|
@@ -1310,7 +1245,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1310
1245
|
}
|
|
1311
1246
|
m = { requestPaymentMessage };
|
|
1312
1247
|
}
|
|
1313
|
-
//
|
|
1248
|
+
// Lia@Changes 01-02-26 --- Add invoice message
|
|
1314
1249
|
else if (hasNonNullishProperty(message, 'invoiceNote')) {
|
|
1315
1250
|
const attachment = m.imageMessage || m.documentMessage;
|
|
1316
1251
|
const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
|
|
@@ -1336,7 +1271,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1336
1271
|
}
|
|
1337
1272
|
m = { invoiceMessage };
|
|
1338
1273
|
}
|
|
1339
|
-
//
|
|
1274
|
+
// Lia@Changes 31-01-26 --- Add direct externalAdReply access (no need to create contextInfo first)
|
|
1340
1275
|
if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
|
|
1341
1276
|
const messageType = Object.keys(m)[0];
|
|
1342
1277
|
const key = m[messageType];
|
|
@@ -1345,7 +1280,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1345
1280
|
throw new Boom('Thumbnail must in buffer type', { statusCode: 400 });
|
|
1346
1281
|
}
|
|
1347
1282
|
if (!content.url || typeof content.url !== 'string') {
|
|
1348
|
-
content.url = DONATE_URL; //
|
|
1283
|
+
content.url = DONATE_URL; // Lia@Note 02-02-26 --- Apologies if this feels cheeky, just a fallback
|
|
1349
1284
|
}
|
|
1350
1285
|
const externalAdReply = {
|
|
1351
1286
|
...content,
|
|
@@ -1398,7 +1333,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1398
1333
|
key.contextInfo = message.contextInfo;
|
|
1399
1334
|
}
|
|
1400
1335
|
}
|
|
1401
|
-
//
|
|
1336
|
+
// Lia@Changes 31-01-26 --- Add "groupStatus" boolean to set contextInfo.isGroupStatus and wrap message into groupStatusMessageV2
|
|
1402
1337
|
if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
|
|
1403
1338
|
const messageType = Object.keys(m)[0];
|
|
1404
1339
|
const key = m[messageType];
|
|
@@ -1408,12 +1343,12 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1408
1343
|
else if (key) {
|
|
1409
1344
|
key.contextInfo = {
|
|
1410
1345
|
isGroupStatus: message.groupStatus
|
|
1411
|
-
}
|
|
1346
|
+
};
|
|
1412
1347
|
}
|
|
1413
1348
|
m = { groupStatusMessageV2: { message: m } };
|
|
1414
1349
|
delete message.groupStatus;
|
|
1415
1350
|
}
|
|
1416
|
-
//
|
|
1351
|
+
// Lia@Changes 06-05-26 --- Add "spoiler" boolean to set contextInfo.isSpoiler and wrap message into spoilerMessage
|
|
1417
1352
|
if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
|
|
1418
1353
|
const messageType = Object.keys(m)[0];
|
|
1419
1354
|
const key = m[messageType];
|
|
@@ -1428,33 +1363,37 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1428
1363
|
m = { spoilerMessage: { message: m } };
|
|
1429
1364
|
delete message.spoiler;
|
|
1430
1365
|
}
|
|
1431
|
-
//
|
|
1366
|
+
// Lia@Changes 02-02-26 --- Add "interactiveAsTemplate" boolean to wrap interactiveMessage into templateMessage
|
|
1432
1367
|
else if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
|
|
1433
1368
|
if (!m.interactiveMessage) {
|
|
1434
|
-
throw new Boom('Invalid message type for template', { statusCode: 400 }); //
|
|
1369
|
+
throw new Boom('Invalid message type for template', { statusCode: 400 }); // Lia@Note 02-02-26 --- To avoid bug 👀
|
|
1435
1370
|
}
|
|
1436
1371
|
m = {
|
|
1437
1372
|
templateMessage: {
|
|
1438
1373
|
interactiveMessageTemplate: m.interactiveMessage,
|
|
1439
|
-
templateId: message.id || 'template-' + Date.now() //
|
|
1374
|
+
templateId: message.id || 'template-' + Date.now() // Lia@Note 04-02-26 --- Minimal templateId to satisfy WhatsApp ( ꈍᴗꈍ)
|
|
1440
1375
|
}
|
|
1441
1376
|
};
|
|
1442
1377
|
delete message.interactiveAsTemplate;
|
|
1443
1378
|
}
|
|
1444
|
-
//
|
|
1379
|
+
// Lia@Changes 30-01-26 --- Add "ephemeral" boolean to wrap message into ephemeralMessage like "viewOnce"
|
|
1445
1380
|
if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
|
|
1446
1381
|
m = { ephemeralMessage: { message: m } };
|
|
1447
1382
|
delete message.ephemeral;
|
|
1448
1383
|
}
|
|
1384
|
+
// Lia@Changes 16-05-26 --- Add wrap message into lottieStickerMessage by isLottie boolean
|
|
1385
|
+
if (hasOptionalProperty(message, 'isLottie') && !!message.isLottie) {
|
|
1386
|
+
m = { lottieStickerMessage: { message: m } };
|
|
1387
|
+
}
|
|
1449
1388
|
else if (hasOptionalProperty(message, 'viewOnce') && !!message.viewOnce) {
|
|
1450
1389
|
m = { viewOnceMessage: { message: m } };
|
|
1451
1390
|
}
|
|
1452
|
-
//
|
|
1391
|
+
// Lia@Changes 03-02-26 --- Add "viewOnceV2" boolean to wrap message into viewOnceMessageV2 like "viewOnce"
|
|
1453
1392
|
else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
|
|
1454
1393
|
m = { viewOnceMessageV2: { message: m } };
|
|
1455
1394
|
delete message.viewOnceV2;
|
|
1456
1395
|
}
|
|
1457
|
-
//
|
|
1396
|
+
// Lia@Changes 03-02-26 --- Add "viewOnceV2Extension" boolean to wrap message into viewOnceMessageV2Extension like "viewOnce"
|
|
1458
1397
|
else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
|
|
1459
1398
|
m = { viewOnceMessageV2Extension: { message: m } };
|
|
1460
1399
|
delete message.viewOnceV2Extension;
|
|
@@ -1467,7 +1406,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
1467
1406
|
timestampMs: Date.now(),
|
|
1468
1407
|
type: WAProto.Message.ProtocolMessage.Type.MESSAGE_EDIT
|
|
1469
1408
|
}
|
|
1470
|
-
}
|
|
1409
|
+
};
|
|
1471
1410
|
}
|
|
1472
1411
|
if (shouldIncludeReportingToken(m)) {
|
|
1473
1412
|
m.messageContextInfo = m.messageContextInfo || {};
|
|
@@ -1483,8 +1422,8 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
1483
1422
|
if (!options.timestamp) {
|
|
1484
1423
|
options.timestamp = new Date();
|
|
1485
1424
|
}
|
|
1486
|
-
const messageContextInfo = message.messageContextInfo
|
|
1487
1425
|
const innerMessage = normalizeMessageContent(message);
|
|
1426
|
+
const messageContextInfo = message.messageContextInfo;
|
|
1488
1427
|
const key = getContentType(innerMessage);
|
|
1489
1428
|
const timestamp = unixTimestampSeconds(options.timestamp);
|
|
1490
1429
|
const isNewsletter = isJidNewsletter(jid);
|
|
@@ -1531,13 +1470,13 @@ export const generateWAMessageFromContent = (jid, message, options) => {
|
|
|
1531
1470
|
//ephemeralSettingTimestamp: options.ephemeralOptions.eph_setting_ts?.toString()
|
|
1532
1471
|
};
|
|
1533
1472
|
}
|
|
1534
|
-
//
|
|
1473
|
+
// Lia@Changes 30-01-26 --- Add deviceListMetadata inside messageContextInfo for private chat
|
|
1535
1474
|
if (messageContextInfo?.messageSecret && (isPnUser(jid) || isLidUser(jid))) {
|
|
1536
1475
|
messageContextInfo.deviceListMetadata = {
|
|
1537
1476
|
recipientKeyHash: randomBytes(10),
|
|
1538
1477
|
recipientTimestamp: unixTimestampSeconds()
|
|
1539
1478
|
};
|
|
1540
|
-
messageContextInfo.deviceListMetadataVersion = 2
|
|
1479
|
+
messageContextInfo.deviceListMetadataVersion = 2;
|
|
1541
1480
|
}
|
|
1542
1481
|
message = WAProto.Message.create(message);
|
|
1543
1482
|
const messageJSON = {
|
|
@@ -1590,10 +1529,9 @@ export const normalizeMessageContent = (content) => {
|
|
|
1590
1529
|
content = inner.message;
|
|
1591
1530
|
}
|
|
1592
1531
|
return content;
|
|
1593
|
-
//
|
|
1532
|
+
// Lia@Changes 03-02-26 --- Add all futureProofMessage into getFutureProofMessage()
|
|
1594
1533
|
function getFutureProofMessage(message) {
|
|
1595
|
-
return (
|
|
1596
|
-
message?.associatedChildMessage ||
|
|
1534
|
+
return (message?.associatedChildMessage ||
|
|
1597
1535
|
message?.botForwardedMessage ||
|
|
1598
1536
|
message?.botInvokeMessage ||
|
|
1599
1537
|
message?.botTaskMessage ||
|
|
@@ -1619,8 +1557,7 @@ export const normalizeMessageContent = (content) => {
|
|
|
1619
1557
|
message?.statusMentionMessage ||
|
|
1620
1558
|
message?.viewOnceMessage ||
|
|
1621
1559
|
message?.viewOnceMessageV2 ||
|
|
1622
|
-
message?.viewOnceMessageV2Extension
|
|
1623
|
-
);
|
|
1560
|
+
message?.viewOnceMessageV2Extension);
|
|
1624
1561
|
}
|
|
1625
1562
|
};
|
|
1626
1563
|
/**
|
|
@@ -1859,8 +1796,7 @@ export const assertMediaContent = (content) => {
|
|
|
1859
1796
|
*/
|
|
1860
1797
|
const isAnimatedWebP = (buffer) => {
|
|
1861
1798
|
// WebP must start with RIFF....WEBP
|
|
1862
|
-
if (
|
|
1863
|
-
buffer.length < 12 ||
|
|
1799
|
+
if (buffer.length < 12 ||
|
|
1864
1800
|
buffer[0] !== 0x52 ||
|
|
1865
1801
|
buffer[1] !== 0x49 ||
|
|
1866
1802
|
buffer[2] !== 0x46 ||
|
|
@@ -1868,10 +1804,10 @@ const isAnimatedWebP = (buffer) => {
|
|
|
1868
1804
|
buffer[8] !== 0x57 ||
|
|
1869
1805
|
buffer[9] !== 0x45 ||
|
|
1870
1806
|
buffer[10] !== 0x42 ||
|
|
1871
|
-
buffer[11] !== 0x50
|
|
1872
|
-
) {
|
|
1807
|
+
buffer[11] !== 0x50) {
|
|
1873
1808
|
return false;
|
|
1874
|
-
}
|
|
1809
|
+
}
|
|
1810
|
+
;
|
|
1875
1811
|
// Parse chunks starting after RIFF header (12 bytes)
|
|
1876
1812
|
let offset = 12;
|
|
1877
1813
|
while (offset < buffer.length - 8) {
|
|
@@ -1884,23 +1820,27 @@ const isAnimatedWebP = (buffer) => {
|
|
|
1884
1820
|
const flags = buffer[flagsOffset];
|
|
1885
1821
|
if (flags & 0x02) {
|
|
1886
1822
|
return true;
|
|
1887
|
-
}
|
|
1888
|
-
|
|
1889
|
-
|
|
1823
|
+
}
|
|
1824
|
+
;
|
|
1825
|
+
}
|
|
1826
|
+
;
|
|
1827
|
+
}
|
|
1828
|
+
else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
|
|
1890
1829
|
// ANIM or ANMF chunks indicate animation
|
|
1891
1830
|
return true;
|
|
1892
|
-
}
|
|
1831
|
+
}
|
|
1832
|
+
;
|
|
1893
1833
|
// Move to next chunk (chunk size + 8 bytes header, padded to even)
|
|
1894
1834
|
offset += 8 + chunkSize + (chunkSize % 2);
|
|
1895
|
-
}
|
|
1835
|
+
}
|
|
1836
|
+
;
|
|
1896
1837
|
return false;
|
|
1897
1838
|
};
|
|
1898
1839
|
/**
|
|
1899
1840
|
* Checks if a buffer is a WebP file
|
|
1900
1841
|
*/
|
|
1901
1842
|
const isWebPBuffer = (buffer) => {
|
|
1902
|
-
return (
|
|
1903
|
-
buffer.length >= 12 &&
|
|
1843
|
+
return (buffer.length >= 12 &&
|
|
1904
1844
|
buffer[0] === 0x52 &&
|
|
1905
1845
|
buffer[1] === 0x49 &&
|
|
1906
1846
|
buffer[2] === 0x46 &&
|
|
@@ -1908,11 +1848,10 @@ const isWebPBuffer = (buffer) => {
|
|
|
1908
1848
|
buffer[8] === 0x57 &&
|
|
1909
1849
|
buffer[9] === 0x45 &&
|
|
1910
1850
|
buffer[10] === 0x42 &&
|
|
1911
|
-
buffer[11] === 0x50
|
|
1912
|
-
);
|
|
1851
|
+
buffer[11] === 0x50);
|
|
1913
1852
|
};
|
|
1914
1853
|
/**
|
|
1915
|
-
*
|
|
1854
|
+
* Lia@Changes 30-01-26
|
|
1916
1855
|
* ---
|
|
1917
1856
|
* Determines whether a message should include a Biz Binary Node.
|
|
1918
1857
|
* A Biz Binary Node is added only for interactive messages
|