sauruspro-bail 5.0.2-alpha.spr.test → 5.0.2

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/docs/CHANGELOG.md CHANGED
@@ -1,6 +1,31 @@
1
1
  # Changelog
2
2
 
3
- ## 5.0.2-alpha.spr.test
3
+ ## 5.0.2
4
+ ### Added (dari zapo-js, MIT)
5
+ - **Greeting Card `/buss`**: `sock.sendGreetingCard(jid, { text, title, greeting, imageUrl, contactUrl, thumbnail?, sourceApp?, ref?, mentions?, adReply? }, sendOptions?)` dan `sock.buildGreetingCard(opts)`.
6
+ Pesan `extendedTextMessage` + `contextInfo.externalAdReply`; `sourceId`, `ctwaClid`, `ctaPayload` diacak tiap kirim. Kartu hanya tampil di WhatsApp Business; user biasa hanya melihat teks.
7
+ Contoh:
8
+ ```js
9
+ await sock.sendGreetingCard(jid, {
10
+ text: 'wow', title: 'Megumin Dominada BUSI', greeting: 'Hi Business',
11
+ imageUrl: 'https://.../img.jpg', contactUrl: 'https://wa.me/NOMOR'
12
+ }, { quoted: null })
13
+ ```
14
+ - **Group History Bundle** (`sock.GroupHistory`, juga export `Utils`): `encodeGroupHistoryBundle`, `decodeGroupHistoryBundle`, `streamGroupHistoryBundle` (baca per pesan, hemat memori).
15
+ - **Meta AI Bot helpers** (`sock.MetaAIBot`, juga `Utils.MetaAIBot`): `BOT_KNOWN_JIDS`, `genBotMsgSecret`, `deriveBotChunkKey`, `decryptBotChunk`, `attachBotMetadata`, `attachBotThread`, `extractInvokedBotJid`, `buildBotInvokeProtoCopy`.
16
+ - **Plugin system** (dari zapo-js): `sock.use(plugin)`, `sock.removePlugin(id)`, `sock.hasPlugin(id)`, `sock.listPlugins()`, helper `definePlugin({ id, exposeAs?, setup(ctx), dispose? })`.
17
+ `ctx` berisi `sock`, `config`, `logger`, `ev`, `on/off`, `registerDispose`, `extend({...})` (member yang ditambah otomatis dicabut saat plugin dihapus). Opsi socket: `plugins: [...]`, `disablePlugins: ['spr', ...]`.
18
+ Fitur bawaan sekarang berupa plugin: `spr`, `greeting-card`, `group-history`, `meta-ai-bot`, `presence`, `profile` (API tetap sama; bisa dimatikan lewat `disablePlugins`).
19
+ ```js
20
+ const myPlugin = definePlugin({ id: 'halo', setup: ({ extend }) => extend({ halo: () => 'hai' }) })
21
+ sock.use(myPlugin)
22
+ ```
23
+ - **Presence & Profile API**: `sock.presence` (`setOnline`, `setOffline`, `typing`, `recording`, `paused`, `subscribe`, `typeFor(jid, ms)`, `withTyping(jid, fn)`) dan `sock.profile` (`getPicture`, `setPicture`, `removePicture`, `getStatus`, `setStatus`, `setName`, `username.{get,set,delete,check,recommendations,setPin,find}`). Pembungkus dari fungsi socket yang sudah ada.
24
+ - **Akselerator kripto native opsional**: jika `@zapo-js/native` terpasang, X25519 dan XEdDSA (sign/verify) dipakai lewat native (NAPI lalu WASM); tanpa paket itu otomatis tetap JS (libsignal). Env `SAURUS_NATIVE_BACKEND` = `auto|napi|wasm|js|none`. Util: `isNativeCryptoActive()`.
25
+
26
+ ### Fixed
27
+ - `requestPairingCode`: kode pairing tanpa custom kini acak tiap request (sebelumnya selalu `VINXSYNC`).
28
+
4
29
  ### Changed
5
30
  - `lib/Utils/dinonic-spr.js` ditulis ulang dari nol (engine baru, sender key sekali pakai). API `sPR` / `selectiveRelay` tetap kompatibel.
6
31
 
package/docs/SPR.md CHANGED
@@ -85,4 +85,4 @@ Foto profil diambil konkuren (10 sekaligus) dengan budget 10 dtk dan batas keras
85
85
 
86
86
  - Penerima `personal` hanya melihat pesan personalnya; pesan umum tidak dikirim ke mereka.
87
87
  - Fitur privat/broadcast diadaptasi dari plugin zapo-js (MIT).
88
- - Perubahan v5.0.2-alpha.spr.test: engine ditulis ulang (kode baru), tambah `personal`, `sPRPrivate`, `sPRBroadcast`, `getSPRTargets`, `loadSPRAvatars`. API `sPR`/`selectiveRelay` lama tetap kompatibel.
88
+ - Perubahan v5.0.2: engine ditulis ulang (kode baru), tambah `personal`, `sPRPrivate`, `sPRBroadcast`, `getSPRTargets`, `loadSPRAvatars`. API `sPR`/`selectiveRelay` lama tetap kompatibel.
@@ -6,6 +6,11 @@ import { makePrivacySocket } from './privacy.js'
6
6
  import { makeGraphQLSocket } from './graphql.js'
7
7
  import { makeMessageBuilderSocket } from './message-builder.js'
8
8
  import { sPR, sPRPrivate, sPRBroadcast, getSPRTargets } from '../Utils/dinonic-spr.js'
9
+ import { sendGreetingCard, buildGreetingCardContent } from '../Utils/greeting-card.js'
10
+ import * as GroupHistory from '../Utils/group-history.js'
11
+ import * as MetaAIBot from '../Utils/meta-ai-bot.js'
12
+ import { definePlugin, makePluginHost } from '../Utils/plugins.js'
13
+ import { makePresence, makeProfile } from '../Utils/presence-profile.js'
9
14
  import * as DinonicBuilder from '../Utils/MessageBuilder.js'
