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 +26 -1
- package/docs/SPR.md +1 -1
- package/lib/Socket/index.js +37 -6
- package/lib/Socket/socket.js +3 -2
- package/lib/Utils/crypto.js +25 -1
- package/lib/Utils/greeting-card.d.ts +15 -0
- package/lib/Utils/greeting-card.js +70 -0
- package/lib/Utils/group-history.d.ts +4 -0
- package/lib/Utils/group-history.js +58 -0
- package/lib/Utils/index.js +6 -0
- package/lib/Utils/meta-ai-bot.d.ts +10 -0
- package/lib/Utils/meta-ai-bot.js +94 -0
- package/lib/Utils/native-backend.d.ts +3 -0
- package/lib/Utils/native-backend.js +56 -0
- package/lib/Utils/plugins.d.ts +20 -0
- package/lib/Utils/plugins.js +78 -0
- package/lib/Utils/presence-profile.d.ts +18 -0
- package/lib/Utils/presence-profile.js +50 -0
- package/package.json +6 -2
- package/README.md +0 -133
- package/docs/NEWSLETTER-STATUS.md +0 -120
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 5.0.2
|
|
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
|
|
88
|
+
- Perubahan v5.0.2: engine ditulis ulang (kode baru), tambah `personal`, `sPRPrivate`, `sPRBroadcast`, `getSPRTargets`, `loadSPRAvatars`. API `sPR`/`selectiveRelay` lama tetap kompatibel.
|
package/lib/Socket/index.js
CHANGED
|
@@ -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
|
-
//
|
|
40
|
-
|
|
41
|
-
clientWithBuilder.
|
|
42
|
-
clientWithBuilder.
|
|
43
|
-
clientWithBuilder.
|
|
44
|
-
clientWithBuilder.
|
|
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) {
|
package/lib/Socket/socket.js
CHANGED
|
@@ -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
|
-
|
|
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'),
|
package/lib/Utils/crypto.js
CHANGED
|
@@ -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) =>
|
|
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
|
+
}
|
package/lib/Utils/index.js
CHANGED
|
@@ -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
|
|
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
|
-

|
|
10
|
-

|
|
11
|
-

|
|
12
|
-

|
|
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.
|