aunubaileys 1.0.1 → 1.0.3

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,24 @@ 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
+
63
+ ### 🆕 What's new in 1.0.3 / Baru di 1.0.3
64
+
65
+ - ⚡ **Modern core** — latest WA version `2.3000.1035194821`, 812 pre-keys, tuned WebSocket (no per-message deflate, 100 MB payload).
66
+ - 🧩 **More message types** — quiz polls (newsletter), poll result snapshots, poll updates, payment invites, order messages.
67
+ - 🛠️ **Developer API** — `sock.aunuHandler` (send/reply/react helpers), `fetchAccountReachoutTimelock()`, `fetchNewChatMessageCap()`, `registerSocketEndHandler()`, `newsletterId()` helper.
68
+
69
+ ### 🆕 What's new in 1.0.2 / Baru di 1.0.2
70
+
71
+ - 📦 **Sticker packs** — send full sticker packs with `stickerPack` (cover + up to 60 stickers).
72
+ - 🖼️ **Album messages** — send grouped photos/videos with `album`.
73
+ - 📣 **Newsletter media fixed** — media uploads to channels use the proper newsletter paths.
74
+ - 🤖 **AI label** — mark bot replies with `ai: true` in private chats.
75
+ - 🔐 **Sturdier connections & sessions** — pre-key upload timeout + atomic session file writes.
76
+
77
+ **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. 🙏
78
+
61
79
  ---
62
80
 
63
81
  ## 🇮🇩 Bahasa Indonesia
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": [2, 3000, 1043857760]
2
+ "version": [2, 3000, 1035194821]
3
3
  }
@@ -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': '',
@@ -91,11 +93,29 @@ export const MEDIA_HKDF_KEY_MAPPING = {
91
93
  };
92
94
  export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP);
93
95
  export const MIN_PREKEY_COUNT = 5;
