aunubaileys 1.0.1 → 1.0.2

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 CHANGED
@@ -58,6 +58,17 @@ sock.ev.on('creds.update', saveCreds)
58
58
 
59
59
  Everything else is pure upstream. When upstream moves, we rebase the patch layer — that discipline is the whole point of this fork.
60
60
 
61
+
62
+ ### 🆕 What's new in 1.0.2 / Baru di 1.0.2
63
+
64
+ - 📦 **Sticker packs** — send full sticker packs with `stickerPack` (cover + up to 60 stickers).
65
+ - 🖼️ **Album messages** — send grouped photos/videos with `album`.
66
+ - 📣 **Newsletter media fixed** — media uploads to channels use the proper newsletter paths.
67
+ - 🤖 **AI label** — mark bot replies with `ai: true` in private chats.
68
+ - 🔐 **Sturdier connections & sessions** — pre-key upload timeout + atomic session file writes.
69
+
70
+ **Credits:** features adapted from the community — [@itsliaaa/baileys](https://www.npmjs.com/package/@itsliaaa/baileys) (sticker pack, album, newsletter media, AI label, atomic auth writes), [MichelleBot/baileys](https://github.com/MichelleBot/baileys) (connection fix), [neoxr/baileys](https://github.com/neoxr/baileys) (sticker pack) — merged into AunuBaileys with gratitude. 🙏
71
+
61
72
  ---
62
73
 
63
74
  ## 🇮🇩 Bahasa Indonesia
@@ -64,6 +64,8 @@ export const MEDIA_PATH_MAP = {
64
64
  document: '/mms/document',
65
65
  audio: '/mms/audio',
66
66
  sticker: '/mms/image',
67
+ 'sticker-pack': '/mms/sticker-pack',
68
+ 'thumbnail-sticker-pack': '/mms/thumbnail-sticker-pack',
67
69
  'thumbnail-link': '/mms/image',
68
70
  'product-catalog-image': '/product/image',
69
71
  'md-app-state': '',
@@ -98,4 +100,22 @@ export const DEFAULT_CACHE_TTLS = {
98
100
  CALL_OFFER: 5 * 60, // 5 minutes
99
101
  USER_DEVICES: 5 * 60 // 5 minutes
100
102
  };
101
- //# sourceMappingURL=index.js.map
103
+ //# sourceMappingURL=index.js.map
104
+ // AunuBaileys: peta path upload khusus newsletter (dari @itsliaaa/baileys)
105
+ export const NEWSLETTER_MEDIA_PATH_MAP = {
106
+ image: '/newsletter/newsletter-image',
107
+ video: '/newsletter/newsletter-video',
108
+ audio: '/newsletter/newsletter-audio',
109
+ document: '/newsletter/newsletter-document',
110
+ sticker: '/newsletter/newsletter-sticker',
111
+ gif: '/newsletter/newsletter-gif',
112
+ ptt: '/newsletter/newsletter-ptt',
113
+ ptv: '/newsletter/newsletter-ptv',
114
+ 'music-artwork': '/mms/newsletter-music-artwork',
115
+ 'sticker-pack': '/newsletter/newsletter-sticker-pack',
116
+ 'thumbnail-link': '/newsletter/newsletter-thumbnail-link',
117
+ 'waffle-image': '/mms/waffle-image',
118
+ 'waffle-video': '/mms/waffle-video'
119
+ };
120
+ // AunuBaileys: payload label AI (dari @itsliaaa/baileys)
121
+ export const BIZ_BOT_SUPPORT_PAYLOAD = '{"version":1,"is_ai_message":true,"should_upload_client_logs":false,"should_show_system_message":false,"ticket_id":"7004947587700716","citation_items":[],"ticket_locale":"us"}';
@@ -1,10 +1,10 @@
1
1
  import NodeCache from '@cacheable/node-cache';
2
2
  import { Boom } from '@hapi/boom';
3
3
  import { proto } from '../../WAProto/index.js';
4
- import { DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
- import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
4
+ import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
+ import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, delay, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds } from '../Utils/index.js';
6
6
  import { getUrlInfo } from '../Utils/link-preview.js';
7
- import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
7
+ import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidNewsletter, isJidUser, isLidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
8
8
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
9
9
  import { makeGroupsSocket } from './groups.js';
10
10
  import { makeNewsletterSocket } from './newsletter.js';
@@ -654,7 +654,7 @@ export const makeMessagesSocket = (config) => {
654
654
  }),
655
655
  //TODO: CACHE
656
656
  getProfilePicUrl: sock.profilePictureUrl,
657
- upload: waUploadToServer,
657
+ upload: (fp, opts) => waUploadToServer(fp, { ...opts, newsletter: isJidNewsletter(jid) }),
658
658
  mediaCache: config.mediaCache,
659
659
  options: config.options,
660
660
  messageId: generateMessageIDV2(sock.user?.id),
@@ -664,6 +664,7 @@ export const makeMessagesSocket = (config) => {
664
664
  const isEditMsg = 'edit' in content && !!content.edit;
665
665
  const isPinMsg = 'pin' in content && !!content.pin;
666
666
  const isPollMessage = 'poll' in content && !!content.poll;
667
+ const isAiMsg = 'ai' in content && !!content.ai;
667
668
  const additionalAttributes = {};
668
669
  const additionalNodes = [];
669
670
  // required for delete
@@ -690,6 +691,21 @@ export const makeMessagesSocket = (config) => {
690
691
  }
691
692
  });
