@systemzero/baileys 1.0.4 → 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.
@@ -568,7 +568,7 @@ export const makeMessagesSocket = (config) => {
568
568
  if (isNewsletter) {
569
569
  const patched = patchMessageBeforeSending ? await patchMessageBeforeSending(message, []) : message;
570
570
  const bytes = encodeNewsletterMessage(patched);
571
- binaryNodeContent.push({ tag: 'plaintext', attrs: {}, content: bytes });
571
+ binaryNodeContent.push({ tag: 'plaintext', attrs: extraAttrs, content: bytes });
572
572
  const stanza = {
573
573
  tag: 'message',
574
574
  attrs: { to: jid, id: msgId, type: getMessageType(message), ...(additionalAttributes || {}) },
@@ -1,4 +1,11 @@
1
1
  import { proto } from '../../WAProto/index.js';
2
+ export const ButtonHeaderType = proto.Message.ButtonsMessage.HeaderType;
3
+ export const ButtonType = proto.Message.ButtonsMessage.Button.Type;
4
+ export const CarouselCardType = proto.Message.InteractiveMessage.CarouselMessage.CarouselCardType;
5
+ export const ListType = proto.Message.ListMessage.ListType;
6
+ export const ProtocolType = proto.Message.ProtocolMessage.Type;
7
+ export const AssociationType = proto.MessageAssociation?.AssociationType;
8
+
2
9
  // export the WAMessage Prototypes
3
10
  export { proto as WAProto };
4
11
  export const WAMessageStubType = proto.WebMessageInfo.StubType;
@@ -13,7 +13,7 @@ import { getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from
13
13
  import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
14
14
  import { generateMessageIDV2 } from './generics.js';
15
15
  const getTmpFilesDirectory = () => tmpdir();
16
- const getImageProcessingLibrary = async () => {
16
+ export const getImageProcessingLibrary = async () => {
17
17
  //@ts-ignore
18
18
  const [jimp, sharp] = await Promise.all([import('jimp').catch(() => { }), import('sharp').catch(() => { })]);
19
19
  if (sharp) {
@@ -210,7 +210,8 @@ export async function getAudioWaveform(buffer, logger) {
210
210
  }
211
211
  const audioBuffer = await decoder(audioData);
212
212
  const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
213
- const samples = 64; // Number of samples we want to have in our final data set
213
+ const samples = 64;
214
+ const minHeight = 0.05;
214
215
  const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
215
216
  const filteredData = [];
216
217
  for (let i = 0; i < samples; i++) {
@@ -225,7 +226,7 @@ export async function getAudioWaveform(buffer, logger) {
225
226
  const multiplier = Math.pow(Math.max(...filteredData), -1);
226
227
  const normalizedData = filteredData.map(n => n * multiplier);
227
228
  // Generate waveform like WhatsApp
228
- const waveform = new Uint8Array(normalizedData.map(n => Math.floor(100 * n)));
229
+ const waveform = new Uint8Array(normalizedData.map(n => Math.max(minHeight, n)).map(n => Math.floor(100 * n)));
229
230
  return waveform;
230
231
  }
231
232
  catch (e) {
@@ -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/ogg; codecs=opus',
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
- if (!uploadData.mimetype) {
79
- uploadData.mimetype = MIMETYPE_MAP[mediaType];
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
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@systemzero/baileys",
3
3
  "type": "module",
4
- "version": "1.0.4",
5
- "description": "System-zero baileys bot ultra",
4
+ "version": "1.0.5",
5
+ "description": "System-zero baileys bot",
6
6
  "keywords": [
7
7
  "whatsapp",
8
8
  "automation",