@systemzero/baileys 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +595 -762
  2. package/lib/Defaults/index.js +9 -3
  3. package/lib/MB.cjs +8 -0
  4. package/lib/MessageBuilder.cjs +2509 -0
  5. package/lib/Socket/chats.js +12 -1
  6. package/lib/Socket/groups.js +13 -4
  7. package/lib/Socket/messages-recv.js +36 -2
  8. package/lib/Socket/messages-recv.js.bak +1273 -0
  9. package/lib/Socket/messages-send.js +2 -1
  10. package/lib/Types/Message.js +7 -0
  11. package/lib/Utils/bad-mac-handler.js +158 -0
  12. package/lib/Utils/get-best-version.js +41 -0
  13. package/lib/Utils/get-name.d.ts +1 -0
  14. package/lib/Utils/get-name.js +54 -0
  15. package/lib/Utils/group-status-detection.js +113 -0
  16. package/lib/Utils/index.js +7 -0
  17. package/lib/Utils/logger.js +4 -1
  18. package/lib/Utils/messages-media.js +42 -3
  19. package/lib/Utils/messages.js +641 -10
  20. package/lib/Utils/payment-detection.js +212 -0
  21. package/lib/Utils/payment-guard.d.ts +15 -0
  22. package/lib/Utils/payment-guard.js +142 -0
  23. package/lib/Utils/resolve-lid-phone.js +30 -0
  24. package/lib/WABinary/jid-utils.js +245 -1
  25. package/package.json +4 -3
  26. package/lib/Defaults/index.d.ts.map +0 -1
  27. package/lib/Defaults/index.js.map +0 -1
  28. package/lib/Signal/Group/ciphertext-message.d.ts.map +0 -1
  29. package/lib/Signal/Group/ciphertext-message.js.map +0 -1
  30. package/lib/Signal/Group/group-session-builder.d.ts.map +0 -1
  31. package/lib/Signal/Group/group-session-builder.js.map +0 -1
  32. package/lib/Signal/Group/group_cipher.d.ts.map +0 -1
  33. package/lib/Signal/Group/group_cipher.js.map +0 -1
  34. package/lib/Signal/Group/index.d.ts.map +0 -1
  35. package/lib/Signal/Group/index.js.map +0 -1
  36. package/lib/Signal/Group/keyhelper.d.ts.map +0 -1
  37. package/lib/Signal/Group/keyhelper.js.map +0 -1
  38. package/lib/Signal/Group/sender-chain-key.d.ts.map +0 -1
  39. package/lib/Signal/Group/sender-chain-key.js.map +0 -1
  40. package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +0 -1
  41. package/lib/Signal/Group/sender-key-distribution-message.js.map +0 -1
  42. package/lib/Signal/Group/sender-key-message.d.ts.map +0 -1
  43. package/lib/Signal/Group/sender-key-message.js.map +0 -1
  44. package/lib/Signal/Group/sender-key-name.d.ts.map +0 -1
  45. package/lib/Signal/Group/sender-key-name.js.map +0 -1
  46. package/lib/Signal/Group/sender-key-record.d.ts.map +0 -1
  47. package/lib/Signal/Group/sender-key-record.js.map +0 -1
  48. package/lib/Signal/Group/sender-key-state.d.ts.map +0 -1
  49. package/lib/Signal/Group/sender-key-state.js.map +0 -1
  50. package/lib/Signal/Group/sender-message-key.d.ts.map +0 -1
  51. package/lib/Signal/Group/sender-message-key.js.map +0 -1
  52. package/lib/Signal/libsignal.d.ts.map +0 -1
  53. package/lib/Signal/libsignal.js.map +0 -1
  54. package/lib/Signal/lid-mapping.d.ts.map +0 -1
  55. package/lib/Signal/lid-mapping.js.map +0 -1
  56. package/lib/Socket/Client/index.d.ts.map +0 -1
  57. package/lib/Socket/Client/index.js.map +0 -1
  58. package/lib/Socket/Client/types.d.ts.map +0 -1
  59. package/lib/Socket/Client/types.js.map +0 -1
  60. package/lib/Socket/Client/websocket.d.ts.map +0 -1
  61. package/lib/Socket/Client/websocket.js.map +0 -1
  62. package/lib/Socket/business.d.ts.map +0 -1
  63. package/lib/Socket/business.js.map +0 -1
  64. package/lib/Socket/chats.d.ts.map +0 -1
  65. package/lib/Socket/chats.js.map +0 -1
  66. package/lib/Socket/communities.d.ts.map +0 -1
  67. package/lib/Socket/communities.js.map +0 -1
  68. package/lib/Socket/groups.d.ts.map +0 -1
  69. package/lib/Socket/groups.js.map +0 -1
  70. package/lib/Socket/index.d.ts.map +0 -1
  71. package/lib/Socket/index.js.map +0 -1
  72. package/lib/Socket/messages-recv.d.ts.map +0 -1
  73. package/lib/Socket/messages-recv.js.map +0 -1
  74. package/lib/Socket/messages-send.d.ts.map +0 -1
  75. package/lib/Socket/messages-send.js.map +0 -1
  76. package/lib/Socket/mex.d.ts.map +0 -1
  77. package/lib/Socket/mex.js.map +0 -1
  78. package/lib/Socket/newsletter.d.ts.map +0 -1
  79. package/lib/Socket/newsletter.js.map +0 -1
  80. package/lib/Socket/socket.d.ts.map +0 -1
  81. package/lib/Socket/socket.js.map +0 -1
  82. package/lib/Types/Auth.d.ts.map +0 -1
  83. package/lib/Types/Auth.js.map +0 -1
  84. package/lib/Types/Bussines.d.ts.map +0 -1
  85. package/lib/Types/Bussines.js.map +0 -1
  86. package/lib/Types/Call.d.ts.map +0 -1
  87. package/lib/Types/Call.js.map +0 -1
  88. package/lib/Types/Chat.d.ts.map +0 -1
  89. package/lib/Types/Chat.js.map +0 -1
  90. package/lib/Types/Contact.d.ts.map +0 -1
  91. package/lib/Types/Contact.js.map +0 -1
  92. package/lib/Types/Events.d.ts.map +0 -1
  93. package/lib/Types/Events.js.map +0 -1
  94. package/lib/Types/GroupMetadata.d.ts.map +0 -1
  95. package/lib/Types/GroupMetadata.js.map +0 -1
  96. package/lib/Types/Label.d.ts.map +0 -1
  97. package/lib/Types/Label.js.map +0 -1
  98. package/lib/Types/LabelAssociation.d.ts.map +0 -1
  99. package/lib/Types/LabelAssociation.js.map +0 -1
  100. package/lib/Types/Message.d.ts.map +0 -1
  101. package/lib/Types/Message.js.map +0 -1
  102. package/lib/Types/Newsletter.d.ts.map +0 -1
  103. package/lib/Types/Newsletter.js.map +0 -1
  104. package/lib/Types/Product.d.ts.map +0 -1
  105. package/lib/Types/Product.js.map +0 -1
  106. package/lib/Types/Signal.d.ts.map +0 -1
  107. package/lib/Types/Signal.js.map +0 -1
  108. package/lib/Types/Socket.d.ts.map +0 -1
  109. package/lib/Types/Socket.js.map +0 -1
  110. package/lib/Types/State.d.ts.map +0 -1
  111. package/lib/Types/State.js.map +0 -1
  112. package/lib/Types/USync.d.ts.map +0 -1
  113. package/lib/Types/USync.js.map +0 -1
  114. package/lib/Types/index.d.ts.map +0 -1
  115. package/lib/Types/index.js.map +0 -1
  116. package/lib/Utils/auth-utils.d.ts.map +0 -1
  117. package/lib/Utils/auth-utils.js.map +0 -1
  118. package/lib/Utils/browser-utils.d.ts.map +0 -1
  119. package/lib/Utils/browser-utils.js.map +0 -1
  120. package/lib/Utils/business.d.ts.map +0 -1
  121. package/lib/Utils/business.js.map +0 -1
  122. package/lib/Utils/chat-utils.d.ts.map +0 -1
  123. package/lib/Utils/chat-utils.js.map +0 -1
  124. package/lib/Utils/crypto.d.ts.map +0 -1
  125. package/lib/Utils/crypto.js.map +0 -1
  126. package/lib/Utils/decode-wa-message.d.ts.map +0 -1
  127. package/lib/Utils/decode-wa-message.js.map +0 -1
  128. package/lib/Utils/event-buffer.d.ts.map +0 -1
  129. package/lib/Utils/event-buffer.js.map +0 -1
  130. package/lib/Utils/generics.d.ts.map +0 -1
  131. package/lib/Utils/generics.js.map +0 -1
  132. package/lib/Utils/history.d.ts.map +0 -1
  133. package/lib/Utils/history.js.map +0 -1
  134. package/lib/Utils/index.d.ts.map +0 -1
  135. package/lib/Utils/index.js.map +0 -1
  136. package/lib/Utils/link-preview.d.ts.map +0 -1
  137. package/lib/Utils/link-preview.js.map +0 -1
  138. package/lib/Utils/logger.d.ts.map +0 -1
  139. package/lib/Utils/logger.js.map +0 -1
  140. package/lib/Utils/lt-hash.d.ts.map +0 -1
  141. package/lib/Utils/lt-hash.js.map +0 -1
  142. package/lib/Utils/make-mutex.d.ts.map +0 -1
  143. package/lib/Utils/make-mutex.js.map +0 -1
  144. package/lib/Utils/message-retry-manager.d.ts.map +0 -1
  145. package/lib/Utils/message-retry-manager.js.map +0 -1
  146. package/lib/Utils/messages-media.d.ts.map +0 -1
  147. package/lib/Utils/messages-media.js.map +0 -1
  148. package/lib/Utils/messages.d.ts.map +0 -1
  149. package/lib/Utils/messages.js.map +0 -1
  150. package/lib/Utils/noise-handler.d.ts.map +0 -1
  151. package/lib/Utils/noise-handler.js.map +0 -1
  152. package/lib/Utils/pre-key-manager.d.ts.map +0 -1
  153. package/lib/Utils/pre-key-manager.js.map +0 -1
  154. package/lib/Utils/process-message.d.ts.map +0 -1
  155. package/lib/Utils/process-message.js.map +0 -1
  156. package/lib/Utils/signal.d.ts.map +0 -1
  157. package/lib/Utils/signal.js.map +0 -1
  158. package/lib/Utils/use-multi-file-auth-state.d.ts.map +0 -1
  159. package/lib/Utils/use-multi-file-auth-state.js.map +0 -1
  160. package/lib/Utils/validate-connection.d.ts.map +0 -1
  161. package/lib/Utils/validate-connection.js.map +0 -1
  162. package/lib/WABinary/constants.d.ts.map +0 -1
  163. package/lib/WABinary/constants.js.map +0 -1
  164. package/lib/WABinary/decode.d.ts.map +0 -1
  165. package/lib/WABinary/decode.js.map +0 -1
  166. package/lib/WABinary/encode.d.ts.map +0 -1
  167. package/lib/WABinary/encode.js.map +0 -1
  168. package/lib/WABinary/generic-utils.d.ts.map +0 -1
  169. package/lib/WABinary/generic-utils.js.map +0 -1
  170. package/lib/WABinary/index.d.ts.map +0 -1
  171. package/lib/WABinary/index.js.map +0 -1
  172. package/lib/WABinary/jid-utils.d.ts.map +0 -1
  173. package/lib/WABinary/jid-utils.js.map +0 -1
  174. package/lib/WABinary/types.d.ts.map +0 -1
  175. package/lib/WABinary/types.js.map +0 -1
  176. package/lib/WAM/BinaryInfo.d.ts.map +0 -1
  177. package/lib/WAM/BinaryInfo.js.map +0 -1
  178. package/lib/WAM/constants.d.ts.map +0 -1
  179. package/lib/WAM/constants.js.map +0 -1
  180. package/lib/WAM/encode.d.ts.map +0 -1
  181. package/lib/WAM/encode.js.map +0 -1
  182. package/lib/WAM/index.d.ts.map +0 -1
  183. package/lib/WAM/index.js.map +0 -1
  184. package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +0 -1
  185. package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +0 -1
  186. package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +0 -1
  187. package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +0 -1
  188. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +0 -1
  189. package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +0 -1
  190. package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +0 -1
  191. package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +0 -1
  192. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +0 -1
  193. package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +0 -1
  194. package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +0 -1
  195. package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +0 -1
  196. package/lib/WAUSync/Protocols/index.d.ts.map +0 -1
  197. package/lib/WAUSync/Protocols/index.js.map +0 -1
  198. package/lib/WAUSync/USyncQuery.d.ts.map +0 -1
  199. package/lib/WAUSync/USyncQuery.js.map +0 -1
  200. package/lib/WAUSync/USyncUser.d.ts.map +0 -1
  201. package/lib/WAUSync/USyncUser.js.map +0 -1
  202. package/lib/WAUSync/index.d.ts.map +0 -1
  203. package/lib/WAUSync/index.js.map +0 -1
  204. package/lib/index.d.ts.map +0 -1
  205. package/lib/index.js.map +0 -1
