aunubaileys 1.0.9 → 1.0.11

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
@@ -64,6 +64,14 @@ Everything else is pure upstream. When upstream moves, we rebase the patch layer
64
64
 
65
65
 
66
66
 
67
+ ### 🆕 What's new in 1.0.11 / Baru di 1.0.11
68
+
69
+ - 🚑 **Hotfix for broken 1.0.9 / 1.0.10 tarballs** — the published packages exported `./Utils/antiban.js` from `lib/index.js` but the file itself was never committed to the repository, so every install crashed on import with `ERR_MODULE_NOT_FOUND`. The AntiBan module (and its types) is now in the repo, and this release's tarball was verified with a clean install + import test. Also in: RafaelPivots' `INITIAL_PREKEY_COUNT` type correction (30 → 812, matching the runtime) and repository URLs updated to the repo's new home, **AunuCore/AunuBaileys-lib**. If you are on 1.0.9 or 1.0.10, upgrade to 1.0.11.
70
+
71
+ ### 🆕 What's new in 1.0.10 / Baru di 1.0.10
72
+
73
+ - 🛠️ **Reliability hardening against classic upstream weaknesses** — (1) **Retry receipts now get answered even without a custom `getMessage` store:** a built-in per-socket sent-message cache (last 256 relayed messages, whatsmeow-style) backs up your store, ending the silent "message not available" failures behind stuck unreadable/undelivered messages. Disable with `sentMessagesCacheSize: 0`. (2) **Pairing is now single-flight:** `requestPairingCode` refuses to double-fire (double requests invalidate the first code) and refuses outright on already-registered sessions, with clear errors instead of silent corruption. (3) **Handshake timeout raised:** default `connectTimeoutMs` goes 20s → 60s so pairing no longer times out on slow networks.
74
+
67
75
  ### 🆕 What's new in 1.0.9 / Baru di 1.0.9
68
76
 
69
77
  - 🪪 **LID identity layer** — WhatsApp is migrating identities from phone numbers to LIDs, and incoming stanzas already carry both forms. AunuBaileys now learns the mapping from live traffic: every socket exposes `sock.aunuLid`, a bidirectional PN <-> LID store with local `getLIDForPN()` / `getPNForLID()` lookups (no extra server queries), LRU capping, and optional JSON persistence. Prefer a standalone store? `makeLIDStore()` is exported from the package root, fully typed.
@@ -42,7 +42,7 @@ export declare const MEDIA_HKDF_KEY_MAPPING: {
42
42
  };
43
43
  export declare const MEDIA_KEYS: MediaType[];
44
44
  export declare const MIN_PREKEY_COUNT = 5;
45
- export declare const INITIAL_PREKEY_COUNT = 30;
45
+ export declare const INITIAL_PREKEY_COUNT = 812;
46
46
  export declare const DEFAULT_CACHE_TTLS: {
47
47
  SIGNAL_STORE: number;
48
48
  MSG_RETRY: number;
@@ -30,7 +30,7 @@ export const DEFAULT_CONNECTION_CONFIG = {
30
30
  version: version,
31
31
  browser: Browsers.ubuntu('Chrome'),
32
32
  waWebSocketUrl: 'wss://web.whatsapp.com/ws/chat',
33
- connectTimeoutMs: 20000,
33
+ connectTimeoutMs: 60000,
34
34
  keepAliveIntervalMs: 30000,
35
35
  logger: logger.child({ class: 'baileys' }),
36
36
  emitOwnEvents: true,
@@ -7,6 +7,7 @@ import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaul
7
7
  import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
8
  import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
+ import { sentMessageCacheFor } from '../Utils/sent-message-cache.js';
10
11
  import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
11
12
  import { extractGroupMetadata } from './groups.js';
12
13
  import { makeMessagesSocket } from './messages-send.js';
@@ -465,7 +466,7 @@ export const makeMessagesRecvSocket = (config) => {
465
466
  };
466
467
  const sendMessagesAgain = async (key, ids, retryNode) => {
467
468
  // todo: implement a cache to store the last 256 sent messages (copy whatsmeow)
468
- const msgs = await Promise.all(ids.map(id => getMessage({ ...key, id })));
469
+ const msgs = await Promise.all(ids.map(async (id) => (await getMessage({ ...key, id })) ?? sentMessageCacheFor(config).get(key.remoteJid, id)));
469
470
  const remoteJid = key.remoteJid;
470
471
  const participant = key.participant || remoteJid;
471
472
  // if it's the primary jid sending the request
@@ -3,6 +3,7 @@ import { Boom } from '@hapi/boom';
3
3
  import { proto } from '../../WAProto/index.js';
4
4
  import { BIZ_BOT_SUPPORT_PAYLOAD, DEFAULT_CACHE_TTLS, WA_DEFAULT_EPHEMERAL } from '../Defaults/index.js';
5
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
+ import { sentMessageCacheFor } from '../Utils/sent-message-cache.js';
6
7
  import { getUrlInfo } from '../Utils/link-preview.js';
7
8
  import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, isJidGroup, isJidNewsletter, isJidUser, isLidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
8
9
  import { USyncQuery, USyncUser } from '../WAUSync/index.js';
@@ -311,6 +312,7 @@ export const makeMessagesSocket = (config) => {
311
312
  };
312
313
  logger.debug({ msgId }, `sending newsletter message to ${jid}`);
313
314
  await sendNode(stanza);
315
+ sentMessageCacheFor(config).add(jid, msgId, message);
314
316
  return;
315
317
  }
316
318
  if (normalizeMessageContent(message)?.pinInChatMessage) {
@@ -486,6 +488,7 @@ export const makeMessagesSocket = (config) => {
486
488
  logger.debug({ msgId }, `sending message to ${participants.length} devices`);
487
489
  await sendNode(stanza);
488
490
  });
491
+ sentMessageCacheFor(config).add(jid, msgId, message);
489
492
  return msgId;
490
493
  };
491
494
  const getMessageType = (message) => {
@@ -370,11 +370,20 @@ export const makeSocket = (config) => {
370
370
  }
371
371
  end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }));
