aunubaileys 1.0.2 → 1.0.4

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
@@ -59,6 +59,21 @@ sock.ev.on('creds.update', saveCreds)
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
61
 
62
+
63
+
64
+ ### 🆕 What's new in 1.0.4 / Baru di 1.0.4
65
+
66
+ - 🎨 **Rich builders** — `AIRich`, `Button`, `ButtonV2`, `Carousel`, `Toolkit` (+ aliases) for Meta AI-style rich responses, native-flow buttons and carousels; `aunuHandler.sendInteractive()` with automatic plain-text fallback on iOS-paired devices.
67
+ - 🖼️ **Profile pictures fixed** — center crop (was top-left) and jimp v1 compatibility restored.
68
+ - 🛡️ **Hardening** — undecodable app-state patches are skipped with a warning instead of killing sync, media downloads honour the message's own host, `getChatId` fails loudly, real `ev.destroy()` cleanup, group presence carries `groupOnlineCount`, `Browsers.android` added.
69
+ - ✅ Already verified in this codebase: PBKDF2 pairing derivation and the anti-spoof protocol-message guard (GHSA-qvv5-jq5g-4cgg class) match the reference implementations.
70
+
71
+ ### 🆕 What's new in 1.0.3 / Baru di 1.0.3
72
+
73
+ - ⚡ **Modern core** — latest WA version `2.3000.1035194821`, 812 pre-keys, tuned WebSocket (no per-message deflate, 100 MB payload).
74
+ - 🧩 **More message types** — quiz polls (newsletter), poll result snapshots, poll updates, payment invites, order messages.
75
+ - 🛠️ **Developer API** — `sock.aunuHandler` (send/reply/react helpers), `fetchAccountReachoutTimelock()`, `fetchNewChatMessageCap()`, `registerSocketEndHandler()`, `newsletterId()` helper.
76
+
62
77
  ### 🆕 What's new in 1.0.2 / Baru di 1.0.2
63
78
 
64
79
  - 📦 **Sticker packs** — send full sticker packs with `stickerPack` (cover + up to 60 stickers).
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": [2, 3000, 1043857760]
2
+ "version": [2, 3000, 1035194821]
3
3
  }
@@ -93,7 +93,7 @@ export const MEDIA_HKDF_KEY_MAPPING = {
93
93
  };
94
94
  export const MEDIA_KEYS = Object.keys(MEDIA_PATH_MAP);
95
95
  export const MIN_PREKEY_COUNT = 5;
96
- export const INITIAL_PREKEY_COUNT = 30;
96
+ export const INITIAL_PREKEY_COUNT = 812;
97
97
  export const DEFAULT_CACHE_TTLS = {
98
98
  SIGNAL_STORE: 5 * 60, // 5 minutes
99
99
  MSG_RETRY: 60 * 60, // 1 hour
@@ -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,
@@ -546,7 +546,9 @@ export const makeChatsSocket = (config) => {
546
546
  if (tag === 'presence') {
547
547
  presence = {
548
548
  lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
549
- lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
549
+ lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined,
550
+ // AunuBaileys: jumlah online grup bila dikirim WhatsApp
551
+ groupOnlineCount: attrs.count ? +attrs.count : undefined
550
552
  };
551
553
  }
552
554
  else if (Array.isArray(content)) {
@@ -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,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';
@@ -242,6 +244,11 @@ export const makeSocket = (config) => {
242
244
  }
243
245
  });
244
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
+ };
245
252
  const end = (error) => {
246
253
  if (closed) {
247
254
  logger.trace({ trace: error?.stack }, 'connection already closed');
@@ -267,7 +274,14 @@ export const makeSocket = (config) => {
267
274
  date: new Date()
268
275
  }
269
276
  });
277
+ for (const handler of socketEndHandlers) {
278
+ try {
279
+ handler();
280
+ }
281
+ catch { }
282
+ }
270
283
  ev.removeAllListeners('connection.update');