10
15
  import {
11
16
  makeNewsletterStatusSender,
@@ -36,12 +41,38 @@ const makeWASocket = (config) => {
36
41
  // Builder by Saurus (A2UI, AIRich, Button, ButtonV2, Carousel, Toolkit)
37
42
  clientWithBuilder.DinonicBuilder = DinonicBuilder;
38
43
 
39
- // SPR (Selective Relay)
40
- clientWithBuilder.sPR = (jid, message, options) => sPR(clientWithBuilder, jid, message, options);
41
- clientWithBuilder.relayMessageSelective = clientWithBuilder.sPR;
42
- clientWithBuilder.sPRPrivate = (groupJid, targetJid, options) => sPRPrivate(clientWithBuilder, groupJid, targetJid, options);
43
- clientWithBuilder.sPRBroadcast = (groupJid, options) => sPRBroadcast(clientWithBuilder, groupJid, options);
44
- clientWithBuilder.getSPRTargets = (groupJid, options) => getSPRTargets(clientWithBuilder, groupJid, options);
44
+ // Plugin system (sock.use) + fitur bawaan sebagai plugin (bisa dimatikan lewat config.disablePlugins)
45
+ const host = makePluginHost(clientWithBuilder, newConfig);
46
+ clientWithBuilder.use = host.use;
47
+ clientWithBuilder.removePlugin = host.removePlugin;
48
+ clientWithBuilder.hasPlugin = host.hasPlugin;
49
+ clientWithBuilder.listPlugins = host.listPlugins;
50
+ const sock = clientWithBuilder;
51
+ const builtinPlugins = [
52
+ definePlugin({
53
+ id: 'spr',
54
+ setup: ({ extend }) => extend({
55
+ sPR: (jid, message, options) => sPR(sock, jid, message, options),
56
+ relayMessageSelective: (jid, message, options) => sPR(sock, jid, message, options),
57
+ sPRPrivate: (groupJid, targetJid, options) => sPRPrivate(sock, groupJid, targetJid, options),
58
+ sPRBroadcast: (groupJid, options) => sPRBroadcast(sock, groupJid, options),
59
+ getSPRTargets: (groupJid, options) => getSPRTargets(sock, groupJid, options)
60
+ })
61
+ }),
62
+ definePlugin({
63
+ id: 'greeting-card',
64
+ setup: ({ extend }) => extend({
65
+ sendGreetingCard: (jid, opts, sendOptions) => sendGreetingCard(sock, jid, opts, sendOptions),
66
+ buildGreetingCard: buildGreetingCardContent
67
+ })
68
+ }),
69
+ definePlugin({ id: 'group-history', setup: ({ extend }) => extend({ GroupHistory }) }),
70
+ definePlugin({ id: 'meta-ai-bot', setup: ({ extend }) => extend({ MetaAIBot }) }),
71
+ definePlugin({ id: 'presence', exposeAs: 'presence', setup: ({ sock: s }) => makePresence(s) }),
72
+ definePlugin({ id: 'profile', exposeAs: 'profile', setup: ({ sock: s }) => makeProfile(s) })
73
+ ];
74
+ for (const plugin of builtinPlugins) host.use(plugin);
75
+ for (const plugin of newConfig.plugins ?? []) host.use(plugin);
45
76
 
46
77
  // Newsletter Status (Channel Status)
47
78
  if (!clientWithBuilder.sendNewsletterStatus) {
@@ -735,10 +735,11 @@ export const makeSocket = (config) => {
735
735
  void end(new Boom(msg || 'Intentional Logout', { statusCode: DisconnectReason.loggedOut }));
736
736
  };
737
737
  const requestPairingCode = async (phoneNumber, customPairingCode) => {
738
- const pairingCode = customPairingCode ?? 'VINXSYNC';
739
- if (customPairingCode && customPairingCode?.length !== 8) {
738
+ if (customPairingCode && customPairingCode.length !== 8) {
740
739
  throw new Error('Custom pairing code must be exactly 8 chars');
741
740
  }
741
+ // Tanpa kode custom: generate acak (Crockford base32) tiap request, bukan konstanta tetap.
742
+ const pairingCode = customPairingCode ?? bytesToCrockford(randomBytes(5));
742
743
  authState.creds.pairingCode = pairingCode;
743
744
  authState.creds.me = {
744
745
  id: jidEncode(phoneNumber, 's.whatsapp.net'),
@@ -1,6 +1,7 @@
1
1
  import { createCipheriv, createDecipheriv, createHash, createHmac, randomBytes } from 'crypto';
2
2
  import * as curve from 'libsignal/src/curve.js';
3
3
  import { KEY_BUNDLE_TYPE } from '../Defaults/index.js';
4
+ import { getNativeCrypto } from './native-backend.js';
4
5
  export { md5, hkdf } from 'whatsapp-rust-bridge';
5
6
  // insure browser & node compatibility
6
7
  const { subtle } = globalThis.crypto;
@@ -16,11 +17,34 @@ export const Curve = {
16
17
  };
17
18
  },
18
19
  sharedKey: (privateKey, publicKey) => {
20
+ const native = getNativeCrypto().x25519;
21
+ if (native) {
22
+ try {
23
+ return Buffer.from(native(privateKey, publicKey.length === 33 ? publicKey.subarray(1) : publicKey));
24
+ }
25
+ catch { /* fallback ke JS */ }
26
+ }
19
27
  const shared = curve.calculateAgreement(generateSignalPubKey(publicKey), privateKey);
20
28
  return Buffer.from(shared);
21
29
  },
22
- sign: (privateKey, buf) => curve.calculateSignature(privateKey, buf),
30
+ sign: (privateKey, buf) => {
31
+ const native = getNativeCrypto().xeddsaSign;
32
+ if (native) {
33
+ try {
34
+ return Buffer.from(native(privateKey, buf));
35
+ }
36
+ catch { /* fallback ke JS */ }
37
+ }
38
+ return curve.calculateSignature(privateKey, buf);
39
+ },
23
40
  verify: (pubKey, message, signature) => {
41
+ const native = getNativeCrypto().xeddsaVerify;
42
+ if (native) {
43
+ try {
44
+ return !!native(pubKey.length === 33 ? pubKey.subarray(1) : pubKey, message, signature);
45
+ }
46
+ catch { /* fallback ke JS */ }
47
+ }
24
48
  try {
25
49
  curve.verifySignature(generateSignalPubKey(pubKey), message, signature);
26
50
  return true;
@@ -0,0 +1,15 @@
1
+ export interface GreetingCardOptions {
2
+ text?: string;
3
+ title?: string;
4
+ greeting?: string;
5
+ imageUrl?: string;
6
+ contactUrl?: string;
7
+ thumbnail?: Buffer;
8
+ sourceApp?: string;
9
+ ref?: string;
10
+ mentions?: string[];
11
+ /** override field externalAdReply apa pun */
12
+ adReply?: Record<string, any>;
13
+ }
14
+ export declare const buildGreetingCardContent: (opts?: GreetingCardOptions) => any;
15
+ export declare const sendGreetingCard: (sock: any, jid: string, opts?: GreetingCardOptions, sendOptions?: Record<string, any>) => Promise<any>;
@@ -0,0 +1,70 @@
1
+ // Greeting Card (/buss) - diadaptasi dari zapo-js.
2
+ // Pesan teks + contextInfo.externalAdReply. Kartu sapaan hanya terlihat di WhatsApp Business;
3
+ // user WhatsApp biasa hanya melihat teks chat.
4
+ import { randomBytes } from 'crypto';
5
+ import { generateWAMessageFromContent } from './messages.js';
6
+
7
+ export const buildGreetingCardContent = (opts = {}) => {
8
+ const {
9
+ text = '',
10
+ title = '',
11
+ greeting = 'Hi Business',
12
+ imageUrl = '',
13
+ contactUrl = '',
14
+ thumbnail,
15
+ sourceApp = 'myapp',
16
+ ref = 'referencia',
17
+ mentions,
18
+ adReply = {}
19
+ } = opts;
20
+ return {
21
+ extendedTextMessage: {
22
+ text,
23
+ contextInfo: {
24
+ ...(mentions?.length ? { mentionedJid: mentions } : {}),
25
+ externalAdReply: {
26
+ title,
27
+ body: greeting,
28
+ mediaType: 1,
29
+ thumbnailUrl: imageUrl,
30
+ thumbnail: thumbnail ?? Buffer.alloc(0),
31
+ sourceType: 'whatsapp',
32
+ sourceId: String(randomBytes(4).readUInt32BE(0)),
33
+ sourceUrl: contactUrl,
34
+ containsAutoReply: false,
35
+ renderLargerThumbnail: false,
36
+ showAdAttribution: false,
37
+ ctwaClid: `clid${randomBytes(8).toString('hex')}`,
38
+ ref,
39
+ clickToWhatsappCall: true,
40
+ adContextPreviewDismissed: false,
41
+ sourceApp,
42
+ automatedGreetingMessageShown: true,
43
+ greetingMessageBody: greeting,
44
+ ctaPayload: `payload${randomBytes(8).toString('hex')}`,
45
+ disableNudge: true,
46
+ originalImageUrl: imageUrl,
47
+ wtwaAdFormat: false,
48
+ containsCtwaFlowsAutoReply: false,
49
+ agmThumbnailStrategy: 0,
50
+ agmTitleStrategy: 0,
51
+ agmSubtitleStrategy: 0,
52
+ agmHeaderInteractionStrategy: 0,
53
+ ...adReply
54
+ }
55
+ }
56
+ }
57
+ };
58
+ };
59
+
60
+ export const sendGreetingCard = async (sock, jid, opts = {}, sendOptions = {}) => {
61
+ const content = buildGreetingCardContent(opts);
62
+ const userJid = sock.authState?.creds?.me?.id ?? sock.user?.id;
63
+ const msg = generateWAMessageFromContent(jid, content, {
64
+ userJid,
65
+ ...(sendOptions.messageId ? { messageId: sendOptions.messageId } : {}),
66
+ ...(sendOptions.quoted ? { quoted: sendOptions.quoted } : {})
67
+ });
68
+ await sock.relayMessage(jid, msg.message, { messageId: msg.key.id, ...sendOptions });
69
+ return msg;
70
+ };
@@ -0,0 +1,4 @@
1
+ export declare const GROUP_HISTORY_FIELDS: Readonly<{ MESSAGES: 1; OUT_OF_WINDOW_PINNED_MESSAGES: 4 }>;
2
+ export declare function encodeGroupHistoryBundle(messages: any[], outOfWindowPinnedMessages?: any[]): Promise<{ compressed: Buffer; encoded: Uint8Array }>;
3
+ export declare function decodeGroupHistoryBundle(blob: Uint8Array): Promise<any>;
4
+ export declare function streamGroupHistoryBundle(inflated: Uint8Array, onMessage: (msg: any, isOutOfWindowPinned: boolean) => void | Promise<void>, maxRecordBytes?: number): Promise<void>;
@@ -0,0 +1,58 @@
1
+ // Group History Bundle - diadaptasi dari zapo-js (MIT).
2
+ // Serialize/deserialize riwayat pesan grup yang dibagikan ke anggota baru.
3
+ import { promisify } from 'util';
4
+ import { deflate, unzip } from 'zlib';
5
+ import $protobuf from 'protobufjs/minimal.js';
6
+ import { proto } from '../../WAProto/index.js';
7
+
8
+ const deflateAsync = promisify(deflate);
9
+ const unzipAsync = promisify(unzip);
10
+
11
+ export const GROUP_HISTORY_FIELDS = Object.freeze({
12
+ MESSAGES: 1,
13
+ OUT_OF_WINDOW_PINNED_MESSAGES: 4
14
+ });
15
+
16
+ /** Encode pesan -> payload `GroupHistory` terkompres (zlib). */
17
+ export async function encodeGroupHistoryBundle(messages, outOfWindowPinnedMessages) {
18
+ const encoded = proto.GroupHistory.encode({
19
+ messages,
20
+ outOfWindowPinnedMessages: outOfWindowPinnedMessages?.length ? outOfWindowPinnedMessages : undefined
21
+ }).finish();
22
+ const compressed = await deflateAsync(encoded);
23
+ return { compressed, encoded };
24
+ }
25
+
26
+ /** Inflate + decode blob bundle hasil download. */
27
+ export async function decodeGroupHistoryBundle(blob) {
28
+ const inflated = await unzipAsync(blob);
29
+ return proto.GroupHistory.decode(inflated);
30
+ }
31
+
32
+ /**
33
+ * Baca bundle (sudah di-inflate) satu pesan per satu pesan, tanpa decode seluruh bundle sekaligus.
34
+ * onMessage(webMessageInfo, isOutOfWindowPinned)
35
+ */
36
+ export async function streamGroupHistoryBundle(inflated, onMessage, maxRecordBytes) {
37
+ const reader = $protobuf.Reader.create(inflated);
38
+ while (reader.pos < reader.len) {
39
+ const tag = reader.uint32();
40
+ const fieldNumber = tag >>> 3;
41
+ const wireType = tag & 7;
42
+ if (wireType === 2) {
43
+ const bytes = reader.bytes();
44
+ if (maxRecordBytes !== undefined && bytes.length > maxRecordBytes) {
45
+ throw new Error(`group history record exceeds ${maxRecordBytes} bytes`);
46
+ }
47
+ if (fieldNumber === GROUP_HISTORY_FIELDS.MESSAGES) {
48
+ await onMessage(proto.WebMessageInfo.decode(bytes), false);
49
+ }
50
+ else if (fieldNumber === GROUP_HISTORY_FIELDS.OUT_OF_WINDOW_PINNED_MESSAGES) {
51
+ await onMessage(proto.WebMessageInfo.decode(bytes), true);
52
+ }
53
+ }
54
+ else {
55
+ reader.skipType(wireType);
56
+ }
57
+ }
58
+ }
@@ -23,6 +23,12 @@ export * from './stanza-ack.js';
23
23
  export * from './message-composer.js';
24
24
  export * from './newsletter-status.js';
25
25
  export { sPR, selectiveRelay, sPRPrivate, sPRBroadcast, getSPRTargets, loadSPRAvatars, createOneShotSenderKey, SPR_LIMITS, SPR_TEXT } from './dinonic-spr.js';
26
+ export * from './group-history.js';
27
+ export { definePlugin, makePluginHost } from './plugins.js';
28
+ export { makePresence, makeProfile } from './presence-profile.js';
29
+ export { resolveNativeBackend, getNativeCrypto, isNativeCryptoActive } from './native-backend.js';
30
+ export * as MetaAIBot from './meta-ai-bot.js';
31
+ export { sendGreetingCard, buildGreetingCardContent } from './greeting-card.js';
26
32
  //# sourceMappingURL=index.js.map
27
33
  export * from './a2ui.js';
28
34
  export * from './html-app.js';
@@ -0,0 +1,10 @@
1
+ export declare const BOT_SERVER: 'bot';
2
+ export declare const BOT_KNOWN_JIDS: Readonly<Record<'META_AI_PN' | 'META_AI_FBID' | 'META_AI_TEE_FBID' | 'META_AI_SIDECHAT_FBID' | 'MANUS_FBID' | 'HATCH_FBID', string>>;
3
+ export declare const isBotServerJid: (jid: string) => boolean;
4
+ export declare function genBotMsgSecret(parentMessageSecret: Uint8Array): Buffer;
5
+ export declare function deriveBotChunkKey(input: { botMsgSecret: Uint8Array; saltId: string; targetSenderJid: string; authorJid: string }): Buffer;
6
+ export declare function decryptBotChunk(input: { parentMessageSecret: Uint8Array; saltId: string; targetSenderJid: string; authorJid: string; encIv: Uint8Array; encPayload: Uint8Array }): Buffer;
7
+ export declare function attachBotMetadata(message: any, input?: { personaId?: string; invokerJid?: string; capabilities?: any[] }): any;
8
+ export declare function attachBotThread(message: any, input: { remoteJid: string; aiThreadId: string; threadType?: number }): any;
9
+ export declare function extractInvokedBotJid(message: any): string | null;
10
+ export declare function buildBotInvokeProtoCopy(message: any, botMessageSecret?: Uint8Array): any;
@@ -0,0 +1,94 @@
1
+ // Meta AI Bot helpers - diadaptasi dari zapo-js (MIT).
2
+ // Metadata bot, thread AI, dekripsi chunk balasan bot, deteksi bot yang di-invoke.
3
+ import { hkdfSync } from 'crypto';
4
+ import { proto } from '../../WAProto/index.js';
5
+ import { aesDecryptGCM } from './crypto.js';
6
+
7
+ export const BOT_SERVER = 'bot';
8
+ export const BOT_HKDF_INFO = Buffer.from('Bot Message');
9
+ export const BOT_MSG_SECRET_BYTES = 32;
10
+ export const BOT_KNOWN_JIDS = Object.freeze({
11
+ META_AI_PN: '13135550002@s.whatsapp.net',
12
+ META_AI_FBID: `867051314767696@${BOT_SERVER}`,
13
+ META_AI_TEE_FBID: `1273596044787272@${BOT_SERVER}`,
14
+ META_AI_SIDECHAT_FBID: `867051314767696555@${BOT_SERVER}`,
15
+ MANUS_FBID: `1807055946647696@${BOT_SERVER}`,
16
+ HATCH_FBID: `1807055946647697@${BOT_SERVER}`
17
+ });
18
+
19
+ export const isBotServerJid = (jid) => typeof jid === 'string' && jid.endsWith(`@${BOT_SERVER}`);
20
+
21
+ const hkdf = (ikm, info) => Buffer.from(hkdfSync('sha256', ikm, Buffer.alloc(0), info, BOT_MSG_SECRET_BYTES));
22
+
23
+ export function genBotMsgSecret(parentMessageSecret) {
24
+ const ikm = Buffer.from(parentMessageSecret);
25
+ if (ikm.length !== 32) {
26
+ throw new Error(`bot parent message secret must be 32 bytes (got ${ikm.length})`);
27
+ }
28
+ return hkdf(ikm, BOT_HKDF_INFO);
29
+ }
30
+
31
+ export function deriveBotChunkKey({ botMsgSecret, saltId, targetSenderJid, authorJid }) {
32
+ if (!saltId) throw new Error('bot chunk salt id must be a non-empty string');
33
+ if (!targetSenderJid) throw new Error('bot chunk target sender jid must be a non-empty string');
34
+ if (!authorJid) throw new Error('bot chunk author jid must be a non-empty string');
35
+ return hkdf(Buffer.from(botMsgSecret), Buffer.from(saltId + targetSenderJid + authorJid));
36
+ }
37
+
38
+ /** Dekripsi chunk balasan bot (AES-GCM, AAD = saltId + NUL + authorJid). */
39
+ export function decryptBotChunk({ parentMessageSecret, saltId, targetSenderJid, authorJid, encIv, encPayload }) {
40
+ const botMsgSecret = genBotMsgSecret(parentMessageSecret);
41
+ const key = deriveBotChunkKey({ botMsgSecret, saltId, targetSenderJid, authorJid });
42
+ const aad = Buffer.from(`${saltId}\u0000${authorJid}`);
43
+ return aesDecryptGCM(Buffer.from(encPayload), key, Buffer.from(encIv), aad);
44
+ }
45
+
46
+ export function attachBotMetadata(message, { personaId, invokerJid, capabilities } = {}) {
47
+ if (!personaId && !invokerJid && !capabilities) return message;
48
+ const botMetadata = {};
49
+ if (personaId) botMetadata.personaId = personaId;
50
+ if (invokerJid) botMetadata.invokerJid = invokerJid;
51
+ if (capabilities?.length) botMetadata.capabilityMetadata = { capabilities };
52
+ return {
53
+ ...message,
54
+ messageContextInfo: { ...(message.messageContextInfo ?? {}), botMetadata }
55
+ };
56
+ }
57
+
58
+ export function attachBotThread(message, { remoteJid, aiThreadId, threadType }) {
59
+ const ctx = message.messageContextInfo ?? {};
60
+ const type = threadType ?? proto.AIThreadInfo.AIThreadClientInfo.AIThreadType.DEFAULT;
61
+ return {
62
+ ...message,
63
+ messageContextInfo: {
64
+ ...ctx,
65
+ threadId: [
66
+ {
67
+ threadType: proto.ThreadID.ThreadType.AI_THREAD,
68
+ threadKey: { remoteJid, fromMe: true, id: aiThreadId }
69
+ }
70
+ ],
71
+ botMetadata: { ...(ctx.botMetadata ?? {}), botThreadInfo: { clientInfo: { type } } }
72
+ }
73
+ };
74
+ }
75
+
76
+ /** JID bot yang di-mention di dalam botInvokeMessage, atau null. */
77
+ export function extractInvokedBotJid(message) {
78
+ const inner = message.botInvokeMessage?.message;
79
+ if (!inner) return null;
80
+ const mentioned = inner.extendedTextMessage?.contextInfo?.mentionedJid ??
81
+ inner.imageMessage?.contextInfo?.mentionedJid ??
82
+ inner.videoMessage?.contextInfo?.mentionedJid ??
83
+ inner.documentMessage?.contextInfo?.mentionedJid;
84
+ if (!mentioned) return null;
85
+ return mentioned.find(isBotServerJid) ?? null;
86
+ }
87
+
88
+ export function buildBotInvokeProtoCopy(message, botMessageSecret) {
89
+ const { messageContextInfo, ...rest } = message;
90
+ if (!messageContextInfo && !botMessageSecret) return rest;
91
+ const next = { ...(messageContextInfo ?? {}), messageSecret: undefined };
92
+ if (botMessageSecret) next.botMessageSecret = botMessageSecret;
93
+ return { ...rest, messageContextInfo: next };
94
+ }
@@ -0,0 +1,3 @@
1
+ export declare const resolveNativeBackend: () => any | null;
2
+ export declare const getNativeCrypto: () => { x25519: ((priv: Uint8Array, pub: Uint8Array) => Uint8Array) | null; xeddsaSign: ((priv: Uint8Array, msg: Uint8Array) => Uint8Array) | null; xeddsaVerify: ((pub: Uint8Array, msg: Uint8Array, sig: Uint8Array) => boolean) | null };
3
+ export declare const isNativeCryptoActive: () => boolean;
@@ -0,0 +1,56 @@
1
+ // Akselerator kripto native opsional (@zapo-js/native, MIT) - diadaptasi dari zapo-js.
2
+ // Mempercepat X25519 ECDH dan XEdDSA sign/verify. Otomatis dipakai jika paket terpasang,
3
+ // jika tidak ada atau gagal load -> fallback ke implementasi JS (libsignal), tanpa perubahan kode.
4
+ // Env: SAURUS_NATIVE_BACKEND (atau ZAPO_NATIVE_BACKEND) = auto | napi | wasm | js | none
5
+ import { createRequire } from 'module';
6
+ import { readFileSync } from 'fs';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const WASM_MODULE = '@zapo-js/native/wasm/pkg/zapo_native_wasm.js';
10
+ const WASM_BINARY = '@zapo-js/native/wasm/pkg/zapo_native_wasm_bg.wasm';
11
+
12
+ const isAbsence = (error) => {
13
+ const code = error?.code;
14
+ const message = String(error?.message ?? '');
15
+ return (code === 'MODULE_NOT_FOUND' || code === 'ERR_MODULE_NOT_FOUND' || message.includes('Cannot find native binding')) &&
16
+ (message.includes('@zapo-js/native') || message.includes('zapo_native_wasm') || message.includes('Cannot find native binding'));
17
+ };
18
+ const warn = (backend, error) => process.emitWarning('failed to load native crypto accelerator, falling back to JS', {
19
+ code: 'SAURUS_NATIVE_LOAD_FAILED',
20
+ detail: `${backend}: ${error?.message ?? error}`
21
+ });
22
+
23
+ let cached;
24
+ export const resolveNativeBackend = () => {
25
+ if (cached !== undefined) return cached;
26
+ const mode = (process.env.SAURUS_NATIVE_BACKEND ?? process.env.ZAPO_NATIVE_BACKEND ?? 'auto').toLowerCase();
27
+ cached = null;
28
+ if (mode === 'js' || mode === 'none') return cached;
29
+ if (mode === 'napi' || mode === 'auto') {
30
+ try { cached = require('@zapo-js/native'); }
31
+ catch (error) { if (!isAbsence(error)) warn('napi', error); }
32
+ }
33
+ if (cached === null && (mode === 'wasm' || mode === 'auto')) {
34
+ try {
35
+ const mod = require(WASM_MODULE);
36
+ mod.initSync({ module: readFileSync(require.resolve(WASM_BINARY)) });
37
+ cached = mod;
38
+ }
39
+ catch (error) { if (!isAbsence(error)) warn('wasm', error); }
40
+ }
41
+ return cached;
42
+ };
43
+
44
+ /** Fungsi native yang tersedia (atau null jika memakai JS). */
45
+ export const getNativeCrypto = () => {
46
+ const mod = resolveNativeBackend();
47
+ return {
48
+ x25519: mod && typeof mod.x25519ScalarMult === 'function' ? mod.x25519ScalarMult : null,
49
+ xeddsaSign: mod && typeof mod.xeddsaSign === 'function' ? mod.xeddsaSign : null,
50
+ xeddsaVerify: mod && typeof mod.xeddsaVerify === 'function' ? mod.xeddsaVerify : null
51
+ };
52
+ };
53
+ export const isNativeCryptoActive = () => {
54
+ const n = getNativeCrypto();
55
+ return !!(n.x25519 || n.xeddsaSign || n.xeddsaVerify);
56
+ };
@@ -0,0 +1,20 @@
1
+ export interface PluginContext {
2
+ sock: any; config: any; logger?: any; ev: any;
3
+ on: (...args: any[]) => void; off: (...args: any[]) => void;
4
+ registerDispose: (fn: () => void | Promise<void>) => void;
5
+ extend: (members: Record<string, any>) => void;
6
+ }
7
+ export interface PluginDefinition<T = any> {
8
+ id: string;
9
+ exposeAs?: string;
10
+ setup: (ctx: PluginContext) => T;
11
+ dispose?: (instance: T, ctx: PluginContext) => void | Promise<void>;
12
+ }
13
+ export declare const definePlugin: <T = any>(input: PluginDefinition<T>) => Readonly<PluginDefinition<T>>;
14
+ export declare const makePluginHost: (sock: any, config?: any) => {
15
+ use: (plugin: PluginDefinition) => any;
16
+ removePlugin: (id: string) => Promise<boolean>;
17
+ hasPlugin: (id: string) => boolean;
18
+ listPlugins: () => string[];
19
+ disposeAll: () => Promise<void>;
20
+ };
@@ -0,0 +1,78 @@
1
+ // Plugin system: sock.use(plugin) - terinspirasi defineWaClientPlugin milik zapo-js (MIT).
2
+ // Plugin = { id, exposeAs?, setup(ctx), dispose?(instance, ctx) }.
3
+ // - exposeAs: hasil setup() dipasang di sock[exposeAs]
4
+ // - ctx.extend({ nama: nilai }): tambah banyak member ke sock (otomatis dicabut saat dispose)
5
+ export const definePlugin = (input) => {
6
+ if (!input || typeof input.id !== 'string' || !input.id) {
7
+ throw new Error('plugin requires a non-empty string id');
8
+ }
9
+ if (typeof input.setup !== 'function') {
10
+ throw new Error(`plugin ${input.id} requires a setup function`);
11
+ }
12
+ return Object.freeze({ id: input.id, exposeAs: input.exposeAs, setup: input.setup, dispose: input.dispose });
13
+ };
14
+
15
+ export const makePluginHost = (sock, config = {}) => {
16
+ const installed = new Map(); // id -> { plugin, instance, ctx, disposers, members }
17
+ const disabled = new Set(config.disablePlugins ?? []);
18
+ const define = (name, descriptor) => {
19
+ Object.defineProperty(sock, name, { configurable: true, enumerable: true, ...descriptor });
20
+ };
21
+ const use = (input) => {
22
+ const plugin = definePlugin(input);
23
+ if (disabled.has(plugin.id)) {
24
+ return sock;
25
+ }
26
+ if (installed.has(plugin.id)) {
27
+ throw new Error(`duplicate plugin id: ${plugin.id}`);
28
+ }
29
+ const members = [];
30
+ const disposers = [];
31
+ const claim = (name) => {
32
+ if (name in sock) {
33
+ throw new Error(`plugin ${plugin.id}: member "${name}" collides with an existing socket member`);
34
+ }
35
+ members.push(name);
36
+ };
37
+ const ctx = {
38
+ sock,
39
+ config,
40
+ logger: config.logger?.child ? config.logger.child({ plugin: plugin.id }) : config.logger,
41
+ ev: sock.ev,
42
+ on: (...a) => sock.ev.on(...a),
43
+ off: (...a) => sock.ev.off(...a),
44
+ registerDispose: (fn) => disposers.push(fn),
45
+ extend: (obj) => {
46
+ for (const [name, value] of Object.entries(obj)) {
47
+ claim(name);
48
+ define(name, { value, writable: true });
49
+ }
50
+ }
51
+ };
52
+ const instance = plugin.setup(ctx);
53
+ if (plugin.exposeAs) {
54
+ claim(plugin.exposeAs);
55
+ define(plugin.exposeAs, { value: instance, writable: true });
56
+ }
57
+ installed.set(plugin.id, { plugin, instance, ctx, disposers, members });
58
+ return sock;
59
+ };
60
+ const remove = async (id) => {
61
+ const entry = installed.get(id);
62
+ if (!entry) return false;
63
+ installed.delete(id);
64
+ for (let i = entry.disposers.length - 1; i >= 0; i--) {
65
+ try { await entry.disposers[i](); } catch { /* abaikan */ }
66
+ }
67
+ try { await entry.plugin.dispose?.(entry.instance, entry.ctx); } catch { /* abaikan */ }
68
+ for (const name of entry.members) delete sock[name];
69
+ return true;
70
+ };
71
+ return {
72
+ use,
73
+ removePlugin: remove,
74
+ hasPlugin: (id) => installed.has(id),
75
+ listPlugins: () => [...installed.keys()],
76
+ disposeAll: async () => { for (const id of [...installed.keys()].reverse()) await remove(id); }
77
+ };
78
+ };
@@ -0,0 +1,18 @@
1
+ export interface PresenceApi {
2
+ setOnline(): Promise<void>; setOffline(): Promise<void>;
3
+ typing(jid: string): Promise<void>; recording(jid: string): Promise<void>; paused(jid: string): Promise<void>;
4
+ subscribe(jid: string): Promise<void>;
5
+ typeFor(jid: string, ms?: number): Promise<void>;
6
+ withTyping<T>(jid: string, fn: () => Promise<T> | T, kind?: 'composing' | 'recording'): Promise<T>;
7
+ }
8
+ export interface ProfileApi {
9
+ getPicture(jid: string, type?: 'preview' | 'image', timeoutMs?: number): Promise<string | undefined>;
10
+ setPicture(jid: string, content: any, dimensions?: { width: number; height: number }): Promise<void>;
11
+ removePicture(jid: string): Promise<void>;
12
+ getStatus(...jids: string[]): Promise<any>;
13
+ setStatus(text: string): Promise<void>;
14
+ setName(name: string): Promise<void>;
15
+ username: { get(): Promise<any>; set(username: string, options?: any): Promise<any>; delete(): Promise<any>; check(username: string): Promise<any>; recommendations(source?: string): Promise<any>; setPin(pin: string): Promise<any>; find(username: string, pin?: string): Promise<any> };
16
+ }
17
+ export declare const makePresence: (sock: any) => PresenceApi;
18
+ export declare const makeProfile: (sock: any) => ProfileApi;
@@ -0,0 +1,50 @@
1
+ // Presence & Profile coordinator - API rapi di atas fungsi socket yang sudah ada.
2
+ // Terinspirasi client.presence / client.profile milik zapo-js (MIT).
3
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
4
+
5
+ export const makePresence = (sock) => {
6
+ const send = (type, jid) => sock.sendPresenceUpdate(type, jid);
7
+ return {
8
+ setOnline: () => send('available'),
9
+ setOffline: () => send('unavailable'),
10
+ typing: (jid) => send('composing', jid),
11
+ recording: (jid) => send('recording', jid),
12
+ paused: (jid) => send('paused', jid),
13
+ subscribe: (jid) => sock.presenceSubscribe(jid),
14
+ /** composing -> tunggu ms -> paused */
15
+ async typeFor(jid, ms = 1000) {
16
+ await send('composing', jid);
17
+ await sleep(ms);
18
+ await send('paused', jid);
19
+ },
20
+ /** Tampilkan "mengetik" selama fn berjalan, lalu paused (tetap paused walau fn error). */
21
+ async withTyping(jid, fn, kind = 'composing') {
22
+ await send(kind, jid);
23
+ try {
24
+ return await fn();
25
+ }
26
+ finally {
27
+ await send('paused', jid).catch(() => { });
28
+ }
29
+ }
30
+ };
31
+ };
32
+
33
+ export const makeProfile = (sock) => ({
34
+ getPicture: (jid, type = 'preview', timeoutMs) => sock.profilePictureUrl(jid, type, timeoutMs),
35
+ setPicture: (jid, content, dimensions) => sock.updateProfilePicture(jid, content, dimensions),
36
+ removePicture: (jid) => sock.removeProfilePicture(jid),
37
+ getStatus: (...jids) => sock.fetchStatus(...jids),
38
+ setStatus: (text) => sock.updateProfileStatus(text),
39
+ setName: (name) => sock.updateProfileName(name),
40
+ /** Username (jika modul username tersedia di socket) */
41
+ username: {
42
+ get: () => sock.getMyUsername?.(),
43
+ set: (username, options) => sock.setUsername(username, options),
44
+ delete: () => sock.deleteUsername(),
45
+ check: (username) => sock.checkUsername(username),
46
+ recommendations: (source) => sock.getUsernameRecommendations(source),
47
+ setPin: (pin) => sock.setUsernamePin(pin),
48
+ find: (username, pin) => sock.findUserByUsername?.(username, pin)
49
+ }
50
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sauruspro-bail",
3
- "version": "5.0.2-alpha.spr.test",
3
+ "version": "5.0.2",
4
4
  "description": "Sauruspro Baileys - Custom WhatsApp Web API with SPR (Selective Relay), Newsletter Status, Rich Messages and pairing support.",
5
5
  "keywords": [
6
6
  "baileys",
@@ -96,7 +96,8 @@
96
96
  "audio-decode": "^2.1.3",
97
97
  "jimp": "^1.6.1",
98
98
  "link-preview-js": "^3.0.0",
99
- "sharp": "*"
99
+ "sharp": "*",
100
+ "@zapo-js/native": "*"
100
101
  },
101
102
  "resolutions": {
102
103
  "ajv@npm:^6.12.4": "^6.14.0",
@@ -136,6 +137,9 @@
136
137
  },
137
138
  "link-preview-js": {
138
139
  "optional": true
140
+ },
141
+ "@zapo-js/native": {
142
+ "optional": true
139
143
  }
140
144
  },
141
145
  "packageManager": "yarn@4.9.2",
package/README.md DELETED
@@ -1,133 +0,0 @@
1
- <div align="center">
2
-
3
- # 🦖 SAURUSPRO BAILEYS
4
-
5
- **Custom WhatsApp Web API Library — SPR, Newsletter Status, Rich Messages & more**
6
-
7
- <img src="https://cdn.saurus.qzz.io/image/EGYoX.png" width="140" alt="Sauruspro">
8
-
9
- ![npm](https://img.shields.io/npm/v/sauruspro-bail?style=for-the-badge&color=8b5cf6)
10
- ![downloads](https://img.shields.io/npm/dt/sauruspro-bail?style=for-the-badge&color=c084fc)
11
- ![node](https://img.shields.io/badge/Node.js-20%2B-22c55e?style=for-the-badge)
12
- ![license](https://img.shields.io/badge/License-MIT-3b82f6?style=for-the-badge)
13
-
14
- **Owner: Saurus / Dinonic** · **Version: 5.0.2-alpha.spr.test**
15
-
16
- </div>
17
-
18
- ---
19
-
20
- ## 📦 Package Info
21
-
22
- | | |
23
- |---|---|
24
- | Package | `sauruspro-bail` |
25
- | Version | `5.0.2-alpha.spr.test` |
26
- | Owner | Saurus / Dinonic |
27
- | Module type | ESM (`"type": "module"`) |
28
- | Runtime | Node.js >= 20 |
29
- | Telegram | [@lordsaurus](https://t.me/lordsaurus) · [@lordsaurus](https://t.me/lordsaurus) |
30
-
31
- ## ✨ Features
32
-
33
- | Feature | Description |
34
- |---|---|
35
- | 🔐 Pairing Code & QR | Login via pairing code or QR |
36
- | 💬 Rich Messages | MessageBuilder: `AIRich`, `Carousel`, `Button`, `ButtonV2`, `Toolkit` |
37
- | 🎯 **SPR** | Selective Relay: kirim pesan hanya ke device/JID tertentu |
38
- | 📢 **Newsletter Status** | Kirim, react, revoke & ambil status Channel (newsletter) |
39
- | 🔗 LID & PN | Kompatibel LID dan nomor telepon |
40
- | 👤 Username | Fetch username |
41
- | 👥 Groups & Communities | Manajemen grup dan komunitas |
42
- | 🗂️ Auth | Multi-file & SQLite auth state |
43
-
44
- ## 🚀 Install
45
-
46
- ```bash
47
- npm install sauruspro-bail
48
- ```
49
-
50
- > Paket ini **ESM**. Gunakan `import`, atau `await import()` dari CommonJS.
51
-
52
- ## ⚡ Quick Start
53
-
54
- ```js
55
- import makeWASocket, { useMultiFileAuthState } from 'sauruspro-bail'
56
-
57
- const { state, saveCreds } = await useMultiFileAuthState('session')
58
- const sock = makeWASocket({ auth: state })
59
-
60
- sock.ev.on('creds.update', saveCreds)
61
-
62
- // Pairing code
63
- if (!sock.authState.creds.registered) {
64
- const code = await sock.requestPairingCode('628xxxxxxxxxx')
65
- console.log('Pairing code:', code)
66
- }
67
- ```
68
-
69
- ## 📢 Newsletter Status
70
-
71
- Kirim status ke Channel (newsletter) yang kamu admin-i.
72
-
73
- ```js
74
- const jid = '120363xxxxxxxxxxxxx@newsletter'
75
-
76
- // Teks
77
- const sent = await sock.newsletter.sendStatus(jid, { text: 'Halo Channel!' })
78
- console.log(sent.newsletterStatusServerId)
79
-
80
- // Gambar
81
- await sock.sendNewsletterStatus(jid, {
82
- image: { url: './foto.jpg' },
83
- caption: 'Update terbaru'
84
- })
85
-
86
- // React ke status (butuh serverId dari status)
87
- await sock.newsletter.reactToStatus(jid, serverId, '🔥')
88
-
89
- // Hapus status
90
- await sock.newsletter.revokeStatus(jid, statusId)
91
-
92
- // Ambil daftar status
93
- const list = await sock.newsletter.fetchStatuses(jid, { count: 20 })
94
- ```
95
-
96
- Dokumentasi lengkap: **[docs/NEWSLETTER-STATUS.md](docs/NEWSLETTER-STATUS.md)**
97
-
98
- ## 🎯 SPR (Selective Relay)
99
-
100
- ```js
101
- await sock.sPR(jid, { conversation: 'Halo!' })
102
- // alias: sock.relayMessageSelective(jid, message, options)
103
-
104
- // pesan privat per orang + foto profil
105
- await sock.sPRPrivate(groupJid, targetJid, { publicText: 'Ada pesan', privateText: 'rahasia' })
106
- await sock.sPRBroadcast(groupJid, { template: 'Halo {name}!' })
107
- ```
108
-
109
- Dokumentasi lengkap: **[docs/SPR.md](docs/SPR.md)**
110
-
111
- ## 📚 Dokumentasi
112
-
113
- | File | Isi |
114
- |---|---|
115
- | [docs/NEWSLETTER-STATUS.md](docs/NEWSLETTER-STATUS.md) | API newsletter status, opsi, error |
116
- | [docs/SPR.md](docs/SPR.md) | Selective Relay |
117
- | [docs/CHANGELOG.md](docs/CHANGELOG.md) | Riwayat versi |
118
-
119
- ## ⚠️ Disclaimer
120
-
121
- Library ini tidak berafiliasi dengan WhatsApp/Meta. Gunakan dengan bijak dan patuhi Terms of Service WhatsApp. Pengguna bertanggung jawab atas penggunaannya.
122
-
123
- ## 💻 Developer
124
-
125
- <div align="center">
126
-
127
- **Saurus / Dinonic**
128
-
129
- Telegram [@lordsaurus](https://t.me/lordsaurus)
130
-
131
- Made with ❤️ by Saurus / Dinonic
132
-
133
- </div>
@@ -1,120 +0,0 @@
1
- # 📢 Newsletter Status
2
-
3
- Kirim, react, hapus, dan ambil **status Channel (newsletter)** WhatsApp.
4
- Tersedia sejak **v5.0.0**. Kamu harus **admin/owner** channel tersebut.
5
-
6
- Semua fungsi tersedia dalam 3 cara:
7
-
8
- | Cara | Contoh |
9
- |---|---|
10
- | `sock.newsletter.*` | `sock.newsletter.sendStatus(jid, content)` |
11
- | `sock.*` langsung | `sock.sendNewsletterStatus(jid, content)` |
12
- | Import factory | `makeNewsletterStatusSender(sock, config)` |
13
-
14
- JID newsletter harus berakhiran `@newsletter`, kalau tidak akan melempar `TypeError`.
15
-
16
- ---
17
-
18
- ## 1. Kirim status
19
-
20
- ```js
21
- await sock.newsletter.sendStatus(jid, content, options?)
22
- // atau object style:
23
- await sock.newsletter.sendStatus({ jid, content, ...options })
24
- ```
25
-
26
- ### Content
27
-
28
- | Jenis | Contoh |
29
- |---|---|
30
- | Teks | `{ text: 'Halo' }` |
31
- | Gambar | `{ image: { url }, caption }` |
32
- | Video | `{ video: { url }, caption }` |
33
- | GIF | `{ video: { url }, gifPlayback: true }` |
34
- | Audio | `{ audio: { url }, mimetype: 'audio/mpeg' }` |
35
-
36
- Dokumen dan sticker **tidak didukung** (akan melempar error).
37
- Hanya `image` dan `video` yang dipublish WhatsApp Web sendiri; audio/gif bisa ditolak server (ada log warning).
38
-
39
- ### Options
40
-
41
- | Option | Default | Keterangan |
42
- |---|---|---|
43
- | `messageId` | auto | ID pesan kustom |
44
- | `statusAttribution` | `true` | Tambah atribusi `NEWSLETTER_STATUS` + `canBeReshared` |
45
- | `interactionType` | - | `question` / `question_response` / `question_reshare` |
46
- | `parentServerId` | - | Wajib untuk `question_reshare` |
47
- | `responseServerId` | - | Wajib untuk `question_reshare` |
48
- | `aiContent` | - | Tandai konten AI |
49
- | `mediaHandle` / `mediaId` | auto | Handle media hasil upload |
50
- | `ackTimeoutMs` | `20000` | Timeout menunggu ack server |
51
- | `resolveServerId` | `true` | Tunggu echo server untuk dapat `serverId`. `false` = lebih cepat |
52
- | `serverIdTimeoutMs` | `8000` | Timeout menunggu echo |
53
-
54
- ### Hasil
55
-
56
- Mengembalikan `WebMessageInfo` dengan field tambahan:
57
-
58
- ```js
59
- {
60
- newsletterStatusServerId, // number, dipakai untuk react/revoke
61
- newsletterStatusAck, // ack terurai
62
- newsletterStatusResponse, // node mentah
63
- newsletterStatusDelivered // node echo (jika resolveServerId)
64
- }
65
- ```
66
-
67
- ### Reshare status channel ke status sendiri
68
-
69
- ```js
70
- await sock.newsletter.sendStatus(jid, {
71
- text: 'Lihat ini',
72
- newsletterStatus: { newsletterJid: '120363...@newsletter', messageId: 150 }
73
- })
74
- ```
75
-
76
- ---
77
-
78
- ## 2. React
79
-
80
- ```js
81
- await sock.newsletter.reactToStatus(jid, parentServerId, '🔥')
82
- ```
83
-
84
- `parentServerId` harus integer antara **99 dan 2147476647**.
85
-
86
- ## 3. Hapus (revoke)
87
-
88
- ```js
89
- await sock.newsletter.revokeStatus(jid, statusId)
90
- ```
91
-
92
- ## 4. Ambil status
93
-
94
- ```js
95
- const list = await sock.newsletter.fetchStatuses(jid, { count: 20, before, after })
96
- const updates = await sock.newsletter.fetchStatusUpdates(jid, { count: 20, since })
97
- ```
98
-
99
- Entry berisi: `id`, `serverId`, `t`, `type`, `mediaType`, `reactionCounts`, `viewsCount`, `responsesCount`, `message`, dll. Lihat `lib/Utils/newsletter-status.d.ts`.
100
-
101
- ---
102
-
103
- ## Helper tambahan (named export)
104
-
105
- `buildNewsletterStatusNode`, `buildNewsletterStatusReactionNode`, `buildNewsletterStatusRevokeNode`, `parseNewsletterStatusAck`, `toNewsletterServerId`, `getNewsletterStatusMediaType`, `makeNewsletterStatusAttribution`, `NEWSLETTER_SERVER_ID_MIN/MAX`.
106
-
107
- ## Error umum
108
-
109
- | Pesan | Penyebab |
110
- |---|---|
111
- | `Newsletter status target must be a @newsletter JID` | JID bukan newsletter |
112
- | `Not authenticated` | Socket belum login |
113
- | `... is not a newsletter server id` | serverId di luar 99..2147476647 |
114
- | `Native newsletter status supports text, image, video, gif, and audio` | Kirim dokumen/sticker |
115
- | Timeout ack | Server tidak membalas dalam `ackTimeoutMs` |
116
-
117
- ## Catatan
118
-
119
- - Fitur bergantung pada protokol WhatsApp yang bisa berubah sewaktu-waktu.
120
- - Tidak mengubah perilaku `sendMessage` biasa.