692
693
  }
694
+ // AunuBaileys: label AI (dari @itsliaaa/baileys) — hanya chat pribadi
695
+ else if (isAiMsg) {
696
+ if (!(isJidUser(jid) || isLidUser(jid))) {
697
+ throw new Boom('AI icon on message are only allowed in private chat', { statusCode: 400 });
698
+ }
699
+ if (fullMsg.message.messageContextInfo) {
700
+ fullMsg.message.messageContextInfo.supportPayload = BIZ_BOT_SUPPORT_PAYLOAD;
701
+ }
702
+ additionalNodes.push({
703
+ tag: 'bot',
704
+ attrs: { biz_bot: '1' },
705
+ content: undefined
706
+ });
707
+ delete content.ai;
708
+ }
693
709
  if ('cachedGroupMetadata' in options) {
694
710
  console.warn('cachedGroupMetadata in sendMessage are deprecated, now cachedGroupMetadata is part of the socket config.');
695
711
  }
@@ -700,6 +716,38 @@ export const makeMessagesSocket = (config) => {
700
716
  statusJidList: options.statusJidList,
701
717
  additionalNodes
702
718
  });
719
+ // AunuBaileys: kirim isi album sebagai pesan terasosiasi (dari @itsliaaa/baileys)
720
+ if ('album' in content) {
721
+ const { delayMs = 1500 } = options;
722
+ for (const albumMedia of content.album) {
723
+ const albumMsg = await generateWAMessage(jid, albumMedia, {
724
+ logger,
725
+ userJid,
726
+ upload: (fp, opts) => waUploadToServer(fp, { ...opts, newsletter: isJidNewsletter(jid) }),
727
+ mediaCache: config.mediaCache,
728
+ options: config.options,
729
+ ...options,
730
+ messageId: generateMessageIDV2(sock.user?.id)
731
+ });
732
+ const normalized = normalizeMessageContent(albumMsg.message);
733
+ if (!normalized || !('imageMessage' in normalized || 'videoMessage' in normalized)) {
734
+ throw new Boom('Invalid message type for album', { statusCode: 400 });
735
+ }
736
+ albumMsg.message.messageContextInfo = albumMsg.message.messageContextInfo || {};
737
+ albumMsg.message.messageContextInfo.messageAssociation = {
738
+ parentMessageKey: fullMsg.key,
739
+ associationType: proto.MessageAssociation.AssociationType.MEDIA_ALBUM
740
+ };
741
+ await relayMessage(jid, albumMsg.message, {
742
+ messageId: albumMsg.key.id,
743
+ useCachedGroupMetadata: options.useCachedGroupMetadata,
744
+ statusJidList: options.statusJidList,
745
+ additionalAttributes,
746
+ additionalNodes
747
+ });
748
+ await delay(delayMs);
749
+ }
750
+ }
703
751
  if (config.emitOwnEvents) {
704
752
  process.nextTick(() => {
705
753
  processingMutex.mutex(() => upsertMessage(fullMsg, 'append'));
@@ -191,7 +191,15 @@ export const makeSocket = (config) => {
191
191
  await keys.transaction(async () => {
192
192
  logger.info({ count }, 'uploading pre-keys');
193
193
  const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
194
- await query(node);
194
+ // AunuBaileys: timeout upload pre-keys agar koneksi tidak menggantung (dari MichelleBot/baileys)
195
+ await promiseTimeout(30000, async (resolve, reject) => {
196
+ try {
197
+ resolve(await query(node));
198
+ }
199
+ catch (error) {
200
+ reject(error);
201
+ }
202
+ });
195
203
  ev.emit('creds.update', update);
196
204
  logger.info({ count }, 'uploaded pre-keys');
197
205
  });
@@ -172,7 +172,22 @@ export type AnyRegularMessageContent = (({
172
172
  body?: string;
173
173
  footer?: string;
174
174
  } | SharePhoneNumber | RequestPhoneNumber) & ViewOnce;
175
- export type AnyMessageContent = AnyRegularMessageContent | {
175
+ export type StickerPackStickerInfo = {
176
+ data: WAMediaUpload;
177
+ emojis?: string[];
178
+ isAnimated?: boolean;
179
+ accessibilityLabel?: string;
180
+ isLottie?: boolean;
181
+ };
182
+ export type StickerPackInfo = {
183
+ stickers: StickerPackStickerInfo[];
184
+ cover: WAMediaUpload;
185
+ name?: string;
186
+ publisher?: string;
187
+ packId?: string;
188
+ description?: string;
189
+ };
190
+ export type AnyMessageContent = (AnyRegularMessageContent | {
176
191
  forward: WAMessage;
177
192
  force?: boolean;
178
193
  } | {
@@ -180,6 +195,22 @@ export type AnyMessageContent = AnyRegularMessageContent | {
180
195
  delete: WAMessageKey;
181
196
  } | {
182
197
  disappearingMessagesInChat: boolean | number;
198
+ } | {
199
+ /** AunuBaileys: send a sticker pack (cover + stickers) */
200
+ stickerPack: StickerPackInfo;
201
+ } | {
202
+ /** AunuBaileys: send a sticker pack, flat form (cover + stickers) */
203
+ stickers: StickerPackStickerInfo[];
204
+ cover: WAMediaUpload;
205
+ name?: string;
206
+ publisher?: string;
207
+ description?: string;
208
+ } | {
209
+ /** AunuBaileys: send an album of images/videos */
210
+ album: AnyRegularMessageContent[];
211
+ }) & {
212
+ /** AunuBaileys: mark the message with the AI label (private chats only) */
213
+ ai?: boolean;
183
214
  };
184
215
  export type GroupMetadataParticipants = Pick<GroupMetadata, 'participants'>;
185
216
  type MinimalRelayOptions = {
@@ -109,4 +109,5 @@ export declare const decryptMediaRetryData: ({ ciphertext, iv }: {
109
109
  }, mediaKey: Uint8Array, msgId: string) => Promise<proto.MediaRetryNotification>;
110
110
  export declare const getStatusCodeForMediaRetry: (code: number) => 200 | 412 | 404 | 418;
111
111
  export {};
112
- //# sourceMappingURL=messages-media.d.ts.map
112
+ //# sourceMappingURL=messages-media.d.ts.map
113
+ export declare const getImageProcessingLibrary: () => Promise<Record<string, any>>;
@@ -9,12 +9,12 @@ import { join } from 'path';
9
9
  import { Readable, Transform } from 'stream';
10
10
  import { URL } from 'url';
11
11
  import { proto } from '../../WAProto/index.js';
12
- import { DEFAULT_ORIGIN, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults/index.js';
12
+ import { DEFAULT_ORIGIN, NEWSLETTER_MEDIA_PATH_MAP, MEDIA_HKDF_KEY_MAPPING, MEDIA_PATH_MAP } from '../Defaults/index.js';
13
13
  import { getBinaryNodeChild, getBinaryNodeChildBuffer, jidNormalizedUser } from '../WABinary/index.js';
14
14
  import { aesDecryptGCM, aesEncryptGCM, hkdf } from './crypto.js';
15
15
  import { generateMessageIDV2 } from './generics.js';
16
16
  const getTmpFilesDirectory = () => tmpdir();
17
- const getImageProcessingLibrary = async () => {
17
+ export const getImageProcessingLibrary = async () => {
18
18
  //@ts-ignore
19
19
  const [jimp, sharp] = await Promise.all([import('jimp').catch(() => { }), import('sharp').catch(() => { })]);
20
20
  if (sharp) {
@@ -495,7 +495,7 @@ export function extensionForMediaMessage(message) {
495
495
  return extension;
496
496
  }
497
497
  export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, options }, refreshMediaConn) => {
498
- return async (filePath, { mediaType, fileEncSha256B64, timeoutMs }) => {
498
+ return async (filePath, { mediaType, fileEncSha256B64, timeoutMs, newsletter }) => {
499
499
  // send a query JSON to obtain the url & auth token to upload our media
500
500
  let uploadInfo = await refreshMediaConn(false);
501
501
  let urls;
@@ -504,7 +504,14 @@ export const getWAUploadToServer = ({ customUploadHosts, fetchAgent, logger, opt
504
504
  for (const { hostname } of hosts) {
505
505
  logger.debug(`uploading to "${hostname}"`);
506
506
  const auth = encodeURIComponent(uploadInfo.auth); // the auth token
507
- const url = `https://${hostname}${MEDIA_PATH_MAP[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
507
+ const mediaPathMap = newsletter ? NEWSLETTER_MEDIA_PATH_MAP : MEDIA_PATH_MAP;
508
+ let url = `https://${hostname}${mediaPathMap[mediaType]}/${fileEncSha256B64}?auth=${auth}&token=${fileEncSha256B64}`;
509
+ if (newsletter) {
510
+ url += '&server_thumb_gen=1';
511
+ if (mediaType === 'video' || mediaType === 'gif' || mediaType === 'ptv') {
512
+ url += '&server_transcode=1';
513
+ }
514
+ }
508
515
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
509
516
  let result;
510
517
  try {
@@ -3,13 +3,14 @@ import axios from 'axios';
3
3
  import { randomBytes } from 'crypto';
4
4
  import { promises as fs } from 'fs';
5
5
  import {} from 'stream';
6
+ import { zip } from 'fflate';
6
7
  import { proto } from '../../WAProto/index.js';
7
8
  import { MEDIA_KEYS, URL_REGEX, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
8
9
  import { WAMessageStatus, WAProto } from '../Types/index.js';
9
10
  import { isJidGroup, isJidNewsletter, isJidStatusBroadcast, jidNormalizedUser } from '../WABinary/index.js';
10
11
  import { sha256 } from './crypto.js';
11
12
  import { generateMessageIDV2, getKeyAuthor, unixTimestampSeconds } from './generics.js';
12
- import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData } from './messages-media.js';
13
+ import { downloadContentFromMessage, encryptedStream, generateThumbnail, getAudioDuration, getAudioWaveform, getRawMediaUploadData, getImageProcessingLibrary, getStream, toBuffer } from './messages-media.js';
13
14
  const MIMETYPE_MAP = {
14
15
  image: 'image/jpeg',
15
16
  video: 'video/mp4',
@@ -257,6 +258,205 @@ export const generateForwardMessageContent = (message, forceForward) => {
257
258
  }
258
259
  return content;
259
260
  };
261
+ // AunuBaileys: StickerPack (diadaptasi dari neoxr/baileys & @itsliaaa/baileys)
262
+ function isWebPBuffer(buffer) {
263
+ return (buffer.length >= 12 &&
264
+ buffer[0] === 0x52 &&
265
+ buffer[1] === 0x49 &&
266
+ buffer[2] === 0x46 &&
267
+ buffer[3] === 0x46 &&
268
+ buffer[8] === 0x57 &&
269
+ buffer[9] === 0x45 &&
270
+ buffer[10] === 0x42 &&
271
+ buffer[11] === 0x50);
272
+ }
273
+ function isAnimatedWebP(buffer) {
274
+ // WebP must start with RIFF....WEBP
275
+ if (buffer.length < 12 ||
276
+ buffer[0] !== 0x52 ||
277
+ buffer[1] !== 0x49 ||
278
+ buffer[2] !== 0x46 ||
279
+ buffer[3] !== 0x46 ||
280
+ buffer[8] !== 0x57 ||
281
+ buffer[9] !== 0x45 ||
282
+ buffer[10] !== 0x42 ||
283
+ buffer[11] !== 0x50) {
284
+ return false;
285
+ }
286
+ // Parse chunks starting after RIFF header (12 bytes)
287
+ let offset = 12;
288
+ while (offset < buffer.length - 8) {
289
+ const chunkFourCC = buffer.toString('ascii', offset, offset + 4);
290
+ const chunkSize = buffer.readUInt32LE(offset + 4);
291
+ if (chunkFourCC === 'VP8X') {
292
+ // VP8X extended header, check animation flag (bit 1 at offset+8)
293
+ const flagsOffset = offset + 8;
294
+ if (flagsOffset < buffer.length) {
295
+ const flags = buffer[flagsOffset];
296
+ if (flags & 0x02) {
297
+ return true;
298
+ }
299
+ }
300
+ }
301
+ else if (chunkFourCC === 'ANIM' || chunkFourCC === 'ANMF') {
302
+ // ANIM or ANMF chunks indicate animation
303
+ return true;
304
+ }
305
+ // Move to next chunk (chunk size + 8 bytes header, padded to even)
306
+ offset += 8 + chunkSize + (chunkSize % 2);
307
+ }
308
+ return false;
309
+ }
310
+ async function prepareStickerPackMessage(stickerPack, options) {
311
+ const { stickers, name, publisher, packId, description } = stickerPack;
312
+ if (stickers.length > 60) {
313
+ throw new Boom('Sticker pack exceeds the maximum limit of 60 stickers', { statusCode: 400 });
314
+ }
315
+ if (stickers.length === 0) {
316
+ throw new Boom('Sticker pack must contain at least one sticker', { statusCode: 400 });
317
+ }
318
+ const stickerPackIdValue = packId || generateMessageIDV2();
319
+ const lib = await getImageProcessingLibrary();
320
+ const stickerData = {};
321
+ const stickerPromises = stickers.map(async (s, i) => {
322
+ try {
323
+ const { stream } = await getStream(s.data);
324
+ const buffer = await toBuffer(stream);
325
+ let webpBuffer;
326
+ let isAnimated = false;
327
+ const isWebP = isWebPBuffer(buffer);
328
+ if (isWebP) {
329
+ // Already WebP - preserve original to keep exif metadata and animation
330
+ webpBuffer = buffer;
331
+ isAnimated = isAnimatedWebP(buffer);
332
+ }
333
+ else if ('sharp' in lib && lib.sharp) {
334
+ // Convert to WebP, preserving metadata
335
+ webpBuffer = await lib.sharp.default(buffer).webp().toBuffer();
336
+ // Non-WebP inputs converted to WebP are not animated
337
+ isAnimated = false;
338
+ }
339
+ else {
340
+ throw new Boom('No image processing library (sharp) available for converting sticker to WebP. Either install sharp or provide stickers in WebP format.');
341
+ }
342
+ if (webpBuffer.length > 1024 * 1024) {
343
+ throw new Boom(`Sticker at index ${i} exceeds the 1MB size limit`, { statusCode: 400 });
344
+ }
345
+ const hash = sha256(webpBuffer).toString('base64').replace(/\//g, '-');
346
+ const fileName = `${hash}.webp`;
347
+ stickerData[fileName] = [new Uint8Array(webpBuffer), { level: 0 }];
348
+ return {
349
+ fileName,
350
+ mimetype: 'image/webp',
351
+ isAnimated: s.isAnimated || isAnimated,
352
+ emojis: s.emojis || [],
353
+ accessibilityLabel: s.accessibilityLabel || '',
354
+ isLottie: s.isLottie || false
355
+ };
356
+ }
357
+ catch (e) {
358
+ options.logger?.warn(`Skipping sticker ${i}: ${e.message}`);
359
+ return null;
360
+ }
361
+ });
362
+ const stickerMetadata = (await Promise.all(stickerPromises)).filter(Boolean);
363
+ // Process and add cover/tray icon to the ZIP
364
+ const trayIconFileName = `${stickerPackIdValue}.webp`;
365
+ const { stream: coverStream } = await getStream(stickerPack.cover);
366
+ const coverBuffer = await toBuffer(coverStream);
367
+ let coverWebpBuffer;
368
+ const isCoverWebP = isWebPBuffer(coverBuffer);
369
+ if (isCoverWebP) {
370
+ // Already WebP - preserve original to keep exif metadata
371
+ coverWebpBuffer = coverBuffer;
372
+ }
373
+ else if ('sharp' in lib && lib.sharp) {
374
+ coverWebpBuffer = await lib.sharp.default(coverBuffer).webp().toBuffer();
375
+ }
376
+ else {
377
+ throw new Boom('No image processing library (sharp) available for converting cover to WebP. Either install sharp or provide cover in WebP format.');
378
+ }
379
+ // Add cover to ZIP data
380
+ stickerData[trayIconFileName] = [new Uint8Array(coverWebpBuffer), { level: 0 }];
381
+ const zipBuffer = await new Promise((resolve, reject) => {
382
+ zip(stickerData, (err, data) => {
383
+ if (err) {
384
+ reject(err);
385
+ }
386
+ else {
387
+ resolve(Buffer.from(data));
388
+ }
389
+ });
390
+ });
391
+ const stickerPackSize = zipBuffer.length;
392
+ const stickerPackUpload = await encryptedStream(zipBuffer, 'sticker-pack', {
393
+ logger: options.logger,
394
+ opts: options.options
395
+ });
396
+ const stickerPackUploadResult = await options.upload(stickerPackUpload.encFilePath, {
397
+ fileEncSha256B64: stickerPackUpload.fileEncSha256.toString('base64'),
398
+ mediaType: 'sticker-pack',
399
+ timeoutMs: options.mediaUploadTimeoutMs
400
+ });
401
+ await fs.unlink(stickerPackUpload.encFilePath);
402
+ const stickerPackMessage = {
403
+ name: name,
404
+ publisher: publisher,
405
+ stickerPackId: stickerPackIdValue,
406
+ packDescription: description,
407
+ stickerPackOrigin: WAProto.Message.StickerPackMessage.StickerPackOrigin.USER_CREATED,
408
+ stickerPackSize: stickerPackSize,
409
+ stickers: stickerMetadata,
410
+ fileSha256: stickerPackUpload.fileSha256,
411
+ fileEncSha256: stickerPackUpload.fileEncSha256,
412
+ mediaKey: stickerPackUpload.mediaKey,
413
+ directPath: stickerPackUploadResult.directPath,
414
+ fileLength: stickerPackUpload.fileLength,
415
+ mediaKeyTimestamp: unixTimestampSeconds(),
416
+ trayIconFileName: trayIconFileName
417
+ };
418
+ try {
419
+ // Reuse the cover buffer we already processed for thumbnail generation
420
+ let thumbnailBuffer;
421
+ if ('sharp' in lib && lib.sharp) {
422
+ thumbnailBuffer = await lib.sharp.default(coverBuffer).resize(252, 252).jpeg().toBuffer();
423
+ }
424
+ else if ('jimp' in lib && lib.jimp) {
425
+ const jimpImage = await lib.jimp.Jimp.read(coverBuffer);
426
+ thumbnailBuffer = await jimpImage.resize({ w: 252, h: 252 }).getBuffer('image/jpeg');
427
+ }
428
+ else {
429
+ throw new Error('No image processing library available for thumbnail generation');
430
+ }
431
+ if (!thumbnailBuffer || thumbnailBuffer.length === 0) {
432
+ throw new Error('Failed to generate thumbnail buffer');
433
+ }
434
+ const thumbUpload = await encryptedStream(thumbnailBuffer, 'thumbnail-sticker-pack', {
435
+ logger: options.logger,
436
+ opts: options.options,
437
+ mediaKey: stickerPackUpload.mediaKey // Use same mediaKey as the sticker pack ZIP
438
+ });
439
+ const thumbUploadResult = await options.upload(thumbUpload.encFilePath, {
440
+ fileEncSha256B64: thumbUpload.fileEncSha256.toString('base64'),
441
+ mediaType: 'thumbnail-sticker-pack',
442
+ timeoutMs: options.mediaUploadTimeoutMs
443
+ });
444
+ await fs.unlink(thumbUpload.encFilePath);
445
+ Object.assign(stickerPackMessage, {
446
+ thumbnailDirectPath: thumbUploadResult.directPath,
447
+ thumbnailSha256: thumbUpload.fileSha256,
448
+ thumbnailEncSha256: thumbUpload.fileEncSha256,
449
+ thumbnailHeight: 252,
450
+ thumbnailWidth: 252,
451
+ imageDataHash: sha256(thumbnailBuffer).toString('base64')
452
+ });
453
+ }
454
+ catch (e) {
455
+ options.logger?.warn?.(`Thumbnail generation failed: ${e}`);
456
+ }
457
+ return { stickerPackMessage };
458
+ }
459
+
260
460
  export const generateWAMessageContent = async (message, options) => {
261
461
  var _a, _b;
262
462
  let m = {};
@@ -434,6 +634,28 @@ export const generateWAMessageContent = async (message, options) => {
434
634
  else if ('requestPhoneNumber' in message) {
435
635
  m.requestPhoneNumberMessage = {};
436
636
  }
637
+ else if ('album' in message) {
638
+ // AunuBaileys: album message (dari @itsliaaa/baileys)
639
+ if (!Array.isArray(message.album)) {
640
+ throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
641
+ }
642
+ let videoCount = 0;
643
+ let imageCount = 0;
644
+ for (const item of message.album) {
645
+ if (item.video) videoCount++;
646
+ if (item.image) imageCount++;
647
+ }
648
+ if (videoCount + imageCount < 2) {
649
+ throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
650
+ }
651
+ m.albumMessage = { expectedImageCount: imageCount, expectedVideoCount: videoCount };
652
+ }
653
+ else if ('stickerPack' in message) {
654
+ m = await prepareStickerPackMessage(message.stickerPack, options);
655
+ }
656
+ else if ('stickers' in message && 'cover' in message) {
657
+ m = await prepareStickerPackMessage(message, options);
658
+ }
437
659
  else {
438
660
  m = await prepareWAMessageMedia(message, options);
439
661
  }
@@ -1,5 +1,5 @@
1
1
  import { Mutex } from 'async-mutex';
2
- import { mkdir, readFile, stat, unlink, writeFile } from 'fs/promises';
2
+ import { mkdir, readFile, rename, stat, unlink, writeFile } from 'fs/promises';
3
3
  import { join } from 'path';
4
4
  import { proto } from '../../WAProto/index.js';
5
5
  import { initAuthCreds } from './auth-utils.js';
@@ -32,7 +32,10 @@ export const useMultiFileAuthState = async (folder) => {
32
32
  const mutex = getFileLock(filePath);
33
33
  return mutex.acquire().then(async (release) => {
34
34
  try {
35
- await writeFile(filePath, JSON.stringify(data, BufferJSON.replacer));
35
+ // AunuBaileys: atomic write (tmp + rename) anti-korupsi sesi — terinspirasi @itsliaaa/baileys
36
+ const tmpPath = `${filePath}.tmp-${process.pid}`;
37
+ await writeFile(tmpPath, JSON.stringify(data, BufferJSON.replacer));
38
+ await rename(tmpPath, filePath);
36
39
  }
37
40
  finally {
38
41
  release();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aunubaileys",
3
3
  "type": "module",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "description": "AunuBaileys \u2014 a WhatsApp Web API for bots that never sleep. Full upstream power with the beloved in-memory store restored and pairing that just works. Forged by AunuCore.",
6
6
  "keywords": [
7
7
  "whatsapp",
@@ -46,7 +46,8 @@
46
46
  "pino": "^9.6",
47
47
  "protobufjs": "^7.2.4",
48
48
  "ws": "^8.13.0",
49
- "@adiwajshing/keyed-db": "^0.2.4"
49
+ "@adiwajshing/keyed-db": "^0.2.4",
50
+ "fflate": "^0.8.2"
50
51
  },
51
52
  "devDependencies": {
52
53
  "@eslint/eslintrc": "^3.3.1",