284
+ ev.destroy?.();
271
285
  };
272
286
  const waitForSocketOpen = async () => {
273
287
  if (ws.isOpen) {
@@ -577,6 +591,21 @@ export const makeSocket = (config) => {
577
591
  }
578
592
  Object.assign(creds, update);
579
593
  });
594
+ const fetchAccountReachoutTimelock = async () => {
595
+ const queryResult = await executeWMexQuery({}, QueryIds.REACHOUT_TIMELOCK, XWAPaths.xwa2_fetch_account_reachout_timelock, query, generateMessageTag);
596
+ const result = {
597
+ isActive: !!queryResult?.is_active,
598
+ timeEnforcementEnds: queryResult?.time_enforcement_ends && queryResult?.time_enforcement_ends !== '0'
599
+ ? new Date(parseInt(queryResult.time_enforcement_ends, 10) * 1000)
600
+ : undefined,
601
+ enforcementType: queryResult?.enforcement_type ?? 'DEFAULT'
602
+ };
603
+ ev.emit('connection.update', { reachoutTimeLock: result });
604
+ return result;
605
+ };
606
+ const fetchNewChatMessageCap = async () => {
607
+ return executeWMexQuery({ input: { type: 'INDIVIDUAL_NEW_CHAT_MSG' } }, QueryIds.MESSAGE_CAPPING_INFO, XWAPaths.xwa2_message_capping_info, query, generateMessageTag);
608
+ };
580
609
  return {
581
610
  type: 'md',
582
611
  ws,
@@ -594,6 +623,9 @@ export const makeSocket = (config) => {
594
623
  sendNode,
595
624
  logout,
596
625
  end,
626
+ registerSocketEndHandler,
627
+ fetchAccountReachoutTimelock,
628
+ fetchNewChatMessageCap,
597
629
  onUnexpectedError,
598
630
  uploadPreKeys,
599
631
  uploadPreKeysToServerIfRequired,
@@ -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,44 @@
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
+ /**
24
+ * Kirim pesan interaktif; otomatis fallback ke teks polos bila
25
+ * perangkat pasangan adalah iOS (tombol tidak tampil andal di sana)
26
+ * atau bila options.forceFallback diaktifkan.
27
+ */
28
+ async sendInteractive(jid, content, options = {}) {
29
+ const platform = this.sock.authState?.creds?.platform;
30
+ if (options.forceFallback || platform === 'ios') {
31
+ const i = content?.interactive || {};
32
+ const text = options.fallbackText || i.text || i.body?.text || '';
33
+ const rest = { ...options };
34
+ delete rest.forceFallback;
35
+ delete rest.fallbackText;
36
+ return this.sock.sendMessage(jid, { text }, rest);
37
+ }
38
+ return this.sock.sendMessage(jid, content, options);
39
+ }
40
+ /** ID numerik dari JID newsletter. */
41
+ newsletterId(jid) {
42
+ return this.sock.newsletterId(jid);
43
+ }
44
+ }
@@ -0,0 +1,31 @@
1
+ import type { AnyMessageContent } from '../Types';
2
+ export declare class Button {
3
+ title(t: string): this; body(t: string): this; footer(t: string): this;
4
+ reply(id: string, text: string): this; url(text: string, url: string): this;
5
+ copy(text: string, code: string): this; call(text: string, phoneNumber: string): this;
6
+ build(): AnyMessageContent;
7
+ }
8
+ export declare class ButtonV2 {
9
+ body(t: string): this; footer(t: string): this;
10
+ list(buttonText: string, sections: unknown[]): this;
11
+ reply(id: string, text: string): this; url(text: string, url: string): this;
12
+ build(): AnyMessageContent;
13
+ }
14
+ export declare class Carousel {
15
+ body(t: string): this; card(card: Record<string, unknown>): this;
16
+ build(): AnyMessageContent;
17
+ }
18
+ export declare class AIRich {
19
+ text(t: string): this; code(code: string, language?: string): this;
20
+ latex(t: string): this; table(table: unknown): this; raw(sub: unknown): this;
21
+ build(): AnyMessageContent;
22
+ }
23
+ export declare class Toolkit {
24
+ static quickReply(id: string, text: string): unknown;
25
+ static urlButton(text: string, url: string): unknown;
26
+ static copyButton(text: string, code: string): unknown;
27
+ static callButton(text: string, phoneNumber: string): unknown;
28
+ static listSection(title: string, rows: { id: string; title: string; description?: string }[]): unknown;
29
+ static listButton(text: string, title: string, rows: { id: string; title: string; description?: string }[]): unknown;
30
+ }
31
+ export { Button as Buttons, Button as ButtonBuilder, ButtonV2 as ListButton, Carousel as CarouselBuilder, AIRich as AiRich, AIRich as RichResponse, Toolkit as Tools };
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Rich message builders khas AunuBaileys — API chainable untuk tombol,
3
+ * carousel, dan respons kaya ala Meta AI. Hasil build() adalah
4
+ * AnyMessageContent yang siap dikirim via sock.sendMessage().
5
+ */
6
+ const nf = (name, params) => ({ name, buttonParamsJson: JSON.stringify(params) });
7
+
8
+ /** Builder tombol interaktif (native flow). Alias: Buttons, ButtonBuilder. */
9
+ export class Button {
10
+ constructor() {
11
+ this._text = '';
12
+ this._footer = '';
13
+ this._title = '';
14
+ this._buttons = [];
15
+ }
16
+ title(t) { this._title = t; return this; }
17
+ body(t) { this._text = t; return this; }
18
+ footer(t) { this._footer = t; return this; }
19
+ /** Tombol balasan cepat. */
20
+ reply(id, text) { this._buttons.push(nf('quick_reply', { display_text: text, id: String(id) })); return this; }
21
+ /** Tombol buka tautan. */
22
+ url(text, url) { this._buttons.push(nf('cta_url', { display_text: text, url, merchant_url: url })); return this; }
23
+ /** Tombol salin teks/kode. */
24
+ copy(text, code) { this._buttons.push(nf('cta_copy', { display_text: text, copy_code: code, id: 'copy' })); return this; }
25
+ /** Tombol telepon. */
26
+ call(text, phoneNumber) { this._buttons.push(nf('cta_call', { display_text: text, phone_number: phoneNumber })); return this; }
27
+ build() {
28
+ return {
29
+ interactive: {
30
+ text: this._text,
31
+ footerText: this._footer || undefined,
32
+ header: this._title ? { title: this._title, hasMediaAttachment: false } : undefined,
33
+ buttons: this._buttons
34
+ }
35
+ };
36
+ }
37
+ }
38
+
39
+ /** Builder daftar pilihan (single_select) + tombol lanjutan. Alias: ListButton. */
40
+ export class ButtonV2 {
41
+ constructor() {
42
+ this._text = '';
43
+ this._footer = '';
44
+ this._buttons = [];
45
+ this._sections = [];
46
+ }
47
+ body(t) { this._text = t; return this; }
48
+ footer(t) { this._footer = t; return this; }
49
+ /** Satu tombol daftar berisi sections/rows. */
50
+ list(buttonText, sections) {
51
+ this._buttons.push(nf('single_select', { title: buttonText, sections }));
52
+ return this;
53
+ }
54
+ reply(id, text) { this._buttons.push(nf('quick_reply', { display_text: text, id: String(id) })); return this; }
55
+ url(text, url) { this._buttons.push(nf('cta_url', { display_text: text, url, merchant_url: url })); return this; }
56
+ build() {
57
+ return { interactive: { text: this._text, footerText: this._footer || undefined, buttons: this._buttons } };
58
+ }
59
+ }
60
+
61
+ /** Builder carousel kartu geser. Alias: CarouselBuilder. */
62
+ export class Carousel {
63
+ constructor() {
64
+ this._text = '';
65
+ this._cards = [];
66
+ }
67
+ body(t) { this._text = t; return this; }
68
+ /** card: { title, body, footer?, imageMessage?, buttons?: Button instance | array nativeFlow } */
69
+ card(card) {
70
+ let buttons = card.buttons || [];
71
+ if (buttons instanceof Button) {
72
+ buttons = buttons.build().interactive.buttons;
73
+ }
74
+ this._cards.push({ title: card.title, body: card.body || '', footer: card.footer, imageMessage: card.imageMessage, buttons });
75
+ return this;
76
+ }
77
+ build() {
78
+ return { interactive: { text: this._text, carousel: this._cards } };
79
+ }
80
+ }
81
+
82
+ /** Builder respons kaya ala Meta AI (AIRichResponseMessage). Alias: AiRich, RichResponse. */
83
+ export class AIRich {
84
+ constructor() {
85
+ this._subs = [];
86
+ }
87
+ text(t) { this._subs.push({ type: 'text', text: t }); return this; }
88
+ code(code, language = 'text') { this._subs.push({ type: 'code', code, language }); return this; }
89
+ latex(t) { this._subs.push({ type: 'latex', text: t }); return this; }
90
+ /** table: proto tableMetadata mentah { title, rows: [...] } */
91
+ table(table) { this._subs.push({ type: 'table', table }); return this; }
92
+ raw(sub) { this._subs.push({ raw: sub }); return this; }
93
+ build() {
94
+ return { aiRich: { submessages: this._subs } };
95
+ }
96
+ }
97
+
98
+ /** Helper statis campuran. Alias: Tools. */
99
+ export class Toolkit {
100
+ static quickReply(id, text) { return nf('quick_reply', { display_text: text, id: String(id) }); }
101
+ static urlButton(text, url) { return nf('cta_url', { display_text: text, url, merchant_url: url }); }
102
+ static copyButton(text, code) { return nf('cta_copy', { display_text: text, copy_code: code, id: 'copy' }); }
103
+ static callButton(text, phoneNumber) { return nf('cta_call', { display_text: text, phone_number: phoneNumber }); }
104
+ static listSection(title, rows) {
105
+ return { title, rows: rows.map(r => ({ id: String(r.id), title: r.title, description: r.description || '' })) };
106
+ }
107
+ /** Ubah daftar {id, title, description} menjadi satu tombol single_select. */
108
+ static listButton(text, title, rows) {
109
+ return nf('single_select', { title: text, sections: [Toolkit.listSection(title, rows)] });
110
+ }
111
+ }
112
+ export const Buttons = Button;
113
+ export const ButtonBuilder = Button;
114
+ export const ListButton = ButtonV2;
115
+ export const CarouselBuilder = Carousel;
116
+ export const AiRich = AIRich;
117
+ export const RichResponse = AIRich;
118
+ export const Tools = Toolkit;
@@ -286,6 +286,7 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
286
286
  };
