aunubaileys 1.0.47 β†’ 1.0.49

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.49 / Baru di 1.0.49 β€” EXPERIMENTAL
68
+
69
+ - πŸͺͺ **Personalised tag-all: every member sees themselves.** New socket method `sock.aunuPersonalTagAll(groupJid, options)`: for EVERY group member the bot sends one card carrying **that member's own profile picture** and their own name, mentioning exactly them β€” not one generic tag-all message. Pictures come from `profilePictureUrl` (the PN form is used when a member is LID-addressed and the LID store knows the pair); the caption is built from a template with `{name}`, `{number}`, `{mention}`, `{jid}` placeholders; names come from a consumer-supplied `getName` resolver when provided, falling back to the number. Members without a profile picture get a text card, or the `fallbackImage` when set. Built-in anti-spam pacing: a delay between sends (`delayMs`, default 1500) and a member cap (`maxMembers`, default 30); `dm: true` delivers each card to the member's private chat instead. Returns a `{ total, sent, failed, skipped }` summary. Experimental, like the five features of 1.0.47.
70
+
71
+ ### πŸ†• What's new in 1.0.48 / Baru di 1.0.48
72
+
73
+ - 🩹 **Group Bad MAC now heals both layers.** The "Bad MAC" error (from Tenka's live log on 11 Nov 2026, the same LID address as the B-01 finding and the 1.0.46 flood) is thrown by `SessionCipher` β€” the whisper/E2E session layer β€” and in groups it can equally come from the sender-key layer. The old strike-limit path (1.0.14) for groups deleted only the sender key, so a rotten E2E session of the author was never touched and the failures repeated forever: the same wrong-layer class that 1.0.46 closed for session failures, now closed on the strike path. At the 3-failure limit, groups now reset the author's E2E session (including the alternate PN↔LID address record) AND drop the sender key, with the retry receipt carrying fresh keys. DM behaviour and the 3-strike threshold are unchanged. The `messages.decrypt-failure` event now distinguishes `errorClass: 'bad-mac'` from `'session'`/`'other'` (new exported predicate: `isBadMacError(text)`).
74
+
67
75
  ### πŸ§ͺ What's new in 1.0.47 / Baru di 1.0.47 β€” EXPERIMENTAL
68
76
 
69
77
  - πŸ§ͺ **Five experimental features that exist only in AunuBaileys**, in a single version. All are attached automatically to the socket returned by `makeWASocket` and are explicitly experimental β€” semantics may change in any release: (1) **Invisible text watermark** β€” `sock.aunuWatermark(text, tag)` hides a short tag (bot name, campaign, build) inside message text as zero-width characters for leak tracing and fingerprinting; the visible text is unchanged; companions `aunuReadWatermark(text)` and `aunuStripWatermark(text)`, plus root utils `encodeWatermark`/`appendWatermark`/`extractWatermark`/`stripWatermark`. (2) **Human send** β€” `sock.aunuHumanSend(jid, content)`: shows "composing" for a plausible typing duration derived from the text length (util `computeTypingDuration`, clamped 700–8000 ms, Β±20% jitter), then sends. (3) **Scheduled send** β€” `sock.aunuSchedule(jid, content, when)` (ms delay or Date) with `aunuCancelScheduled(id)` and `aunuListScheduled()`; in-memory, lost on restart. (4) **Self-destruct** β€” `sock.aunuSelfDestruct(jid, content, seconds)`: sends, then revokes the message for everyone after N seconds; `aunuCancelSelfDestruct(messageId)` cancels. (5) **Connection doctor** β€” `sock.aunuDoctor()`: one structured health snapshot (bundled WA version, registration state, open transport, known LID pair count, pending experimental timers).
@@ -17,6 +17,27 @@ export interface AunuScheduledSend {
17
17
  at: number;
18
18
  cancel: () => boolean;
19
19
  }
20
+ export interface AunuPersonalTagAllOptions {
21
+ /** Caption template; placeholders {name}, {number}, {mention}, {jid}. Default 'Hai {mention} πŸ‘‹'. */
22
+ text?: string;
23
+ /** Pause between per-member sends in ms. Default 1500. */
24
+ delayMs?: number;
25
+ /** Maximum members processed. Default 30. */
26
+ maxMembers?: number;
27
+ /** Send each card as a DM to the member instead of into the group. Default false. */
28
+ dm?: boolean;
29
+ /** Image (Buffer or URL) used when a member has no profile picture. Without it, that member gets a text card. */
30
+ fallbackImage?: Buffer | string;
31
+ /** Resolve a display name for a participant JID; falls back to the number. */
32
+ getName?: (jid: string) => string | undefined | Promise<string | undefined>;
33
+ messageOptions?: MiscMessageGenerationOptions;
34
+ }
35
+ export interface AunuPersonalTagAllResult {
36
+ total: number;
37
+ sent: number;
38
+ failed: { jid: string; error: string }[];
39
+ skipped: number;
40
+ }
20
41
  export interface AunuExperimentalAPI {
21
42
  aunuWatermark: (text: string, tag: string) => string;
22
43
  aunuReadWatermark: (text: string) => string | null;
@@ -28,5 +49,6 @@ export interface AunuExperimentalAPI {
28
49
  aunuSelfDestruct: (jid: string, content: AnyMessageContent, seconds: number, options?: MiscMessageGenerationOptions) => Promise<WAMessage | undefined>;
29
50
  aunuCancelSelfDestruct: (messageId: string) => boolean;
30
51
  aunuDoctor: () => AunuDoctorReport;
52
+ aunuPersonalTagAll: (groupJid: string, options?: AunuPersonalTagAllOptions) => Promise<AunuPersonalTagAllResult>;
31
53
  }
32
54
  export declare const attachAunuExperimental: <T>(sock: T) => T & AunuExperimentalAPI;
@@ -1,5 +1,6 @@
1
1
  import baileysVersion from '../Defaults/baileys-version.json' with { type: 'json' };
2
2
  import { appendWatermark, computeTypingDuration, extractWatermark, stripWatermark } from '../Utils/aunu-experimental.js';
3
+ import { isLidUser } from '../WABinary/jid-utils.js';
3
4
  const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
4
5
  /**
5
6
  * AunuBaileys EXPERIMENTAL socket features β€” these exist only in
@@ -19,6 +20,11 @@ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
19
20
  * 5. Connection doctor β€” `aunuDoctor`: one structured health snapshot
20
21
  * (registration, transport, bundled WA version, known LID pairs,
21
22
  * pending experimental timers).
23
+ * 6. Personalised tag-all β€” `aunuPersonalTagAll`: for EVERY group member,
24
+ * one message carrying that member's OWN profile picture and name,
25
+ * mentioning exactly them β€” each member sees themselves, not a
26
+ * generic tag-all. Paced sends + a member cap guard against spam
27
+ * limits.
22
28
  */
23
29
  export const attachAunuExperimental = (sock) => {
24
30
  const scheduled = new Map();
@@ -88,6 +94,56 @@ export const attachAunuExperimental = (sock) => {
88
94
  destructs.delete(messageId);
89
95
  return true;
90
96
  };
97
+ sock.aunuPersonalTagAll = async (groupJid, options = {}) => {
98
+ const metadata = await sock.groupMetadata(groupJid);
99
+ const all = metadata?.participants || [];
100
+ const participants = all.slice(0, options.maxMembers ?? 30);
101
+ const template = options.text || 'Hai {mention} πŸ‘‹';
102
+ const delayMs = options.delayMs ?? 1500;
103
+ const results = { total: all.length, sent: 0, failed: [], skipped: all.length - participants.length };
104
+ let first = true;
105
+ for (const participant of participants) {
106
+ const pid = participant.id;
107
+ if (!first && delayMs > 0) {
108
+ await delay(delayMs);
109
+ }
110
+ first = false;
111
+ try {
112
+ const pn = isLidUser(pid) ? (sock.aunuLid?.getPNForLID?.(pid) || pid) : pid;
113
+ const number = pn.split('@')[0].split(':')[0];
114
+ let name = number;
115
+ if (typeof options.getName === 'function') {
116
+ name = (await options.getName(pid)) || number;
117
+ }
118
+ let caption = template
119
+ .replaceAll('{name}', name)
120
+ .replaceAll('{number}', number)
121
+ .replaceAll('{mention}', '@' + number)
122
+ .replaceAll('{jid}', pid);
123
+ if (!caption.includes('@' + number)) {
124
+ caption += ' @' + number;
125
+ }
126
+ let ppUrl;
127
+ try {
128
+ ppUrl = await sock.profilePictureUrl(pn, 'image');
129
+ }
130
+ catch {
131
+ ppUrl = undefined;
132
+ }
133
+ const content = ppUrl
134
+ ? { image: { url: ppUrl }, caption, mentions: [pid] }
135
+ : options.fallbackImage
136
+ ? { image: Buffer.isBuffer(options.fallbackImage) ? options.fallbackImage : { url: options.fallbackImage }, caption, mentions: [pid] }
137
+ : { text: caption, mentions: [pid] };
138
+ await sock.sendMessage(options.dm ? pn : groupJid, content, options.messageOptions || {});
139
+ results.sent += 1;
140
+ }
141
+ catch (err) {
142
+ results.failed.push({ jid: pid, error: String(err?.message || err) });
143
+ }
144
+ }
145
+ return results;
146
+ };
91
147
  sock.aunuDoctor = () => ({
92
148
  experimental: true,
93
149
  bundledWaVersion: Array.isArray(baileysVersion.version) ? baileysVersion.version.join('.') : null,
@@ -5,7 +5,7 @@ import Long from 'long';
5
5
  import { proto } from '../../WAProto/index.js';
6
6
  import { DEFAULT_CACHE_TTLS, KEY_BUNDLE_TYPE, MIN_PREKEY_COUNT } from '../Defaults/index.js';
7
7
  import { WAMessageStatus, WAMessageStubType } from '../Types/index.js';
8
- import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, isUnrecoverableSessionError, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, buildCallControlStanza, parseRetryReceiptSession, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
8
+ import { aesDecryptCTR, aesEncryptGCM, cleanMessage, Curve, decodeMediaRetryNode, decodeMessageNode, decryptMessageNode, delay, derivePairingCodeKey, encodeBigEndian, encodeSignedDeviceIdentity, getCallStatusFromNode, getHistoryMsg, getNextPreKeys, getStatusFromReceiptType, hkdf, isBadMacError, isUnrecoverableSessionError, MISSING_KEYS_ERROR_TEXT, NACK_REASONS, NO_MESSAGE_FOUND_ERROR_TEXT, buildCallControlStanza, parseRetryReceiptSession, unixTimestampSeconds, xmppPreKey, xmppSignedPreKey } from '../Utils/index.js';
9
9
  import { makeMutex } from '../Utils/make-mutex.js';
10
10
  import { sentMessageCacheFor } from '../Utils/sent-message-cache.js';
11
11
  import { areJidsSameUser, getAllBinaryNodeChildren, getBinaryNodeChild, getBinaryNodeChildBuffer, getBinaryNodeChildren, getBinaryNodeChildString, isJidGroup, isJidStatusBroadcast, isJidUser, isLidUser, jidDecode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
@@ -789,8 +789,15 @@ export const makeMessagesRecvSocket = (config) => {
789
789
  }
790
790
  else if (fails >= DECRYPT_FAIL_SESSION_RESET_LIMIT) {
791
791
  if (isGroupMsg) {
792
+ // AunuBaileys (1.0.48): Bad MAC adalah error
793
+ // lapis whisper/sesi E2E (SessionCipher), dan
794
+ // di grup ia juga bisa datang dari lapis
795
+ // sender key β€” menghapus sender key SAJA
796
+ // (perilaku 1.0.14) tidak menyentuh sesi E2E
797
+ // penulis yang busuk. Bersihkan keduanya.
798
+ await resetE2ESession();
792
799
  await signalRepository.deleteSenderKey(msg.key.remoteJid, author);
793
- logger.warn({ author, group: msg.key.remoteJid }, 'aunu: deleted stale sender-key record after repeated group decrypt failures; retry request asks the author to resend with a fresh distribution message');
800
+ logger.warn({ author, group: msg.key.remoteJid }, 'aunu: reset E2E session and deleted stale sender-key record after repeated group decrypt failures; retry receipt carries fresh keys');
794
801
  }
795
802
  else {
796
803
  await resetE2ESession();
@@ -813,7 +820,7 @@ export const makeMessagesRecvSocket = (config) => {
813
820
  isGroup: isGroupMsg,
814
821
  failures: fails,
815
822
  healed,
816
- errorClass: sessionLayerFailure ? 'session' : 'other',
823
+ errorClass: sessionLayerFailure ? 'session' : (isBadMacError(msg?.messageStubParameters?.[0]) ? 'bad-mac' : 'other'),
817
824
  messageId: node.attrs.id,
818
825
  timestamp: Date.now()
819
826
  });
@@ -3,6 +3,7 @@ import { type BinaryNode } from '../WABinary/index.js';
3
3
  import type { ILogger } from './logger.js';
4
4
  export declare const NO_MESSAGE_FOUND_ERROR_TEXT = "Message absent from node";
5
5
  export declare const isUnrecoverableSessionError: (text: unknown) => boolean;
6
+ export declare const isBadMacError: (text: unknown) => boolean;
6
7
  export declare const MISSING_KEYS_ERROR_TEXT = "Key used already or never filled";
7
8
  export declare const NACK_REASONS: {
8
9
  ParsingError: number;
@@ -227,4 +227,11 @@ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
227
227
  */
228
228
  export const isUnrecoverableSessionError = (text) => typeof text === 'string' &&
229
229
  /no matching sessions found|no session record|no sessions available/i.test(text);
230
+ /**
231
+ * AunuBaileys: true when a decrypt failure text is a MAC verification
232
+ * failure ("Bad MAC") β€” the stored session/sender-key state and the
233
+ * sender's state disagree about the message authentication code, the
234
+ * classic symptom of a desynchronised ratchet on either side.
235
+ */
236
+ export const isBadMacError = (text) => typeof text === 'string' && /bad mac/i.test(text);
230
237
  //# sourceMappingURL=decode-wa-message.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aunubaileys",
3
3
  "type": "module",
4
- "version": "1.0.47",
4
+ "version": "1.0.49",
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",