372
372
  };
373
+ let pairingCodeRequestInFlight = false;
373
374
  const requestPairingCode = async (phoneNumber, customPairingCode) => {
375
+ if (authState.creds.registered) {
376
+ throw new Error('requestPairingCode: this session is already registered — pairing codes are only for new logins');
377
+ }
378
+ if (pairingCodeRequestInFlight) {
379
+ throw new Error('requestPairingCode: a pairing code request is already in flight — wait for it to finish instead of firing another one (double requests invalidate the first code)');
380
+ }
374
381
  const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5));
375
382
  if (customPairingCode && customPairingCode?.length !== 8) {
376
383
  throw new Error('Custom pairing code must be exactly 8 chars');
377
384
  }
385
+ pairingCodeRequestInFlight = true;
386
+ try {
378
387
  authState.creds.pairingCode = pairingCode;
379
388
  authState.creds.me = {
380
389
  id: jidEncode(phoneNumber, 's.whatsapp.net'),
@@ -428,6 +437,10 @@ export const makeSocket = (config) => {
428
437
  ]
429
438
  });
430
439
  return authState.creds.pairingCode;
440
+ }
441
+ finally {
442
+ pairingCodeRequestInFlight = false;
443
+ }
431
444
  };
432
445
  async function generatePairingKey() {
433
446
  const salt = randomBytes(32);
@@ -75,6 +75,8 @@ export type SocketConfig = {
75
75
  * map to store the retry counts for failed messages;
76
76
  * used to determine whether to retry a message or not */
77
77
  msgRetryCounterCache?: CacheStore;
78
+ /** AunuBaileys: capacity of the built-in sent-message cache used to answer retry receipts when `getMessage` has no copy. Default 256; set 0 to disable. */
79
+ sentMessagesCacheSize?: number;
78
80
  /** provide a cache to store a user's device list */
79
81
  userDevicesCache?: CacheStore;
80
82
  /** cache to store call offers */
@@ -0,0 +1,21 @@
1
+ import type { WASocket } from '../Types';
2
+ export declare const gaussianJitter: (mean?: number, sigma?: number) => number;
3
+ export declare class RateLimiter {
4
+ constructor(max: number, windowMs: number);
5
+ acquire(key: string): Promise<number>;
6
+ tryAcquire(key: string): boolean;
7
+ }
8
+ export interface DisconnectClassification { category: string; action: 'stop' | 'restart' | 'reconnect' | 'backoff'; detail: string; }
9
+ export declare const classifyDisconnect: (error: unknown) => DisconnectClassification;
10
+ export interface AunuAntiBanOptions {
11
+ perChatPerMinute?: number; globalPerMinute?: number; minDelayMs?: number; jitterMs?: number;
12
+ warmupDays?: number; warmupStart?: number; typingSimulation?: boolean;
13
+ groupAddMax?: number; groupAddWindowMs?: number; groupCreateMax?: number; groupCreateWindowMs?: number;
14
+ }
15
+ export declare class AunuAntiBan {
16
+ constructor(options?: AunuAntiBanOptions);
17
+ warmupFactor(): number;
18
+ effectiveLimit(base: number): number;
19
+ wrap<T extends WASocket>(sock: T): T;
20
+ }
21
+ export declare const wrapAntiBan: (sock: WASocket, options?: AunuAntiBanOptions) => WASocket;
@@ -0,0 +1,170 @@
1
+ /**
2
+ * AunuAntiBan — lapisan perlindungan akun bawaan AunuBaileys.
3
+ * Konsep terinspirasi baileys-antiban (kobie3717), diimplementasikan ulang
4
+ * secara bersih di dalam paket: rate limiting dengan jitter Gaussian,
5
+ * jadwal warmup akun baru, penjaga operasi grup, simulasi mengetik,
6
+ * dan klasifikasi disconnect. Tanpa telemetri, tanpa jaringan tambahan.
7
+ */
8
+ import { Boom } from '@hapi/boom';
9
+ import { DisconnectReason } from '../Types/index.js';
10
+ import { delay } from './generics.js';
11
+
12
+ /** Jitter Gaussian (Box-Muller), hasil dibatasi >= 0. */
13
+ export const gaussianJitter = (mean = 0, sigma = 1) => {
14
+ let u = 0;
15
+ let v = 0;
16
+ while (u === 0)
17
+ u = Math.random();
18
+ while (v === 0)
19
+ v = Math.random();
20
+ const n = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
21
+ return Math.max(0, mean + n * sigma);
22
+ };
23
+
24
+ /** Rate limiter jendela geser per kunci. */
25
+ export class RateLimiter {
26
+ constructor(max, windowMs) {
27
+ this.max = max;
28
+ this.windowMs = windowMs;
29
+ this.hits = new Map();
30
+ }
31
+ /** Tunggu sampai ada slot untuk key; kembalikan waktu tunggu (ms). */
32
+ async acquire(key) {
33
+ const now = Date.now();
34
+ let arr = this.hits.get(key);
35
+ if (!arr) {
36
+ arr = [];
37
+ this.hits.set(key, arr);
38
+ }
39
+ while (arr.length && now - arr[0] >= this.windowMs)
40
+ arr.shift();
41
+ if (arr.length >= this.max) {
42
+ const wait = this.windowMs - (now - arr[0]) + 5;
43
+ await delay(wait);
44
+ return this.acquire(key);
45
+ }
46
+ arr.push(Date.now());
47
+ return 0;
48
+ }
49
+ /** Cek tanpa menunggu; true bila masih boleh. */
50
+ tryAcquire(key) {
51
+ const now = Date.now();
52
+ let arr = this.hits.get(key);
53
+ if (!arr) {
54
+ arr = [];
55
+ this.hits.set(key, arr);
56
+ }
57
+ while (arr.length && now - arr[0] >= this.windowMs)
58
+ arr.shift();
59
+ if (arr.length >= this.max) {
60
+ return false;
61
+ }
62
+ arr.push(now);
63
+ return true;
64
+ }
65
+ }
66
+
67
+ /** Klasifikasi error disconnect -> kategori + tindakan yang disarankan. */
68
+ export const classifyDisconnect = (error) => {
69
+ const code = error?.output?.statusCode ?? error?.statusCode ?? error;
70
+ switch (code) {
71
+ case DisconnectReason.loggedOut:
72
+ return { category: 'logged_out', action: 'stop', detail: 'Sesi logout — perlu pairing ulang. Jangan auto-reconnect.' };
73
+ case DisconnectReason.restartRequired:
74
+ return { category: 'restart_required', action: 'restart', detail: 'Server meminta restart — hubungkan ulang sekali.' };
75
+ case DisconnectReason.connectionReplaced:
76
+ return { category: 'replaced', action: 'stop', detail: 'Sesi digantikan koneksi lain — periksa perangkat tertaut.' };
77
+ case DisconnectReason.badSession:
78
+ return { category: 'bad_session', action: 'stop', detail: 'Sesi rusak — hapus auth state dan pairing ulang.' };
79
+ case DisconnectReason.multideviceMismatch:
80
+ return { category: 'mismatch', action: 'stop', detail: 'Ketidakcocokan multidevice — pairing ulang.' };
81
+ case 403:
82
+ return { category: 'forbidden', action: 'stop', detail: '403 — kemungkinan dibatasi/diblokir. Berhenti dan periksa akun.' };
83
+ case 429:
84
+ return { category: 'rate_limited', action: 'backoff', detail: '429 — terlalu cepat. Mundur dan turunkan laju kirim.' };
85
+ case DisconnectReason.timedOut:
86
+ case DisconnectReason.connectionLost:
87
+ case DisconnectReason.connectionClosed:
88
+ default:
89
+ return { category: 'transient', action: 'reconnect', detail: 'Gangguan jaringan/server sementara — aman reconnect dengan jeda.' };
90
+ }
91
+ };
92
+
93
+ export class AunuAntiBan {
94
+ constructor(options = {}) {
95
+ this.options = {
96
+ perChatPerMinute: 12,
97
+ globalPerMinute: 60,
98
+ minDelayMs: 700,
99
+ jitterMs: 900,
100
+ warmupDays: 7,
101
+ warmupStart: Date.now(),
102
+ typingSimulation: true,
103
+ groupAddMax: 3,
104
+ groupAddWindowMs: 600000,
105
+ groupCreateMax: 2,
106
+ groupCreateWindowMs: 600000,
107
+ ...options
108
+ };
109
+ const o = this.options;
110
+ this.perChat = new RateLimiter(o.perChatPerMinute, 60000);
111
+ this.global = new RateLimiter(o.globalPerMinute, 60000);
112
+ this.groupAdd = new RateLimiter(o.groupAddMax, o.groupAddWindowMs);
113
+ this.groupCreate = new RateLimiter(o.groupCreateMax, o.groupCreateWindowMs);
114
+ }
115
+ /** Faktor warmup 0.3 -> 1.0 selama warmupDays sejak warmupStart. */
116
+ warmupFactor() {
117
+ const days = (Date.now() - this.options.warmupStart) / 86400000;
118
+ if (days >= this.options.warmupDays) {
119
+ return 1;
120
+ }
121
+ return 0.3 + 0.7 * Math.max(0, days / this.options.warmupDays);
122
+ }
123
+ /** Batasi laju efektif mengikuti warmup. */
124
+ effectiveLimit(base) {
125
+ return Math.max(1, Math.floor(base * this.warmupFactor()));
126
+ }
127
+ /** Terapkan perlindungan ke socket; socket yang sama dikembalikan. */
128
+ wrap(sock) {
129
+ const self = this;
130
+ const origSend = sock.sendMessage.bind(sock);
131
+ sock.sendMessage = async (jid, content, options) => {
132
+ await self.global.acquire('__global__');
133
+ await self.perChat.acquire(jid);
134
+ if (self.options.typingSimulation && (content?.text || content?.caption) && typeof sock.sendPresenceUpdate === 'function') {
135
+ try {
136
+ await sock.sendPresenceUpdate('composing', jid);
137
+ }
138
+ catch { }
139
+ }
140
+ const wait = self.options.minDelayMs * self.warmupFactor() + gaussianJitter(0, self.options.jitterMs / 2);
141
+ if (wait > 0) {
142
+ await delay(wait);
143
+ }
144
+ return origSend(jid, content, options);
145
+ };
146
+ if (typeof sock.groupParticipantsUpdate === 'function') {
147
+ const orig = sock.groupParticipantsUpdate.bind(sock);
148
+ sock.groupParticipantsUpdate = async (jid, participants, action, inviteMessage) => {
149
+ if (action === 'add' && !self.groupAdd.tryAcquire(jid)) {
150
+ throw new Boom('AunuAntiBan: batas tambah anggota grup tercapai — jeda dulu agar akun aman', { statusCode: 429 });
151
+ }
152
+ return orig(jid, participants, action, inviteMessage);
153
+ };
154
+ }
155
+ if (typeof sock.groupCreate === 'function') {
156
+ const origCreate = sock.groupCreate.bind(sock);
157
+ sock.groupCreate = async (subject, participants) => {
158
+ if (!self.groupCreate.tryAcquire('__create__')) {
159
+ throw new Boom('AunuAntiBan: batas buat grup tercapai — jeda dulu agar akun aman', { statusCode: 429 });
160
+ }
161
+ return origCreate(subject, participants);
162
+ };
163
+ }
164
+ sock.aunuAntiBan = self;
165
+ return sock;
166
+ }
167
+ }
168
+
169
+ /** Bungkus socket dengan AunuAntiBan (jalan pintas). */
170
+ export const wrapAntiBan = (sock, options) => new AunuAntiBan(options).wrap(sock);
@@ -0,0 +1,15 @@
1
+ import type { WAMessageContent } from '../Types/index.js';
2
+ export interface SentMessageCache {
3
+ add(remoteJid: string, id: string, message: WAMessageContent): void;
4
+ get(remoteJid: string, id: string): WAMessageContent | undefined;
5
+ readonly size: number;
6
+ clear(): void;
7
+ }
8
+ /**
9
+ * Create a bounded FIFO cache of recently relayed message contents,
10
+ * used to answer retry receipts when the app's `getMessage` store
11
+ * has no copy. Default capacity 256.
12
+ */
13
+ export declare const makeSentMessageCache: (maxEntries?: number) => SentMessageCache;
14
+ /** Returns the per-socket cache bound to a socket config object (internal plumbing, also handy in tests). */
15
+ export declare const sentMessageCacheFor: (config: object) => SentMessageCache;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * AunuBaileys sent-message cache for retry receipts.
3
+ *
4
+ * When a peer cannot decrypt a message we sent, WhatsApp asks for a
5
+ * resend via a retry receipt. Upstream Baileys can only answer when
6
+ * the app supplied a `getMessage` store; with the default
7
+ * `getMessage: async () => undefined` every retry dies with
8
+ * "message not available" and the peer never receives the message
9
+ * (the classic "read but never delivered / stuck unreadable" bug).
10
+ *
11
+ * This cache keeps the last N relayed message contents per socket
12
+ * (whatsmeow-style, default 256) so `sendMessagesAgain` can fall
13
+ * back to it when the app's own `getMessage` has nothing. The app's
14
+ * store always wins; this is only a fallback. Set
15
+ * `sentMessagesCacheSize: 0` in the socket config to disable.
16
+ */
17
+ export const makeSentMessageCache = (maxEntries = 256) => {
18
+ const cache = new Map();
19
+ const keyOf = (remoteJid, id) => `${remoteJid}:${id}`;
20
+ return {
21
+ add: (remoteJid, id, message) => {
22
+ if (!remoteJid || !id || !message || maxEntries <= 0) {
23
+ return;
24
+ }
25
+ const key = keyOf(remoteJid, id);
26
+ cache.delete(key);
27
+ cache.set(key, message);
28
+ while (cache.size > maxEntries) {
29
+ cache.delete(cache.keys().next().value);
30
+ }
31
+ },
32
+ get: (remoteJid, id) => {
33
+ if (!remoteJid || !id) {
34
+ return undefined;
35
+ }
36
+ return cache.get(keyOf(remoteJid, id));
37
+ },
38
+ get size() {
39
+ return cache.size;
40
+ },
41
+ clear: () => cache.clear()
42
+ };
43
+ };
44
+ const cachesByConfig = new WeakMap();
45
+ /**
46
+ * Returns the per-socket sent-message cache bound to a socket
47
+ * config object. Send and receive layers of the same socket share
48
+ * one config object, so a WeakMap keyed by it gives every socket
49
+ * its own isolated cache without any extra plumbing.
50
+ */
51
+ export const sentMessageCacheFor = (config) => {
52
+ let cache = cachesByConfig.get(config);
53
+ if (!cache) {
54
+ const size = typeof config?.sentMessagesCacheSize === 'number' ? config.sentMessagesCacheSize : 256;
55
+ cache = makeSentMessageCache(size);
56
+ cachesByConfig.set(config, cache);
57
+ }
58
+ return cache;
59
+ };
package/lib/index.d.ts CHANGED
@@ -17,3 +17,4 @@ export * from './Utils/aunu-handler';
17
17
  export * from './Utils/builders';
18
18
  export * from './Utils/antiban';
19
19
  export * from './Utils/lid-store.js';
20
+ export * from './Utils/sent-message-cache.js';
package/lib/index.js CHANGED
@@ -16,3 +16,4 @@ export * from './Utils/aunu-handler.js';
16
16
  export * from './Utils/builders.js';
17
17
  export * from './Utils/antiban.js';
18
18
  export * from './Utils/lid-store.js';
19
+ export * from './Utils/sent-message-cache.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aunubaileys",
3
3
  "type": "module",
4
- "version": "1.0.9",
4
+ "version": "1.0.11",
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",
@@ -22,7 +22,7 @@
22
22
  "homepage": "https://docsjs.aunucore.eu.cc",
23
23
  "repository": {
24
24
  "type": "git",
25
- "url": "git+https://github.com/AunuHost/AunuBaileys-lib.git"
25
+ "url": "git+https://github.com/AunuCore/AunuBaileys-lib.git"
26
26
  },
27
27
  "license": "MIT",
28
28
  "author": "AunuCore Creative Network",
@@ -101,7 +101,7 @@
101
101
  "node": ">=20.0.0"
102
102
  },
103
103
  "bugs": {
104
- "url": "https://github.com/AunuHost/AunuBaileys-lib/issues"
104
+ "url": "https://github.com/AunuCore/AunuBaileys-lib/issues"
105
105
  },
106
106
  "publishConfig": {
107
107
  "access": "public"