@systemzero/baileys 1.0.3 → 1.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.
- package/README.md +1020 -0
- package/lib/Defaults/index.js +9 -3
- package/lib/MB.cjs +8 -0
- package/lib/MessageBuilder.cjs +2509 -0
- package/lib/Socket/messages-send.js +51 -20
- package/lib/Types/Message.js +7 -0
- package/lib/Utils/messages-media.js +4 -3
- package/lib/Utils/messages.js +533 -8
- package/package.json +2 -2
package/lib/Utils/messages.js
CHANGED
|
@@ -3,17 +3,17 @@ import { randomBytes, createHash, randomUUID } from 'crypto';
|
|
|
3
3
|
import { promises as fs } from 'fs';
|
|
4
4
|
import {} from 'stream';
|
|
5
5
|
import { proto } from '../../WAProto/index.js';
|
|
6
|
-
import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
|
|
7
|
-
import { WAMessageStatus, WAProto } from '../Types/index.js';
|
|
8
|
-
import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
|
|
6
|
+
import { CALL_AUDIO_PREFIX, CALL_VIDEO_PREFIX, MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL, DONATE_URL, LIBRARY_NAME } from '../Defaults/index.js';
|
|
7
|
+
import { WAMessageStatus, WAProto, ButtonHeaderType, ButtonType, CarouselCardType, ListType } from '../Types/index.js';
|
|
8
|
+
import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser, isLidUser, isPnUser } from '../WABinary/index.js';
|
|
9
9
|
import { sha256 } from './crypto.js';
|
|
10
10
|
import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
|
|
11
|
-
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData } from './messages-media.js';
|
|
11
|
+
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData, getStream, toBuffer, getImageProcessingLibrary } from './messages-media.js';
|
|
12
12
|
const MIMETYPE_MAP = {
|
|
13
13
|
image: 'image/jpeg',
|
|
14
14
|
video: 'video/mp4',
|
|
15
15
|
document: 'application/pdf',
|
|
16
|
-
audio: 'audio/
|
|
16
|
+
audio: 'audio/mpeg',
|
|
17
17
|
sticker: 'image/webp',
|
|
18
18
|
'product-catalog-image': 'image/jpeg'
|
|
19
19
|
};
|
|
@@ -75,9 +75,11 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
75
75
|
if (mediaType === 'document' && !uploadData.fileName) {
|
|
76
76
|
uploadData.fileName = 'file';
|
|
77
77
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
if (!uploadData.mimetype) {
|
|
79
|
+
uploadData.mimetype = MIMETYPE_MAP[mediaType];
|
|
80
|
+
} else if (mediaType === 'audio' && uploadData.mimetype === 'audio/ogg; codecs=opus') {
|
|
81
|
+
uploadData.mimetype = 'audio/mpeg';
|
|
82
|
+
}
|
|
81
83
|
if (cacheableKey) {
|
|
82
84
|
const mediaBuff = await options.mediaCache.get(cacheableKey);
|
|
83
85
|
if (mediaBuff) {
|
|
@@ -125,6 +127,7 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
125
127
|
const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
|
|
126
128
|
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
|
|
127
129
|
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
|
|
130
|
+
const requiresPlaybackSpeed = mediaType === 'audio' && uploadData.ptt === true;
|
|
128
131
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
|
|
129
132
|
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
|
|
130
133
|
const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
|
|
@@ -163,6 +166,13 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
163
166
|
uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
|
|
164
167
|
logger?.debug('processed waveform');
|
|
165
168
|
}
|
|
169
|
+
if (mediaType === 'audio' && uploadData.ptt === true) {
|
|
170
|
+
uploadData.isPlaybackSpeedEnabled = true;
|
|
171
|
+
}
|
|
172
|
+
if (requiresPlaybackSpeed) {
|
|
173
|
+
uploadData.isPlaybackSpeedEnabled = true;
|
|
174
|
+
logger?.debug('enabled playback speed control');
|
|
175
|
+
}
|
|
166
176
|
if (requiresAudioBackground) {
|
|
167
177
|
uploadData.backgroundArgb = await assertColor(options.backgroundColor);
|
|
168
178
|
logger?.debug('computed backgroundColor audio status');
|
|
@@ -245,6 +255,240 @@ export const generateForwardMessageContent = (message, forceForward) => {
|
|
|
245
255
|
}
|
|
246
256
|
return content;
|
|
247
257
|
};
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
// ── WebP helpers (para stickerPack com animadas) ──────────────────────────────
|
|
261
|
+
const CONCURRENCY_LIMIT = 15;
|
|
262
|
+
const isAnimatedWebP = (buffer) => {
|
|
263
|
+
if (buffer.length < 12 || buffer[0] !== 0x52 || buffer[1] !== 0x49 || buffer[2] !== 0x46 || buffer[3] !== 0x46 ||
|
|
264
|
+
buffer[8] !== 0x57 || buffer[9] !== 0x45 || buffer[10] !== 0x42 || buffer[11] !== 0x50) return false;
|
|
265
|
+
let offset = 12;
|
|
266
|
+
while (offset < buffer.length - 8) {
|
|
267
|
+
const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
|
|
268
|
+
const chunkSize = buffer.readUInt32LE(offset + 4);
|
|
269
|
+
if (chunkFourCC === 'VP8X') {
|
|
270
|
+
const flagsOffset = offset + 8;
|
|
271
|
+
if (flagsOffset < buffer.length && (buffer[flagsOffset] & 0x02)) return true;
|
|
272
|
+
} else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') return true;
|
|
273
|
+
offset += 8 + chunkSize + (chunkSize % 2);
|
|
274
|
+
}
|
|
275
|
+
return false;
|
|
276
|
+
};
|
|
277
|
+
const isWebPBuffer = (buffer) => {
|
|
278
|
+
return buffer.length >= 12 && buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
|
|
279
|
+
buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50;
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// ── prepareStickerPackMessage (da @boruto_vk7/baileys — suporte a animadas) ──
|
|
283
|
+
const prepareStickerPackMessage = async (message, options) => {
|
|
284
|
+
const { cover, stickers = [], name = '📦 Sticker Pack', publisher = '@systemzero/baileys', description = '' } = message;
|
|
285
|
+
if (stickers.length > 60) throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
|
|
286
|
+
if (stickers.length === 0) throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
|
|
287
|
+
if (!cover) throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
|
|
288
|
+
const logger = options.logger;
|
|
289
|
+
const lib = await getImageProcessingLibrary();
|
|
290
|
+
const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
|
|
291
|
+
const hasImage = 'image' in lib && !!lib.image?.Transformer;
|
|
292
|
+
const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
|
|
293
|
+
const stickerPackIdValue = generateMessageIDV2();
|
|
294
|
+
const stickerData = {};
|
|
295
|
+
const stickerMetadata = new Array(stickers.length);
|
|
296
|
+
for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
|
|
297
|
+
const promises = [];
|
|
298
|
+
const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
|
|
299
|
+
for (let j = i; j < chunkEnd; j++) {
|
|
300
|
+
promises.push((async (index) => {
|
|
301
|
+
const sticker = stickers[index];
|
|
302
|
+
const { stream } = await getStream(sticker.data);
|
|
303
|
+
const buffer = await toBuffer(stream);
|
|
304
|
+
let webpBuffer, isAnimated = false;
|
|
305
|
+
if (isWebPBuffer(buffer)) {
|
|
306
|
+
webpBuffer = buffer;
|
|
307
|
+
isAnimated = isAnimatedWebP(buffer);
|
|
308
|
+
} else if (hasSharp) {
|
|
309
|
+
webpBuffer = await lib.sharp.default(buffer).resize(512, 512, { fit: 'inside' }).webp({ quality: 80 }).toBuffer();
|
|
310
|
+
} else if (hasImage) {
|
|
311
|
+
webpBuffer = await new lib.image.Transformer(buffer).resize(512, 512).webp(80);
|
|
312
|
+
} else {
|
|
313
|
+
throw new Boom('No image processing library (sharp or @napi-rs/image) available', { statusCode: 500 });
|
|
314
|
+
}
|
|
315
|
+
if (webpBuffer.length > 1024 * 1024) throw new Boom(`Sticker at index ${index} exceeds 1MB limit`, { statusCode: 400 });
|
|
316
|
+
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
|
|
317
|
+
const fileName = `${hash}.webp`;
|
|
318
|
+
stickerData[fileName] = webpBuffer;
|
|
319
|
+
stickerMetadata[index] = {
|
|
320
|
+
fileName, mimetype: 'image/webp', isAnimated,
|
|
321
|
+
emojis: sticker.emojis || ['✨'],
|
|
322
|
+
accessibilityLabel: sticker.accessibilityLabel || ''
|
|
323
|
+
};
|
|
324
|
+
})(j));
|
|
325
|
+
}
|
|
326
|
+
await Promise.all(promises);
|
|
327
|
+
}
|
|
328
|
+
const trayIconFileName = `${stickerPackIdValue}.webp`;
|
|
329
|
+
const { stream: coverStream } = await getStream(cover);
|
|
330
|
+
const coverBuffer = await toBuffer(coverStream);
|
|
331
|
+
let coverWebpBuffer;
|
|
332
|
+
if (isWebPBuffer(coverBuffer)) {
|
|
333
|
+
coverWebpBuffer = coverBuffer;
|
|
334
|
+
} else if (hasSharp) {
|
|
335
|
+
coverWebpBuffer = await lib.sharp.default(coverBuffer).resize(512, 512, { fit: 'inside' }).webp({ quality: 80 }).toBuffer();
|
|
336
|
+
} else if (hasImage) {
|
|
337
|
+
coverWebpBuffer = await new lib.image.Transformer(coverBuffer).resize(512, 512).webp(80);
|
|
338
|
+
} else {
|
|
339
|
+
throw new Boom('No image processing library available for cover', { statusCode: 500 });
|
|
340
|
+
}
|
|
341
|
+
stickerData[trayIconFileName] = coverWebpBuffer;
|
|
342
|
+
// Monta o ZIP usando _createZip da sz (sem fflate)
|
|
343
|
+
const zipBuffer = _createZip(Object.entries(stickerData).map(([name, data]) => ({ name, data })));
|
|
344
|
+
const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', { logger });
|
|
345
|
+
let stickerPackUploadResult;
|
|
346
|
+
try {
|
|
347
|
+
stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
|
|
348
|
+
fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
|
|
349
|
+
mediaType: 'sticker-pack',
|
|
350
|
+
timeoutMs: options.mediaUploadTimeoutMs
|
|
351
|
+
});
|
|
352
|
+
} finally {
|
|
353
|
+
fs.unlink(stickerPackUpload.encFilePath).catch(() => {});
|
|
354
|
+
}
|
|
355
|
+
const obj = {
|
|
356
|
+
name, publisher, packDescription: description,
|
|
357
|
+
stickerPackId: stickerPackIdValue,
|
|
358
|
+
stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
|
|
359
|
+
stickerPackSize: zipBuffer.length,
|
|
360
|
+
stickers: stickerMetadata,
|
|
361
|
+
fileSha256: stickerPackUpload.fileSha256,
|
|
362
|
+
fileEncSha256: stickerPackUpload.fileEncSha256,
|
|
363
|
+
mediaKey: stickerPackUpload.mediaKey,
|
|
364
|
+
directPath: stickerPackUploadResult.directPath,
|
|
365
|
+
fileLength: stickerPackUpload.fileLength,
|
|
366
|
+
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
367
|
+
trayIconFileName
|
|
368
|
+
};
|
|
369
|
+
// Gerar thumbnail
|
|
370
|
+
try {
|
|
371
|
+
let thumbnailBuffer;
|
|
372
|
+
if (hasSharp) {
|
|
373
|
+
thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
|
|
374
|
+
} else if (hasImage) {
|
|
375
|
+
thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
|
|
376
|
+
} else if (hasJimp) {
|
|
377
|
+
const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
|
|
378
|
+
thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
|
|
379
|
+
}
|
|
380
|
+
if (thumbnailBuffer?.length > 0) {
|
|
381
|
+
const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', { logger, mediaKey: stickerPackUpload.mediaKey });
|
|
382
|
+
let thumbResult;
|
|
383
|
+
try {
|
|
384
|
+
thumbResult = await options.upload(thumbUpload.encFilePath, {
|
|
385
|
+
fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
|
|
386
|
+
mediaType: 'thumbnail-sticker-pack',
|
|
387
|
+
timeoutMs: options.mediaUploadTimeoutMs
|
|
388
|
+
});
|
|
389
|
+
} finally {
|
|
390
|
+
fs.unlink(thumbUpload.encFilePath).catch(() => {});
|
|
391
|
+
}
|
|
392
|
+
Object.assign(obj, {
|
|
393
|
+
thumbnailDirectPath: thumbResult.directPath,
|
|
394
|
+
thumbnailSha256: thumbUpload.fileSha256,
|
|
395
|
+
thumbnailEncSha256: thumbUpload.fileEncSha256,
|
|
396
|
+
thumbnailHeight: 252,
|
|
397
|
+
thumbnailWidth: 252,
|
|
398
|
+
imageDataHash: sha256(thumbnailBuffer).toString('base64')
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
} catch(e) {
|
|
402
|
+
logger?.warn('Thumbnail generation failed: ' + e.message);
|
|
403
|
+
}
|
|
404
|
+
return WAProto.Message.StickerPackMessage.fromObject(obj);
|
|
405
|
+
};
|
|
406
|
+
// ── Helpers adicionados da @boruto_vk7/baileys ──────────────────────────────
|
|
407
|
+
export const hasNonNullishProperty = (message, key) => {
|
|
408
|
+
return message != null &&
|
|
409
|
+
typeof message === 'object' &&
|
|
410
|
+
key in message &&
|
|
411
|
+
message[key] != null;
|
|
412
|
+
};
|
|
413
|
+
export const hasOptionalProperty = (obj, key) => {
|
|
414
|
+
return obj != null &&
|
|
415
|
+
typeof obj === 'object' &&
|
|
416
|
+
key in obj &&
|
|
417
|
+
obj[key] != null;
|
|
418
|
+
};
|
|
419
|
+
export const hasValidAlbumMedia = (message) => {
|
|
420
|
+
return !!(message.imageMessage || message.videoMessage);
|
|
421
|
+
};
|
|
422
|
+
export const hasValidInteractiveHeader = (message) => {
|
|
423
|
+
return !!(message.imageMessage ||
|
|
424
|
+
message.videoMessage ||
|
|
425
|
+
message.documentMessage ||
|
|
426
|
+
message.productMessage ||
|
|
427
|
+
message.locationMessage);
|
|
428
|
+
};
|
|
429
|
+
export const hasValidCarouselHeader = (message) => {
|
|
430
|
+
return !!(message.imageMessage ||
|
|
431
|
+
message.videoMessage ||
|
|
432
|
+
message.productMessage);
|
|
433
|
+
};
|
|
434
|
+
// prepareNativeFlowButtons — versão completa da @boruto_vk7/baileys
|
|
435
|
+
const prepareNativeFlowButtons = (message) => {
|
|
436
|
+
const buttons = message.nativeFlow;
|
|
437
|
+
const isButtonsFieldArray = Array.isArray(buttons);
|
|
438
|
+
const correctedField = isButtonsFieldArray ? buttons : (buttons?.buttons || []);
|
|
439
|
+
const messageParamsJson = {};
|
|
440
|
+
if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
|
|
441
|
+
Object.assign(messageParamsJson, {
|
|
442
|
+
limited_time_offer: {
|
|
443
|
+
text: message.offerText || '@systemzero/baileys',
|
|
444
|
+
url: message.offerUrl || 'https://systemzone.store',
|
|
445
|
+
copy_code: message.offerCode,
|
|
446
|
+
expiration_time: message.offerExpiration
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
|
|
451
|
+
Object.assign(messageParamsJson, {
|
|
452
|
+
bottom_sheet: {
|
|
453
|
+
in_thread_buttons_limit: 1,
|
|
454
|
+
divider_indices: Array.from({ length: correctedField.length }, (_, i) => i),
|
|
455
|
+
list_title: message.optionTitle || '📄 Select Options',
|
|
456
|
+
button_title: message.optionText
|
|
457
|
+
}
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
buttons: correctedField.map(button => {
|
|
462
|
+
const buttonText = button.text || button.buttonText;
|
|
463
|
+
const buttonIcon = button.icon?.toUpperCase();
|
|
464
|
+
if (hasOptionalProperty(button, 'id') && !!button.id) {
|
|
465
|
+
return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: buttonText || '👉🏻 Click', id: button.id, icon: buttonIcon }) };
|
|
466
|
+
} else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
|
|
467
|
+
return { name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📋 Copy', copy_code: button.copy, icon: buttonIcon }) };
|
|
468
|
+
} else if (hasOptionalProperty(button, 'url') && !!button.url) {
|
|
469
|
+
return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🌐 Visit', url: button.url, merchant_url: button.url, webview_interaction: button.useWebview, icon: buttonIcon }) };
|
|
470
|
+
} else if (hasOptionalProperty(button, 'call') && !!button.call) {
|
|
471
|
+
return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📞 Call', phone_number: button.call, icon: buttonIcon }) };
|
|
472
|
+
} else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
|
|
473
|
+
return { name: 'single_select', buttonParamsJson: JSON.stringify({ title: buttonText || '📋 Select', sections: button.sections, icon: buttonIcon }) };
|
|
474
|
+
}
|
|
475
|
+
return button;
|
|
476
|
+
}),
|
|
477
|
+
messageParamsJson: JSON.stringify(messageParamsJson)
|
|
478
|
+
};
|
|
479
|
+
};
|
|
480
|
+
// prepareProductMessage — monta productMessage
|
|
481
|
+
const prepareProductMessage = async (message, options) => {
|
|
482
|
+
const { imageMessage } = await prepareWAMessageMedia({ image: message.product?.productImage || message.image }, options);
|
|
483
|
+
return {
|
|
484
|
+
product: {
|
|
485
|
+
productImage: imageMessage,
|
|
486
|
+
...message.product
|
|
487
|
+
},
|
|
488
|
+
businessOwnerJid: message.businessOwnerJid || '0@s.whatsapp.net'
|
|
489
|
+
};
|
|
490
|
+
};
|
|
491
|
+
// ── Fim dos helpers ──────────────────────────────────────────────────────────
|
|
248
492
|
export const generateWAMessageContent = async (message, options) => {
|
|
249
493
|
var _a, _b;
|
|
250
494
|
let m = {};
|
|
@@ -447,9 +691,288 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
447
691
|
}
|
|
448
692
|
};
|
|
449
693
|
}
|
|
694
|
+
|
|
695
|
+
// ── NOVOS TIPOS (@boruto_vk7/baileys) ────────────────────────────────────
|
|
696
|
+
else if (hasNonNullishProperty(message, 'keep')) {
|
|
697
|
+
m.keepInChatMessage = {};
|
|
698
|
+
m.keepInChatMessage.key = message.keep;
|
|
699
|
+
m.keepInChatMessage.keepType = message.type;
|
|
700
|
+
m.keepInChatMessage.timestampMs = Date.now();
|
|
701
|
+
}
|
|
702
|
+
else if (hasNonNullishProperty(message, 'flowReply')) {
|
|
703
|
+
m.interactiveResponseMessage = {
|
|
704
|
+
body: {
|
|
705
|
+
format: message.flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
|
|
706
|
+
text: message.flowReply.text
|
|
707
|
+
},
|
|
708
|
+
nativeFlowResponseMessage: {
|
|
709
|
+
name: message.flowReply.name,
|
|
710
|
+
paramsJson: message.flowReply.paramsJson || '{}',
|
|
711
|
+
version: message.flowReply.version || 1
|
|
712
|
+
}
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
else if (hasNonNullishProperty(message, 'pollResult')) {
|
|
716
|
+
const pollResultSnapshotMessage = {
|
|
717
|
+
name: message.pollResult.name,
|
|
718
|
+
pollVotes: message.pollResult.votes.map(vote => ({
|
|
719
|
+
optionName: vote.name,
|
|
720
|
+
optionVoteCount: parseInt(vote.voteCount)
|
|
721
|
+
}))
|
|
722
|
+
};
|
|
723
|
+
if (message.pollResult.pollType === 1) {
|
|
724
|
+
pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
|
|
725
|
+
m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
|
|
726
|
+
} else {
|
|
727
|
+
pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
|
|
728
|
+
m.pollResultSnapshotMessage = pollResultSnapshotMessage;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
else if (hasNonNullishProperty(message, 'pollUpdate')) {
|
|
732
|
+
if (!message.pollUpdate.key) throw new Boom('Message key is required', { statusCode: 400 });
|
|
733
|
+
if (!message.pollUpdate.vote) throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
|
|
734
|
+
m.pollUpdateMessage = {
|
|
735
|
+
metadata: message.pollUpdate.metadata,
|
|
736
|
+
pollCreationMessageKey: message.pollUpdate.key,
|
|
737
|
+
senderTimestampMs: Date.now(),
|
|
738
|
+
vote: message.pollUpdate.vote
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
|
|
742
|
+
m.paymentInviteMessage = {
|
|
743
|
+
expiryTimestamp: Date.now(),
|
|
744
|
+
serviceType: message.paymentInviteServiceType
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
else if (hasNonNullishProperty(message, 'orderText')) {
|
|
748
|
+
if (!Buffer.isBuffer(message.thumbnail)) throw new Boom('Must provide thumbnail buffer', { statusCode: 400 });
|
|
749
|
+
m.orderMessage = {
|
|
750
|
+
itemCount: 1, messageVersion: 1,
|
|
751
|
+
orderTitle: 'Order', status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
|
|
752
|
+
surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
|
|
753
|
+
token: generateMessageIDV2(), totalAmount1000: 1000, totalCurrencyCode: 'BRL',
|
|
754
|
+
...message, message: message.orderText
|
|
755
|
+
};
|
|
756
|
+
delete m.orderMessage.orderText;
|
|
757
|
+
}
|
|
758
|
+
else if (hasNonNullishProperty(message, 'stickers')) {
|
|
759
|
+
m.stickerPackMessage = await prepareStickerPackMessage(message, options);
|
|
760
|
+
}
|
|
761
|
+
else if (hasNonNullishProperty(message, 'album')) {
|
|
762
|
+
if (!Array.isArray(message.album)) throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
|
|
763
|
+
let videoCount = 0, imageCount = 0;
|
|
764
|
+
for (let i = 0; i < message.album.length; i++) {
|
|
765
|
+
if (message.album[i].video) videoCount++;
|
|
766
|
+
if (message.album[i].image) imageCount++;
|
|
767
|
+
}
|
|
768
|
+
if ((videoCount + imageCount) < 2) throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
|
|
769
|
+
m.albumMessage = { expectedImageCount: imageCount, expectedVideoCount: videoCount };
|
|
770
|
+
}
|
|
771
|
+
else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
|
|
772
|
+
const requestPaymentMessage = {
|
|
773
|
+
amount: { currencyCode: 'BRL', offset: 100, value: 1 },
|
|
774
|
+
amount1000: 1000, currencyCodeIso4217: 'BRL',
|
|
775
|
+
expiryTimestamp: Date.now(), noteMessage: m,
|
|
776
|
+
requestFrom: message.requestPaymentFrom, ...message
|
|
777
|
+
};
|
|
778
|
+
delete requestPaymentMessage.requestPaymentFrom;
|
|
779
|
+
m = { requestPaymentMessage };
|
|
780
|
+
}
|
|
781
|
+
else if (hasNonNullishProperty(message, 'invoiceNote')) {
|
|
782
|
+
const attachment = m.imageMessage || m.documentMessage;
|
|
783
|
+
const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
|
|
784
|
+
const invoiceMessage = {
|
|
785
|
+
attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
|
|
786
|
+
note: message.invoiceNote
|
|
787
|
+
};
|
|
788
|
+
if (attachment) {
|
|
789
|
+
const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
|
|
790
|
+
Object.assign(invoiceMessage, {
|
|
791
|
+
attachmentDirectPath: directPath, attachmentFileEncSha256: fileEncSha256,
|
|
792
|
+
attachmentFileSha256: fileSha256, attachmentJpegThumbnail: jpegThumbnail,
|
|
793
|
+
attachmentMediaKey: mediaKey, attachmentMediaKeyTimestamp: mediaKeyTimestamp,
|
|
794
|
+
attachmentMimetype: mimetype, token: generateMessageIDV2()
|
|
795
|
+
});
|
|
796
|
+
} else {
|
|
797
|
+
throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
|
|
798
|
+
}
|
|
799
|
+
m = { invoiceMessage };
|
|
800
|
+
}
|
|
801
|
+
|
|
450
802
|
else {
|
|
451
803
|
m = await prepareWAMessageMedia(message, options);
|
|
452
804
|
}
|
|
805
|
+
|
|
806
|
+
// ── NOVOS TIPOS FASE 2: buttons/sections/templateButtons/nativeFlow/cards ─
|
|
807
|
+
if (hasNonNullishProperty(message, 'buttons')) {
|
|
808
|
+
const buttonsMessage = {
|
|
809
|
+
buttons: message.buttons.map(button => {
|
|
810
|
+
const buttonText = button.text || button.buttonText;
|
|
811
|
+
if (hasOptionalProperty(button, 'sections')) {
|
|
812
|
+
return { nativeFlowInfo: { name: 'single_select', paramsJson: JSON.stringify({ title: buttonText, sections: button.sections }) }, type: ButtonType.NATIVE_FLOW };
|
|
813
|
+
}
|
|
814
|
+
if (hasOptionalProperty(button, 'name')) {
|
|
815
|
+
return { nativeFlowInfo: { name: button.name, paramsJson: button.paramsJson }, type: ButtonType.NATIVE_FLOW };
|
|
816
|
+
}
|
|
817
|
+
return { buttonId: button.id || button.buttonId, buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText, type: button.type || ButtonType.RESPONSE };
|
|
818
|
+
})
|
|
819
|
+
};
|
|
820
|
+
if (hasOptionalProperty(message, 'text')) {
|
|
821
|
+
buttonsMessage.contentText = message.text;
|
|
822
|
+
buttonsMessage.headerType = ButtonHeaderType.EMPTY;
|
|
823
|
+
} else {
|
|
824
|
+
if (hasOptionalProperty(message, 'caption')) buttonsMessage.contentText = message.caption;
|
|
825
|
+
const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
|
|
826
|
+
buttonsMessage.headerType = ButtonHeaderType[type];
|
|
827
|
+
Object.assign(buttonsMessage, m);
|
|
828
|
+
}
|
|
829
|
+
if (hasOptionalProperty(message, 'footer')) buttonsMessage.footerText = message.footer;
|
|
830
|
+
m = { buttonsMessage };
|
|
831
|
+
}
|
|
832
|
+
else if (hasNonNullishProperty(message, 'sections')) {
|
|
833
|
+
m = { listMessage: { sections: message.sections, buttonText: message.buttonText, title: message.title, footerText: message.footer, description: message.text, listType: ListType.SINGLE_SELECT } };
|
|
834
|
+
}
|
|
835
|
+
else if (hasNonNullishProperty(message, 'templateButtons')) {
|
|
836
|
+
const hydratedTemplate = {
|
|
837
|
+
hydratedButtons: message.templateButtons.map((button, i) => {
|
|
838
|
+
const buttonText = button.text || button.buttonText;
|
|
839
|
+
if (hasOptionalProperty(button, 'id')) return { index: i, quickReplyButton: { displayText: buttonText || '👉🏻 Click', id: button.id } };
|
|
840
|
+
if (hasOptionalProperty(button, 'url')) return { index: i, urlButton: { displayText: buttonText || '🌐 Visit', url: button.url } };
|
|
841
|
+
if (hasOptionalProperty(button, 'call')) return { index: i, callButton: { displayText: buttonText || '📞 Call', phoneNumber: button.call } };
|
|
842
|
+
button.index = button.index || i;
|
|
843
|
+
return button;
|
|
844
|
+
})
|
|
845
|
+
};
|
|
846
|
+
if (hasOptionalProperty(message, 'text')) hydratedTemplate.hydratedContentText = message.text;
|
|
847
|
+
else {
|
|
848
|
+
if (hasOptionalProperty(message, 'caption')) { hydratedTemplate.hydratedTitleText = message.title; hydratedTemplate.hydratedContentText = message.caption; }
|
|
849
|
+
Object.assign(hydratedTemplate, m);
|
|
850
|
+
}
|
|
851
|
+
if (hasOptionalProperty(message, 'footer')) hydratedTemplate.hydratedFooterText = message.footer;
|
|
852
|
+
hydratedTemplate.templateId = message.id || 'template-' + Date.now();
|
|
853
|
+
m = { templateMessage: { hydratedFourRowTemplate: hydratedTemplate, hydratedTemplate } };
|
|
854
|
+
}
|
|
855
|
+
else if (hasNonNullishProperty(message, 'nativeFlow')) {
|
|
856
|
+
const interactiveMessage = { nativeFlowMessage: prepareNativeFlowButtons(message) };
|
|
857
|
+
if (hasOptionalProperty(message, 'text')) {
|
|
858
|
+
interactiveMessage.body = { text: message.text };
|
|
859
|
+
} else {
|
|
860
|
+
if (hasOptionalProperty(message, 'caption')) {
|
|
861
|
+
const isValidHeader = hasValidInteractiveHeader(m);
|
|
862
|
+
if (!isValidHeader) throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
|
|
863
|
+
interactiveMessage.header = { title: message.title || '', subtitle: message.subtitle || '', hasMediaAttachment: isValidHeader };
|
|
864
|
+
interactiveMessage.body = { text: message.caption };
|
|
865
|
+
}
|
|
866
|
+
if (hasOptionalProperty(message, 'thumbnail') && !!message.thumbnail) interactiveMessage.jpegThumbnail = message.thumbnail;
|
|
867
|
+
if (interactiveMessage.header) Object.assign(interactiveMessage.header, m);
|
|
868
|
+
}
|
|
869
|
+
if (hasOptionalProperty(message, 'audioFooter')) {
|
|
870
|
+
const { audioMessage } = await prepareWAMessageMedia({ audio: message.audioFooter }, options);
|
|
871
|
+
interactiveMessage.footer = { audioMessage, hasMediaAttachment: true };
|
|
872
|
+
} else if (hasOptionalProperty(message, 'footer')) {
|
|
873
|
+
interactiveMessage.footer = { text: message.footer };
|
|
874
|
+
}
|
|
875
|
+
m = { interactiveMessage };
|
|
876
|
+
}
|
|
877
|
+
else if (hasNonNullishProperty(message, 'cards')) {
|
|
878
|
+
const interactiveMessage = {
|
|
879
|
+
carouselMessage: {
|
|
880
|
+
cards: await Promise.all(message.cards.map(async (card) => {
|
|
881
|
+
let carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
|
|
882
|
+
const isValidHeader = hasValidCarouselHeader(carouselHeader);
|
|
883
|
+
if (!isValidHeader) throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
|
|
884
|
+
const carouselCard = { nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : { nativeFlow: [] }) };
|
|
885
|
+
if (hasOptionalProperty(card, 'text')) {
|
|
886
|
+
carouselCard.body = { text: card.text };
|
|
887
|
+
} else {
|
|
888
|
+
if (hasOptionalProperty(card, 'caption')) {
|
|
889
|
+
carouselCard.header = { title: card.title || '', subtitle: card.subtitle || '', hasMediaAttachment: isValidHeader };
|
|
890
|
+
carouselCard.body = { text: card.caption };
|
|
891
|
+
}
|
|
892
|
+
if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) carouselCard.jpegThumbnail = card.thumbnail;
|
|
893
|
+
if (carouselCard.header) Object.assign(carouselCard.header, carouselHeader);
|
|
894
|
+
}
|
|
895
|
+
if (hasOptionalProperty(card, 'audioFooter')) {
|
|
896
|
+
const { audioMessage } = await prepareWAMessageMedia({ audio: card.audioFooter }, options);
|
|
897
|
+
carouselCard.footer = { audioMessage, hasMediaAttachment: true };
|
|
898
|
+
} else if (hasOptionalProperty(card, 'footer')) {
|
|
899
|
+
carouselCard.footer = { text: card.footer };
|
|
900
|
+
}
|
|
901
|
+
return carouselCard;
|
|
902
|
+
})),
|
|
903
|
+
carouselCardType: 0, messageVersion: 1
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
if (hasOptionalProperty(message, 'text')) interactiveMessage.body = { text: message.text };
|
|
907
|
+
if (hasOptionalProperty(message, 'footer')) interactiveMessage.footer = { text: message.footer };
|
|
908
|
+
m = { interactiveMessage };
|
|
909
|
+
}
|
|
910
|
+
// ── externalAdReply ───────────────────────────────────────────────────────
|
|
911
|
+
if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
|
|
912
|
+
const messageType = Object.keys(m)[0];
|
|
913
|
+
const key = m[messageType];
|
|
914
|
+
const content = message.externalAdReply;
|
|
915
|
+
if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) throw new Boom('Thumbnail must be a buffer', { statusCode: 400 });
|
|
916
|
+
const externalAdReply = {
|
|
917
|
+
...content, body: content.body, mediaType: content.mediaType || 1,
|
|
918
|
+
mediaUrl: content.url, renderLargerThumbnail: content.largeThumbnail,
|
|
919
|
+
sourceUrl: content.url, thumbnail: content.thumbnail,
|
|
920
|
+
title: content.title || 'System Zero'
|
|
921
|
+
};
|
|
922
|
+
delete externalAdReply.largeThumbnail; delete externalAdReply.url;
|
|
923
|
+
if (key && 'contextInfo' in key && !!key.contextInfo) key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
|
|
924
|
+
else if (key) key.contextInfo = { externalAdReply };
|
|
925
|
+
}
|
|
926
|
+
// ── mentionAll ────────────────────────────────────────────────────────────
|
|
927
|
+
if (hasOptionalProperty(message, 'mentionAll') && message.mentionAll) {
|
|
928
|
+
const messageType = Object.keys(m)[0];
|
|
929
|
+
const key = m[messageType];
|
|
930
|
+
if (key && 'contextInfo' in key) { key.contextInfo = key.contextInfo || {}; key.contextInfo.nonJidMentions = 1; }
|
|
931
|
+
else if (key) key.contextInfo = { nonJidMentions: 1 };
|
|
932
|
+
}
|
|
933
|
+
// ── groupStatus ───────────────────────────────────────────────────────────
|
|
934
|
+
if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
|
|
935
|
+
const messageType = Object.keys(m)[0];
|
|
936
|
+
const key = m[messageType];
|
|
937
|
+
if (key && 'contextInfo' in key && !!key.contextInfo) key.contextInfo.isGroupStatus = message.groupStatus;
|
|
938
|
+
else if (key) key.contextInfo = { isGroupStatus: message.groupStatus };
|
|
939
|
+
m = { groupStatusMessageV2: { message: m } };
|
|
940
|
+
delete message.groupStatus;
|
|
941
|
+
}
|
|
942
|
+
// ── spoiler ───────────────────────────────────────────────────────────────
|
|
943
|
+
if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
|
|
944
|
+
const messageType = Object.keys(m)[0];
|
|
945
|
+
const key = m[messageType];
|
|
946
|
+
if (key && 'contextInfo' in key && !!key.contextInfo) key.contextInfo.isSpoiler = message.spoiler;
|
|
947
|
+
else if (key) key.contextInfo = { isSpoiler: message.spoiler };
|
|
948
|
+
m = { spoilerMessage: { message: m } };
|
|
949
|
+
delete message.spoiler;
|
|
950
|
+
}
|
|
951
|
+
// ── interactiveAsTemplate ─────────────────────────────────────────────────
|
|
952
|
+
if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
|
|
953
|
+
if (!m.interactiveMessage) throw new Boom('Invalid message type for template', { statusCode: 400 });
|
|
954
|
+
m = { templateMessage: { interactiveMessageTemplate: m.interactiveMessage, templateId: message.id || 'template-' + Date.now() } };
|
|
955
|
+
delete message.interactiveAsTemplate;
|
|
956
|
+
}
|
|
957
|
+
// ── ephemeral ─────────────────────────────────────────────────────────────
|
|
958
|
+
if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
|
|
959
|
+
m = { ephemeralMessage: { message: m } };
|
|
960
|
+
delete message.ephemeral;
|
|
961
|
+
}
|
|
962
|
+
// ── isLottie ──────────────────────────────────────────────────────────────
|
|
963
|
+
if (hasOptionalProperty(message, 'isLottie') && !!message.isLottie) {
|
|
964
|
+
m = { lottieStickerMessage: { message: m } };
|
|
965
|
+
}
|
|
966
|
+
// ── viewOnceV2 / viewOnceV2Extension ──────────────────────────────────────
|
|
967
|
+
else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
|
|
968
|
+
m = { viewOnceMessageV2: { message: m } };
|
|
969
|
+
delete message.viewOnceV2;
|
|
970
|
+
}
|
|
971
|
+
else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
|
|
972
|
+
m = { viewOnceMessageV2Extension: { message: m } };
|
|
973
|
+
delete message.viewOnceV2Extension;
|
|
974
|
+
}
|
|
975
|
+
|
|
453
976
|
if ('viewOnce' in message && !!message.viewOnce) {
|
|
454
977
|
m = { viewOnceMessage: { message: m } };
|
|
455
978
|
}
|
|
@@ -813,6 +1336,8 @@ function _createZip(files) {
|
|
|
813
1336
|
eocd.writeUInt32LE(cdBuf.length, 12); eocd.writeUInt32LE(offset, 16);
|
|
814
1337
|
return Buffer.concat([...locals, cdBuf, eocd]);
|
|
815
1338
|
}
|
|
1339
|
+
// sendStickerPack — wrapper de compatibilidade
|
|
1340
|
+
// Nova API: sock.sendMessage(jid, { cover, stickers, name, publisher })
|
|
816
1341
|
export const sendStickerPack = async (conn, chatId, pack, options = {}) => {
|
|
817
1342
|
const { name = '', publisher = '', stickers = [] } = pack;
|
|
818
1343
|
const zipFiles = stickers.map((s, i) => {
|
package/package.json
CHANGED