aunubaileys 1.0.45 β 1.0.47
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 +8 -0
- package/lib/Socket/aunu-experimental.d.ts +32 -0
- package/lib/Socket/aunu-experimental.js +104 -0
- package/lib/Socket/index.js +2 -0
- package/lib/Socket/messages-recv.js +35 -4
- package/lib/Utils/aunu-experimental.d.ts +16 -0
- package/lib/Utils/aunu-experimental.js +99 -0
- package/lib/Utils/decode-wa-message.d.ts +1 -0
- package/lib/Utils/decode-wa-message.js +11 -0
- package/lib/Utils/index.js +1 -0
- package/package.json +1 -1
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.47 / Baru di 1.0.47 β EXPERIMENTAL
|
|
68
|
+
|
|
69
|
+
- π§ͺ **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).
|
|
70
|
+
|
|
71
|
+
### π What's new in 1.0.46 / Baru di 1.0.46
|
|
72
|
+
|
|
73
|
+
- π©Ή **The "Over 2000 messages into the future" flood now heals itself.** Session-layer decrypt failures β libsignal's aggregate "No matching sessions found for message" / "No session record" / "No sessions available", which includes the unfillable counter gap left by regressed session state (e.g. sessions restored from a backup while the sender kept sending; observed live in the Tenka deployment on 11 Nov 2026, on the same LID address as the old B-01 finding) β are now recognised from `messageStubParameters` and trigger an **immediate E2E session reset** instead of waiting for 3 failures: for DMs and groups alike (groups also drop the author's sender key), including the session record under its alternate PNβLID address form, with the retry receipt carrying fresh keys. The old self-heal missed this flood because it waited 3 strikes, in groups only touched the sender key while the broken layer is the E2E session, and deletion could miss the aliased record. Other error classes (ordinary Bad MAC) keep the exact 1.0.14/1.0.17 3-strike behaviour. New exported classifier: `isUnrecoverableSessionError(text)`; the `messages.decrypt-failure` event gains an `errorClass` field (`'session'`/`'other'`) and `healed` now reflects a reset that actually happened on that message.
|
|
74
|
+
|
|
67
75
|
### π What's new in 1.0.45 / Baru di 1.0.45
|
|
68
76
|
|
|
69
77
|
- π **VOIP call support (from plogme, original implementation)** β one combined release from an 8-package sweep (raiden-baileys, @neoxr/wb, @zervonex/baileys, @itsliaaa/starseal, alya-baileys, @ryuu-reinzz/baileys, @cogitator-ai/channels, plogme β all audited clean). New socket methods: `offerCall(jid, isVideo?)` and `groupCall(groupJid, participants, isVideo?)` build the `<offer>` with a per-device encrypted call-key envelope, `cancelGroupCall(groupJid, callId)`, and `acceptCall(callId, callFrom)` / `terminateCall(callId, callFrom, reason?)` join the existing `rejectCall`. Pure stanza builders are exported from `lib/Utils/call-utils.js` so the wire shapes are unit-testable. The stanza shapes follow the WhatsApp call protocol; the code is an original AunuBaileys implementation (plogme's license forbids copying its code). This is the signalling layer β two-way VOIP media is out of scope. plogme's voip-rekey decoder was **not** ported: the 6.7.24 proto has no `E2eRekeyPayload`.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { AnyMessageContent, MiscMessageGenerationOptions, WAMessage } from '../Types/index.js';
|
|
2
|
+
import type { TypingDurationOptions } from '../Utils/aunu-experimental.js';
|
|
3
|
+
/** EXPERIMENTAL β AunuBaileys-only socket features. Semantics may change in any release. */
|
|
4
|
+
export interface AunuDoctorReport {
|
|
5
|
+
experimental: true;
|
|
6
|
+
bundledWaVersion: string | null;
|
|
7
|
+
registered: boolean;
|
|
8
|
+
me: string | null;
|
|
9
|
+
wsOpen: boolean;
|
|
10
|
+
lidPairsKnown: number | null;
|
|
11
|
+
scheduledPending: number;
|
|
12
|
+
selfDestructPending: number;
|
|
13
|
+
generatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
export interface AunuScheduledSend {
|
|
16
|
+
id: string;
|
|
17
|
+
at: number;
|
|
18
|
+
cancel: () => boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface AunuExperimentalAPI {
|
|
21
|
+
aunuWatermark: (text: string, tag: string) => string;
|
|
22
|
+
aunuReadWatermark: (text: string) => string | null;
|
|
23
|
+
aunuStripWatermark: (text: string) => string;
|
|
24
|
+
aunuHumanSend: (jid: string, content: AnyMessageContent, options?: MiscMessageGenerationOptions & TypingDurationOptions) => Promise<WAMessage | undefined>;
|
|
25
|
+
aunuSchedule: (jid: string, content: AnyMessageContent, when: number | Date, options?: MiscMessageGenerationOptions) => AunuScheduledSend;
|
|
26
|
+
aunuCancelScheduled: (id: string) => boolean;
|
|
27
|
+
aunuListScheduled: () => { id: string; jid: string; at: number }[];
|
|
28
|
+
aunuSelfDestruct: (jid: string, content: AnyMessageContent, seconds: number, options?: MiscMessageGenerationOptions) => Promise<WAMessage | undefined>;
|
|
29
|
+
aunuCancelSelfDestruct: (messageId: string) => boolean;
|
|
30
|
+
aunuDoctor: () => AunuDoctorReport;
|
|
31
|
+
}
|
|
32
|
+
export declare const attachAunuExperimental: <T>(sock: T) => T & AunuExperimentalAPI;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import baileysVersion from '../Defaults/baileys-version.json' with { type: 'json' };
|
|
2
|
+
import { appendWatermark, computeTypingDuration, extractWatermark, stripWatermark } from '../Utils/aunu-experimental.js';
|
|
3
|
+
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
4
|
+
/**
|
|
5
|
+
* AunuBaileys EXPERIMENTAL socket features β these exist only in
|
|
6
|
+
* AunuBaileys and may change or disappear in any release:
|
|
7
|
+
*
|
|
8
|
+
* 1. Invisible text watermark β `aunuWatermark` / `aunuReadWatermark` /
|
|
9
|
+
* `aunuStripWatermark`: hide a short tag (bot name, campaign, build)
|
|
10
|
+
* inside message text as zero-width characters for leak tracing and
|
|
11
|
+
* fingerprinting; the visible text is unchanged.
|
|
12
|
+
* 2. Humanised send β `aunuHumanSend`: shows "composing" for a plausible
|
|
13
|
+
* typing duration derived from the text length, then sends.
|
|
14
|
+
* 3. Scheduled send β `aunuSchedule` / `aunuCancelScheduled` /
|
|
15
|
+
* `aunuListScheduled`: in-memory delayed sends (lost on restart).
|
|
16
|
+
* 4. Self-destructing send β `aunuSelfDestruct` /
|
|
17
|
+
* `aunuCancelSelfDestruct`: sends, then revokes the message for
|
|
18
|
+
* everyone after N seconds.
|
|
19
|
+
* 5. Connection doctor β `aunuDoctor`: one structured health snapshot
|
|
20
|
+
* (registration, transport, bundled WA version, known LID pairs,
|
|
21
|
+
* pending experimental timers).
|
|
22
|
+
*/
|
|
23
|
+
export const attachAunuExperimental = (sock) => {
|
|
24
|
+
const scheduled = new Map();
|
|
25
|
+
const destructs = new Map();
|
|
26
|
+
let seq = 0;
|
|
27
|
+
sock.aunuWatermark = (text, tag) => appendWatermark(text, tag);
|
|
28
|
+
sock.aunuReadWatermark = (text) => extractWatermark(text);
|
|
29
|
+
sock.aunuStripWatermark = (text) => stripWatermark(text);
|
|
30
|
+
sock.aunuHumanSend = async (jid, content, options = {}) => {
|
|
31
|
+
if (content && typeof content.text === 'string' && typeof sock.sendPresenceUpdate === 'function') {
|
|
32
|
+
try {
|
|
33
|
+
await sock.sendPresenceUpdate('composing', jid);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// presence is best-effort; the send itself must not fail over it
|
|
37
|
+
}
|
|
38
|
+
await delay(computeTypingDuration(content.text, options));
|
|
39
|
+
try {
|
|
40
|
+
await sock.sendPresenceUpdate('paused', jid);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// best-effort, as above
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return sock.sendMessage(jid, content, options);
|
|
47
|
+
};
|
|
48
|
+
sock.aunuSchedule = (jid, content, when, options = {}) => {
|
|
49
|
+
const at = when instanceof Date ? when.getTime() : Date.now() + Math.max(0, Number(when) || 0);
|
|
50
|
+
const id = `aunu-sched-${++seq}`;
|
|
51
|
+
const timer = setTimeout(() => {
|
|
52
|
+
scheduled.delete(id);
|
|
53
|
+
Promise.resolve(sock.sendMessage(jid, content, options)).catch(() => { });
|
|
54
|
+
}, Math.max(0, at - Date.now()));
|
|
55
|
+
timer.unref?.();
|
|
56
|
+
scheduled.set(id, { jid, at, timer });
|
|
57
|
+
return { id, at, cancel: () => sock.aunuCancelScheduled(id) };
|
|
58
|
+
};
|
|
59
|
+
sock.aunuCancelScheduled = (id) => {
|
|
60
|
+
const entry = scheduled.get(id);
|
|
61
|
+
if (!entry) {
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
clearTimeout(entry.timer);
|
|
65
|
+
scheduled.delete(id);
|
|
66
|
+
return true;
|
|
67
|
+
};
|
|
68
|
+
sock.aunuListScheduled = () => [...scheduled.entries()].map(([id, entry]) => ({ id, jid: entry.jid, at: entry.at }));
|
|
69
|
+
sock.aunuSelfDestruct = async (jid, content, seconds, options = {}) => {
|
|
70
|
+
const sent = await sock.sendMessage(jid, content, options);
|
|
71
|
+
const key = sent?.key;
|
|
72
|
+
if (key?.id) {
|
|
73
|
+
const timer = setTimeout(() => {
|
|
74
|
+
destructs.delete(key.id);
|
|
75
|
+
Promise.resolve(sock.sendMessage(jid, { delete: key })).catch(() => { });
|
|
76
|
+
}, Math.max(0.05, Number(seconds) || 0) * 1000);
|
|
77
|
+
timer.unref?.();
|
|
78
|
+
destructs.set(key.id, timer);
|
|
79
|
+
}
|
|
80
|
+
return sent;
|
|
81
|
+
};
|
|
82
|
+
sock.aunuCancelSelfDestruct = (messageId) => {
|
|
83
|
+
const timer = destructs.get(messageId);
|
|
84
|
+
if (!timer) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
clearTimeout(timer);
|
|
88
|
+
destructs.delete(messageId);
|
|
89
|
+
return true;
|
|
90
|
+
};
|
|
91
|
+
sock.aunuDoctor = () => ({
|
|
92
|
+
experimental: true,
|
|
93
|
+
bundledWaVersion: Array.isArray(baileysVersion.version) ? baileysVersion.version.join('.') : null,
|
|
94
|
+
registered: !!sock.authState?.creds?.registered,
|
|
95
|
+
me: sock.user?.id ?? null,
|
|
96
|
+
wsOpen: !!sock.ws?.isOpen,
|
|
97
|
+
lidPairsKnown: typeof sock.aunuLid?.size === 'number' ? sock.aunuLid.size : null,
|
|
98
|
+
scheduledPending: scheduled.size,
|
|
99
|
+
selfDestructPending: destructs.size,
|
|
100
|
+
generatedAt: new Date().toISOString(),
|
|
101
|
+
});
|
|
102
|
+
return sock;
|
|
103
|
+
};
|
|
104
|
+
//# sourceMappingURL=aunu-experimental.js.map
|
package/lib/Socket/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DEFAULT_CONNECTION_CONFIG } from '../Defaults/index.js';
|
|
2
2
|
import { makeCommunitiesSocket } from './communities.js';
|
|
3
|
+
import { attachAunuExperimental } from './aunu-experimental.js';
|
|
3
4
|
import { AunuHandler } from '../Utils/aunu-handler.js';
|
|
4
5
|
import { makeLIDStore } from '../Utils/lid-store.js';
|
|
5
6
|
// export the last socket layer
|
|
@@ -14,6 +15,7 @@ const makeWASocket = (config) => {
|
|
|
14
15
|
newConfig.shouldSyncHistoryMessage = () => !!newConfig.syncFullHistory;
|
|
15
16
|
}
|
|
16
17
|
const sock = makeCommunitiesSocket(newConfig);
|
|
18
|
+
attachAunuExperimental(sock);
|
|
17
19
|
sock.aunuHandler = new AunuHandler(sock);
|
|
18
20
|
if (!sock.aunuLid) {
|
|
19
21
|
sock.aunuLid = makeLIDStore();
|
|
@@ -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, 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, 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';
|
|
@@ -758,17 +758,47 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
758
758
|
const addr = signalRepository.jidToSignalProtocolAddress(author);
|
|
759
759
|
const failKey = isGroupMsg ? `sk:${msg.key.remoteJid}:${addr}` : addr;
|
|
760
760
|
const fails = (decryptFailCounts.get(failKey) || 0) + 1;
|
|
761
|
-
|
|
761
|
+
// AunuBaileys (1.0.46): kegagalan lapis sesi β record
|
|
762
|
+
// sesi tersimpan tidak dapat mendekripsi pesan ini
|
|
763
|
+
// sama sekali (termasuk jurang "Over 2000 messages
|
|
764
|
+
// into the future" akibat state sesi yang mundur).
|
|
765
|
+
// Jurang itu tidak bisa diisi ulang, jadi menunggu
|
|
766
|
+
// 3x hanya membanjiri log; dan di grup, menghapus
|
|
767
|
+
// sender key saja tidak menyentuh sesi E2E yang
|
|
768
|
+
// rusak. Kelas ini direset SEKETIKA.
|
|
769
|
+
const sessionLayerFailure = isUnrecoverableSessionError(msg?.messageStubParameters?.[0]);
|
|
770
|
+
let healed = false;
|
|
771
|
+
const resetE2ESession = async () => {
|
|
772
|
+
await signalRepository.deleteSession(author);
|
|
773
|
+
// sesi yang sama bisa tersimpan di bawah bentuk
|
|
774
|
+
// alamat alternatifnya (PN <-> LID)
|
|
775
|
+
const alt = sock.aunuLid?.getAltJid?.(author);
|
|
776
|
+
if (alt && alt !== author) {
|
|
777
|
+
await signalRepository.deleteSession(alt);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
if (sessionLayerFailure) {
|
|
781
|
+
await resetE2ESession();
|
|
782
|
+
if (isGroupMsg) {
|
|
783
|
+
await signalRepository.deleteSenderKey(msg.key.remoteJid, author);
|
|
784
|
+
}
|
|
785
|
+
decryptFailCounts.delete(failKey);
|
|
786
|
+
forceIncludeKeys = true;
|
|
787
|
+
healed = true;
|
|
788
|
+
logger.warn({ author, addr, group: isGroupMsg ? msg.key.remoteJid : undefined }, 'aunu: reset E2E session after an unrecoverable session-layer decrypt failure (stored session cannot decrypt, e.g. regressed state); retry receipt carries fresh keys');
|
|
789
|
+
}
|
|
790
|
+
else if (fails >= DECRYPT_FAIL_SESSION_RESET_LIMIT) {
|
|
762
791
|
if (isGroupMsg) {
|
|
763
792
|
await signalRepository.deleteSenderKey(msg.key.remoteJid, author);
|
|
764
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');
|
|
765
794
|
}
|
|
766
795
|
else {
|
|
767
|
-
await
|
|
796
|
+
await resetE2ESession();
|
|
768
797
|
logger.warn({ author, addr }, 'aunu: deleted stale E2E session after repeated decrypt failures (Bad MAC self-heal); retry receipt carries fresh keys');
|
|
769
798
|
}
|
|
770
799
|
decryptFailCounts.delete(failKey);
|
|
771
800
|
forceIncludeKeys = true;
|
|
801
|
+
healed = true;
|
|
772
802
|
}
|
|
773
803
|
else {
|
|
774
804
|
decryptFailCounts.set(failKey, fails);
|
|
@@ -782,7 +812,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
782
812
|
address: addr,
|
|
783
813
|
isGroup: isGroupMsg,
|
|
784
814
|
failures: fails,
|
|
785
|
-
healed
|
|
815
|
+
healed,
|
|
816
|
+
errorClass: sessionLayerFailure ? 'session' : 'other',
|
|
786
817
|
messageId: node.attrs.id,
|
|
787
818
|
timestamp: Date.now()
|
|
788
819
|
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** EXPERIMENTAL β see aunu-experimental.js. Semantics may change in any release. */
|
|
2
|
+
export declare const encodeWatermark: (payload: string) => string;
|
|
3
|
+
export declare const appendWatermark: (text: string, payload: string) => string;
|
|
4
|
+
export declare const extractWatermark: (text: string) => string | null;
|
|
5
|
+
export declare const stripWatermark: (text: string) => string;
|
|
6
|
+
export interface TypingDurationOptions {
|
|
7
|
+
/** Characters per second while "typing". Default 5. */
|
|
8
|
+
cps?: number;
|
|
9
|
+
/** Lower clamp in ms. Default 700. */
|
|
10
|
+
minMs?: number;
|
|
11
|
+
/** Upper clamp in ms. Default 8000. */
|
|
12
|
+
maxMs?: number;
|
|
13
|
+
/** Set false for a deterministic duration. Default true (Β±20%). */
|
|
14
|
+
jitter?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare const computeTypingDuration: (text: string, options?: TypingDurationOptions) => number;
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AunuBaileys EXPERIMENTAL utilities β features that exist only in
|
|
3
|
+
* AunuBaileys. Everything in this module is experimental: semantics may
|
|
4
|
+
* change in any release. See lib/Socket/aunu-experimental.js for the
|
|
5
|
+
* socket-level API built on top of these helpers.
|
|
6
|
+
*/
|
|
7
|
+
// Zero-width alphabet for the invisible text watermark. A payload is stored
|
|
8
|
+
// as UTF-8 bytes, each byte as 8 bits (MSB first), wrapped in markers so it
|
|
9
|
+
// can be found and removed again without touching visible text.
|
|
10
|
+
const WM_BIT0 = '\u200B'; // ZERO WIDTH SPACE (bit 0)
|
|
11
|
+
const WM_BIT1 = '\u200C'; // ZERO WIDTH NON-JOINER (bit 1)
|
|
12
|
+
const WM_START = '\u200D'; // ZERO WIDTH JOINER (span start)
|
|
13
|
+
const WM_END = '\u2060'; // WORD JOINER (span end)
|
|
14
|
+
const textEncoder = new TextEncoder();
|
|
15
|
+
const textDecoder = new TextDecoder();
|
|
16
|
+
/**
|
|
17
|
+
* Encode a short string as an invisible zero-width sequence. Returns an
|
|
18
|
+
* empty string for an empty payload.
|
|
19
|
+
*/
|
|
20
|
+
export const encodeWatermark = (payload) => {
|
|
21
|
+
if (!payload) {
|
|
22
|
+
return '';
|
|
23
|
+
}
|
|
24
|
+
const bytes = textEncoder.encode(String(payload));
|
|
25
|
+
let bits = '';
|
|
26
|
+
for (const byte of bytes) {
|
|
27
|
+
bits += byte.toString(2).padStart(8, '0');
|
|
28
|
+
}
|
|
29
|
+
return WM_START + [...bits].map((b) => (b === '1' ? WM_BIT1 : WM_BIT0)).join('') + WM_END;
|
|
30
|
+
};
|
|
31
|
+
/** Append an invisible watermark carrying `payload` to `text`. */
|
|
32
|
+
export const appendWatermark = (text, payload) => String(text ?? '') + encodeWatermark(payload);
|
|
33
|
+
/**
|
|
34
|
+
* Read back the payload hidden in `text` by encodeWatermark/appendWatermark.
|
|
35
|
+
* Returns null when there is no well-formed watermark.
|
|
36
|
+
*/
|
|
37
|
+
export const extractWatermark = (text) => {
|
|
38
|
+
if (typeof text !== 'string') {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const start = text.indexOf(WM_START);
|
|
42
|
+
if (start < 0) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
const end = text.indexOf(WM_END, start + 1);
|
|
46
|
+
if (end < 0) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
let bits = '';
|
|
50
|
+
for (const ch of text.slice(start + 1, end)) {
|
|
51
|
+
if (ch === WM_BIT0) {
|
|
52
|
+
bits += '0';
|
|
53
|
+
}
|
|
54
|
+
else if (ch === WM_BIT1) {
|
|
55
|
+
bits += '1';
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (bits.length === 0 || bits.length % 8 !== 0) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const bytes = new Uint8Array(bits.length / 8);
|
|
65
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
66
|
+
bytes[i] = parseInt(bits.slice(i * 8, i * 8 + 8), 2);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return textDecoder.decode(bytes);
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
/** Remove every watermark span from `text`, leaving the visible text intact. */
|
|
76
|
+
export const stripWatermark = (text) => {
|
|
77
|
+
if (typeof text !== 'string') {
|
|
78
|
+
return text;
|
|
79
|
+
}
|
|
80
|
+
return text.replace(/\u200D[\u200B\u200C]*\u2060/g, '');
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* How long a human would plausibly spend typing `text`, in milliseconds β
|
|
84
|
+
* used by the experimental humanised send. Roughly `chars / cps`, clamped
|
|
85
|
+
* to [minMs, maxMs], with up to Β±20% jitter unless disabled.
|
|
86
|
+
*/
|
|
87
|
+
export const computeTypingDuration = (text, options = {}) => {
|
|
88
|
+
const cps = options.cps && options.cps > 0 ? options.cps : 5;
|
|
89
|
+
const minMs = options.minMs ?? 700;
|
|
90
|
+
const maxMs = options.maxMs ?? 8000;
|
|
91
|
+
const base = Math.round(((typeof text === 'string' ? text.length : 0) / cps) * 1000);
|
|
92
|
+
const clamped = Math.min(maxMs, Math.max(minMs, base));
|
|
93
|
+
if (options.jitter === false) {
|
|
94
|
+
return clamped;
|
|
95
|
+
}
|
|
96
|
+
const jittered = Math.round(clamped * (0.8 + Math.random() * 0.4));
|
|
97
|
+
return Math.min(maxMs, Math.max(minMs, jittered));
|
|
98
|
+
};
|
|
99
|
+
//# sourceMappingURL=aunu-experimental.js.map
|
|
@@ -2,6 +2,7 @@ import type { SignalRepository, WAMessage } from '../Types/index.js';
|
|
|
2
2
|
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
|
+
export declare const isUnrecoverableSessionError: (text: unknown) => boolean;
|
|
5
6
|
export declare const MISSING_KEYS_ERROR_TEXT = "Key used already or never filled";
|
|
6
7
|
export declare const NACK_REASONS: {
|
|
7
8
|
ParsingError: number;
|
|
@@ -216,4 +216,15 @@ export const decryptMessageNode = (stanza, meId, meLid, repository, logger) => {
|
|
|
216
216
|
}
|
|
217
217
|
};
|
|
218
218
|
};
|
|
219
|
+
/**
|
|
220
|
+
* AunuBaileys: true when a decrypt failure text is libsignal's session-layer
|
|
221
|
+
* aggregate β the stored session record(s) could not decrypt the message at
|
|
222
|
+
* all ("No matching sessions found for message", "No session record",
|
|
223
|
+
* "No sessions available"). This class includes the unfillable counter gap
|
|
224
|
+
* ("Over 2000 messages into the future!") thrown when the stored session
|
|
225
|
+
* state has regressed behind the sender's ratchet, e.g. after a backup
|
|
226
|
+
* restore; such a session can never decrypt again and must be reset.
|
|
227
|
+
*/
|
|
228
|
+
export const isUnrecoverableSessionError = (text) => typeof text === 'string' &&
|
|
229
|
+
/no matching sessions found|no session record|no sessions available/i.test(text);
|
|
219
230
|
//# sourceMappingURL=decode-wa-message.js.map
|
package/lib/Utils/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aunubaileys",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.47",
|
|
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",
|