@kelvdra/baileys 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/WAProto/index.js +65472 -137440
- package/lib/Defaults/index.d.ts +1 -1
- package/lib/Defaults/index.js +22 -3
- package/lib/Socket/chats.js +12 -13
- package/lib/Socket/groups.js +140 -7
- package/lib/Socket/hydra.js +44 -0
- package/lib/Socket/messages-recv.js +736 -324
- package/lib/Socket/messages-send.js +481 -110
- package/lib/Socket/mex.js +44 -6
- package/lib/Socket/newsletter.d.ts +16 -9
- package/lib/Socket/newsletter.js +259 -70
- package/lib/Types/Mex.d.ts +141 -0
- package/lib/Types/Mex.js +37 -0
- package/lib/Types/State.js +54 -1
- package/lib/Utils/auth-utils.js +12 -1
- package/lib/Utils/chat-utils.js +36 -2
- package/lib/Utils/companion-reg-client-utils.d.ts +17 -0
- package/lib/Utils/companion-reg-client-utils.js +35 -0
- package/lib/Utils/decode-wa-message.js +23 -4
- package/lib/Utils/generics.js +4 -1
- package/lib/Utils/identity-change-handler.d.ts +44 -0
- package/lib/Utils/identity-change-handler.js +50 -0
- package/lib/Utils/index.js +1 -1
- package/lib/Utils/message-retry-manager.js +25 -1
- package/lib/Utils/messages-media.js +162 -43
- package/lib/Utils/messages.d.ts +1 -1
- package/lib/Utils/messages.js +230 -9
- package/lib/Utils/offline-node-processor.d.ts +17 -0
- package/lib/Utils/offline-node-processor.js +40 -0
- package/lib/Utils/reporting-utils.d.ts +11 -0
- package/lib/Utils/reporting-utils.js +258 -0
- package/lib/Utils/signal.js +45 -1
- package/lib/Utils/stanza-ack.d.ts +11 -0
- package/lib/Utils/stanza-ack.js +38 -0
- package/lib/Utils/sync-action-utils.d.ts +19 -0
- package/lib/Utils/sync-action-utils.js +49 -0
- package/lib/Utils/tc-token-utils.d.ts +37 -0
- package/lib/Utils/tc-token-utils.js +163 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.d.ts +10 -0
- package/lib/WAUSync/Protocols/USyncUsernameProtocol.js +25 -0
- package/package.json +3 -1
|
@@ -196,55 +196,63 @@ export async function getAudioDuration(buffer) {
|
|
|
196
196
|
*/
|
|
197
197
|
export async function getAudioWaveform(buffer, logger) {
|
|
198
198
|
try {
|
|
199
|
-
const
|
|
200
|
-
const ff =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
audioData = await toBuffer(rStream);
|
|
208
|
-
} else {
|
|
209
|
-
audioData = await toBuffer(buffer);
|
|
199
|
+
const ffmpegModule = await import('fluent-ffmpeg');
|
|
200
|
+
const ff = ffmpegModule.default || ffmpegModule;
|
|
201
|
+
let input;
|
|
202
|
+
if (Buffer.isBuffer(buffer) || typeof buffer === 'string') {
|
|
203
|
+
input = buffer;
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
input = await toBuffer(buffer);
|
|
210
207
|
}
|
|
211
|
-
|
|
212
208
|
return await new Promise((resolve, reject) => {
|
|
213
|
-
const inputStream = new PassThrough();
|
|
214
|
-
inputStream.end(audioData);
|
|
215
209
|
const chunks = [];
|
|
216
210
|
const bars = 64;
|
|
217
|
-
|
|
218
|
-
ff(inputStream)
|
|
211
|
+
ff(input)
|
|
219
212
|
.audioChannels(1)
|
|
220
213
|
.audioFrequency(16000)
|
|
221
214
|
.format('s16le')
|
|
222
215
|
.on('error', reject)
|
|
223
216
|
.on('end', () => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
217
|
+
try {
|
|
218
|
+
const rawData = Buffer.concat(chunks);
|
|
219
|
+
const samples = Math.floor(rawData.length / 2);
|
|
220
|
+
if (!samples) {
|
|
221
|
+
return resolve(new Uint8Array(bars).fill(0));
|
|
222
|
+
}
|
|
223
|
+
const amplitudes = new Array(samples);
|
|
224
|
+
for (let i = 0; i < samples; i++) {
|
|
225
|
+
amplitudes[i] = Math.abs(rawData.readInt16LE(i * 2)) / 32768;
|
|
226
|
+
}
|
|
227
|
+
const blockSize = Math.max(1, Math.floor(amplitudes.length / bars));
|
|
228
|
+
const avg = [];
|
|
229
|
+
for (let i = 0; i < bars; i++) {
|
|
230
|
+
const start = i * blockSize;
|
|
231
|
+
const end = i === bars - 1 ? amplitudes.length : Math.min(start + blockSize, amplitudes.length);
|
|
232
|
+
const block = amplitudes.slice(start, end);
|
|
233
|
+
if (!block.length) {
|
|
234
|
+
avg.push(0);
|
|
235
|
+
continue;
|
|
236
|
+
}
|
|
237
|
+
avg.push(block.reduce((a, b) => a + b, 0) / block.length);
|
|
238
|
+
}
|
|
239
|
+
const max = Math.max(...avg, 0.0001);
|
|
240
|
+
const normalized = avg.map(v => {
|
|
241
|
+
const scaled = Math.round((v / max) * 100);
|
|
242
|
+
return Math.max(0, Math.min(100, scaled));
|
|
243
|
+
});
|
|
244
|
+
resolve(new Uint8Array(normalized));
|
|
230
245
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
const avg = [];
|
|
234
|
-
for (let i = 0; i < bars; i++) {
|
|
235
|
-
const block = amplitudes.slice(i * blockSize, (i + 1) * blockSize);
|
|
236
|
-
avg.push(block.reduce((a, b) => a + b, 0) / block.length);
|
|
246
|
+
catch (error) {
|
|
247
|
+
reject(error);
|
|
237
248
|
}
|
|
238
|
-
|
|
239
|
-
const max = Math.max(...avg);
|
|
240
|
-
const normalized = avg.map(v => Math.floor((v / max) * 100));
|
|
241
|
-
resolve(new Uint8Array(normalized));
|
|
242
249
|
})
|
|
243
250
|
.pipe()
|
|
244
251
|
.on('data', chunk => chunks.push(chunk));
|
|
245
252
|
});
|
|
246
|
-
}
|
|
247
|
-
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
logger?.debug({ trace: e?.stack || e }, 'failed to generate waveform');
|
|
248
256
|
}
|
|
249
257
|
}
|
|
250
258
|
export const toReadable = (buffer) => {
|
|
@@ -263,21 +271,21 @@ export const toBuffer = async (stream) => {
|
|
|
263
271
|
};
|
|
264
272
|
export const getStream = async (item, opts) => {
|
|
265
273
|
if (Buffer.isBuffer(item)) {
|
|
266
|
-
return { stream: toReadable(item), type: 'buffer' }
|
|
274
|
+
return { stream: toReadable(item), type: 'buffer' }
|
|
267
275
|
}
|
|
268
276
|
if ('stream' in item) {
|
|
269
|
-
return { stream: item.stream, type: 'readable' }
|
|
270
|
-
}
|
|
271
|
-
const urlStr = item.url.toString()
|
|
277
|
+
return { stream: item.stream, type: 'readable' }
|
|
278
|
+
}
|
|
279
|
+
const urlStr = item.url.toString()
|
|
272
280
|
if (urlStr.startsWith('data:')) {
|
|
273
|
-
const buffer = Buffer.from(urlStr.split(',')[1], 'base64')
|
|
274
|
-
return { stream: toReadable(buffer), type: 'buffer' }
|
|
281
|
+
const buffer = Buffer.from(urlStr.split(',')[1], 'base64')
|
|
282
|
+
return { stream: await toReadable(buffer), type: 'buffer' }
|
|
275
283
|
}
|
|
276
284
|
if (urlStr.startsWith('http://') || urlStr.startsWith('https://')) {
|
|
277
|
-
return { stream: await getHttpStream(item.url, opts), type: 'remote' }
|
|
285
|
+
return { stream: await getHttpStream(item.url, opts), type: 'remote' }
|
|
278
286
|
}
|
|
279
|
-
return { stream: createReadStream(item.url), type: 'file' }
|
|
280
|
-
}
|
|
287
|
+
return { stream: createReadStream(item.url), type: 'file' }
|
|
288
|
+
}
|
|
281
289
|
/** generates a thumbnail for a given media, if required */
|
|
282
290
|
export async function generateThumbnail(file, mediaType, options) {
|
|
283
291
|
let thumbnail;
|
|
@@ -697,3 +705,114 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|
|
697
705
|
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
|
698
706
|
};
|
|
699
707
|
//# sourceMappingURL=messages-media.js.map
|
|
708
|
+
|
|
709
|
+
// Added from Baileys v7.0.0-rc10
|
|
710
|
+
export const DEF_MEDIA_HOST = 'mmg.whatsapp.net';
|
|
711
|
+
|
|
712
|
+
// Added from Baileys v7.0.0-rc10
|
|
713
|
+
export const uploadWithNodeHttp = async ({ url, filePath, headers, timeoutMs, agent }, redirectCount = 0) => {
|
|
714
|
+
if (redirectCount > 5) {
|
|
715
|
+
throw new Error('Too many redirects');
|
|
716
|
+
}
|
|
717
|
+
const parsedUrl = new URL(url);
|
|
718
|
+
const httpModule = parsedUrl.protocol === 'https:' ? await import('https') : await import('http');
|
|
719
|
+
// Get file size for Content-Length header (required for Node.js streaming)
|
|
720
|
+
const fileStats = await fs.stat(filePath);
|
|
721
|
+
const fileSize = fileStats.size;
|
|
722
|
+
return new Promise((resolve, reject) => {
|
|
723
|
+
const req = httpModule.request({
|
|
724
|
+
hostname: parsedUrl.hostname,
|
|
725
|
+
port: parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80),
|
|
726
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
727
|
+
method: 'POST',
|
|
728
|
+
headers: {
|
|
729
|
+
...headers,
|
|
730
|
+
'Content-Length': fileSize
|
|
731
|
+
},
|
|
732
|
+
agent,
|
|
733
|
+
timeout: timeoutMs
|
|
734
|
+
}, res => {
|
|
735
|
+
// Handle redirects (3xx)
|
|
736
|
+
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
737
|
+
res.resume(); // Consume response to free resources
|
|
738
|
+
const newUrl = new URL(res.headers.location, url).toString();
|
|
739
|
+
resolve(uploadWithNodeHttp({
|
|
740
|
+
url: newUrl,
|
|
741
|
+
filePath,
|
|
742
|
+
headers,
|
|
743
|
+
timeoutMs,
|
|
744
|
+
agent
|
|
745
|
+
}, redirectCount + 1));
|
|
746
|
+
return;
|
|
747
|
+
}
|
|
748
|
+
let body = '';
|
|
749
|
+
res.on('data', chunk => (body += chunk));
|
|
750
|
+
res.on('end', () => {
|
|
751
|
+
try {
|
|
752
|
+
resolve(JSON.parse(body));
|
|
753
|
+
}
|
|
754
|
+
catch {
|
|
755
|
+
resolve(undefined);
|
|
756
|
+
}
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
req.on('error', reject);
|
|
760
|
+
req.on('timeout', () => {
|
|
761
|
+
req.destroy();
|
|
762
|
+
reject(new Error('Upload timeout'));
|
|
763
|
+
});
|
|
764
|
+
const stream = createReadStream(filePath);
|
|
765
|
+
stream.pipe(req);
|
|
766
|
+
stream.on('error', err => {
|
|
767
|
+
req.destroy();
|
|
768
|
+
reject(err);
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
const uploadWithFetch = async ({ url, filePath, headers, timeoutMs, agent }) => {
|
|
773
|
+
// Convert Node.js Readable to Web ReadableStream
|
|
774
|
+
const nodeStream = createReadStream(filePath);
|
|
775
|
+
const webStream = Readable.toWeb(nodeStream);
|
|
776
|
+
const response = await fetch(url, {
|
|
777
|
+
dispatcher: agent,
|
|
778
|
+
method: 'POST',
|
|
779
|
+
body: webStream,
|
|
780
|
+
headers,
|
|
781
|
+
duplex: 'half',
|
|
782
|
+
signal: timeoutMs ? AbortSignal.timeout(timeoutMs) : undefined
|
|
783
|
+
});
|
|
784
|
+
try {
|
|
785
|
+
return (await response.json());
|
|
786
|
+
}
|
|
787
|
+
catch {
|
|
788
|
+
return undefined;
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
/**
|
|
792
|
+
* Uploads media to WhatsApp servers.
|
|
793
|
+
*
|
|
794
|
+
* ## Why we have two upload implementations:
|
|
795
|
+
*
|
|
796
|
+
* Node.js's native `fetch` (powered by undici) has a known bug where it buffers
|
|
797
|
+
* the entire request body in memory before sending, even when using streams.
|
|
798
|
+
* This causes memory issues with large files (e.g., 1GB file = 1GB+ memory usage).
|
|
799
|
+
* See: https://github.com/nodejs/undici/issues/4058
|
|
800
|
+
*
|
|
801
|
+
* Other runtimes (Bun, Deno, browsers) correctly stream the request body without
|
|
802
|
+
* buffering, so we can use the web-standard Fetch API there.
|
|
803
|
+
*
|
|
804
|
+
* ## Future considerations:
|
|
805
|
+
* Once the undici bug is fixed, we can simplify this to use only the Fetch API
|
|
806
|
+
* across all runtimes. Monitor the GitHub issue for updates.
|
|
807
|
+
*/
|
|
808
|
+
const uploadMedia = async (params, logger) => {
|
|
809
|
+
if (isNodeRuntime()) {
|
|
810
|
+
logger?.debug('Using Node.js https module for upload (avoids undici buffering bug)');
|
|
811
|
+
return uploadWithNodeHttp(params);
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
logger?.debug('Using web-standard Fetch API for upload');
|
|
815
|
+
return uploadWithFetch(params);
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
|
package/lib/Utils/messages.d.ts
CHANGED
|
@@ -86,4 +86,4 @@ export declare const downloadMediaMessage: <Type extends "buffer" | "stream">(me
|
|
|
86
86
|
/** Checks whether the given message is a media message; if it is returns the inner content */
|
|
87
87
|
export declare const assertMediaContent: (content: proto.IMessage | null | undefined) => proto.Message.IVideoMessage | proto.Message.IImageMessage | proto.Message.IAudioMessage | proto.Message.IDocumentMessage | proto.Message.IStickerMessage;
|
|
88
88
|
export {};
|
|
89
|
-
//# sourceMappingURL=messages.d.ts.map
|
|
89
|
+
//# sourceMappingURL=messages.d.ts.map
|
package/lib/Utils/messages.js
CHANGED
|
@@ -8,7 +8,8 @@ import { WAMessageStatus, WAProto } from '../Types/index.js';
|
|
|
8
8
|
import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
|
|
9
9
|
import { sha256 } from './crypto.js';
|
|
10
10
|
import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
|
|
11
|
-
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData } from './messages-media.js';
|
|
11
|
+
import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData, getStream, toBuffer } from './messages-media.js';
|
|
12
|
+
import { shouldIncludeReportingToken } from './reporting-utils.js';
|
|
12
13
|
const MIMETYPE_MAP = {
|
|
13
14
|
image: 'image/jpeg',
|
|
14
15
|
video: 'video/mp4',
|
|
@@ -99,6 +100,25 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
99
100
|
if (isNewsletter) {
|
|
100
101
|
logger?.info({ key: cacheableKey }, 'Preparing raw media for newsletter');
|
|
101
102
|
const { filePath, fileSha256, fileLength } = await getRawMediaUploadData(uploadData.media, options.mediaTypeOverride || mediaType, logger);
|
|
103
|
+
try {
|
|
104
|
+
if (mediaType === 'audio') {
|
|
105
|
+
if (typeof uploadData.seconds === 'undefined') {
|
|
106
|
+
uploadData.seconds = await getAudioDuration(filePath);
|
|
107
|
+
logger?.debug('computed newsletter audio duration');
|
|
108
|
+
}
|
|
109
|
+
if (uploadData.ptt === true && typeof uploadData.waveform === 'undefined') {
|
|
110
|
+
uploadData.waveform = await getAudioWaveform(filePath, logger);
|
|
111
|
+
logger?.debug('processed newsletter waveform');
|
|
112
|
+
}
|
|
113
|
+
if (options.backgroundColor && uploadData.ptt === true && typeof uploadData.backgroundArgb === 'undefined') {
|
|
114
|
+
uploadData.backgroundArgb = await assertColor(options.backgroundColor);
|
|
115
|
+
logger?.debug('computed newsletter backgroundColor audio status');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
logger?.warn({ trace: error.stack }, 'failed to obtain newsletter media extra info');
|
|
121
|
+
}
|
|
102
122
|
const fileSha256B64 = fileSha256.toString('base64');
|
|
103
123
|
const { mediaUrl, directPath } = await options.upload(filePath, {
|
|
104
124
|
fileEncSha256B64: fileSha256B64,
|
|
@@ -131,7 +151,10 @@ export const prepareWAMessageMedia = async (message, options) => {
|
|
|
131
151
|
const requiresThumbnailComputation = (mediaType === 'image' || mediaType === 'video') && typeof uploadData['jpegThumbnail'] === 'undefined';
|
|
132
152
|
const requiresWaveformProcessing = mediaType === 'audio' && uploadData.ptt === true;
|
|
133
153
|
const requiresAudioBackground = options.backgroundColor && mediaType === 'audio' && uploadData.ptt === true;
|
|
134
|
-
|
|
154
|
+
// waveform processing also needs the original media file.
|
|
155
|
+
// if the caller already provides `seconds`, the old logic skips saving the original file,
|
|
156
|
+
// so `getAudioWaveform(originalFilePath)` receives `undefined` and waveform generation fails.
|
|
157
|
+
const requiresOriginalForSomeProcessing = requiresDurationComputation || requiresThumbnailComputation || requiresWaveformProcessing;
|
|
135
158
|
const { mediaKey, encFilePath, originalFilePath, fileEncSha256, fileSha256, fileLength } = await encryptedStream(uploadData.media, options.mediaTypeOverride || mediaType, {
|
|
136
159
|
logger,
|
|
137
160
|
saveOriginalFileIfRequired: requiresOriginalForSomeProcessing,
|
|
@@ -523,6 +546,174 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
523
546
|
|
|
524
547
|
m.requestPaymentMessage = requestPaymentMessage
|
|
525
548
|
}
|
|
549
|
+
else if ('stickerPack' in message) {
|
|
550
|
+
const { stickers, cover, name, publisher, packId, description } = message.stickerPack;
|
|
551
|
+
const { zip } = await import('fflate');
|
|
552
|
+
|
|
553
|
+
if (!Array.isArray(stickers) || stickers.length === 0) {
|
|
554
|
+
throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
|
|
555
|
+
}
|
|
556
|
+
if (stickers.length > 120) {
|
|
557
|
+
throw new Boom('Sticker pack exceeds the maximum limit of 120 stickers', { statusCode: 400 });
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
const stickerPackId = packId || generateMessageIDV2();
|
|
561
|
+
const [_sharp, _jimp] = await Promise.all([
|
|
562
|
+
import('sharp').catch(() => null),
|
|
563
|
+
import('jimp').catch(() => null)
|
|
564
|
+
]);
|
|
565
|
+
const lib = _sharp ? { sharp: _sharp } : _jimp ? { jimp: _jimp } : null;
|
|
566
|
+
if (!lib) {
|
|
567
|
+
throw new Boom('No image processing library available (install sharp or jimp)', { statusCode: 500 });
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
const isWebPBuffer = (buf) => (
|
|
571
|
+
buf.length >= 12 &&
|
|
572
|
+
buf[0] === 0x52 && buf[1] === 0x49 && buf[2] === 0x46 && buf[3] === 0x46 &&
|
|
573
|
+
buf[8] === 0x57 && buf[9] === 0x45 && buf[10] === 0x42 && buf[11] === 0x50
|
|
574
|
+
);
|
|
575
|
+
const isAnimatedWebP = (buf) => {
|
|
576
|
+
if (!isWebPBuffer(buf)) return false;
|
|
577
|
+
let offset = 12;
|
|
578
|
+
while (offset < buf.length - 8) {
|
|
579
|
+
const fourCC = buf.toString('ascii', offset, offset + 4);
|
|
580
|
+
const chunkSize = buf.readUInt32LE(offset + 4);
|
|
581
|
+
if (fourCC === 'VP8X') {
|
|
582
|
+
const flagsOffset = offset + 8;
|
|
583
|
+
if (flagsOffset < buf.length && (buf[flagsOffset] & 0x02)) return true;
|
|
584
|
+
} else if (fourCC === 'ANIM' || fourCC === 'ANMF') {
|
|
585
|
+
return true;
|
|
586
|
+
}
|
|
587
|
+
offset += 8 + chunkSize + (chunkSize % 2);
|
|
588
|
+
}
|
|
589
|
+
return false;
|
|
590
|
+
};
|
|
591
|
+
const imageToWebp = async (buffer) => {
|
|
592
|
+
if (isWebPBuffer(buffer)) {
|
|
593
|
+
if ('sharp' in lib && lib.sharp) {
|
|
594
|
+
return await lib.sharp.default(buffer)
|
|
595
|
+
.resize(512, 512, { fit: 'inside', withoutEnlargement: true })
|
|
596
|
+
.webp({ quality: 75, effort: 6 })
|
|
597
|
+
.toBuffer();
|
|
598
|
+
}
|
|
599
|
+
return buffer;
|
|
600
|
+
}
|
|
601
|
+
if ('sharp' in lib && lib.sharp) {
|
|
602
|
+
return await lib.sharp.default(buffer)
|
|
603
|
+
.resize(512, 512, { fit: 'inside', withoutEnlargement: true })
|
|
604
|
+
.webp({ quality: 75, effort: 6 })
|
|
605
|
+
.toBuffer();
|
|
606
|
+
}
|
|
607
|
+
throw new Boom('No image processing library (sharp) available for converting sticker to WebP', { statusCode: 500 });
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
const stickerData = {};
|
|
611
|
+
const stickerMetadata = [];
|
|
612
|
+
for (let i = 0; i < stickers.length; i++) {
|
|
613
|
+
const s = stickers[i];
|
|
614
|
+
try {
|
|
615
|
+
const { stream } = await getStream(s.data || s.sticker);
|
|
616
|
+
const buffer = await toBuffer(stream);
|
|
617
|
+
if (!buffer || buffer.length === 0) continue;
|
|
618
|
+
const animated = isAnimatedWebP(buffer);
|
|
619
|
+
const webpBuffer = await imageToWebp(buffer);
|
|
620
|
+
if (webpBuffer.length > 1024 * 1024) continue;
|
|
621
|
+
const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-').replace(/=/g, '');
|
|
622
|
+
const fileName = `${hash}.webp`;
|
|
623
|
+
stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 6 }];
|
|
624
|
+
stickerMetadata.push({
|
|
625
|
+
fileName,
|
|
626
|
+
mimetype: 'image/webp',
|
|
627
|
+
isAnimated: s.isAnimated ?? animated,
|
|
628
|
+
isLottie: s.isLottie || false,
|
|
629
|
+
emojis: s.emojis || [],
|
|
630
|
+
accessibilityLabel: s.accessibilityLabel || ''
|
|
631
|
+
});
|
|
632
|
+
} catch (err) {
|
|
633
|
+
options.logger?.warn?.({ err }, 'failed processing sticker pack item');
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
if (stickerMetadata.length === 0) {
|
|
637
|
+
throw new Boom('No valid stickers could be processed', { statusCode: 400 });
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
const trayIconFileName = `${stickerPackId}.webp`;
|
|
641
|
+
const coverBuffer = await toBuffer((await getStream(cover)).stream);
|
|
642
|
+
const coverWebpBuffer = await imageToWebp(coverBuffer);
|
|
643
|
+
stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 6 }];
|
|
644
|
+
|
|
645
|
+
const zipBuffer = await new Promise((resolve, reject) => {
|
|
646
|
+
zip(stickerData, { level: 6, memLevel: 9 }, (err, data) => {
|
|
647
|
+
if (err) reject(err);
|
|
648
|
+
else resolve(Buffer.from(data));
|
|
649
|
+
});
|
|
650
|
+
});
|
|
651
|
+
if (zipBuffer.length > 10 * 1024 * 1024) {
|
|
652
|
+
throw new Boom(`Sticker pack too large: ${(zipBuffer.length / 1024 / 1024).toFixed(2)}MB`, { statusCode: 400 });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
|
|
656
|
+
logger: options.logger,
|
|
657
|
+
opts: options.options
|
|
658
|
+
});
|
|
659
|
+
const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
|
|
660
|
+
fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
|
|
661
|
+
mediaType: 'sticker-pack',
|
|
662
|
+
timeoutMs: options.mediaUploadTimeoutMs || 300000
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
m.stickerPackMessage = {
|
|
666
|
+
name,
|
|
667
|
+
publisher,
|
|
668
|
+
stickerPackId,
|
|
669
|
+
packDescription: description,
|
|
670
|
+
stickerPackOrigin: proto.Message.StickerPackMessage.StickerPackOrigin.THIRD_PARTY,
|
|
671
|
+
stickerPackSize: zipBuffer.length,
|
|
672
|
+
stickers: stickerMetadata,
|
|
673
|
+
fileSha256: stickerPackUpload.fileSha256,
|
|
674
|
+
fileEncSha256: stickerPackUpload.fileEncSha256,
|
|
675
|
+
mediaKey: stickerPackUpload.mediaKey,
|
|
676
|
+
directPath: stickerPackUploadResult.directPath,
|
|
677
|
+
fileLength: stickerPackUpload.fileLength,
|
|
678
|
+
mediaKeyTimestamp: unixTimestampSeconds(),
|
|
679
|
+
trayIconFileName
|
|
680
|
+
};
|
|
681
|
+
|
|
682
|
+
try {
|
|
683
|
+
let thumbnailBuffer;
|
|
684
|
+
if ('sharp' in lib && lib.sharp) {
|
|
685
|
+
thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252, { fit: 'cover' }).jpeg({ quality: 80 }).toBuffer();
|
|
686
|
+
} else {
|
|
687
|
+
const jimpImage = await (lib.jimp.Jimp || lib.jimp.default).read(coverBuffer);
|
|
688
|
+
thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
|
|
689
|
+
}
|
|
690
|
+
const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
|
|
691
|
+
logger: options.logger,
|
|
692
|
+
opts: options.options,
|
|
693
|
+
mediaKey: stickerPackUpload.mediaKey
|
|
694
|
+
});
|
|
695
|
+
const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
|
|
696
|
+
fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
|
|
697
|
+
mediaType: 'thumbnail-sticker-pack',
|
|
698
|
+
timeoutMs: options.mediaUploadTimeoutMs || 60000
|
|
699
|
+
});
|
|
700
|
+
Object.assign(m.stickerPackMessage, {
|
|
701
|
+
thumbnailDirectPath: thumbUploadResult.directPath,
|
|
702
|
+
thumbnailSha256: thumbUpload.fileSha256,
|
|
703
|
+
thumbnailEncSha256: thumbUpload.fileEncSha256,
|
|
704
|
+
thumbnailHeight: 252,
|
|
705
|
+
thumbnailWidth: 252,
|
|
706
|
+
imageDataHash: sha256(thumbnailBuffer).toString('base64')
|
|
707
|
+
});
|
|
708
|
+
} catch (err) {
|
|
709
|
+
options.logger?.warn?.({ err }, 'failed generating sticker pack thumbnail');
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
m.stickerPackMessage.contextInfo = {
|
|
713
|
+
...(message.contextInfo || {}),
|
|
714
|
+
...(message.mentions ? { mentionedJid: message.mentions } : {})
|
|
715
|
+
};
|
|
716
|
+
}
|
|
526
717
|
else if ('sharePhoneNumber' in message) {
|
|
527
718
|
m.protocolMessage = {
|
|
528
719
|
type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
|
|
@@ -857,7 +1048,7 @@ export const normalizeMessageContent = (content) => {
|
|
|
857
1048
|
if (!content) {
|
|
858
1049
|
return undefined;
|
|
859
1050
|
}
|
|
860
|
-
|
|
1051
|
+
|
|
861
1052
|
for (let i = 0; i < 5; i++) {
|
|
862
1053
|
const inner = getFutureProofMessage(content);
|
|
863
1054
|
if (!inner) {
|
|
@@ -867,12 +1058,29 @@ export const normalizeMessageContent = (content) => {
|
|
|
867
1058
|
}
|
|
868
1059
|
return content;
|
|
869
1060
|
function getFutureProofMessage(message) {
|
|
870
|
-
return (message
|
|
871
|
-
message
|
|
872
|
-
message
|
|
873
|
-
message
|
|
874
|
-
message
|
|
875
|
-
message
|
|
1061
|
+
return ((message === null || message === void 0 ? void 0 : message.ephemeralMessage)
|
|
1062
|
+
|| (message === null || message === void 0 ? void 0 : message.viewOnceMessage)
|
|
1063
|
+
|| (message === null || message === void 0 ? void 0 : message.documentWithCaptionMessage)
|
|
1064
|
+
|| (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2)
|
|
1065
|
+
|| (message === null || message === void 0 ? void 0 : message.viewOnceMessageV2Extension)
|
|
1066
|
+
|| (message === null || message === void 0 ? void 0 : message.editedMessage)
|
|
1067
|
+
|| (message === null || message === void 0 ? void 0 : message.groupMentionedMessage)
|
|
1068
|
+
|| (message === null || message === void 0 ? void 0 : message.botInvokeMessage)
|
|
1069
|
+
|| (message === null || message === void 0 ? void 0 : message.lottieStickerMessage)
|
|
1070
|
+
|| (message === null || message === void 0 ? void 0 : message.eventCoverImage)
|
|
1071
|
+
|| (message === null || message === void 0 ? void 0 : message.statusMentionMessage)
|
|
1072
|
+
|| (message === null || message === void 0 ? void 0 : message.pollCreationOptionImageMessage)
|
|
1073
|
+
|| (message === null || message === void 0 ? void 0 : message.associatedChildMessage)
|
|
1074
|
+
|| (message === null || message === void 0 ? void 0 : message.groupStatusMentionMessage)
|
|
1075
|
+
|| (message === null || message === void 0 ? void 0 : message.pollCreationMessageV4)
|
|
1076
|
+
|| (message === null || message === void 0 ? void 0 : message.pollCreationMessageV5)
|
|
1077
|
+
|| (message === null || message === void 0 ? void 0 : message.statusAddYours)
|
|
1078
|
+
|| (message === null || message === void 0 ? void 0 : message.groupStatusMessage)
|
|
1079
|
+
|| (message === null || message === void 0 ? void 0 : message.limitSharingMessage)
|
|
1080
|
+
|| (message === null || message === void 0 ? void 0 : message.botTaskMessage)
|
|
1081
|
+
|| (message === null || message === void 0 ? void 0 : message.questionMessage)
|
|
1082
|
+
|| (message === null || message === void 0 ? void 0 : message.groupStatusMessageV2)
|
|
1083
|
+
|| (message === null || message === void 0 ? void 0 : message.botForwardedMessage));
|
|
876
1084
|
}
|
|
877
1085
|
};
|
|
878
1086
|
/**
|
|
@@ -1140,3 +1348,16 @@ export const patchMessageForMdIfRequired = (message) => {
|
|
|
1140
1348
|
}
|
|
1141
1349
|
return message
|
|
1142
1350
|
}
|
|
1351
|
+
|
|
1352
|
+
// Added from Baileys v7.0.0-rc10
|
|
1353
|
+
export const hasNonNullishProperty = (message, key) => {
|
|
1354
|
+
return (typeof message === 'object' &&
|
|
1355
|
+
message !== null &&
|
|
1356
|
+
key in message &&
|
|
1357
|
+
message[key] !== null &&
|
|
1358
|
+
message[key] !== undefined);
|
|
1359
|
+
};
|
|
1360
|
+
function hasOptionalProperty(obj, key) {
|
|
1361
|
+
return typeof obj === 'object' && obj !== null && key in obj && obj[key] !== null;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BinaryNode } from '../WABinary/index.js';
|
|
2
|
+
export type MessageType = 'message' | 'call' | 'receipt' | 'notification';
|
|
3
|
+
export type OfflineNodeProcessorDeps = {
|
|
4
|
+
isWsOpen: () => boolean;
|
|
5
|
+
onUnexpectedError: (error: Error, msg: string) => void;
|
|
6
|
+
yieldToEventLoop: () => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Creates a processor for offline stanza nodes that:
|
|
10
|
+
* - Queues nodes for sequential processing
|
|
11
|
+
* - Yields to the event loop periodically to avoid blocking
|
|
12
|
+
* - Catches handler errors to prevent the processing loop from crashing
|
|
13
|
+
*/
|
|
14
|
+
export declare function makeOfflineNodeProcessor(nodeProcessorMap: Map<MessageType, (node: BinaryNode) => Promise<void>>, deps: OfflineNodeProcessorDeps, batchSize?: number): {
|
|
15
|
+
enqueue: (type: MessageType, node: BinaryNode) => void;
|
|
16
|
+
};
|
|
17
|
+
//# sourceMappingURL=offline-node-processor.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a processor for offline stanza nodes that:
|
|
3
|
+
* - Queues nodes for sequential processing
|
|
4
|
+
* - Yields to the event loop periodically to avoid blocking
|
|
5
|
+
* - Catches handler errors to prevent the processing loop from crashing
|
|
6
|
+
*/
|
|
7
|
+
export function makeOfflineNodeProcessor(nodeProcessorMap, deps, batchSize = 10) {
|
|
8
|
+
const nodes = [];
|
|
9
|
+
let isProcessing = false;
|
|
10
|
+
const enqueue = (type, node) => {
|
|
11
|
+
nodes.push({ type, node });
|
|
12
|
+
if (isProcessing) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
isProcessing = true;
|
|
16
|
+
const promise = async () => {
|
|
17
|
+
let processedInBatch = 0;
|
|
18
|
+
while (nodes.length && deps.isWsOpen()) {
|
|
19
|
+
const { type, node } = nodes.shift();
|
|
20
|
+
const nodeProcessor = nodeProcessorMap.get(type);
|
|
21
|
+
if (!nodeProcessor) {
|
|
22
|
+
deps.onUnexpectedError(new Error(`unknown offline node type: ${type}`), 'processing offline node');
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
await nodeProcessor(node).catch(err => deps.onUnexpectedError(err, `processing offline ${type}`));
|
|
26
|
+
processedInBatch++;
|
|
27
|
+
// Yield to event loop after processing a batch
|
|
28
|
+
// This prevents blocking the event loop for too long when there are many offline nodes
|
|
29
|
+
if (processedInBatch >= batchSize) {
|
|
30
|
+
processedInBatch = 0;
|
|
31
|
+
await deps.yieldToEventLoop();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
isProcessing = false;
|
|
35
|
+
};
|
|
36
|
+
promise().catch(error => deps.onUnexpectedError(error, 'processing offline nodes'));
|
|
37
|
+
};
|
|
38
|
+
return { enqueue };
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=offline-node-processor.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { proto } from '../../WAProto/index.js';
|
|
2
|
+
import type { WAMessageContent, WAMessageKey } from '../Types/index.js';
|
|
3
|
+
import type { BinaryNode } from '../WABinary/index.js';
|
|
4
|
+
export type ReportingField = {
|
|
5
|
+
f: number;
|
|
6
|
+
m?: boolean;
|
|
7
|
+
s?: ReportingField[];
|
|
8
|
+
};
|
|
9
|
+
export declare const shouldIncludeReportingToken: (message: proto.IMessage) => boolean;
|
|
10
|
+
export declare const getMessageReportingToken: (msgProtobuf: Buffer, message: WAMessageContent, key: WAMessageKey) => Promise<BinaryNode | null>;
|
|
11
|
+
//# sourceMappingURL=reporting-utils.d.ts.map
|