aunubaileys 1.0.44 β 1.0.46
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 +10 -0
- package/lib/Socket/messages-recv.d.ts +2 -0
- package/lib/Socket/messages-recv.js +59 -4
- package/lib/Socket/messages-send.d.ts +3 -0
- package/lib/Socket/messages-send.js +82 -1
- package/lib/Types/Message.d.ts +3 -0
- package/lib/Utils/call-utils.d.ts +25 -0
- package/lib/Utils/call-utils.js +81 -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/lib/Utils/messages-media.d.ts +7 -0
- package/lib/Utils/messages-media.js +45 -0
- package/lib/Utils/messages.js +1 -1
- package/lib/Utils/rich-message-utils.d.ts +4 -0
- package/lib/Utils/rich-message-utils.js +97 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -64,6 +64,16 @@ 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.46 / Baru di 1.0.46
|
|
68
|
+
|
|
69
|
+
- π©Ή **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.
|
|
70
|
+
|
|
71
|
+
### π What's new in 1.0.45 / Baru di 1.0.45
|
|
72
|
+
|
|
73
|
+
- π **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`.
|
|
74
|
+
- π **HTML messages β EXPERIMENTAL (Aunu's call):** content `{ html, trustedSources }` adds an `FOAHtmlPrimitiveDemoDONOTUSE` section to the unified response (or use `prepareHtmlMessage(...)`), and `{ richHtml: { html, title, tabs, ... } }` / `prepareRichHtmlMessage(...)` builds an interactive screen as an embedded screen (`GenAIaeacdsnwHtmlPrimitive` inside `FOAIDNixelButtonSheets` tabs) β the surface interactive HTML+JS lives on. The library transports the payload intact; whether the receiving WhatsApp client renders/executes it depends on the client version and is not guaranteed.
|
|
75
|
+
- π·οΈ **`addStickerExif(webpBuffer, { packId?, packname?, author?, emojis?, categories? })`** (the @itsliaaa/starseal idea): writes sticker EXIF metadata into a WebP buffer β an existing EXIF chunk is replaced, not stacked; non-WebP input is rejected.
|
|
76
|
+
|
|
67
77
|
### π What's new in 1.0.44 / Baru di 1.0.44
|
|
68
78
|
|
|
69
79
|
- πΌοΈ **A third image backend: `@napi-rs/image`** in `getImageProcessingLibrary`, between sharp and jimp (priority: sharp β @napi-rs/image β jimp). `extractImageThumb` and `generateProfilePicture` gain a napi branch (Transformer: metadata + resize + quality-50 JPEG, matching the existing branches), and the package now declares `@napi-rs/image ^1.12.0` as an optional peer dependency. Consumers without sharp get a fast native path instead of falling back to jimp. Follows @itsliaaa/baileys.
|
|
@@ -7,6 +7,8 @@ export declare const makeMessagesRecvSocket: (config: SocketConfig) => {
|
|
|
7
7
|
sendMessageAck: ({ tag, attrs, content }: BinaryNode, errorCode?: number) => Promise<void>;
|
|
8
8
|
sendRetryRequest: (node: BinaryNode, forceIncludeKeys?: boolean) => Promise<void>;
|
|
9
9
|
rejectCall: (callId: string, callFrom: string) => Promise<void>;
|
|
10
|
+
acceptCall: (callId: string, callFrom: string) => Promise<void>;
|
|
11
|
+
terminateCall: (callId: string, callFrom: string, reason?: string) => Promise<void>;
|
|
10
12
|
fetchMessageHistory: (count: number, oldestMsgKey: WAMessageKey, oldestMsgTimestamp: number | Long) => Promise<string>;
|
|
11
13
|
requestPlaceholderResend: (messageKey: WAMessageKey) => Promise<string | undefined>;
|
|
12
14
|
getPrivacyTokens: (jids: string[]) => Promise<any>;
|
|
@@ -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, 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';
|
|
@@ -67,6 +67,28 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
67
67
|
logger.debug({ recv: { tag, attrs }, sent: stanza.attrs }, 'sent ack');
|
|
68
68
|
await sendNode(stanza);
|
|
69
69
|
};
|
|
70
|
+
// AunuBaileys: terima panggilan masuk (node kontrol <accept>) β pasangan
|
|
71
|
+
// rejectCall bawaan; media VOIP-nya sendiri di luar cakupan protokol chat.
|
|
72
|
+
const acceptCall = async (callId, callFrom) => {
|
|
73
|
+
await query(buildCallControlStanza({
|
|
74
|
+
from: authState.creds.me.id,
|
|
75
|
+
to: callFrom,
|
|
76
|
+
action: 'accept',
|
|
77
|
+
callId,
|
|
78
|
+
callCreator: callFrom
|
|
79
|
+
}));
|
|
80
|
+
};
|
|
81
|
+
// AunuBaileys: akhiri panggilan yang sedang berjalan (<terminate>).
|
|
82
|
+
const terminateCall = async (callId, callFrom, reason = 'user-terminated') => {
|
|
83
|
+
await query(buildCallControlStanza({
|
|
84
|
+
from: authState.creds.me.id,
|
|
85
|
+
to: callFrom,
|
|
86
|
+
action: 'terminate',
|
|
87
|
+
callId,
|
|
88
|
+
callCreator: callFrom,
|
|
89
|
+
reason
|
|
90
|
+
}));
|
|
91
|
+
};
|
|
70
92
|
const rejectCall = async (callId, callFrom) => {
|
|
71
93
|
const stanza = {
|
|
72
94
|
tag: 'call',
|
|
@@ -736,17 +758,47 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
736
758
|
const addr = signalRepository.jidToSignalProtocolAddress(author);
|
|
737
759
|
const failKey = isGroupMsg ? `sk:${msg.key.remoteJid}:${addr}` : addr;
|
|
738
760
|
const fails = (decryptFailCounts.get(failKey) || 0) + 1;
|
|
739
|
-
|
|
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) {
|
|
740
791
|
if (isGroupMsg) {
|
|
741
792
|
await signalRepository.deleteSenderKey(msg.key.remoteJid, author);
|
|
742
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');
|
|
743
794
|
}
|
|
744
795
|
else {
|
|
745
|
-
await
|
|
796
|
+
await resetE2ESession();
|
|
746
797
|
logger.warn({ author, addr }, 'aunu: deleted stale E2E session after repeated decrypt failures (Bad MAC self-heal); retry receipt carries fresh keys');
|
|
747
798
|
}
|
|
748
799
|
decryptFailCounts.delete(failKey);
|
|
749
800
|
forceIncludeKeys = true;
|
|
801
|
+
healed = true;
|
|
750
802
|
}
|
|
751
803
|
else {
|
|
752
804
|
decryptFailCounts.set(failKey, fails);
|
|
@@ -760,7 +812,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
760
812
|
address: addr,
|
|
761
813
|
isGroup: isGroupMsg,
|
|
762
814
|
failures: fails,
|
|
763
|
-
healed
|
|
815
|
+
healed,
|
|
816
|
+
errorClass: sessionLayerFailure ? 'session' : 'other',
|
|
764
817
|
messageId: node.attrs.id,
|
|
765
818
|
timestamp: Date.now()
|
|
766
819
|
});
|
|
@@ -1201,6 +1254,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1201
1254
|
sendMessageAck,
|
|
1202
1255
|
sendRetryRequest,
|
|
1203
1256
|
rejectCall,
|
|
1257
|
+
acceptCall,
|
|
1258
|
+
terminateCall,
|
|
1204
1259
|
fetchMessageHistory,
|
|
1205
1260
|
requestPlaceholderResend
|
|
1206
1261
|
};
|
|
@@ -23,6 +23,9 @@ export declare const makeMessagesSocket: (config: SocketConfig) => {
|
|
|
23
23
|
getUSyncDevices: (jids: string[], useCache: boolean, ignoreZeroDevices: boolean) => Promise<JidWithDevice[]>;
|
|
24
24
|
updateMediaMessage: (message: proto.IWebMessageInfo) => Promise<proto.IWebMessageInfo>;
|
|
25
25
|
sendMessage: (jid: string, content: AnyMessageContent, options?: MiscMessageGenerationOptions) => Promise<proto.WebMessageInfo | undefined>;
|
|
26
|
+
offerCall: (toJid: string, isVideo?: boolean) => Promise<{ id: string; to: string }>;
|
|
27
|
+
groupCall: (groupJid: string, participants?: string[], isVideo?: boolean) => Promise<{ id: string; to: string }>;
|
|
28
|
+
cancelGroupCall: (groupJid: string, callId: string) => Promise<{ id: string; to: string }>;
|
|
26
29
|
newsletterCreate: (name: string, description?: string) => Promise<import("../Types/index.js").NewsletterMetadata>;
|
|
27
30
|
newsletterUpdate: (jid: string, updates: import("../Types/index.js").NewsletterUpdate) => Promise<unknown>;
|
|
28
31
|
newsletterSubscribers: (jid: string) => Promise<{
|
|
@@ -2,7 +2,7 @@ import NodeCache from '@cacheable/node-cache';
|
|
|
2
2
|
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
|
-
import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, delay, extractDeviceJids, generateMessageIDV2, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds, shouldIncludeBizBinaryNode } from '../Utils/index.js';
|
|
5
|
+
import { aggregateMessageKeysNotFromMe, assertMediaContent, bindWaitForEvent, decryptMediaRetryData, encodeNewsletterMessage, encodeSignedDeviceIdentity, encodeWAMessage, encryptMediaRetryRequest, delay, extractDeviceJids, generateMessageIDV2, buildCallOfferContent, buildGroupCallCancelStanza, buildGroupCallStanza, createCallKey, newCallId, generateWAMessage, getStatusCodeForMediaRetry, getUrlFromDirectPath, getWAUploadToServer, normalizeMessageContent, parseAndInjectE2ESessions, unixTimestampSeconds, shouldIncludeBizBinaryNode } from '../Utils/index.js';
|
|
6
6
|
import { sentMessageCacheFor } from '../Utils/sent-message-cache.js';
|
|
7
7
|
import { getUrlInfo } from '../Utils/link-preview.js';
|
|
8
8
|
import { areJidsSameUser, getBinaryNodeChild, getBinaryNodeChildren, getBizBinaryNode, isJidGroup, isJidNewsletter, isJidUser, isLidUser, jidDecode, jidEncode, jidNormalizedUser, S_WHATSAPP_NET } from '../WABinary/index.js';
|
|
@@ -609,11 +609,92 @@ export const makeMessagesSocket = (config) => {
|
|
|
609
609
|
};
|
|
610
610
|
const waUploadToServer = getWAUploadToServer(config, refreshMediaConn);
|
|
611
611
|
const waitForMsgMediaUpdate = bindWaitForEvent(ev, 'messages.media-update');
|
|
612
|
+
// AunuBaileys: mulai panggilan suara/video 1:1 β susun <offer> dengan
|
|
613
|
+
// amplop callKey terenkripsi per perangkat tujuan (destination), sama
|
|
614
|
+
// seperti jalur fan-out relayMessage. Mengembalikan { id, to }.
|
|
615
|
+
const offerCall = async (toJid, isVideo = false) => {
|
|
616
|
+
const callId = newCallId();
|
|
617
|
+
const offerContent = buildCallOfferContent(isVideo);
|
|
618
|
+
const devices = (await getUSyncDevices([toJid], true, false)).map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device));
|
|
619
|
+
await assertSessions(devices, true);
|
|
620
|
+
const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
|
|
621
|
+
call: { callKey: createCallKey() }
|
|
622
|
+
}, { count: '0' });
|
|
623
|
+
offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
|
|
624
|
+
if (shouldIncludeDeviceIdentity) {
|
|
625
|
+
offerContent.push({
|
|
626
|
+
tag: 'device-identity',
|
|
627
|
+
attrs: {},
|
|
628
|
+
content: encodeSignedDeviceIdentity(authState.creds.account, true)
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
const stanza = {
|
|
632
|
+
tag: 'call',
|
|
633
|
+
attrs: {
|
|
634
|
+
id: generateMessageIDV2(authState.creds.me?.id),
|
|
635
|
+
to: toJid
|
|
636
|
+
},
|
|
637
|
+
content: [
|
|
638
|
+
{
|
|
639
|
+
tag: 'offer',
|
|
640
|
+
attrs: {
|
|
641
|
+
'call-id': callId,
|
|
642
|
+
'call-creator': authState.creds.me.id
|
|
643
|
+
},
|
|
644
|
+
content: offerContent
|
|
645
|
+
}
|
|
646
|
+
]
|
|
647
|
+
};
|
|
648
|
+
await query(stanza);
|
|
649
|
+
return { id: callId, to: toJid };
|
|
650
|
+
};
|
|
651
|
+
// AunuBaileys: mulai panggilan grup β <offer type="group"> ke JID grup,
|
|
652
|
+
// destination dienkripsi untuk perangkat para peserta yang di-ring.
|
|
653
|
+
const groupCall = async (groupJid, participants = [], isVideo = false) => {
|
|
654
|
+
const callId = newCallId();
|
|
655
|
+
const offerContent = buildCallOfferContent(isVideo);
|
|
656
|
+
const devices = (await getUSyncDevices(participants, true, false)).map(({ user, device }) => jidEncode(user, 's.whatsapp.net', device));
|
|
657
|
+
await assertSessions(devices, true);
|
|
658
|
+
const { nodes: destinations, shouldIncludeDeviceIdentity } = await createParticipantNodes(devices, {
|
|
659
|
+
call: { callKey: createCallKey() }
|
|
660
|
+
}, { count: '0' });
|
|
661
|
+
offerContent.push({ tag: 'destination', attrs: {}, content: destinations });
|
|
662
|
+
if (shouldIncludeDeviceIdentity) {
|
|
663
|
+
offerContent.push({
|
|
664
|
+
tag: 'device-identity',
|
|
665
|
+
attrs: {},
|
|
666
|
+
content: encodeSignedDeviceIdentity(authState.creds.account, true)
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
const stanza = buildGroupCallStanza({
|
|
670
|
+
id: generateMessageIDV2(authState.creds.me?.id),
|
|
671
|
+
to: groupJid,
|
|
672
|
+
callId,
|
|
673
|
+
callCreator: authState.creds.me.id,
|
|
674
|
+
offerContent
|
|
675
|
+
});
|
|
676
|
+
await query(stanza);
|
|
677
|
+
return { id: callId, to: groupJid };
|
|
678
|
+
};
|
|
679
|
+
// AunuBaileys: batalkan/akhiri panggilan grup yang dimulai groupCall.
|
|
680
|
+
const cancelGroupCall = async (groupJid, callId) => {
|
|
681
|
+
const stanza = buildGroupCallCancelStanza({
|
|
682
|
+
from: authState.creds.me.id,
|
|
683
|
+
to: groupJid,
|
|
684
|
+
callId,
|
|
685
|
+
callCreator: authState.creds.me.id
|
|
686
|
+
});
|
|
687
|
+
await query(stanza);
|
|
688
|
+
return { id: callId, to: groupJid };
|
|
689
|
+
};
|
|
612
690
|
return {
|
|
613
691
|
...sock,
|
|
614
692
|
getPrivacyTokens,
|
|
615
693
|
assertSessions,
|
|
616
694
|
relayMessage,
|
|
695
|
+
offerCall,
|
|
696
|
+
groupCall,
|
|
697
|
+
cancelGroupCall,
|
|
617
698
|
sendReceipt,
|
|
618
699
|
sendReceipts,
|
|
619
700
|
readMessages,
|
package/lib/Types/Message.d.ts
CHANGED
|
@@ -269,6 +269,9 @@ export type AnyMessageContent = (AnyRegularMessageContent | {
|
|
|
269
269
|
footerText?: string;
|
|
270
270
|
disclaimerText?: string;
|
|
271
271
|
noHeading?: boolean;
|
|
272
|
+
html?: string;
|
|
273
|
+
trustedSources?: string[];
|
|
274
|
+
richHtml?: { html?: string; title?: string; tabHeader?: string; url?: string; trustedSources?: string[]; tabs?: any[] };
|
|
272
275
|
};
|
|
273
276
|
export type GroupMetadataParticipants = Pick<GroupMetadata, 'participants'>;
|
|
274
277
|
type MinimalRelayOptions = {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { BinaryNode } from '../WABinary/index.js';
|
|
2
|
+
export declare const newCallId: () => string;
|
|
3
|
+
export declare const createCallKey: () => Uint8Array;
|
|
4
|
+
export declare const buildCallOfferContent: (isVideo?: boolean) => BinaryNode[];
|
|
5
|
+
export declare const buildCallControlStanza: (opts: {
|
|
6
|
+
from: string;
|
|
7
|
+
to: string;
|
|
8
|
+
action: 'accept' | 'terminate' | 'reject';
|
|
9
|
+
callId: string;
|
|
10
|
+
callCreator: string;
|
|
11
|
+
reason?: string;
|
|
12
|
+
}) => BinaryNode;
|
|
13
|
+
export declare const buildGroupCallStanza: (opts: {
|
|
14
|
+
id: string;
|
|
15
|
+
to: string;
|
|
16
|
+
callId: string;
|
|
17
|
+
callCreator: string;
|
|
18
|
+
offerContent: BinaryNode[];
|
|
19
|
+
}) => BinaryNode;
|
|
20
|
+
export declare const buildGroupCallCancelStanza: (opts: {
|
|
21
|
+
from: string;
|
|
22
|
+
to: string;
|
|
23
|
+
callId: string;
|
|
24
|
+
callCreator: string;
|
|
25
|
+
}) => BinaryNode;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { randomBytes } from 'crypto';
|
|
2
|
+
// AunuBaileys: pembangun stanza panggilan (VOIP) β fungsi murni, tanpa state
|
|
3
|
+
// socket, supaya bentuk wire-nya bisa diuji langsung. Bentuk stanza mengikuti
|
|
4
|
+
// protokol panggilan WhatsApp (node <call> dengan anak offer/accept/reject/
|
|
5
|
+
// terminate), implementasi orisinal AunuBaileys.
|
|
6
|
+
/** ID panggilan baru: 32 karakter hex huruf besar. */
|
|
7
|
+
export const newCallId = () => randomBytes(16).toString('hex').toUpperCase();
|
|
8
|
+
/** Kunci media panggilan baru (32 byte) untuk amplop destination terenkripsi. */
|
|
9
|
+
export const createCallKey = () => new Uint8Array(randomBytes(32));
|
|
10
|
+
/**
|
|
11
|
+
* Isi node <offer> untuk panggilan 1:1 maupun grup: codec audio opus
|
|
12
|
+
* (16 kHz + 8 kHz), video vp8 opsional, net, capability, dan encopt.
|
|
13
|
+
* Node destination/device-identity ditambahkan pemanggil setelah sesi siap.
|
|
14
|
+
*/
|
|
15
|
+
export const buildCallOfferContent = (isVideo = false) => {
|
|
16
|
+
const content = [
|
|
17
|
+
{ tag: 'audio', attrs: { enc: 'opus', rate: '16000' }, content: undefined },
|
|
18
|
+
{ tag: 'audio', attrs: { enc: 'opus', rate: '8000' }, content: undefined }
|
|
19
|
+
];
|
|
20
|
+
if (isVideo) {
|
|
21
|
+
content.push({
|
|
22
|
+
tag: 'video',
|
|
23
|
+
attrs: {
|
|
24
|
+
enc: 'vp8',
|
|
25
|
+
dec: 'vp8',
|
|
26
|
+
orientation: '0',
|
|
27
|
+
screen_width: '1920',
|
|
28
|
+
screen_height: '1080',
|
|
29
|
+
device_orientation: '0'
|
|
30
|
+
},
|
|
31
|
+
content: undefined
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
content.push({ tag: 'net', attrs: { medium: '3' }, content: undefined });
|
|
35
|
+
content.push({ tag: 'capability', attrs: { ver: '1' }, content: new Uint8Array([1, 4, 255, 131, 207, 4]) });
|
|
36
|
+
content.push({ tag: 'encopt', attrs: { keygen: '2' }, content: undefined });
|
|
37
|
+
return content;
|
|
38
|
+
};
|
|
39
|
+
/** Stanza <call> berisi satu node kontrol (accept / terminate / reject). */
|
|
40
|
+
export const buildCallControlStanza = ({ from, to, action, callId, callCreator, reason }) => ({
|
|
41
|
+
tag: 'call',
|
|
42
|
+
attrs: { from, to },
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
tag: action,
|
|
46
|
+
attrs: {
|
|
47
|
+
'call-id': callId,
|
|
48
|
+
'call-creator': callCreator,
|
|
49
|
+
...(reason ? { reason } : {}),
|
|
50
|
+
count: '0'
|
|
51
|
+
},
|
|
52
|
+
content: undefined
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
});
|
|
56
|
+
/** Stanza <call><offer type="group" group-jid=..> untuk memulai panggilan grup. */
|
|
57
|
+
export const buildGroupCallStanza = ({ id, to, callId, callCreator, offerContent }) => ({
|
|
58
|
+
tag: 'call',
|
|
59
|
+
attrs: { id, to },
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
tag: 'offer',
|
|
63
|
+
attrs: {
|
|
64
|
+
'call-id': callId,
|
|
65
|
+
'call-creator': callCreator,
|
|
66
|
+
type: 'group',
|
|
67
|
+
'group-jid': to
|
|
68
|
+
},
|
|
69
|
+
content: offerContent
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
});
|
|
73
|
+
/** Stanza <call><terminate> untuk membatalkan/mengakhiri panggilan grup. */
|
|
74
|
+
export const buildGroupCallCancelStanza = ({ from, to, callId, callCreator }) => buildCallControlStanza({
|
|
75
|
+
from,
|
|
76
|
+
to,
|
|
77
|
+
action: 'terminate',
|
|
78
|
+
callId,
|
|
79
|
+
callCreator,
|
|
80
|
+
reason: 'user-terminated'
|
|
81
|
+
});
|
|
@@ -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
|
@@ -3,6 +3,7 @@ export * from './decode-wa-message.js';
|
|
|
3
3
|
export * from './messages.js';
|
|
4
4
|
export * from './messages-media.js';
|
|
5
5
|
export * from './rich-message-utils.js';
|
|
6
|
+
export * from './call-utils.js';
|
|
6
7
|
export * from './validate-connection.js';
|
|
7
8
|
export * from './crypto.js';
|
|
8
9
|
export * from './signal.js';
|
|
@@ -110,4 +110,11 @@ export declare const decryptMediaRetryData: ({ ciphertext, iv }: {
|
|
|
110
110
|
export declare const getStatusCodeForMediaRetry: (code: number) => 200 | 412 | 404 | 418;
|
|
111
111
|
export {};
|
|
112
112
|
//# sourceMappingURL=messages-media.d.ts.map
|
|
113
|
+
export declare const addStickerExif: (webpBuffer: Buffer, metadata?: {
|
|
114
|
+
packId?: string;
|
|
115
|
+
packname?: string;
|
|
116
|
+
author?: string;
|
|
117
|
+
emojis?: string[];
|
|
118
|
+
categories?: string[];
|
|
119
|
+
}) => Buffer;
|
|
113
120
|
export declare const getImageProcessingLibrary: () => Promise<Record<string, any>>;
|
|
@@ -683,4 +683,49 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|
|
683
683
|
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
684
684
|
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
|
685
685
|
};
|
|
686
|
+
/**
|
|
687
|
+
* AunuBaileys: tulis metadata EXIF stiker (nama pack, penerbit, emoji) ke
|
|
688
|
+
* buffer WebP β format chunk EXIF standar ekosistem WhatsApp (header TIFF
|
|
689
|
+
* mini + JSON metadata). Chunk EXIF lama diganti, bukan ditumpuk. Mengikuti
|
|
690
|
+
* gagasan @itsliaaa/starseal; implementasi orisinal di sini.
|
|
691
|
+
*/
|
|
692
|
+
export const addStickerExif = (webpBuffer, metadata = {}) => {
|
|
693
|
+
if (!Buffer.isBuffer(webpBuffer) || webpBuffer.length < 12 ||
|
|
694
|
+
webpBuffer.toString('ascii', 0, 4) !== 'RIFF' || webpBuffer.toString('ascii', 8, 12) !== 'WEBP') {
|
|
695
|
+
throw new Error('addStickerExif: input bukan buffer WebP (RIFF) yang valid');
|
|
696
|
+
}
|
|
697
|
+
const json = {
|
|
698
|
+
'sticker-pack-id': metadata.packId || 'aunubaileys',
|
|
699
|
+
'sticker-pack-name': metadata.packname || '',
|
|
700
|
+
'sticker-pack-publisher': metadata.author || '',
|
|
701
|
+
'emojis': Array.isArray(metadata.emojis) ? metadata.emojis : ['π€'],
|
|
702
|
+
...(metadata.categories ? { 'categories': metadata.categories } : {})
|
|
703
|
+
};
|
|
704
|
+
const jsonBytes = Buffer.from(JSON.stringify(json), 'utf8');
|
|
705
|
+
const header = Buffer.from([0x49, 0x49, 0x2A, 0x00, 0x08, 0x00, 0x00, 0x00, 0x01, 0x00, 0x41, 0x57, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00]);
|
|
706
|
+
header.writeUInt32LE(jsonBytes.length, 14);
|
|
707
|
+
const exifPayload = Buffer.concat([header, jsonBytes]);
|
|
708
|
+
// pisahkan chunk-chunk RIFF yang ada (selain EXIF lama)
|
|
709
|
+
const chunks = [];
|
|
710
|
+
let offset = 12;
|
|
711
|
+
while (offset + 8 <= webpBuffer.length) {
|
|
712
|
+
const type = webpBuffer.toString('ascii', offset, offset + 4);
|
|
713
|
+
const size = webpBuffer.readUInt32LE(offset + 4);
|
|
714
|
+
const total = 8 + size + (size % 2);
|
|
715
|
+
if (type !== 'EXIF') {
|
|
716
|
+
chunks.push(webpBuffer.subarray(offset, offset + total));
|
|
717
|
+
}
|
|
718
|
+
offset += total;
|
|
719
|
+
}
|
|
720
|
+
const exifChunk = Buffer.alloc(8 + exifPayload.length + (exifPayload.length % 2));
|
|
721
|
+
exifChunk.write('EXIF', 0, 'ascii');
|
|
722
|
+
exifChunk.writeUInt32LE(exifPayload.length, 4);
|
|
723
|
+
exifPayload.copy(exifChunk, 8);
|
|
724
|
+
const body = Buffer.concat([Buffer.from('WEBP', 'ascii'), ...chunks, exifChunk]);
|
|
725
|
+
const out = Buffer.alloc(8 + body.length);
|
|
726
|
+
out.write('RIFF', 0, 'ascii');
|
|
727
|
+
out.writeUInt32LE(body.length, 4);
|
|
728
|
+
body.copy(out, 8);
|
|
729
|
+
return out;
|
|
730
|
+
};
|
|
686
731
|
//# sourceMappingURL=messages-media.js.map
|
package/lib/Utils/messages.js
CHANGED
|
@@ -462,7 +462,7 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
462
462
|
var _a, _b;
|
|
463
463
|
let m = {};
|
|
464
464
|
// AunuBaileys: rich response β blok kode / tabel / tautan (dari @itsliaaa/baileys)
|
|
465
|
-
if ('code' in message || 'links' in message || 'table' in message || 'richResponse' in message) {
|
|
465
|
+
if ('code' in message || 'links' in message || 'table' in message || 'richResponse' in message || 'html' in message || 'richHtml' in message) {
|
|
466
466
|
m = prepareRichResponseMessage(message);
|
|
467
467
|
}
|
|
468
468
|
else if ('text' in message) {
|
|
@@ -4,5 +4,9 @@ export declare const tokenizeCode: (code: string, language?: string) => {
|
|
|
4
4
|
codeContent: string;
|
|
5
5
|
}[];
|
|
6
6
|
export declare const toUnified: (submessages: any[], uuid?: string) => any;
|
|
7
|
+
export declare const buildFoaHtmlSection: (html: string, trustedSources?: string[]) => any;
|
|
8
|
+
export declare const prepareHtmlMessage: (content: { html?: string; trustedSources?: string[] }) => any;
|
|
9
|
+
export declare const buildRichHtmlScreen: (options: any) => any;
|
|
10
|
+
export declare const prepareRichHtmlMessage: (options: any) => any;
|
|
7
11
|
export declare const prepareRichResponseMessage: (content: any) => any;
|
|
8
12
|
export declare const wrapToBotForwardedMessage: (richResponseMessage: proto.AIRichResponseMessage) => any;
|
|
@@ -99,7 +99,97 @@ export const toUnified = (submessages, uuid) => ({
|
|
|
99
99
|
return submessage;
|
|
100
100
|
})
|
|
101
101
|
});
|
|
102
|
+
// AunuBaileys: seksi layar HTML (primitive FOAHtmlPrimitiveDemoDONOTUSE milik
|
|
103
|
+
// klien WhatsApp) untuk unified response β payload HTML mentah + daftar
|
|
104
|
+
// sumber tepercaya opsional. EKSPERIMENTAL (keputusan Aunu 11 Nov 2026):
|
|
105
|
+
// apakah klien WhatsApp penerima merender HTML/JS-nya bergantung versi
|
|
106
|
+
// klien dan tidak dijamin library.
|
|
107
|
+
export const buildFoaHtmlSection = (html, trustedSources = []) => ({
|
|
108
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
109
|
+
view_model: {
|
|
110
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
111
|
+
primitive: {
|
|
112
|
+
__typename: 'FOAHtmlPrimitiveDemoDONOTUSE',
|
|
113
|
+
trusted_sources: Array.isArray(trustedSources) ? trustedSources : [],
|
|
114
|
+
payload: String(html ?? '').trim()
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// AunuBaileys: pesan HTML mandiri β rich response yang unified-nya hanya
|
|
119
|
+
// berisi seksi HTML (submessages kosong, mengikuti bentuk sumbernya).
|
|
120
|
+
export const prepareHtmlMessage = (content = {}) => prepareRichResponseMessage({
|
|
121
|
+
html: content.html,
|
|
122
|
+
trustedSources: content.trustedSources
|
|
123
|
+
});
|
|
124
|
+
// AunuBaileys: layar HTML interaktif sebagai embedded screen di unified
|
|
125
|
+
// response β primitive GenAIaeacdsnwHtmlPrimitive di dalam tab
|
|
126
|
+
// FOAIDNixelButtonSheets; permukaan mini-app tempat HTML+JS interaktif
|
|
127
|
+
// (game dsb.) tinggal, berbeda dari seksi FOA pada pesan HTML biasa.
|
|
128
|
+
// EKSPERIMENTAL (keputusan Aunu 11 Nov 2026): render bergantung klien.
|
|
129
|
+
export const buildRichHtmlScreen = (options = {}) => {
|
|
130
|
+
const { html = '', trustedSources = [], url = '', title = 'Preview', tabHeader = 'HTML', tabs } = options;
|
|
131
|
+
const tabItems = Array.isArray(tabs) && tabs.length
|
|
132
|
+
? tabs
|
|
133
|
+
: [{ id: 'tab_0', tab_header: tabHeader, html }];
|
|
134
|
+
const embeddedTabs = tabItems.map((tab, index) => ({
|
|
135
|
+
id: tab.id || `tab_${index}`,
|
|
136
|
+
tab_header: tab.tab_header || tab.title || `HTML ${index + 1}`,
|
|
137
|
+
sections: [{
|
|
138
|
+
__typename: 'GenAIUnifiedResponseSection',
|
|
139
|
+
view_model: {
|
|
140
|
+
__typename: 'GenAISingleLayoutViewModel',
|
|
141
|
+
primitive: {
|
|
142
|
+
__typename: 'GenAIaeacdsnwHtmlPrimitive',
|
|
143
|
+
payload: String(tab.html ?? tab.payload ?? html).trim(),
|
|
144
|
+
url: tab.url || url,
|
|
145
|
+
trusted_sources: tab.trustedSources || trustedSources
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}],
|
|
149
|
+
step_entries: []
|
|
150
|
+
}));
|
|
151
|
+
return {
|
|
152
|
+
title,
|
|
153
|
+
embeddedScreen: {
|
|
154
|
+
title,
|
|
155
|
+
content: [{
|
|
156
|
+
__typename: 'FOAIDNixelButtonSheets',
|
|
157
|
+
tabs: embeddedTabs
|
|
158
|
+
}]
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
};
|
|
162
|
+
// AunuBaileys: pesan layar HTML interaktif β unified membawa embedded_screens,
|
|
163
|
+
// submessage-nya satu teks judul bergaya kutipan.
|
|
164
|
+
export const prepareRichHtmlMessage = (options = {}) => {
|
|
165
|
+
const { title, embeddedScreen } = buildRichHtmlScreen(options);
|
|
166
|
+
const unified = {
|
|
167
|
+
response_id: randomUUID(),
|
|
168
|
+
sections: [{
|
|
169
|
+
view_model: {
|
|
170
|
+
primitive: { text: `> ${title}`, __typename: 'GenAIMarkdownTextUXPrimitive' },
|
|
171
|
+
__typename: 'GenAISingleLayoutViewModel'
|
|
172
|
+
}
|
|
173
|
+
}],
|
|
174
|
+
embedded_screens: [embeddedScreen]
|
|
175
|
+
};
|
|
176
|
+
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
177
|
+
messageType: proto.AIRichResponseMessage.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
178
|
+
submessages: [{ messageType: RichSubMessageType.TEXT, messageText: `> ${title}` }],
|
|
179
|
+
unifiedResponse: { data: TEXT_ENCODER.encode(JSON.stringify(unified)) },
|
|
180
|
+
contextInfo: {
|
|
181
|
+
isForwarded: true,
|
|
182
|
+
forwardingScore: 1,
|
|
183
|
+
forwardedAiBotMessageInfo: { botJid: '867051314767696@bot' },
|
|
184
|
+
forwardOrigin: 4
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return wrapToBotForwardedMessage(richResponseMessage);
|
|
188
|
+
};
|
|
102
189
|
export const prepareRichResponseMessage = (content) => {
|
|
190
|
+
if (content.richHtml) {
|
|
191
|
+
return prepareRichHtmlMessage(content.richHtml);
|
|
192
|
+
}
|
|
103
193
|
let { alignment, code, contentText, disclaimerText, footerText, headerText, imageText, inlineImage, inlineVideo, items, language, latex, links, noHeading, posts, products, suggested, richResponse, table, tapLinkUrl, title } = content;
|
|
104
194
|
let submessages = [];
|
|
105
195
|
if (Array.isArray(richResponse)) {
|
|
@@ -311,7 +401,14 @@ export const prepareRichResponseMessage = (content) => {
|
|
|
311
401
|
}
|
|
312
402
|
}
|
|
313
403
|
const uuid = randomUUID();
|
|
404
|
+
const extraSections = [];
|
|
405
|
+
if (content.html) {
|
|
406
|
+
extraSections.push(buildFoaHtmlSection(content.html, content.trustedSources));
|
|
407
|
+
}
|
|
314
408
|
const unified = toUnified(submessages, uuid);
|
|
409
|
+
if (extraSections.length) {
|
|
410
|
+
unified.sections.push(...extraSections);
|
|
411
|
+
}
|
|
315
412
|
const richResponseMessage = proto.AIRichResponseMessage.create({
|
|
316
413
|
submessages,
|
|
317
414
|
messageType: proto.AIRichResponseMessage.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
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.46",
|
|
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",
|