287
287
  const mutationMap = {};
288
288
  for (const syncd of syncds) {
289
+ try {
289
290
  const { version, keyId, snapshotMac } = syncd;
290
291
  if (syncd.externalMutations) {
291
292
  logger?.trace({ name, version }, 'downloading external patch');
@@ -318,6 +319,12 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
318
319
  }
319
320
  // clear memory used up by the mutations
320
321
  syncd.mutations = [];
322
+ }
323
+ catch (err) {
324
+ // AunuBaileys: satu patch rusak tidak membunuh seluruh sinkronisasi
325
+ logger?.warn({ err, name, version: syncd.version }, 'skipping undecodable app state patch');
326
+ syncd.mutations = [];
327
+ }
321
328
  }
322
329
  return { state: newState, mutationMap };
323
330
  };
@@ -100,7 +100,21 @@ export const makeEventBuffer = (logger) => {
100
100
  },
101
101
  on: (...args) => ev.on(...args),
102
102
  off: (...args) => ev.off(...args),
103
- removeAllListeners: (...args) => ev.removeAllListeners(...args)
103
+ removeAllListeners: (...args) => ev.removeAllListeners(...args),
104
+ // AunuBaileys: destroy sungguhan — bersihkan buffer, cache & listener
105
+ destroy() {
106
+ try {
107
+ const fresh = makeBufferData();
108
+ for (const key of Object.keys(data)) {
109
+ delete data[key];
110
+ }
111
+ Object.assign(data, fresh);
112
+ }
113
+ catch { }
114
+ historyCache.clear();
115
+ isBuffering = false;
116
+ ev.removeAllListeners();
117
+ }
104
118
  };