94
- export const INITIAL_PREKEY_COUNT = 30;
96
+ export const INITIAL_PREKEY_COUNT = 812;
95
97
  export const DEFAULT_CACHE_TTLS = {
96
98
  SIGNAL_STORE: 5 * 60, // 5 minutes
97
99
  MSG_RETRY: 60 * 60, // 1 hour
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"}';
@@ -23,6 +23,9 @@ export class WebSocketClient extends AbstractSocketClient {
23
23
  return;
24
24
  }
25
25
  this.socket = new WebSocket(this.url, {
26
+ // AunuBaileys: stabilitas WS — tanpa kompresi per-message, payload besar aman
27
+ perMessageDeflate: false,
28
+ maxPayload: 100 * 1024 * 1024,
26
29
  origin: DEFAULT_ORIGIN,
27
30
  headers: this.config.options?.headers,
28
31
  handshakeTimeout: this.config.connectTimeoutMs,
@@ -1,5 +1,6 @@
1
1
  import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
2
2
  import { makeCommunitiesSocket } from './communities.js';
3
+ import { AunuHandler } from '../Utils/aunu-handler.js';
3
4
  // export the last socket layer
4
5
  const makeWASocket = (config) => {
5
6
  const newConfig = {
@@ -11,7 +12,9 @@ const makeWASocket = (config) => {
11
12
  if (config.shouldSyncHistoryMessage === undefined) {
12
13
  newConfig.shouldSyncHistoryMessage = () => !!newConfig.syncFullHistory;
13
14
  }
14
- return makeCommunitiesSocket(newConfig);
15
+ const sock = makeCommunitiesSocket(newConfig);
16
+ sock.aunuHandler = new AunuHandler(sock);
17
+ return sock;
15
18
  };
16
19
  export default makeWASocket;
17
20
  //# sourceMappingURL=index.js.map
@@ -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'));
@@ -1,3 +1,4 @@
1
+ import Boom from '@hapi/boom';
1
2
  import { QueryIds, XWAPaths } from '../Types/index.js';
2
3
  import { generateProfilePicture } from '../Utils/messages-media.js';
3
4
  import { getBinaryNodeChild } from '../WABinary/index.js';
@@ -49,6 +50,13 @@ export const makeNewsletterSocket = (sock) => {
49
50
  };
50
51
  return {
51
52
  ...sock,
53
+ // AunuBaileys: ambil ID numerik dari JID newsletter
54
+ newsletterId: (jid) => {
55
+ if (!jid || !jid.endsWith('@newsletter')) {
56
+ throw new Boom('Invalid newsletter jid', { statusCode: 400 });
57
+ }
58
+ return jid.split('@')[0];
59
+ },
52
60
  newsletterCreate: async (name, description) => {
53
61
  const variables = {
54
62
  input: {
@@ -4,7 +4,9 @@ import { URL } from 'url';
4
4
  import { promisify } from 'util';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEF_CALLBACK_PREFIX, DEF_TAG_PREFIX, INITIAL_PREKEY_COUNT, MIN_PREKEY_COUNT, NOISE_WA_HEADER } from '../Defaults/index.js';
7
- import { DisconnectReason } from '../Types/index.js';
7
+ import { DisconnectReason } from '../Types/index.js'
8
+ import { executeWMexQuery } from './mex.js';
9
+ import { QueryIds, XWAPaths } from '../Types/index.js';
8
10
  import { addTransactionCapability, aesEncryptCTR, bindWaitForConnectionUpdate, bytesToCrockford, configureSuccessfulPairing, Curve, derivePairingCodeKey, generateLoginNode, generateMdTagPrefix, generateRegistrationNode, getCodeFromWSError, getErrorCodeFromStreamError, getNextPreKeysNode, getPlatformId, makeEventBuffer, makeNoiseHandler, promiseTimeout } from '../Utils/index.js';
9
11
  import { assertNodeErrorFree, binaryNodeToString, encodeBinaryNode, getBinaryNodeChild, getBinaryNodeChildren, jidEncode, S_WHATSAPP_NET } from '../WABinary/index.js';
10
12
  import { WebSocketClient } from './Client/index.js';
@@ -191,7 +193,15 @@ export const makeSocket = (config) => {
191
193
  await keys.transaction(async () => {
192
194
  logger.info({ count }, 'uploading pre-keys');
193
195
  const { update, node } = await getNextPreKeysNode({ creds, keys }, count);
194
- await query(node);
196
+ // AunuBaileys: timeout upload pre-keys agar koneksi tidak menggantung (dari MichelleBot/baileys)
197
+ await promiseTimeout(30000, async (resolve, reject) => {
198
+ try {
199
+ resolve(await query(node));
200
+ }
201
+ catch (error) {
202
+ reject(error);
203
+ }
204
+ });
195
205
  ev.emit('creds.update', update);
196
206
  logger.info({ count }, 'uploaded pre-keys');
197
207
  });
@@ -234,6 +244,11 @@ export const makeSocket = (config) => {
234
244
  }
235
245
  });
236
246
  };
247
+ // AunuBaileys: socket end handlers (pola v7) + fetch Mex (dari @itsliaaa/baileys)
248
+ const socketEndHandlers = [];
249
+ const registerSocketEndHandler = (handler) => {
250
+ socketEndHandlers.push(handler);
251
+ };
237
252
  const end = (error) => {
238
253
  if (closed) {
239
254
  logger.trace({ trace: error?.stack }, 'connection already closed');
@@ -259,6 +274,12 @@ export const makeSocket = (config) => {
259
274
  date: new Date()
260
275
  }
261
276
  });
277
+ for (const handler of socketEndHandlers) {
278
+ try {
279
+ handler();
280
+ }
281
+ catch { }
282
+ }
262
283
  ev.removeAllListeners('connection.update');
263
284
  };
264
285
  const waitForSocketOpen = async () => {
@@ -569,6 +590,21 @@ export const makeSocket = (config) => {
569
590
  }
570
591
  Object.assign(creds, update);
571
592
  });
593
+ const fetchAccountReachoutTimelock = async () => {
594
+ const queryResult = await executeWMexQuery({}, QueryIds.REACHOUT_TIMELOCK, XWAPaths.xwa2_fetch_account_reachout_timelock, query, generateMessageTag);
595
+ const result = {
596
+ isActive: !!queryResult?.is_active,
597
+ timeEnforcementEnds: queryResult?.time_enforcement_ends && queryResult?.time_enforcement_ends !== '0'
598
+ ? new Date(parseInt(queryResult.time_enforcement_ends, 10) * 1000)
599
+ : undefined,
600
+ enforcementType: queryResult?.enforcement_type ?? 'DEFAULT'
601
+ };
602
+ ev.emit('connection.update', { reachoutTimeLock: result });
603
+ return result;
604
+ };
605
+ const fetchNewChatMessageCap = async () => {
606
+ return executeWMexQuery({ input: { type: 'INDIVIDUAL_NEW_CHAT_MSG' } }, QueryIds.MESSAGE_CAPPING_INFO, XWAPaths.xwa2_message_capping_info, query, generateMessageTag);
607
+ };
572
608
  return {
573
609
  type: 'md',
574
610
  ws,
@@ -586,6 +622,9 @@ export const makeSocket = (config) => {
586
622
  sendNode,
587
623
  logout,
588
624
  end,
625
+ registerSocketEndHandler,
626
+ fetchAccountReachoutTimelock,
627
+ fetchNewChatMessageCap,
589
628
  onUnexpectedError,
590
629
  uploadPreKeys,
591
630
  uploadPreKeysToServerIfRequired,
@@ -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 = {
@@ -12,6 +12,8 @@ export var XWAPaths;
12
12
  XWAPaths["xwa2_newsletter_change_owner"] = "xwa2_newsletter_change_owner";
13
13
  XWAPaths["xwa2_newsletter_demote"] = "xwa2_newsletter_demote";
14
14
  XWAPaths["xwa2_newsletter_delete_v2"] = "xwa2_newsletter_delete_v2";
15
+ XWAPaths["xwa2_fetch_account_reachout_timelock"] = "xwa2_fetch_account_reachout_timelock";
16
+ XWAPaths["xwa2_message_capping_info"] = "xwa2_message_capping_info";
15
17
  })(XWAPaths || (XWAPaths = {}));
16
18
  export var QueryIds;
17
19
  (function (QueryIds) {
@@ -27,5 +29,7 @@ export var QueryIds;
27
29
  QueryIds["CHANGE_OWNER"] = "7341777602580933";
28
30
  QueryIds["DEMOTE"] = "6551828931592903";
29
31
  QueryIds["DELETE"] = "30062808666639665";
32
+ QueryIds["REACHOUT_TIMELOCK"] = "23983697327930364";
33
+ QueryIds["MESSAGE_CAPPING_INFO"] = "24503548349331633";
30
34
  })(QueryIds || (QueryIds = {}));
31
35
  //# sourceMappingURL=Newsletter.js.map
@@ -0,0 +1,11 @@
1
+ import type { AnyMessageContent, MiscMessageGenerationOptions, WAMessage, WASocket } from '../Types';
2
+ export declare class AunuHandler {
3
+ sock: WASocket;
4
+ readonly name: string;
5
+ readonly brand: string;
6
+ constructor(sock: WASocket);
7
+ send(jid: string, content: AnyMessageContent, options?: MiscMessageGenerationOptions): Promise<WAMessage | undefined>;
8
+ reply(jid: string, content: AnyMessageContent, quoted: WAMessage, options?: MiscMessageGenerationOptions): Promise<WAMessage | undefined>;
9
+ react(jid: string, emoji: string, key: WAMessage['key']): Promise<WAMessage | undefined>;
10
+ newsletterId(jid: string): string;
11
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * AunuHandler — lapisan kenyamanan khas AunuBaileys di atas socket.
3
+ * Diekspos sebagai `sock.aunuHandler` dan diekspor sebagai kelas dari paket.
4
+ */
5
+ export class AunuHandler {
6
+ constructor(sock) {
7
+ this.sock = sock;
8
+ this.name = 'AunuBaileys';
9
+ this.brand = 'AunuCore Creative Network';
10
+ }
11
+ /** Kirim pesan apa pun (delegate ke sock.sendMessage). */
12
+ async send(jid, content, options) {
13
+ return this.sock.sendMessage(jid, content, options);
14
+ }
15
+ /** Balas sebuah pesan (quote). */
16
+ async reply(jid, content, quoted, options = {}) {
17
+ return this.sock.sendMessage(jid, content, { ...options, quoted });
18
+ }
19
+ /** Beri reaksi emoji ke sebuah pesan. */
20
+ async react(jid, emoji, key) {
21
+ return this.sock.sendMessage(jid, { react: { text: emoji, key } });
22
+ }
23
+ /** ID numerik dari JID newsletter. */
24
+ newsletterId(jid) {
25
+ return this.sock.newsletterId(jid);
26
+ }
27
+ }
@@ -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 = {};
@@ -415,6 +615,23 @@ export const generateWAMessageContent = async (message, options) => {
415
615
  // poll v2 is for community announcement groups (single select and multiple)
416
616
  m.pollCreationMessageV2 = pollCreationMessage;
417
617
  }
618
+ else if (message.poll.pollType === 1) {
619
+ // AunuBaileys: kuis (dari @itsliaaa/baileys) — untuk newsletter
620
+ if (!message.poll.correctAnswer) {
621
+ throw new Boom('No "correctAnswer" provided for quiz', { statusCode: 400 });
622
+ }
623
+ // proto 6.7.24: V5 adalah FutureProofMessage — konten dibungkus di .message
624
+ m.pollCreationMessageV5 = {
625
+ message: {
626
+ pollCreationMessage: {
627
+ ...pollCreationMessage,
628
+ correctAnswer: { optionName: message.poll.correctAnswer.toString() },
629
+ pollType: 1,
630
+ selectableOptionsCount: 1
631
+ }
632
+ }
633
+ };
634
+ }
418
635
  else {
419
636
  if (message.poll.selectableCount === 1) {
420
637
  //poll v3 is for single select polls
@@ -426,6 +643,62 @@ export const generateWAMessageContent = async (message, options) => {
426
643
  }
427
644
  }
428
645
  }
646
+ else if ('pollResult' in message) {
647
+ // AunuBaileys: poll result snapshot (dari @itsliaaa/baileys)
648
+ const pollResultSnapshotMessage = {
649
+ name: message.pollResult.name,
650
+ pollVotes: message.pollResult.votes.map(vote => ({
651
+ optionName: vote.name,
652
+ optionVoteCount: parseInt(vote.voteCount)
653
+ }))
654
+ };
655
+ if (message.pollResult.pollType === 1) {
656
+ pollResultSnapshotMessage.pollType = proto.Message.PollCreationMessage.PollType.QUIZ;
657
+ m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
658
+ }
659
+ else {
660
+ pollResultSnapshotMessage.pollType = proto.Message.PollCreationMessage.PollType.POLL;
661
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage;
662
+ }
663
+ }
664
+ else if ('pollUpdate' in message) {
665
+ if (!message.pollUpdate.key) {
666
+ throw new Boom('Message key is required', { statusCode: 400 });
667
+ }
668
+ if (!message.pollUpdate.vote) {
669
+ throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
670
+ }
671
+ m.pollUpdateMessage = {
672
+ metadata: message.pollUpdate.metadata,
673
+ pollCreationMessageKey: message.pollUpdate.key,
674
+ senderTimestampMs: Date.now(),
675
+ vote: message.pollUpdate.vote
676
+ };
677
+ }
678
+ else if ('paymentInviteServiceType' in message) {
679
+ // AunuBaileys: payment invite (dari @itsliaaa/baileys)
680
+ m.paymentInviteMessage = {
681
+ expiryTimestamp: Date.now(),
682
+ serviceType: message.paymentInviteServiceType
683
+ };
684
+ }
685
+ else if ('orderText' in message) {
686
+ // AunuBaileys: order message (dari @itsliaaa/baileys)
687
+ if (message.thumbnail !== undefined && !Buffer.isBuffer(message.thumbnail)) {
688
+ throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
689
+ }
690
+ m.orderMessage = {
691
+ itemCount: message.itemCount || 1,
692
+ messageVersion: 1,
693
+ orderTitle: message.orderTitle || 'AunuBaileys',
694
+ status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
695
+ surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
696
+ totalAmount1000: message.totalAmount1000 || 1000,
697
+ totalCurrencyCode: message.currency || 'IDR',
698
+ thumbnail: message.thumbnail,
699
+ message: message.orderText
700
+ };
701
+ }
429
702
  else if ('sharePhoneNumber' in message) {
430
703
  m.protocolMessage = {
431
704
  type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
@@ -434,6 +707,28 @@ export const generateWAMessageContent = async (message, options) => {
434
707
  else if ('requestPhoneNumber' in message) {
435
708
  m.requestPhoneNumberMessage = {};
436
709
  }
710
+ else if ('album' in message) {
711
+ // AunuBaileys: album message (dari @itsliaaa/baileys)
712
+ if (!Array.isArray(message.album)) {
713
+ throw new Boom('Invalid album type. Expected an array.', { statusCode: 400 });
714
+ }
715
+ let videoCount = 0;
716
+ let imageCount = 0;
717
+ for (const item of message.album) {
718
+ if (item.video) videoCount++;
719
+ if (item.image) imageCount++;
720
+ }
721
+ if (videoCount + imageCount < 2) {
722
+ throw new Boom('Minimum provide 2 media to upload album message', { statusCode: 400 });
723
+ }
724
+ m.albumMessage = { expectedImageCount: imageCount, expectedVideoCount: videoCount };
725
+ }
726
+ else if ('stickerPack' in message) {
727
+ m = await prepareStickerPackMessage(message.stickerPack, options);
728
+ }
729
+ else if ('stickers' in message && 'cover' in message) {
730
+ m = await prepareStickerPackMessage(message, options);
731
+ }
437
732
  else {
438
733
  m = await prepareWAMessageMedia(message, options);
439
734
  }
@@ -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/lib/index.d.ts CHANGED
@@ -13,3 +13,4 @@ export default makeWASocket;
13
13
  export * from './Store/index.js';
14
14
  export { processedMessages, initInMemoryKeyStore } from './Utils/fork-compat.js';
15
15
  export { PHONENUMBER_MCC } from './Utils/phonenumber-mcc.js';
16
+ export * from './Utils/aunu-handler';
package/lib/index.js CHANGED
@@ -12,3 +12,4 @@ export default makeWASocket;
12
12
  export * from './Store/index.js';
13
13
  export { processedMessages, initInMemoryKeyStore } from './Utils/fork-compat.js';
14
14
  export { PHONENUMBER_MCC } from './Utils/phonenumber-mcc.js';
15
+ export * from './Utils/aunu-handler.js';
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.3",
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",