aunubaileys 1.0.43 → 1.0.45
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 +25 -1
- 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/index.js +1 -0
- package/lib/Utils/messages-media.d.ts +7 -0
- package/lib/Utils/messages-media.js +70 -1
- 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 +3 -2
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.45 / Baru di 1.0.45
|
|
68
|
+
|
|
69
|
+
- 📞 **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`.
|
|
70
|
+
- 🌐 **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.
|
|
71
|
+
- 🏷️ **`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.
|
|
72
|
+
|
|
73
|
+
### 🆕 What's new in 1.0.44 / Baru di 1.0.44
|
|
74
|
+
|
|
75
|
+
- 🖼️ **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.
|
|
76
|
+
|
|
67
77
|
### 🆕 What's new in 1.0.43 / Baru di 1.0.43
|
|
68
78
|
|
|
69
79
|
- 🎬 **Video thumbnails now invoke FFmpeg via `spawn` with an argument array** instead of a shell-executed command string (`exec`). `extractVideoThumb` builds the invocation as data rather than shell text, so a file path can no longer smuggle shell commands into it; the function signature and behaviour are unchanged (file output, same caller in `generateThumbnail`). The pattern follows @itsliaaa/baileys; their pipe-to-buffer redesign was **not** adopted because it would change this function's public behaviour.
|
|
@@ -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, 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',
|
|
@@ -1201,6 +1223,8 @@ export const makeMessagesRecvSocket = (config) => {
|
|
|
1201
1223
|
sendMessageAck,
|
|
1202
1224
|
sendRetryRequest,
|
|
1203
1225
|
rejectCall,
|
|
1226
|
+
acceptCall,
|
|
1227
|
+
terminateCall,
|
|
1204
1228
|
fetchMessageHistory,
|
|
1205
1229
|
requestPlaceholderResend
|
|
1206
1230
|
};
|
|
@@ -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
|
+
});
|
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>>;
|
|
@@ -16,10 +16,14 @@ import { generateMessageIDV2 } from './generics.js';
|
|
|
16
16
|
const getTmpFilesDirectory = () => tmpdir();
|
|
17
17
|
export const getImageProcessingLibrary = async () => {
|
|
18
18
|
//@ts-ignore
|
|
19
|
-
const [jimp, sharp] = await Promise.all([import('jimp').catch(() => { }), import('sharp').catch(() => { })]);
|
|
19
|
+
const [jimp, sharp, image] = await Promise.all([import('jimp').catch(() => { }), import('sharp').catch(() => { }), import('@napi-rs/image').catch(() => { })]);
|
|
20
20
|
if (sharp) {
|
|
21
21
|
return { sharp };
|
|
22
22
|
}
|
|
23
|
+
// AunuBaileys: backend @napi-rs/image (dari @itsliaaa/baileys) — di atas jimp
|
|
24
|
+
if (image) {
|
|
25
|
+
return { image };
|
|
26
|
+
}
|
|
23
27
|
if (jimp) {
|
|
24
28
|
return { jimp };
|
|
25
29
|
}
|
|
@@ -127,6 +131,22 @@ export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
|
127
131
|
}
|
|
128
132
|
};
|
|
129
133
|
}
|
|
134
|
+
else if ('image' in lib && lib.image?.Transformer) {
|
|
135
|
+
// AunuBaileys: backend @napi-rs/image
|
|
136
|
+
if (!Buffer.isBuffer(bufferOrFilePath)) {
|
|
137
|
+
bufferOrFilePath = await fs.readFile(bufferOrFilePath);
|
|
138
|
+
}
|
|
139
|
+
const img = new lib.image.Transformer(bufferOrFilePath);
|
|
140
|
+
const dimensions = await img.metadata();
|
|
141
|
+
const buffer = await img.resize(width, undefined, 0).jpeg(50);
|
|
142
|
+
return {
|
|
143
|
+
buffer,
|
|
144
|
+
original: {
|
|
145
|
+
width: dimensions.width,
|
|
146
|
+
height: dimensions.height
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
}
|
|
130
150
|
else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) {
|
|
131
151
|
// AunuBaileys: jimp v1 mengekspos Jimp sebagai class (function), bukan object
|
|
132
152
|
const jimp = await lib.jimp.Jimp.read(bufferOrFilePath);
|
|
@@ -170,6 +190,10 @@ export const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
|
170
190
|
})
|
|
171
191
|
.toBuffer();
|
|
172
192
|
}
|
|
193
|
+
else if ('image' in lib && lib.image?.Transformer) {
|
|
194
|
+
// AunuBaileys: backend @napi-rs/image
|
|
195
|
+
img = new lib.image.Transformer(buffer).resize(w, h, 0).jpeg(50);
|
|
196
|
+
}
|
|
173
197
|
else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) {
|
|
174
198
|
// AunuBaileys: jimp v1 mengekspos Jimp sebagai class (function), bukan object
|
|
175
199
|
const jimp = await lib.jimp.Jimp.read(buffer);
|
|
@@ -659,4 +683,49 @@ const MEDIA_RETRY_STATUS_MAP = {
|
|
|
659
683
|
[proto.MediaRetryNotification.ResultType.NOT_FOUND]: 404,
|
|
660
684
|
[proto.MediaRetryNotification.ResultType.GENERAL_ERROR]: 418
|
|
661
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
|
+
};
|
|
662
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.45",
|
|
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",
|
|
@@ -83,7 +83,8 @@
|
|
|
83
83
|
"audio-decode": "^2.1.3",
|
|
84
84
|
"jimp": "^1.6.0",
|
|
85
85
|
"link-preview-js": "^3.0.0",
|
|
86
|
-
"sharp": "*"
|
|
86
|
+
"sharp": "*",
|
|
87
|
+
"@napi-rs/image": "^1.12.0"
|
|
87
88
|
},
|
|
88
89
|
"peerDependenciesMeta": {
|
|
89
90
|
"audio-decode": {
|