105
119
  };
106
120
  const makeBufferData = () => {
@@ -25,6 +25,8 @@ export const Browsers = {
25
25
  macOS: browser => ['Mac OS', browser, '14.4.1'],
26
26
  baileys: browser => ['Baileys', browser, '6.5.0'],
27
27
  windows: browser => ['Windows', browser, '10.0.22631'],
28
+ /** AunuBaileys: sesi yang tampil sebagai Android */
29
+ android: browser => ['Android', browser, '14'],
28
30
  /** The appropriate browser based on your OS & release */
29
31
  appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()]
30
32
  };
@@ -114,7 +114,8 @@ export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
114
114
  }
115
115
  };
116
116
  }
117
- else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
117
+ else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) {
118
+ // AunuBaileys: jimp v1 mengekspos Jimp sebagai class (function), bukan object
118
119
  const jimp = await lib.jimp.Jimp.read(bufferOrFilePath);
119
120
  const dimensions = {
120
121
  width: jimp.width,
@@ -156,10 +157,12 @@ export const generateProfilePicture = async (mediaUpload, dimensions) => {
156
157
  })
157
158
  .toBuffer();
158
159
  }
159
- else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
160
+ else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) {
161
+ // AunuBaileys: jimp v1 mengekspos Jimp sebagai class (function), bukan object
160
162
  const jimp = await lib.jimp.Jimp.read(buffer);
161
163
  const min = Math.min(jimp.width, jimp.height);
162
- const cropped = jimp.crop({ x: 0, y: 0, w: min, h: min });
164
+ // AunuBaileys: crop dari TENGAH (sebelumnya pojok kiri-atas memotong subjek)
165
+ const cropped = jimp.crop({ x: Math.floor((jimp.width - min) / 2), y: Math.floor((jimp.height - min) / 2), w: min, h: min });
163
166
  img = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR }).getBuffer('image/jpeg', { quality: 50 });