@@ -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, transcodeAudioToOpus } 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,34 @@ 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];
78
+ if (!uploadData.mimetype) {
79
+ uploadData.mimetype = MIMETYPE_MAP[mediaType];
80
+ }
81
+ // ptt: true → transcodifica de verdade pra opus/ogg mono 16kHz.
82
+ // Antes, isso só trocava o RÓTULO do mimetype sem mudar os bytes reais,
83
+ // o que fazia o WhatsApp mostrar "algo errado com o arquivo de áudio"
84
+ // pra qualquer entrada que não já fosse opus puro.
85
+ //
86
+ // IMPORTANTE: não confia no `mimetype` que o CALLER declarou (a pessoa
87
+ // pode escrever 'audio/ogg' mesmo passando um mp3/m4a de origem) — só
88
+ // confia na extensão real do arquivo/URL de origem.
89
+ let pttTranscodedPath;
90
+ if (mediaType === 'audio' && uploadData.ptt === true) {
91
+ const mediaUrl = typeof uploadData.media === 'string'
92
+ ? uploadData.media
93
+ : uploadData.media?.url?.toString?.() || '';
94
+ const sourceLooksLikeOpus = /\.(opus|ogg)(\?|$)/i.test(mediaUrl);
95
+
96
+ if (!sourceLooksLikeOpus) {
97
+ const sourceForTranscode = Buffer.isBuffer(uploadData.media)
98
+ ? uploadData.media
99
+ : (typeof uploadData.media === 'string' ? uploadData.media : mediaUrl);
100
+ pttTranscodedPath = await transcodeAudioToOpus(sourceForTranscode);
101
+ uploadData.media = pttTranscodedPath;
80
102
  }
103
+ uploadData.mimetype = 'audio/ogg; codecs=opus';
104
+ }
105
+
81
106
  if (cacheableKey) {
82
107
  const mediaBuff = await options.mediaCache.get(cacheableKey);
83
108
  if (mediaBuff) {
@@ -116,6 +141,10 @@ export const prepareWAMessageMedia = async (message, options) => {
116
141
  if (obj.stickerMessage) {
117
142
  obj.stickerMessage.stickerSentTs = Date.now();
118
143
  }
144
+ // lottie: stickerSentTs também no stickerMessage interno
145
+ if (obj.lottieStickerMessage?.message?.stickerMessage) {
146
+ obj.lottieStickerMessage.message.stickerMessage.stickerSentTs = Date.now();
147
+ }
119
148
  if (cacheableKey) {
120
149
  logger?.debug({ cacheableKey }, 'set cache');
121
150
  await options.mediaCache.set(cacheableKey, WAProto.Message.encode(obj).finish());
@@ -124,9 +153,10 @@ export const prepareWAMessageMedia = async (message, options) => {
124
153
  }
125
154
  const requiresDurationComputation = mediaType === 'audio' && typeof uploadData.seconds === 'undefined';
126
155
  const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
127
- const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
156
+ const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true && typeof uploadData.waveform === 'undefined';
157
+ const requiresPlaybackSpeed = mediaType === 'audio' && uploadData.ptt === true;
128
158
  const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
129
- const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation;
159
+ const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
130
160
  const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
131
161
  logger,
132
162
  saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
@@ -163,6 +193,13 @@ export const prepareWAMessageMedia = async (message, options) => {
163
193
  uploadData.waveform = await getAudioWaveform(originalFilePath, logger);
164
194
  logger?.debug('processed waveform');
165
195
  }
196
+ if (mediaType === 'audio' && uploadData.ptt === true) {
197
+ uploadData.isPlaybackSpeedEnabled = true;
198
+ }
199
+ if (requiresPlaybackSpeed) {
200
+ uploadData.isPlaybackSpeedEnabled = true;
201
+ logger?.debug('enabled playback speed control');
202
+ }
166
203
  if (requiresAudioBackground) {
167
204
  uploadData.backgroundArgb = await assertColor(options.backgroundColor);
168
205
  logger?.debug('computed backgroundColor audio status');
@@ -178,6 +215,9 @@ export const prepareWAMessageMedia = async (message, options) => {
178
215
  if (originalFilePath) {
179
216
  await fs.unlink(originalFilePath);
180
217
  }
218
+ if (pttTranscodedPath) {
219
+ await fs.unlink(pttTranscodedPath);
220
+ }
181
221
  logger?.debug('removed tmp files');
182
222
  }
183
223
  catch (error) {
@@ -245,6 +285,304 @@ export const generateForwardMessageContent = (message, forceForward) => {
245
285
  }
246
286
  return content;
247
287
  };
288
+
289
+
290
+ // ── WebP helpers (para stickerPack com animadas) ──────────────────────────────
291
+ const CONCURRENCY_LIMIT = 15;
292
+ const isAnimatedWebP = (buffer) => {
293
+ if (buffer.length < 12 || buffer[0] !== 0x52 || buffer[1] !== 0x49 || buffer[2] !== 0x46 || buffer[3] !== 0x46 ||
294
+ buffer[8] !== 0x57 || buffer[9] !== 0x45 || buffer[10] !== 0x42 || buffer[11] !== 0x50) return false;
295
+ let offset = 12;
296
+ while (offset < buffer.length - 8) {
297
+ const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
298
+ const chunkSize = buffer.readUInt32LE(offset + 4);
299
+ if (chunkFourCC === 'VP8X') {
300
+ const flagsOffset = offset + 8;
301
+ if (flagsOffset < buffer.length && (buffer[flagsOffset] & 0x02)) return true;
302
+ } else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') return true;
303
+ offset += 8 + chunkSize + (chunkSize % 2);
304
+ }
305
+ return false;
306
+ };
307
+ const isWebPBuffer = (buffer) => {
308
+ return buffer.length >= 12 && buffer[0] === 0x52 && buffer[1] === 0x49 && buffer[2] === 0x46 && buffer[3] === 0x46 &&
309
+ buffer[8] === 0x57 && buffer[9] === 0x45 && buffer[10] === 0x42 && buffer[11] === 0x50;
310
+ };
311
+
312
+ // ── prepareStickerPackMessage (da @boruto_vk7/baileys — suporte a animadas) ──
313
+ const prepareStickerPackMessage = async (message, options) => {
314
+ const { cover, stickers = [], name = '📦 Sticker Pack', publisher = '@systemzero/baileys', description = '' } = message;
315
+ if (stickers.length > 60) throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
316
+ if (stickers.length === 0) throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
317
+ if (!cover) throw new Boom('Sticker pack must contain a cover', { statusCode: 400 });
318
+ const logger = options.logger;
319
+ const lib = await getImageProcessingLibrary();
320
+ const hasSharp = 'sharp' in lib && !!lib.sharp?.default;
321
+ const hasImage = 'image' in lib && !!lib.image?.Transformer;
322
+ const hasJimp = 'jimp' in lib && !!lib.jimp?.Jimp;
323
+ const stickerPackIdValue = generateMessageIDV2();
324
+ const stickerData = {};
325
+ const stickerMetadata = new Array(stickers.length);
326
+ for (let i = 0; i < stickers.length; i += CONCURRENCY_LIMIT) {
327
+ const promises = [];
328
+ const chunkEnd = Math.min(i + CONCURRENCY_LIMIT, stickers.length);
329
+ for (let j = i; j < chunkEnd; j++) {
330
+ promises.push((async (index) => {
331
+ const sticker = stickers[index];
332
+ const { stream } = await getStream(sticker.data);
333
+ const buffer = await toBuffer(stream);
334
+ let webpBuffer, isAnimated = false;
335
+ if (isWebPBuffer(buffer)) {
336
+ webpBuffer = buffer;
337
+ isAnimated = isAnimatedWebP(buffer);
338
+ } else if (hasSharp) {
339
+ webpBuffer = await lib.sharp.default(buffer).resize(512, 512, { fit: 'inside' }).webp({ quality: 80 }).toBuffer();
340
+ } else if (hasImage) {
341
+ webpBuffer = await new lib.image.Transformer(buffer).resize(512, 512).webp(80);
342
+ } else {
343
+ throw new Boom('No image processing library (sharp or @napi-rs/image) available', { statusCode: 500 });
344
+ }
345
+ if (webpBuffer.length > 1024 * 1024) throw new Boom(`Sticker at index ${index} exceeds 1MB limit`, { statusCode: 400 });
346
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
347
+ const fileName = `${hash}.webp`;
348
+ stickerData[fileName] = webpBuffer;
349
+ stickerMetadata[index] = {
350
+ fileName, mimetype: 'image/webp', isAnimated,
351
+ emojis: sticker.emojis || ['✨'],
352
+ accessibilityLabel: sticker.accessibilityLabel || ''
353
+ };
354
+ })(j));
355
+ }
356
+ await Promise.all(promises);
357
+ }
358
+ const trayIconFileName = `${stickerPackIdValue}.webp`;
359
+ const { stream: coverStream } = await getStream(cover);
360
+ const coverBuffer = await toBuffer(coverStream);
361
+ let coverWebpBuffer;
362
+ if (isWebPBuffer(coverBuffer)) {
363
+ coverWebpBuffer = coverBuffer;
364
+ } else if (hasSharp) {
365
+ coverWebpBuffer = await lib.sharp.default(coverBuffer).resize(512, 512, { fit: 'inside' }).webp({ quality: 80 }).toBuffer();
366
+ } else if (hasImage) {
367
+ coverWebpBuffer = await new lib.image.Transformer(coverBuffer).resize(512, 512).webp(80);
368
+ } else {
369
+ throw new Boom('No image processing library available for cover', { statusCode: 500 });
370
+ }
371
+ stickerData[trayIconFileName] = coverWebpBuffer;
372
+ // Monta o ZIP usando _createZip da sz (sem fflate)
373
+ const zipBuffer = _createZip(Object.entries(stickerData).map(([name, data]) => ({ name, data })));
374
+ const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', { logger });
375
+ let stickerPackUploadResult;
376
+ try {
377
+ stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
378
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
379
+ mediaType: 'sticker-pack',
380
+ timeoutMs: options.mediaUploadTimeoutMs
381
+ });
382
+ } finally {
383
+ fs.unlink(stickerPackUpload.encFilePath).catch(() => {});
384
+ }
385
+ const obj = {
386
+ name, publisher, packDescription: description,
387
+ stickerPackId: stickerPackIdValue,
388
+ stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
389
+ stickerPackSize: zipBuffer.length,
390
+ stickers: stickerMetadata,
391
+ fileSha256: stickerPackUpload.fileSha256,
392
+ fileEncSha256: stickerPackUpload.fileEncSha256,
393
+ mediaKey: stickerPackUpload.mediaKey,
394
+ directPath: stickerPackUploadResult.directPath,
395
+ fileLength: stickerPackUpload.fileLength,
396
+ mediaKeyTimestamp: unixTimestampSeconds(),
397
+ trayIconFileName
398
+ };
399
+ // Gerar thumbnail
400
+ try {
401
+ let thumbnailBuffer;
402
+ if (hasSharp) {
403
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
404
+ } else if (hasImage) {
405
+ thumbnailBuffer = await new lib.image.Transformer(coverBuffer).resize(252, 252).jpeg();
406
+ } else if (hasJimp) {
407
+ const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
408
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
409
+ }
410
+ if (thumbnailBuffer?.length > 0) {
411
+ const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', { logger, mediaKey: stickerPackUpload.mediaKey });
412
+ let thumbResult;
413
+ try {
414
+ thumbResult = await options.upload(thumbUpload.encFilePath, {
415
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
416
+ mediaType: 'thumbnail-sticker-pack',
417
+ timeoutMs: options.mediaUploadTimeoutMs
418
+ });
419
+ } finally {
420
+ fs.unlink(thumbUpload.encFilePath).catch(() => {});
421
+ }
422
+ Object.assign(obj, {
423
+ thumbnailDirectPath: thumbResult.directPath,
424
+ thumbnailSha256: thumbUpload.fileSha256,
425
+ thumbnailEncSha256: thumbUpload.fileEncSha256,
426
+ thumbnailHeight: 252,
427
+ thumbnailWidth: 252,
428
+ imageDataHash: sha256(thumbnailBuffer).toString('base64')
429
+ });
430
+ }
431
+ } catch(e) {
432
+ logger?.warn('Thumbnail generation failed: ' + e.message);
433
+ }
434
+ return WAProto.Message.StickerPackMessage.fromObject(obj);
435
+ };
436
+ // ── Helpers adicionados da @boruto_vk7/baileys ──────────────────────────────
437
+ export const hasNonNullishProperty = (message, key) => {
438
+ return message != null &&
439
+ typeof message === 'object' &&
440
+ key in message &&
441
+ message[key] != null;
442
+ };
443
+ export const hasOptionalProperty = (obj, key) => {
444
+ return obj != null &&
445
+ typeof obj === 'object' &&
446
+ key in obj &&
447
+ obj[key] != null;
448
+ };
449
+ export const hasValidAlbumMedia = (message) => {
450
+ return !!(message.imageMessage || message.videoMessage);
451
+ };
452
+ export const hasValidInteractiveHeader = (message) => {
453
+ return !!(message.imageMessage ||
454
+ message.videoMessage ||
455
+ message.documentMessage ||
456
+ message.productMessage ||
457
+ message.locationMessage);
458
+ };
459
+ export const hasValidCarouselHeader = (message) => {
460
+ return !!(message.imageMessage ||
461
+ message.videoMessage ||
462
+ message.productMessage);
463
+ };
464
+ // prepareNativeFlowButtons — versão completa da @boruto_vk7/baileys
465
+ const prepareNativeFlowButtons = (message) => {
466
+ const buttons = message.nativeFlow;
467
+ const isButtonsFieldArray = Array.isArray(buttons);
468
+ const correctedField = isButtonsFieldArray ? buttons : (buttons?.buttons || []);
469
+ const messageParamsJson = {};
470
+ if (hasOptionalProperty(message, 'offerText') && !!message.offerText) {
471
+ Object.assign(messageParamsJson, {
472
+ limited_time_offer: {
473
+ text: message.offerText || '@systemzero/baileys',
474
+ url: message.offerUrl || 'https://systemzone.store',
475
+ copy_code: message.offerCode,
476
+ expiration_time: message.offerExpiration
477
+ }
478
+ });
479
+ }
480
+ if (hasOptionalProperty(message, 'optionText') && !!message.optionText) {
481
+ Object.assign(messageParamsJson, {
482
+ bottom_sheet: {
483
+ in_thread_buttons_limit: 1,
484
+ divider_indices: Array.from({ length: correctedField.length }, (_, i) => i),
485
+ list_title: message.optionTitle || '📄 Select Options',
486
+ button_title: message.optionText
487
+ }
488
+ });
489
+ }
490
+ return {
491
+ buttons: correctedField.map(button => {
492
+ const buttonText = button.text || button.buttonText;
493
+ const buttonIcon = button.icon?.toUpperCase();
494
+ if (hasOptionalProperty(button, 'id') && !!button.id) {
495
+ return { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: buttonText || '👉🏻 Click', id: button.id, icon: buttonIcon }) };
496
+ } else if (hasOptionalProperty(button, 'copy') && !!button.copy) {
497
+ return { name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📋 Copy', copy_code: button.copy, icon: buttonIcon }) };
498
+ } else if (hasOptionalProperty(button, 'url') && !!button.url) {
499
+ return { name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🌐 Visit', url: button.url, merchant_url: button.url, webview_interaction: button.useWebview, icon: buttonIcon }) };
500
+ } else if (hasOptionalProperty(button, 'call') && !!button.call) {
501
+ return { name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📞 Call', phone_number: button.call, icon: buttonIcon }) };
502
+ } else if (hasOptionalProperty(button, 'sections') && !!button.sections) {
503
+ return { name: 'single_select', buttonParamsJson: JSON.stringify({ title: buttonText || '📋 Select', sections: button.sections, icon: buttonIcon }) };
504
+ }
505
+ // ── Botões estendidos (vanzxy) ──────────────────────────────────────
506
+ else if (hasOptionalProperty(button, 'reminder') && !!button.reminder) {
507
+ return { name: 'cta_reminder', buttonParamsJson: JSON.stringify({ display_text: buttonText || '⏰ Remind me', id: button.id || button.reminder, icon: buttonIcon }) };
508
+ }
509
+ else if (hasOptionalProperty(button, 'cancelReminder') && !!button.cancelReminder) {
510
+ return { name: 'cta_cancel_reminder', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🔕 Cancel reminder', id: button.id || button.cancelReminder, icon: buttonIcon }) };
511
+ }
512
+ else if (hasOptionalProperty(button, 'address') && !!button.address) {
513
+ return { name: 'address_message', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📍 Send address', id: button.id || 'address_message' }) };
514
+ }
515
+ else if (hasOptionalProperty(button, 'location') && button.location === true) {
516
+ return { name: 'send_location', buttonParamsJson: JSON.stringify({}) };
517
+ }
518
+ else if (hasOptionalProperty(button, 'catalog') && !!button.catalog) {
519
+ return { name: 'catalog_message', buttonParamsJson: JSON.stringify({ business_phone_number: button.catalog.bizJid || button.catalog, display_text: buttonText || '🛍️ View catalog', id: button.id || 'catalog_message' }) };
520
+ }
521
+ else if (hasOptionalProperty(button, 'products') && !!button.products) {
522
+ return { name: 'mpm', buttonParamsJson: JSON.stringify({ business_phone_number: button.bizJid || button.products.bizJid, product_ids: Array.isArray(button.products) ? button.products : button.products.ids }) };
523
+ }
524
+ else if (hasOptionalProperty(button, 'otp') && !!button.otp) {
525
+ return { name: 'otp_button', buttonParamsJson: JSON.stringify({ display_text: buttonText || 'Copy code', code: button.otp }) };
526
+ }
527
+ else if (hasOptionalProperty(button, 'oneTapOtp') && !!button.oneTapOtp) {
528
+ return { name: 'authentication_button', buttonParamsJson: JSON.stringify({ display_text: buttonText || 'Autofill', code: button.oneTapOtp }) };
529
+ }
530
+ else if (hasOptionalProperty(button, 'phoneNumber') && !!button.phoneNumber) {
531
+ return { name: 'call_button', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📞 Call', phone_number: button.phoneNumber }) };
532
+ }
533
+ else if (hasOptionalProperty(button, 'urlBtn') && !!button.urlBtn) {
534
+ return { name: 'url_button', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🌐 Open', url: button.urlBtn }) };
535
+ }
536
+ else if (hasOptionalProperty(button, 'card') && !!button.card) {
537
+ return { name: 'card_message', buttonParamsJson: JSON.stringify(button.card) };
538
+ }
539
+ else if (hasOptionalProperty(button, 'orderDetails') && !!button.orderDetails) {
540
+ return { name: 'order_details', buttonParamsJson: JSON.stringify(button.orderDetails) };
541
+ }
542
+ else if (hasOptionalProperty(button, 'orderStatus') && !!button.orderStatus) {
543
+ return { name: 'order_status', buttonParamsJson: JSON.stringify(button.orderStatus) };
544
+ }
545
+ else if (hasOptionalProperty(button, 'trackOrder') && !!button.trackOrder) {
546
+ return { name: 'track_order', buttonParamsJson: JSON.stringify({ id: button.id || button.trackOrder, display_text: buttonText || '🚚 Track order' }) };
547
+ }
548
+ else if (hasOptionalProperty(button, 'reorder') && !!button.reorder) {
549
+ return { name: 'reorder', buttonParamsJson: JSON.stringify({ id: button.id || button.reorder, display_text: buttonText || '🔁 Reorder' }) };
550
+ }
551
+ else if (hasOptionalProperty(button, 'cancelOrder') && !!button.cancelOrder) {
552
+ return { name: 'cancel_order', buttonParamsJson: JSON.stringify({ id: button.id || button.cancelOrder, display_text: buttonText || '❌ Cancel order' }) };
553
+ }
554
+ else if (hasOptionalProperty(button, 'clearChat') && button.clearChat === true) {
555
+ return { name: 'clear_chat', buttonParamsJson: JSON.stringify({}) };
556
+ }
557
+ else if (hasOptionalProperty(button, 'screen') && !!button.screen) {
558
+ return { name: 'navigateToScreen', buttonParamsJson: JSON.stringify({ screen_name: button.screen, data: button.data || {} }) };
559
+ }
560
+ else if (hasOptionalProperty(button, 'flow') && !!button.flow) {
561
+ return { name: 'flow_action', buttonParamsJson: JSON.stringify({ flow_message_version: button.flow.version || '3', flow_id: button.flow.id, flow_cta: buttonText || button.flow.cta || 'Continue', flow_action: button.flow.action || 'navigate', flow_action_payload: button.flow.actionPayload || { screen: button.flow.screen || 'WELCOME', data: button.flow.data || {} } }) };
562
+ }
563
+ else if (hasOptionalProperty(button, 'voiceCall') && !!button.voiceCall) {
564
+ return { name: 'voice_call', buttonParamsJson: JSON.stringify({ display_text: buttonText || '📞 Voice call', id: button.id || button.voiceCall }) };
565
+ }
566
+ else if (hasOptionalProperty(button, 'videoCall') && !!button.videoCall) {
567
+ return { name: 'video_call_button', buttonParamsJson: JSON.stringify({ display_text: buttonText || '🎥 Video call', id: button.id || button.videoCall }) };
568
+ }
569
+ return button;
570
+ }),
571
+ messageParamsJson: JSON.stringify(messageParamsJson)
572
+ };
573
+ };
574
+ // prepareProductMessage — monta productMessage
575
+ const prepareProductMessage = async (message, options) => {
576
+ const { imageMessage } = await prepareWAMessageMedia({ image: message.product?.productImage || message.image }, options);
577
+ return {
578
+ product: {
579
+ productImage: imageMessage,
580
+ ...message.product
581
+ },
582
+ businessOwnerJid: message.businessOwnerJid || '0@s.whatsapp.net'
583
+ };
584
+ };
585
+ // ── Fim dos helpers ──────────────────────────────────────────────────────────
248
586
  export const generateWAMessageContent = async (message, options) => {
249
587
  var _a, _b;
250
588
  let m = {};
@@ -447,9 +785,299 @@ export const generateWAMessageContent = async (message, options) => {
447
785
  }
448
786
  };
449
787
  }
788
+
789
+ // ── NOVOS TIPOS (@boruto_vk7/baileys) ────────────────────────────────────
790
+ else if (hasNonNullishProperty(message, 'keep')) {
791
+ m.keepInChatMessage = {};
792
+ m.keepInChatMessage.key = message.keep;
793
+ m.keepInChatMessage.keepType = message.type;
794
+ m.keepInChatMessage.timestampMs = Date.now();
795
+ }
796
+ else if (hasNonNullishProperty(message, 'flowReply')) {
797
+ m.interactiveResponseMessage = {
798
+ body: {
799
+ format: message.flowReply.format || proto.Message.InteractiveResponseMessage.Body.Format.DEFAULT,
800
+ text: message.flowReply.text
801
+ },
802
+ nativeFlowResponseMessage: {
803
+ name: message.flowReply.name,
804
+ paramsJson: message.flowReply.paramsJson || '{}',
805
+ version: message.flowReply.version || 1
806
+ }
807
+ };
808
+ }
809
+ else if (hasNonNullishProperty(message, 'pollResult')) {
810
+ const pollResultSnapshotMessage = {
811
+ name: message.pollResult.name,
812
+ pollVotes: message.pollResult.votes.map(vote => ({
813
+ optionName: vote.name,
814
+ optionVoteCount: parseInt(vote.voteCount)
815
+ }))
816
+ };
817
+ if (message.pollResult.pollType === 1) {
818
+ pollResultSnapshotMessage.pollType = proto.Message.PollType.QUIZ;
819
+ m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
820
+ } else {
821
+ pollResultSnapshotMessage.pollType = proto.Message.PollType.POLL;
822
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage;
823
+ }
824
+ }
825
+ else if (hasNonNullishProperty(message, 'pollUpdate')) {
826
+ if (!message.pollUpdate.key) throw new Boom('Message key is required', { statusCode: 400 });
827
+ if (!message.pollUpdate.vote) throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
828
+ m.pollUpdateMessage = {
829
+ metadata: message.pollUpdate.metadata,
830
+ pollCreationMessageKey: message.pollUpdate.key,
831
+ senderTimestampMs: Date.now(),
832
+ vote: message.pollUpdate.vote
833
+ };
834
+ }
835
+ else if (hasNonNullishProperty(message, 'paymentInviteServiceType')) {
836
+ m.paymentInviteMessage = {
837
+ expiryTimestamp: Date.now(),
838
+ serviceType: message.paymentInviteServiceType
839
+ };
840
+ }
841
+ else if (hasNonNullishProperty(message, 'orderText')) {
842
+ if (!Buffer.isBuffer(message.thumbnail)) throw new Boom('Must provide thumbnail buffer', { statusCode: 400 });
843
+ m.orderMessage = {
844
+ itemCount: 1, messageVersion: 1,
845
+ orderTitle: 'Order', status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
846
+ surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
847
+ token: generateMessageIDV2(), totalAmount1000: 1000, totalCurrencyCode: 'BRL',
848
+ ...message, message: message.orderText
849
+ };
850
+ delete m.orderMessage.orderText;
851
+ }
852
+ else if (hasNonNullishProperty(message, 'stickers')) {
853
+ m.stickerPackMessage = await prepareStickerPackMessage(message, options);
854
+ }
855
+ else if (hasNonNullishProperty(message, 'album')) {
856
+ if (!Array.isArray(message.album)) throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
857
+ let videoCount = 0, imageCount = 0;
858
+ for (let i = 0; i < message.album.length; i++) {
859
+ if (message.album[i].video) videoCount++;
860
+ if (message.album[i].image) imageCount++;
861
+ }
862
+ if ((videoCount + imageCount) < 2) throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
863
+ m.albumMessage = { expectedImageCount: imageCount, expectedVideoCount: videoCount };
864
+ }
865
+ else if (hasNonNullishProperty(message, 'requestPaymentFrom')) {
866
+ const requestPaymentMessage = {
867
+ amount: { currencyCode: 'BRL', offset: 100, value: 1 },
868
+ amount1000: 1000, currencyCodeIso4217: 'BRL',
869
+ expiryTimestamp: Date.now(), noteMessage: m,
870
+ requestFrom: message.requestPaymentFrom, ...message
871
+ };
872
+ delete requestPaymentMessage.requestPaymentFrom;
873
+ m = { requestPaymentMessage };
874
+ }
875
+ else if (hasNonNullishProperty(message, 'invoiceNote')) {
876
+ const attachment = m.imageMessage || m.documentMessage;
877
+ const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
878
+ const invoiceMessage = {
879
+ attachmentType: proto.Message.InvoiceMessage.AttachmentType[type === 'DOCUMENT' ? 'PDF' : 'IMAGE'],
880
+ note: message.invoiceNote
881
+ };
882
+ if (attachment) {
883
+ const { directPath, fileEncSha256, fileSha256, jpegThumbnail = undefined, mediaKey, mediaKeyTimestamp, mimetype } = attachment;
884
+ Object.assign(invoiceMessage, {
885
+ attachmentDirectPath: directPath, attachmentFileEncSha256: fileEncSha256,
886
+ attachmentFileSha256: fileSha256, attachmentJpegThumbnail: jpegThumbnail,
887
+ attachmentMediaKey: mediaKey, attachmentMediaKeyTimestamp: mediaKeyTimestamp,
888
+ attachmentMimetype: mimetype, token: generateMessageIDV2()
889
+ });
890
+ } else {
891
+ throw new Boom('Invalid media type for invoice message', { statusCode: 400 });
892
+ }
893
+ m = { invoiceMessage };
894
+ }
895
+
450
896
  else {
451
897
  m = await prepareWAMessageMedia(message, options);
452
898
  }
899
+
900
+ // ── NOVOS TIPOS FASE 2: buttons/sections/templateButtons/nativeFlow/cards ─
901
+ if (hasNonNullishProperty(message, 'buttons')) {
902
+ const buttonsMessage = {
903
+ buttons: message.buttons.map(button => {
904
+ const buttonText = button.text || button.buttonText;
905
+ if (hasOptionalProperty(button, 'sections')) {
906
+ return { nativeFlowInfo: { name: 'single_select', paramsJson: JSON.stringify({ title: buttonText, sections: button.sections }) }, type: ButtonType.NATIVE_FLOW };
907
+ }
908
+ if (hasOptionalProperty(button, 'name')) {
909
+ return { nativeFlowInfo: { name: button.name, paramsJson: button.paramsJson }, type: ButtonType.NATIVE_FLOW };
910
+ }
911
+ return { buttonId: button.id || button.buttonId, buttonText: typeof buttonText === 'string' ? { displayText: buttonText } : buttonText, type: button.type || ButtonType.RESPONSE };
912
+ })
913
+ };
914
+ if (hasOptionalProperty(message, 'text')) {
915
+ buttonsMessage.contentText = message.text;
916
+ buttonsMessage.headerType = ButtonHeaderType.EMPTY;
917
+ } else {
918
+ if (hasOptionalProperty(message, 'caption')) buttonsMessage.contentText = message.caption;
919
+ const type = Object.keys(m)[0].replace('Message', '').toUpperCase();
920
+ buttonsMessage.headerType = ButtonHeaderType[type];
921
+ Object.assign(buttonsMessage, m);
922
+ }
923
+ if (hasOptionalProperty(message, 'footer')) buttonsMessage.footerText = message.footer;
924
+ m = { buttonsMessage };
925
+ }
926
+ else if (hasNonNullishProperty(message, 'sections')) {
927
+ m = { listMessage: { sections: message.sections, buttonText: message.buttonText, title: message.title, footerText: message.footer, description: message.text, listType: ListType.SINGLE_SELECT } };
928
+ }
929
+ else if (hasNonNullishProperty(message, 'templateButtons')) {
930
+ const hydratedTemplate = {
931
+ hydratedButtons: message.templateButtons.map((button, i) => {
932
+ const buttonText = button.text || button.buttonText;
933
+ if (hasOptionalProperty(button, 'id')) return { index: i, quickReplyButton: { displayText: buttonText || '👉🏻 Click', id: button.id } };
934
+ if (hasOptionalProperty(button, 'url')) return { index: i, urlButton: { displayText: buttonText || '🌐 Visit', url: button.url } };
935
+ if (hasOptionalProperty(button, 'call')) return { index: i, callButton: { displayText: buttonText || '📞 Call', phoneNumber: button.call } };
936
+ button.index = button.index || i;
937
+ return button;
938
+ })
939
+ };
940
+ if (hasOptionalProperty(message, 'text')) hydratedTemplate.hydratedContentText = message.text;
941
+ else {
942
+ if (hasOptionalProperty(message, 'caption')) { hydratedTemplate.hydratedTitleText = message.title; hydratedTemplate.hydratedContentText = message.caption; }
943
+ Object.assign(hydratedTemplate, m);
944
+ }
945
+ if (hasOptionalProperty(message, 'footer')) hydratedTemplate.hydratedFooterText = message.footer;
946
+ hydratedTemplate.templateId = message.id || 'template-' + Date.now();
947
+ m = { templateMessage: { hydratedFourRowTemplate: hydratedTemplate, hydratedTemplate } };
948
+ }
949
+ else if (hasNonNullishProperty(message, 'nativeFlow')) {
950
+ const interactiveMessage = { nativeFlowMessage: prepareNativeFlowButtons(message) };
951
+ if (hasOptionalProperty(message, 'text')) {
952
+ interactiveMessage.body = { text: message.text };
953
+ } else {
954
+ if (hasOptionalProperty(message, 'caption')) {
955
+ const isValidHeader = hasValidInteractiveHeader(m);
956
+ if (!isValidHeader) throw new Boom('Invalid media type for interactive message header', { statusCode: 400 });
957
+ interactiveMessage.header = { title: message.title || '', subtitle: message.subtitle || '', hasMediaAttachment: isValidHeader };
958
+ interactiveMessage.body = { text: message.caption };
959
+ }
960
+ if (hasOptionalProperty(message, 'thumbnail') && !!message.thumbnail) interactiveMessage.jpegThumbnail = message.thumbnail;
961
+ if (interactiveMessage.header) Object.assign(interactiveMessage.header, m);
962
+ }
963
+ if (hasOptionalProperty(message, 'audioFooter')) {
964
+ const { audioMessage } = await prepareWAMessageMedia({ audio: message.audioFooter }, options);
965
+ interactiveMessage.footer = { audioMessage, hasMediaAttachment: true };
966
+ } else if (hasOptionalProperty(message, 'footer')) {
967
+ interactiveMessage.footer = { text: message.footer };
968
+ }
969
+ m = { interactiveMessage };
970
+ }
971
+ else if (hasNonNullishProperty(message, 'cards')) {
972
+ const interactiveMessage = {
973
+ carouselMessage: {
974
+ cards: await Promise.all(message.cards.map(async (card) => {
975
+ let carouselHeader = await prepareWAMessageMedia(card, options).catch(() => ({}));
976
+ const isValidHeader = hasValidCarouselHeader(carouselHeader);
977
+ if (!isValidHeader) throw new Boom('Invalid media type for carousel card', { statusCode: 400 });
978
+ const carouselCard = { nativeFlowMessage: prepareNativeFlowButtons(card.nativeFlow ? card : { nativeFlow: [] }) };
979
+ if (hasOptionalProperty(card, 'text')) {
980
+ carouselCard.body = { text: card.text };
981
+ } else {
982
+ if (hasOptionalProperty(card, 'caption')) {
983
+ carouselCard.header = { title: card.title || '', subtitle: card.subtitle || '', hasMediaAttachment: isValidHeader };
984
+ carouselCard.body = { text: card.caption };
985
+ }
986
+ if (hasOptionalProperty(card, 'thumbnail') && !!card.thumbnail) carouselCard.jpegThumbnail = card.thumbnail;
987
+ if (carouselCard.header) Object.assign(carouselCard.header, carouselHeader);
988
+ }
989
+ if (hasOptionalProperty(card, 'audioFooter')) {
990
+ const { audioMessage } = await prepareWAMessageMedia({ audio: card.audioFooter }, options);
991
+ carouselCard.footer = { audioMessage, hasMediaAttachment: true };
992
+ } else if (hasOptionalProperty(card, 'footer')) {
993
+ carouselCard.footer = { text: card.footer };
994
+ }
995
+ return carouselCard;
996
+ })),
997
+ carouselCardType: 0, messageVersion: 1
998
+ }
999
+ };
1000
+ if (hasOptionalProperty(message, 'text')) interactiveMessage.body = { text: message.text };
1001
+ if (hasOptionalProperty(message, 'footer')) interactiveMessage.footer = { text: message.footer };
1002
+ m = { interactiveMessage };
1003
+ }
1004
+ // ── externalAdReply ───────────────────────────────────────────────────────
1005
+ if (hasOptionalProperty(message, 'externalAdReply') && !!message.externalAdReply) {
1006
+ const messageType = Object.keys(m)[0];
1007
+ const key = m[messageType];
1008
+ const content = message.externalAdReply;
1009
+ if ('thumbnail' in content && !Buffer.isBuffer(content.thumbnail)) throw new Boom('Thumbnail must be a buffer', { statusCode: 400 });
1010
+ const externalAdReply = {
1011
+ ...content, body: content.body, mediaType: content.mediaType || 1,
1012
+ mediaUrl: content.url, renderLargerThumbnail: content.largeThumbnail,
1013
+ sourceUrl: content.url, thumbnail: content.thumbnail,
1014
+ title: content.title || 'System Zero'
1015
+ };
1016
+ delete externalAdReply.largeThumbnail; delete externalAdReply.url;
1017
+ if (key && 'contextInfo' in key && !!key.contextInfo) key.contextInfo.externalAdReply = { ...key.contextInfo.externalAdReply, ...externalAdReply };
1018
+ else if (key) key.contextInfo = { externalAdReply };
1019
+ }
1020
+ // ── mentionAll ────────────────────────────────────────────────────────────
1021
+ if (hasOptionalProperty(message, 'mentionAll') && message.mentionAll) {
1022
+ const messageType = Object.keys(m)[0];
1023
+ const key = m[messageType];
1024
+ if (key && 'contextInfo' in key) { key.contextInfo = key.contextInfo || {}; key.contextInfo.nonJidMentions = 1; }
1025
+ else if (key) key.contextInfo = { nonJidMentions: 1 };
1026
+ }
1027
+ // ── groupStatus ───────────────────────────────────────────────────────────
1028
+ if (hasOptionalProperty(message, 'groupStatus') && !!message.groupStatus) {
1029
+ const messageType = Object.keys(m)[0];
1030
+ const key = m[messageType];
1031
+ if (key && 'contextInfo' in key && !!key.contextInfo) key.contextInfo.isGroupStatus = message.groupStatus;
1032
+ else if (key) key.contextInfo = { isGroupStatus: message.groupStatus };
1033
+ // Fix: audio PTT usa groupStatusMessage (v1) — V2 causa tela preta em versões antigas do WA
1034
+ if (messageType === 'audioMessage') {
1035
+ m = { groupStatusMessage: { message: m } };
1036
+ } else {
1037
+ m = { groupStatusMessageV2: { message: m } };
1038
+ }
1039
+ delete message.groupStatus;
1040
+ }
1041
+ // ── spoiler ───────────────────────────────────────────────────────────────
1042
+ if (hasOptionalProperty(message, 'spoiler') && !!message.spoiler) {
1043
+ const messageType = Object.keys(m)[0];
1044
+ const key = m[messageType];
1045
+ if (key && 'contextInfo' in key && !!key.contextInfo) key.contextInfo.isSpoiler = message.spoiler;
1046
+ else if (key) key.contextInfo = { isSpoiler: message.spoiler };
1047
+ m = { spoilerMessage: { message: m } };
1048
+ delete message.spoiler;
1049
+ }
1050
+ // ── interactiveAsTemplate ─────────────────────────────────────────────────
1051
+ if (hasOptionalProperty(message, 'interactiveAsTemplate') && !!message.interactiveAsTemplate) {
1052
+ if (!m.interactiveMessage) throw new Boom('Invalid message type for template', { statusCode: 400 });
1053
+ m = { templateMessage: { interactiveMessageTemplate: m.interactiveMessage, templateId: message.id || 'template-' + Date.now() } };
1054
+ delete message.interactiveAsTemplate;
1055
+ }
1056
+ // ── ephemeral ─────────────────────────────────────────────────────────────
1057
+ if (hasOptionalProperty(message, 'ephemeral') && !!message.ephemeral) {
1058
+ m = { ephemeralMessage: { message: m } };
1059
+ delete message.ephemeral;
1060
+ }
1061
+ // ── isLottie ──────────────────────────────────────────────────────────────
1062
+ if (hasOptionalProperty(message, 'isLottie') && !!message.isLottie) {
1063
+ // marca o stickerMessage interno com isLottie: true para o WA renderizar como premium
1064
+ if (m.stickerMessage) {
1065
+ m.stickerMessage.isLottie = true;
1066
+ m.stickerMessage.isAnimated = m.stickerMessage.isAnimated ?? false;
1067
+ m.stickerMessage.isAiSticker = false;
1068
+ }
1069
+ m = { lottieStickerMessage: { message: m } };
1070
+ }
1071
+ // ── viewOnceV2 / viewOnceV2Extension ──────────────────────────────────────
1072
+ else if (hasOptionalProperty(message, 'viewOnceV2') && !!message.viewOnceV2) {
1073
+ m = { viewOnceMessageV2: { message: m } };
1074
+ delete message.viewOnceV2;
1075
+ }
1076
+ else if (hasOptionalProperty(message, 'viewOnceV2Extension') && !!message.viewOnceV2Extension) {
1077
+ m = { viewOnceMessageV2Extension: { message: m } };
1078
+ delete message.viewOnceV2Extension;
1079
+ }
1080
+
453
1081
  if ('viewOnce' in message && !!message.viewOnce) {
454
1082
  m = { viewOnceMessage: { message: m } };
455
1083
  }
@@ -571,7 +1199,8 @@ export const normalizeMessageContent = (content) => {
571
1199
  message?.documentWithCaptionMessage ||
572
1200
  message?.viewOnceMessageV2 ||
573
1201
  message?.viewOnceMessageV2Extension ||
574
- message?.editedMessage);
1202
+ message?.editedMessage ||
1203
+ message?.lottieStickerMessage);
575
1204
  }
576
1205
  };
577
1206
  export const extractMessageContent = (content) => {
@@ -813,6 +1442,8 @@ function _createZip(files) {
813
1442
  eocd.writeUInt32LE(cdBuf.length, 12); eocd.writeUInt32LE(offset, 16);
814
1443
  return Buffer.concat([...locals, cdBuf, eocd]);
815
1444
  }
1445
+ // sendStickerPack — wrapper de compatibilidade
1446
+ // Nova API: sock.sendMessage(jid, { cover, stickers, name, publisher })
816
1447
  export const sendStickerPack = async (conn, chatId, pack, options = {}) => {
817
1448
  const { name = '', publisher = '', stickers = [] } = pack;
818
1449
  const zipFiles = stickers.map((s, i) => {