@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.
- package/README.md +595 -762
- package/lib/Defaults/index.js +9 -3
- package/lib/MB.cjs +8 -0
- package/lib/MessageBuilder.cjs +2509 -0
- package/lib/Socket/chats.js +12 -1
- package/lib/Socket/groups.js +13 -4
- package/lib/Socket/messages-recv.js +36 -2
- package/lib/Socket/messages-recv.js.bak +1273 -0
- package/lib/Socket/messages-send.js +2 -1
- package/lib/Types/Message.js +7 -0
- package/lib/Utils/bad-mac-handler.js +158 -0
- package/lib/Utils/get-best-version.js +41 -0
- package/lib/Utils/get-name.d.ts +1 -0
- package/lib/Utils/get-name.js +54 -0
- package/lib/Utils/group-status-detection.js +113 -0
- package/lib/Utils/index.js +7 -0
- package/lib/Utils/logger.js +4 -1
- package/lib/Utils/messages-media.js +42 -3
- package/lib/Utils/messages.js +641 -10
- package/lib/Utils/payment-detection.js +212 -0
- package/lib/Utils/payment-guard.d.ts +15 -0
- package/lib/Utils/payment-guard.js +142 -0
- package/lib/Utils/resolve-lid-phone.js +30 -0
- package/lib/WABinary/jid-utils.js +245 -1
- package/package.json +4 -3
- package/lib/Defaults/index.d.ts.map +0 -1
- package/lib/Defaults/index.js.map +0 -1
- package/lib/Signal/Group/ciphertext-message.d.ts.map +0 -1
- package/lib/Signal/Group/ciphertext-message.js.map +0 -1
- package/lib/Signal/Group/group-session-builder.d.ts.map +0 -1
- package/lib/Signal/Group/group-session-builder.js.map +0 -1
- package/lib/Signal/Group/group_cipher.d.ts.map +0 -1
- package/lib/Signal/Group/group_cipher.js.map +0 -1
- package/lib/Signal/Group/index.d.ts.map +0 -1
- package/lib/Signal/Group/index.js.map +0 -1
- package/lib/Signal/Group/keyhelper.d.ts.map +0 -1
- package/lib/Signal/Group/keyhelper.js.map +0 -1
- package/lib/Signal/Group/sender-chain-key.d.ts.map +0 -1
- package/lib/Signal/Group/sender-chain-key.js.map +0 -1
- package/lib/Signal/Group/sender-key-distribution-message.d.ts.map +0 -1
- package/lib/Signal/Group/sender-key-distribution-message.js.map +0 -1
- package/lib/Signal/Group/sender-key-message.d.ts.map +0 -1
- package/lib/Signal/Group/sender-key-message.js.map +0 -1
- package/lib/Signal/Group/sender-key-name.d.ts.map +0 -1
- package/lib/Signal/Group/sender-key-name.js.map +0 -1
- package/lib/Signal/Group/sender-key-record.d.ts.map +0 -1
- package/lib/Signal/Group/sender-key-record.js.map +0 -1
- package/lib/Signal/Group/sender-key-state.d.ts.map +0 -1
- package/lib/Signal/Group/sender-key-state.js.map +0 -1
- package/lib/Signal/Group/sender-message-key.d.ts.map +0 -1
- package/lib/Signal/Group/sender-message-key.js.map +0 -1
- package/lib/Signal/libsignal.d.ts.map +0 -1
- package/lib/Signal/libsignal.js.map +0 -1
- package/lib/Signal/lid-mapping.d.ts.map +0 -1
- package/lib/Signal/lid-mapping.js.map +0 -1
- package/lib/Socket/Client/index.d.ts.map +0 -1
- package/lib/Socket/Client/index.js.map +0 -1
- package/lib/Socket/Client/types.d.ts.map +0 -1
- package/lib/Socket/Client/types.js.map +0 -1
- package/lib/Socket/Client/websocket.d.ts.map +0 -1
- package/lib/Socket/Client/websocket.js.map +0 -1
- package/lib/Socket/business.d.ts.map +0 -1
- package/lib/Socket/business.js.map +0 -1
- package/lib/Socket/chats.d.ts.map +0 -1
- package/lib/Socket/chats.js.map +0 -1
- package/lib/Socket/communities.d.ts.map +0 -1
- package/lib/Socket/communities.js.map +0 -1
- package/lib/Socket/groups.d.ts.map +0 -1
- package/lib/Socket/groups.js.map +0 -1
- package/lib/Socket/index.d.ts.map +0 -1
- package/lib/Socket/index.js.map +0 -1
- package/lib/Socket/messages-recv.d.ts.map +0 -1
- package/lib/Socket/messages-recv.js.map +0 -1
- package/lib/Socket/messages-send.d.ts.map +0 -1
- package/lib/Socket/messages-send.js.map +0 -1
- package/lib/Socket/mex.d.ts.map +0 -1
- package/lib/Socket/mex.js.map +0 -1
- package/lib/Socket/newsletter.d.ts.map +0 -1
- package/lib/Socket/newsletter.js.map +0 -1
- package/lib/Socket/socket.d.ts.map +0 -1
- package/lib/Socket/socket.js.map +0 -1
- package/lib/Types/Auth.d.ts.map +0 -1
- package/lib/Types/Auth.js.map +0 -1
- package/lib/Types/Bussines.d.ts.map +0 -1
- package/lib/Types/Bussines.js.map +0 -1
- package/lib/Types/Call.d.ts.map +0 -1
- package/lib/Types/Call.js.map +0 -1
- package/lib/Types/Chat.d.ts.map +0 -1
- package/lib/Types/Chat.js.map +0 -1
- package/lib/Types/Contact.d.ts.map +0 -1
- package/lib/Types/Contact.js.map +0 -1
- package/lib/Types/Events.d.ts.map +0 -1
- package/lib/Types/Events.js.map +0 -1
- package/lib/Types/GroupMetadata.d.ts.map +0 -1
- package/lib/Types/GroupMetadata.js.map +0 -1
- package/lib/Types/Label.d.ts.map +0 -1
- package/lib/Types/Label.js.map +0 -1
- package/lib/Types/LabelAssociation.d.ts.map +0 -1
- package/lib/Types/LabelAssociation.js.map +0 -1
- package/lib/Types/Message.d.ts.map +0 -1
- package/lib/Types/Message.js.map +0 -1
- package/lib/Types/Newsletter.d.ts.map +0 -1
- package/lib/Types/Newsletter.js.map +0 -1
- package/lib/Types/Product.d.ts.map +0 -1
- package/lib/Types/Product.js.map +0 -1
- package/lib/Types/Signal.d.ts.map +0 -1
- package/lib/Types/Signal.js.map +0 -1
- package/lib/Types/Socket.d.ts.map +0 -1
- package/lib/Types/Socket.js.map +0 -1
- package/lib/Types/State.d.ts.map +0 -1
- package/lib/Types/State.js.map +0 -1
- package/lib/Types/USync.d.ts.map +0 -1
- package/lib/Types/USync.js.map +0 -1
- package/lib/Types/index.d.ts.map +0 -1
- package/lib/Types/index.js.map +0 -1
- package/lib/Utils/auth-utils.d.ts.map +0 -1
- package/lib/Utils/auth-utils.js.map +0 -1
- package/lib/Utils/browser-utils.d.ts.map +0 -1
- package/lib/Utils/browser-utils.js.map +0 -1
- package/lib/Utils/business.d.ts.map +0 -1
- package/lib/Utils/business.js.map +0 -1
- package/lib/Utils/chat-utils.d.ts.map +0 -1
- package/lib/Utils/chat-utils.js.map +0 -1
- package/lib/Utils/crypto.d.ts.map +0 -1
- package/lib/Utils/crypto.js.map +0 -1
- package/lib/Utils/decode-wa-message.d.ts.map +0 -1
- package/lib/Utils/decode-wa-message.js.map +0 -1
- package/lib/Utils/event-buffer.d.ts.map +0 -1
- package/lib/Utils/event-buffer.js.map +0 -1
- package/lib/Utils/generics.d.ts.map +0 -1
- package/lib/Utils/generics.js.map +0 -1
- package/lib/Utils/history.d.ts.map +0 -1
- package/lib/Utils/history.js.map +0 -1
- package/lib/Utils/index.d.ts.map +0 -1
- package/lib/Utils/index.js.map +0 -1
- package/lib/Utils/link-preview.d.ts.map +0 -1
- package/lib/Utils/link-preview.js.map +0 -1
- package/lib/Utils/logger.d.ts.map +0 -1
- package/lib/Utils/logger.js.map +0 -1
- package/lib/Utils/lt-hash.d.ts.map +0 -1
- package/lib/Utils/lt-hash.js.map +0 -1
- package/lib/Utils/make-mutex.d.ts.map +0 -1
- package/lib/Utils/make-mutex.js.map +0 -1
- package/lib/Utils/message-retry-manager.d.ts.map +0 -1
- package/lib/Utils/message-retry-manager.js.map +0 -1
- package/lib/Utils/messages-media.d.ts.map +0 -1
- package/lib/Utils/messages-media.js.map +0 -1
- package/lib/Utils/messages.d.ts.map +0 -1
- package/lib/Utils/messages.js.map +0 -1
- package/lib/Utils/noise-handler.d.ts.map +0 -1
- package/lib/Utils/noise-handler.js.map +0 -1
- package/lib/Utils/pre-key-manager.d.ts.map +0 -1
- package/lib/Utils/pre-key-manager.js.map +0 -1
- package/lib/Utils/process-message.d.ts.map +0 -1
- package/lib/Utils/process-message.js.map +0 -1
- package/lib/Utils/signal.d.ts.map +0 -1
- package/lib/Utils/signal.js.map +0 -1
- package/lib/Utils/use-multi-file-auth-state.d.ts.map +0 -1
- package/lib/Utils/use-multi-file-auth-state.js.map +0 -1
- package/lib/Utils/validate-connection.d.ts.map +0 -1
- package/lib/Utils/validate-connection.js.map +0 -1
- package/lib/WABinary/constants.d.ts.map +0 -1
- package/lib/WABinary/constants.js.map +0 -1
- package/lib/WABinary/decode.d.ts.map +0 -1
- package/lib/WABinary/decode.js.map +0 -1
- package/lib/WABinary/encode.d.ts.map +0 -1
- package/lib/WABinary/encode.js.map +0 -1
- package/lib/WABinary/generic-utils.d.ts.map +0 -1
- package/lib/WABinary/generic-utils.js.map +0 -1
- package/lib/WABinary/index.d.ts.map +0 -1
- package/lib/WABinary/index.js.map +0 -1
- package/lib/WABinary/jid-utils.d.ts.map +0 -1
- package/lib/WABinary/jid-utils.js.map +0 -1
- package/lib/WABinary/types.d.ts.map +0 -1
- package/lib/WABinary/types.js.map +0 -1
- package/lib/WAM/BinaryInfo.d.ts.map +0 -1
- package/lib/WAM/BinaryInfo.js.map +0 -1
- package/lib/WAM/constants.d.ts.map +0 -1
- package/lib/WAM/constants.js.map +0 -1
- package/lib/WAM/encode.d.ts.map +0 -1
- package/lib/WAM/encode.js.map +0 -1
- package/lib/WAM/index.d.ts.map +0 -1
- package/lib/WAM/index.js.map +0 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/USyncContactProtocol.js.map +0 -1
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/USyncDeviceProtocol.js.map +0 -1
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/USyncDisappearingModeProtocol.js.map +0 -1
- package/lib/WAUSync/Protocols/USyncStatusProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/USyncStatusProtocol.js.map +0 -1
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/UsyncBotProfileProtocol.js.map +0 -1
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/UsyncLIDProtocol.js.map +0 -1
- package/lib/WAUSync/Protocols/index.d.ts.map +0 -1
- package/lib/WAUSync/Protocols/index.js.map +0 -1
- package/lib/WAUSync/USyncQuery.d.ts.map +0 -1
- package/lib/WAUSync/USyncQuery.js.map +0 -1
- package/lib/WAUSync/USyncUser.d.ts.map +0 -1
- package/lib/WAUSync/USyncUser.js.map +0 -1
- package/lib/WAUSync/index.d.ts.map +0 -1
- package/lib/WAUSync/index.js.map +0 -1
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js.map +0 -1
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, 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/
|
|
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
|
-
|
|
79
|
-
|
|
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) => {
|