164
167
  }
165
168
  else {
@@ -386,8 +389,8 @@ const toSmallestChunkSize = (num) => {
386
389
  };
387
390
  export const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
388
391
  export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
389
- const isValidMediaUrl = url?.startsWith('https://mmg.whatsapp.net/');
390
- const downloadUrl = isValidMediaUrl ? url : getUrlFromDirectPath(directPath);
392
+ // AunuBaileys: pakai host dari URL pesan bila ada (bukan selalu mmg.whatsapp.net)
393
+ const downloadUrl = url?.startsWith('https://') ? url : getUrlFromDirectPath(directPath);
391
394
  if (!downloadUrl) {
392
395
  throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
393
396
  }
@@ -615,6 +615,23 @@ export const generateWAMessageContent = async (message, options) => {
615
615
  // poll v2 is for community announcement groups (single select and multiple)
616
616
  m.pollCreationMessageV2 = pollCreationMessage;
617
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
+ }
618
635
  else {
619
636
  if (message.poll.selectableCount === 1) {
620
637
  //poll v3 is for single select polls
@@ -626,6 +643,87 @@ export const generateWAMessageContent = async (message, options) => {
626
643
  }
627
644
  }
628
645
  }
646
+ else if ('aiRich' in message) {
647
+ // AunuBaileys: Meta AI-style rich response (AIRichResponseMessage)
648
+ const subs = (message.aiRich.submessages || []).map((s) => {
649
+ if (s.type === 'text' || (!s.type && s.text)) {
650
+ return { messageText: { text: s.text || '' } };
651
+ }
652
+ if (s.type === 'code') {
653
+ return { codeMetadata: { codeContent: s.code || s.text || '', codeLanguage: s.language || 'text' } };
654
+ }
655
+ if (s.type === 'latex') {
656
+ return { latexMetadata: { text: s.text || '' } };
657
+ }
658
+ if (s.type === 'table') {
659
+ return { tableMetadata: s.table };
660
+ }
661
+ return s.raw || s;
662
+ });
663
+ m.richResponseMessage = {
664
+ messageType: proto.AIRichResponseMessage.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
665
+ submessages: subs
666
+ };
667
+ if ('text' in message) {
668
+ // teks pendamping opsional diabaikan oleh proto bila rich response ada
669
+ }
670
+ }
671
+ else if ('pollResult' in message) {
672
+ // AunuBaileys: poll result snapshot (dari @itsliaaa/baileys)
673
+ const pollResultSnapshotMessage = {
674
+ name: message.pollResult.name,
675
+ pollVotes: message.pollResult.votes.map(vote => ({
676
+ optionName: vote.name,
677
+ optionVoteCount: parseInt(vote.voteCount)
678
+ }))
679
+ };
680
+ if (message.pollResult.pollType === 1) {
681
+ pollResultSnapshotMessage.pollType = proto.Message.PollCreationMessage.PollType.QUIZ;
682
+ m.pollResultSnapshotMessageV3 = pollResultSnapshotMessage;
683
+ }
684
+ else {
685
+ pollResultSnapshotMessage.pollType = proto.Message.PollCreationMessage.PollType.POLL;
686
+ m.pollResultSnapshotMessage = pollResultSnapshotMessage;
687
+ }
688
+ }
689
+ else if ('pollUpdate' in message) {
690
+ if (!message.pollUpdate.key) {
691
+ throw new Boom('Message key is required', { statusCode: 400 });
692
+ }
693
+ if (!message.pollUpdate.vote) {
694
+ throw new Boom('Encrypted vote payload is required', { statusCode: 400 });
695
+ }
696
+ m.pollUpdateMessage = {
697
+ metadata: message.pollUpdate.metadata,
698
+ pollCreationMessageKey: message.pollUpdate.key,
699
+ senderTimestampMs: Date.now(),
700
+ vote: message.pollUpdate.vote
701
+ };
702
+ }
703
+ else if ('paymentInviteServiceType' in message) {
704
+ // AunuBaileys: payment invite (dari @itsliaaa/baileys)
705
+ m.paymentInviteMessage = {
706
+ expiryTimestamp: Date.now(),
707
+ serviceType: message.paymentInviteServiceType
708
+ };
709
+ }
710
+ else if ('orderText' in message) {
711
+ // AunuBaileys: order message (dari @itsliaaa/baileys)
712
+ if (message.thumbnail !== undefined && !Buffer.isBuffer(message.thumbnail)) {
713
+ throw new Boom('Must provide thumbnail buffer in order message', { statusCode: 400 });
714
+ }
715
+ m.orderMessage = {
716
+ itemCount: message.itemCount || 1,
717
+ messageVersion: 1,
718
+ orderTitle: message.orderTitle || 'AunuBaileys',
719
+ status: proto.Message.OrderMessage.OrderStatus.INQUIRY,
720
+ surface: proto.Message.OrderMessage.OrderSurface.CATALOG,
721
+ totalAmount1000: message.totalAmount1000 || 1000,
722
+ totalCurrencyCode: message.currency || 'IDR',
723
+ thumbnail: message.thumbnail,
724
+ message: message.orderText
725
+ };
726
+ }
629
727
  else if ('sharePhoneNumber' in message) {
630
728
  m.protocolMessage = {
631
729
  type: proto.Message.ProtocolMessage.Type.SHARE_PHONE_NUMBER
@@ -634,6 +732,40 @@ export const generateWAMessageContent = async (message, options) => {
634
732
  else if ('requestPhoneNumber' in message) {
635
733
  m.requestPhoneNumberMessage = {};
636
734
  }
735
+ else if ('interactive' in message) {
736
+ // AunuBaileys: interactive / native-flow (pola @itsliaaa/baileys, disederhanakan)
737
+ const iMsg = message.interactive;
738
+ const interactiveMessage = {
739
+ body: iMsg.body ?? (iMsg.text ? { text: iMsg.text } : undefined),
740
+ footer: iMsg.footer ?? (iMsg.footerText ? { text: iMsg.footerText } : undefined),
741
+ header: iMsg.header,
742
+ contextInfo: iMsg.contextInfo
743
+ };
744
+ if (iMsg.carousel) {
745
+ interactiveMessage.carouselMessage = {
746
+ cards: iMsg.carousel.map(card => ({
747
+ header: card.header ?? (card.title ? { title: card.title, hasMediaAttachment: !!card.imageMessage, imageMessage: card.imageMessage } : undefined),
748
+ body: { text: card.body || '' },
749
+ footer: card.footer ? { text: card.footer } : undefined,
750
+ nativeFlowMessage: { buttons: (card.buttons || []).map(b => ({ name: b.name, buttonParamsJson: typeof b.buttonParamsJson === 'string' ? b.buttonParamsJson : JSON.stringify(b.buttonParamsJson ?? {}) })) }
751
+ })),
752
+ messageVersion: 1
753
+ };
754
+ }
755
+ else {
756
+ interactiveMessage.nativeFlowMessage = {
757
+ buttons: (iMsg.buttons || []).map(b => ({
758
+ name: b.name,
759
+ buttonParamsJson: typeof b.buttonParamsJson === 'string' ? b.buttonParamsJson : JSON.stringify(b.buttonParamsJson ?? {})
760
+ })),
761
+ messageParamsJson: iMsg.messageParamsJson || ''
762
+ };
763
+ }
764
+ m = {
765
+ interactiveMessage,
766
+ messageContextInfo: { deviceListMetadata: {}, deviceListMetadataVersion: 2 }
767
+ };
768
+ }
637
769
  else if ('album' in message) {
638
770
  // AunuBaileys: album message (dari @itsliaaa/baileys)
639
771
  if (!Array.isArray(message.album)) {
@@ -1,3 +1,4 @@
1
+ import Boom from '@hapi/boom';
1
2
  import { proto } from '../../WAProto/index.js';
2
3
  import { WAMessageStubType } from '../Types/index.js';
3
4
  import { getContentType, normalizeMessageContent } from '../Utils/messages.js';
@@ -61,10 +62,12 @@ export const shouldIncrementChatUnread = (message) => !message.key.fromMe && !me
61
62
  * Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
62
63
  */
63
64
  export const getChatId = ({ remoteJid, participant, fromMe }) => {
64
- if (isJidBroadcast(remoteJid) && !isJidStatusBroadcast(remoteJid) && !fromMe) {
65
- return participant;
65
+ // AunuBaileys: gagal dengan jelas, jangan diam-diam memakai undefined
66
+ const id = isJidBroadcast(remoteJid) && !isJidStatusBroadcast(remoteJid) && !fromMe ? participant : remoteJid;
67
+ if (!id) {
68
+ throw new Boom('getChatId: missing remoteJid/participant in message key', { statusCode: 400 });
66
69
  }
67
- return remoteJid;
70
+ return id;
68
71
  };
69
72
  /**
70
73
  * Decrypt a poll vote
package/lib/index.d.ts CHANGED
@@ -13,3 +13,5 @@ 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';
17
+ export * from './Utils/builders';
package/lib/index.js CHANGED
@@ -12,3 +12,5 @@ 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';
16
+ export * from './Utils/builders.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aunubaileys",
3
3
  "type": "module",
4
- "version": "1.0.2",
4
+ "version": "1.0.4",
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",