aunubaileys 1.0.3 → 1.0.5
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 +13 -0
- package/lib/Socket/chats.js +3 -1
- package/lib/Socket/socket.js +1 -0
- package/lib/Utils/antiban.d.ts +21 -0
- package/lib/Utils/antiban.js +170 -0
- package/lib/Utils/aunu-handler.js +17 -0
- package/lib/Utils/builders.d.ts +31 -0
- package/lib/Utils/builders.js +118 -0
- package/lib/Utils/chat-utils.js +7 -0
- package/lib/Utils/event-buffer.js +15 -1
- package/lib/Utils/generics.js +2 -0
- package/lib/Utils/messages-media.js +8 -5
- package/lib/Utils/messages.js +59 -0
- package/lib/Utils/process-message.js +6 -3
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,19 @@ Everything else is pure upstream. When upstream moves, we rebase the patch layer
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### 🆕 What's new in 1.0.5 / Baru di 1.0.5
|
|
66
|
+
|
|
67
|
+
- 🛡️ **AunuAntiBan built in** — account-protection layer inspired by [baileys-antiban](https://github.com/kobie3717/baileys-antiban), reimplemented cleanly in-package: per-chat & global rate limiting with Gaussian jitter, 7-day warmup ramp for new accounts, group add/create guards, typing simulation, and `classifyDisconnect()` recovery advice. No telemetry, no extra network calls. Usage: `wrapAntiBan(sock)` or `new AunuAntiBan(opts).wrap(sock)`.
|
|
68
|
+
|
|
69
|
+
### 🆕 What's new in 1.0.4 / Baru di 1.0.4
|
|
70
|
+
|
|
71
|
+
- 🎨 **Rich builders** — `AIRich`, `Button`, `ButtonV2`, `Carousel`, `Toolkit` (+ aliases) for Meta AI-style rich responses, native-flow buttons and carousels; `aunuHandler.sendInteractive()` with automatic plain-text fallback on iOS-paired devices.
|
|
72
|
+
- 🖼️ **Profile pictures fixed** — center crop (was top-left) and jimp v1 compatibility restored.
|
|
73
|
+
- 🛡️ **Hardening** — undecodable app-state patches are skipped with a warning instead of killing sync, media downloads honour the message's own host, `getChatId` fails loudly, real `ev.destroy()` cleanup, group presence carries `groupOnlineCount`, `Browsers.android` added.
|
|
74
|
+
- ✅ Already verified in this codebase: PBKDF2 pairing derivation and the anti-spoof protocol-message guard (GHSA-qvv5-jq5g-4cgg class) match the reference implementations.
|
|
75
|
+
|
|
63
76
|
### 🆕 What's new in 1.0.3 / Baru di 1.0.3
|
|
64
77
|
|
|
65
78
|
- ⚡ **Modern core** — latest WA version `2.3000.1035194821`, 812 pre-keys, tuned WebSocket (no per-message deflate, 100 MB payload).
|
package/lib/Socket/chats.js
CHANGED
|
@@ -546,7 +546,9 @@ export const makeChatsSocket = (config) => {
|
|
|
546
546
|
if (tag === 'presence') {
|
|
547
547
|
presence = {
|
|
548
548
|
lastKnownPresence: attrs.type === 'unavailable' ? 'unavailable' : 'available',
|
|
549
|
-
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined
|
|
549
|
+
lastSeen: attrs.last && attrs.last !== 'deny' ? +attrs.last : undefined,
|
|
550
|
+
// AunuBaileys: jumlah online grup bila dikirim WhatsApp
|
|
551
|
+
groupOnlineCount: attrs.count ? +attrs.count : undefined
|
|
550
552
|
};
|
|
551
553
|
}
|
|
552
554
|
else if (Array.isArray(content)) {
|
package/lib/Socket/socket.js
CHANGED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { WASocket } from '../Types';
|
|
2
|
+
export declare const gaussianJitter: (mean?: number, sigma?: number) => number;
|
|
3
|
+
export declare class RateLimiter {
|
|
4
|
+
constructor(max: number, windowMs: number);
|
|
5
|
+
acquire(key: string): Promise<number>;
|
|
6
|
+
tryAcquire(key: string): boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface DisconnectClassification { category: string; action: 'stop' | 'restart' | 'reconnect' | 'backoff'; detail: string; }
|
|
9
|
+
export declare const classifyDisconnect: (error: unknown) => DisconnectClassification;
|
|
10
|
+
export interface AunuAntiBanOptions {
|
|
11
|
+
perChatPerMinute?: number; globalPerMinute?: number; minDelayMs?: number; jitterMs?: number;
|
|
12
|
+
warmupDays?: number; warmupStart?: number; typingSimulation?: boolean;
|
|
13
|
+
groupAddMax?: number; groupAddWindowMs?: number; groupCreateMax?: number; groupCreateWindowMs?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare class AunuAntiBan {
|
|
16
|
+
constructor(options?: AunuAntiBanOptions);
|
|
17
|
+
warmupFactor(): number;
|
|
18
|
+
effectiveLimit(base: number): number;
|
|
19
|
+
wrap<T extends WASocket>(sock: T): T;
|
|
20
|
+
}
|
|
21
|
+
export declare const wrapAntiBan: (sock: WASocket, options?: AunuAntiBanOptions) => WASocket;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AunuAntiBan — lapisan perlindungan akun bawaan AunuBaileys.
|
|
3
|
+
* Konsep terinspirasi baileys-antiban (kobie3717), diimplementasikan ulang
|
|
4
|
+
* secara bersih di dalam paket: rate limiting dengan jitter Gaussian,
|
|
5
|
+
* jadwal warmup akun baru, penjaga operasi grup, simulasi mengetik,
|
|
6
|
+
* dan klasifikasi disconnect. Tanpa telemetri, tanpa jaringan tambahan.
|
|
7
|
+
*/
|
|
8
|
+
import { Boom } from '@hapi/boom';
|
|
9
|
+
import { DisconnectReason } from '../Types/index.js';
|
|
10
|
+
import { delay } from './generics.js';
|
|
11
|
+
|
|
12
|
+
/** Jitter Gaussian (Box-Muller), hasil dibatasi >= 0. */
|
|
13
|
+
export const gaussianJitter = (mean = 0, sigma = 1) => {
|
|
14
|
+
let u = 0;
|
|
15
|
+
let v = 0;
|
|
16
|
+
while (u === 0)
|
|
17
|
+
u = Math.random();
|
|
18
|
+
while (v === 0)
|
|
19
|
+
v = Math.random();
|
|
20
|
+
const n = Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
|
21
|
+
return Math.max(0, mean + n * sigma);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Rate limiter jendela geser per kunci. */
|
|
25
|
+
export class RateLimiter {
|
|
26
|
+
constructor(max, windowMs) {
|
|
27
|
+
this.max = max;
|
|
28
|
+
this.windowMs = windowMs;
|
|
29
|
+
this.hits = new Map();
|
|
30
|
+
}
|
|
31
|
+
/** Tunggu sampai ada slot untuk key; kembalikan waktu tunggu (ms). */
|
|
32
|
+
async acquire(key) {
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
let arr = this.hits.get(key);
|
|
35
|
+
if (!arr) {
|
|
36
|
+
arr = [];
|
|
37
|
+
this.hits.set(key, arr);
|
|
38
|
+
}
|
|
39
|
+
while (arr.length && now - arr[0] >= this.windowMs)
|
|
40
|
+
arr.shift();
|
|
41
|
+
if (arr.length >= this.max) {
|
|
42
|
+
const wait = this.windowMs - (now - arr[0]) + 5;
|
|
43
|
+
await delay(wait);
|
|
44
|
+
return this.acquire(key);
|
|
45
|
+
}
|
|
46
|
+
arr.push(Date.now());
|
|
47
|
+
return 0;
|
|
48
|
+
}
|
|
49
|
+
/** Cek tanpa menunggu; true bila masih boleh. */
|
|
50
|
+
tryAcquire(key) {
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
let arr = this.hits.get(key);
|
|
53
|
+
if (!arr) {
|
|
54
|
+
arr = [];
|
|
55
|
+
this.hits.set(key, arr);
|
|
56
|
+
}
|
|
57
|
+
while (arr.length && now - arr[0] >= this.windowMs)
|
|
58
|
+
arr.shift();
|
|
59
|
+
if (arr.length >= this.max) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
arr.push(now);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Klasifikasi error disconnect -> kategori + tindakan yang disarankan. */
|
|
68
|
+
export const classifyDisconnect = (error) => {
|
|
69
|
+
const code = error?.output?.statusCode ?? error?.statusCode ?? error;
|
|
70
|
+
switch (code) {
|
|
71
|
+
case DisconnectReason.loggedOut:
|
|
72
|
+
return { category: 'logged_out', action: 'stop', detail: 'Sesi logout — perlu pairing ulang. Jangan auto-reconnect.' };
|
|
73
|
+
case DisconnectReason.restartRequired:
|
|
74
|
+
return { category: 'restart_required', action: 'restart', detail: 'Server meminta restart — hubungkan ulang sekali.' };
|
|
75
|
+
case DisconnectReason.connectionReplaced:
|
|
76
|
+
return { category: 'replaced', action: 'stop', detail: 'Sesi digantikan koneksi lain — periksa perangkat tertaut.' };
|
|
77
|
+
case DisconnectReason.badSession:
|
|
78
|
+
return { category: 'bad_session', action: 'stop', detail: 'Sesi rusak — hapus auth state dan pairing ulang.' };
|
|
79
|
+
case DisconnectReason.multideviceMismatch:
|
|
80
|
+
return { category: 'mismatch', action: 'stop', detail: 'Ketidakcocokan multidevice — pairing ulang.' };
|
|
81
|
+
case 403:
|
|
82
|
+
return { category: 'forbidden', action: 'stop', detail: '403 — kemungkinan dibatasi/diblokir. Berhenti dan periksa akun.' };
|
|
83
|
+
case 429:
|
|
84
|
+
return { category: 'rate_limited', action: 'backoff', detail: '429 — terlalu cepat. Mundur dan turunkan laju kirim.' };
|
|
85
|
+
case DisconnectReason.timedOut:
|
|
86
|
+
case DisconnectReason.connectionLost:
|
|
87
|
+
case DisconnectReason.connectionClosed:
|
|
88
|
+
default:
|
|
89
|
+
return { category: 'transient', action: 'reconnect', detail: 'Gangguan jaringan/server sementara — aman reconnect dengan jeda.' };
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
export class AunuAntiBan {
|
|
94
|
+
constructor(options = {}) {
|
|
95
|
+
this.options = {
|
|
96
|
+
perChatPerMinute: 12,
|
|
97
|
+
globalPerMinute: 60,
|
|
98
|
+
minDelayMs: 700,
|
|
99
|
+
jitterMs: 900,
|
|
100
|
+
warmupDays: 7,
|
|
101
|
+
warmupStart: Date.now(),
|
|
102
|
+
typingSimulation: true,
|
|
103
|
+
groupAddMax: 3,
|
|
104
|
+
groupAddWindowMs: 600000,
|
|
105
|
+
groupCreateMax: 2,
|
|
106
|
+
groupCreateWindowMs: 600000,
|
|
107
|
+
...options
|
|
108
|
+
};
|
|
109
|
+
const o = this.options;
|
|
110
|
+
this.perChat = new RateLimiter(o.perChatPerMinute, 60000);
|
|
111
|
+
this.global = new RateLimiter(o.globalPerMinute, 60000);
|
|
112
|
+
this.groupAdd = new RateLimiter(o.groupAddMax, o.groupAddWindowMs);
|
|
113
|
+
this.groupCreate = new RateLimiter(o.groupCreateMax, o.groupCreateWindowMs);
|
|
114
|
+
}
|
|
115
|
+
/** Faktor warmup 0.3 -> 1.0 selama warmupDays sejak warmupStart. */
|
|
116
|
+
warmupFactor() {
|
|
117
|
+
const days = (Date.now() - this.options.warmupStart) / 86400000;
|
|
118
|
+
if (days >= this.options.warmupDays) {
|
|
119
|
+
return 1;
|
|
120
|
+
}
|
|
121
|
+
return 0.3 + 0.7 * Math.max(0, days / this.options.warmupDays);
|
|
122
|
+
}
|
|
123
|
+
/** Batasi laju efektif mengikuti warmup. */
|
|
124
|
+
effectiveLimit(base) {
|
|
125
|
+
return Math.max(1, Math.floor(base * this.warmupFactor()));
|
|
126
|
+
}
|
|
127
|
+
/** Terapkan perlindungan ke socket; socket yang sama dikembalikan. */
|
|
128
|
+
wrap(sock) {
|
|
129
|
+
const self = this;
|
|
130
|
+
const origSend = sock.sendMessage.bind(sock);
|
|
131
|
+
sock.sendMessage = async (jid, content, options) => {
|
|
132
|
+
await self.global.acquire('__global__');
|
|
133
|
+
await self.perChat.acquire(jid);
|
|
134
|
+
if (self.options.typingSimulation && (content?.text || content?.caption) && typeof sock.sendPresenceUpdate === 'function') {
|
|
135
|
+
try {
|
|
136
|
+
await sock.sendPresenceUpdate('composing', jid);
|
|
137
|
+
}
|
|
138
|
+
catch { }
|
|
139
|
+
}
|
|
140
|
+
const wait = self.options.minDelayMs * self.warmupFactor() + gaussianJitter(0, self.options.jitterMs / 2);
|
|
141
|
+
if (wait > 0) {
|
|
142
|
+
await delay(wait);
|
|
143
|
+
}
|
|
144
|
+
return origSend(jid, content, options);
|
|
145
|
+
};
|
|
146
|
+
if (typeof sock.groupParticipantsUpdate === 'function') {
|
|
147
|
+
const orig = sock.groupParticipantsUpdate.bind(sock);
|
|
148
|
+
sock.groupParticipantsUpdate = async (jid, participants, action, inviteMessage) => {
|
|
149
|
+
if (action === 'add' && !self.groupAdd.tryAcquire(jid)) {
|
|
150
|
+
throw new Boom('AunuAntiBan: batas tambah anggota grup tercapai — jeda dulu agar akun aman', { statusCode: 429 });
|
|
151
|
+
}
|
|
152
|
+
return orig(jid, participants, action, inviteMessage);
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (typeof sock.groupCreate === 'function') {
|
|
156
|
+
const origCreate = sock.groupCreate.bind(sock);
|
|
157
|
+
sock.groupCreate = async (subject, participants) => {
|
|
158
|
+
if (!self.groupCreate.tryAcquire('__create__')) {
|
|
159
|
+
throw new Boom('AunuAntiBan: batas buat grup tercapai — jeda dulu agar akun aman', { statusCode: 429 });
|
|
160
|
+
}
|
|
161
|
+
return origCreate(subject, participants);
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
sock.aunuAntiBan = self;
|
|
165
|
+
return sock;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Bungkus socket dengan AunuAntiBan (jalan pintas). */
|
|
170
|
+
export const wrapAntiBan = (sock, options) => new AunuAntiBan(options).wrap(sock);
|
|
@@ -20,6 +20,23 @@ export class AunuHandler {
|
|
|
20
20
|
async react(jid, emoji, key) {
|
|
21
21
|
return this.sock.sendMessage(jid, { react: { text: emoji, key } });
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Kirim pesan interaktif; otomatis fallback ke teks polos bila
|
|
25
|
+
* perangkat pasangan adalah iOS (tombol tidak tampil andal di sana)
|
|
26
|
+
* atau bila options.forceFallback diaktifkan.
|
|
27
|
+
*/
|
|
28
|
+
async sendInteractive(jid, content, options = {}) {
|
|
29
|
+
const platform = this.sock.authState?.creds?.platform;
|
|
30
|
+
if (options.forceFallback || platform === 'ios') {
|
|
31
|
+
const i = content?.interactive || {};
|
|
32
|
+
const text = options.fallbackText || i.text || i.body?.text || '';
|
|
33
|
+
const rest = { ...options };
|
|
34
|
+
delete rest.forceFallback;
|
|
35
|
+
delete rest.fallbackText;
|
|
36
|
+
return this.sock.sendMessage(jid, { text }, rest);
|
|
37
|
+
}
|
|
38
|
+
return this.sock.sendMessage(jid, content, options);
|
|
39
|
+
}
|
|
23
40
|
/** ID numerik dari JID newsletter. */
|
|
24
41
|
newsletterId(jid) {
|
|
25
42
|
return this.sock.newsletterId(jid);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AnyMessageContent } from '../Types';
|
|
2
|
+
export declare class Button {
|
|
3
|
+
title(t: string): this; body(t: string): this; footer(t: string): this;
|
|
4
|
+
reply(id: string, text: string): this; url(text: string, url: string): this;
|
|
5
|
+
copy(text: string, code: string): this; call(text: string, phoneNumber: string): this;
|
|
6
|
+
build(): AnyMessageContent;
|
|
7
|
+
}
|
|
8
|
+
export declare class ButtonV2 {
|
|
9
|
+
body(t: string): this; footer(t: string): this;
|
|
10
|
+
list(buttonText: string, sections: unknown[]): this;
|
|
11
|
+
reply(id: string, text: string): this; url(text: string, url: string): this;
|
|
12
|
+
build(): AnyMessageContent;
|
|
13
|
+
}
|
|
14
|
+
export declare class Carousel {
|
|
15
|
+
body(t: string): this; card(card: Record<string, unknown>): this;
|
|
16
|
+
build(): AnyMessageContent;
|
|
17
|
+
}
|
|
18
|
+
export declare class AIRich {
|
|
19
|
+
text(t: string): this; code(code: string, language?: string): this;
|
|
20
|
+
latex(t: string): this; table(table: unknown): this; raw(sub: unknown): this;
|
|
21
|
+
build(): AnyMessageContent;
|
|
22
|
+
}
|
|
23
|
+
export declare class Toolkit {
|
|
24
|
+
static quickReply(id: string, text: string): unknown;
|
|
25
|
+
static urlButton(text: string, url: string): unknown;
|
|
26
|
+
static copyButton(text: string, code: string): unknown;
|
|
27
|
+
static callButton(text: string, phoneNumber: string): unknown;
|
|
28
|
+
static listSection(title: string, rows: { id: string; title: string; description?: string }[]): unknown;
|
|
29
|
+
static listButton(text: string, title: string, rows: { id: string; title: string; description?: string }[]): unknown;
|
|
30
|
+
}
|
|
31
|
+
export { Button as Buttons, Button as ButtonBuilder, ButtonV2 as ListButton, Carousel as CarouselBuilder, AIRich as AiRich, AIRich as RichResponse, Toolkit as Tools };
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rich message builders khas AunuBaileys — API chainable untuk tombol,
|
|
3
|
+
* carousel, dan respons kaya ala Meta AI. Hasil build() adalah
|
|
4
|
+
* AnyMessageContent yang siap dikirim via sock.sendMessage().
|
|
5
|
+
*/
|
|
6
|
+
const nf = (name, params) => ({ name, buttonParamsJson: JSON.stringify(params) });
|
|
7
|
+
|
|
8
|
+
/** Builder tombol interaktif (native flow). Alias: Buttons, ButtonBuilder. */
|
|
9
|
+
export class Button {
|
|
10
|
+
constructor() {
|
|
11
|
+
this._text = '';
|
|
12
|
+
this._footer = '';
|
|
13
|
+
this._title = '';
|
|
14
|
+
this._buttons = [];
|
|
15
|
+
}
|
|
16
|
+
title(t) { this._title = t; return this; }
|
|
17
|
+
body(t) { this._text = t; return this; }
|
|
18
|
+
footer(t) { this._footer = t; return this; }
|
|
19
|
+
/** Tombol balasan cepat. */
|
|
20
|
+
reply(id, text) { this._buttons.push(nf('quick_reply', { display_text: text, id: String(id) })); return this; }
|
|
21
|
+
/** Tombol buka tautan. */
|
|
22
|
+
url(text, url) { this._buttons.push(nf('cta_url', { display_text: text, url, merchant_url: url })); return this; }
|
|
23
|
+
/** Tombol salin teks/kode. */
|
|
24
|
+
copy(text, code) { this._buttons.push(nf('cta_copy', { display_text: text, copy_code: code, id: 'copy' })); return this; }
|
|
25
|
+
/** Tombol telepon. */
|
|
26
|
+
call(text, phoneNumber) { this._buttons.push(nf('cta_call', { display_text: text, phone_number: phoneNumber })); return this; }
|
|
27
|
+
build() {
|
|
28
|
+
return {
|
|
29
|
+
interactive: {
|
|
30
|
+
text: this._text,
|
|
31
|
+
footerText: this._footer || undefined,
|
|
32
|
+
header: this._title ? { title: this._title, hasMediaAttachment: false } : undefined,
|
|
33
|
+
buttons: this._buttons
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Builder daftar pilihan (single_select) + tombol lanjutan. Alias: ListButton. */
|
|
40
|
+
export class ButtonV2 {
|
|
41
|
+
constructor() {
|
|
42
|
+
this._text = '';
|
|
43
|
+
this._footer = '';
|
|
44
|
+
this._buttons = [];
|
|
45
|
+
this._sections = [];
|
|
46
|
+
}
|
|
47
|
+
body(t) { this._text = t; return this; }
|
|
48
|
+
footer(t) { this._footer = t; return this; }
|
|
49
|
+
/** Satu tombol daftar berisi sections/rows. */
|
|
50
|
+
list(buttonText, sections) {
|
|
51
|
+
this._buttons.push(nf('single_select', { title: buttonText, sections }));
|
|
52
|
+
return this;
|
|
53
|
+
}
|
|
54
|
+
reply(id, text) { this._buttons.push(nf('quick_reply', { display_text: text, id: String(id) })); return this; }
|
|
55
|
+
url(text, url) { this._buttons.push(nf('cta_url', { display_text: text, url, merchant_url: url })); return this; }
|
|
56
|
+
build() {
|
|
57
|
+
return { interactive: { text: this._text, footerText: this._footer || undefined, buttons: this._buttons } };
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Builder carousel kartu geser. Alias: CarouselBuilder. */
|
|
62
|
+
export class Carousel {
|
|
63
|
+
constructor() {
|
|
64
|
+
this._text = '';
|
|
65
|
+
this._cards = [];
|
|
66
|
+
}
|
|
67
|
+
body(t) { this._text = t; return this; }
|
|
68
|
+
/** card: { title, body, footer?, imageMessage?, buttons?: Button instance | array nativeFlow } */
|
|
69
|
+
card(card) {
|
|
70
|
+
let buttons = card.buttons || [];
|
|
71
|
+
if (buttons instanceof Button) {
|
|
72
|
+
buttons = buttons.build().interactive.buttons;
|
|
73
|
+
}
|
|
74
|
+
this._cards.push({ title: card.title, body: card.body || '', footer: card.footer, imageMessage: card.imageMessage, buttons });
|
|
75
|
+
return this;
|
|
76
|
+
}
|
|
77
|
+
build() {
|
|
78
|
+
return { interactive: { text: this._text, carousel: this._cards } };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Builder respons kaya ala Meta AI (AIRichResponseMessage). Alias: AiRich, RichResponse. */
|
|
83
|
+
export class AIRich {
|
|
84
|
+
constructor() {
|
|
85
|
+
this._subs = [];
|
|
86
|
+
}
|
|
87
|
+
text(t) { this._subs.push({ type: 'text', text: t }); return this; }
|
|
88
|
+
code(code, language = 'text') { this._subs.push({ type: 'code', code, language }); return this; }
|
|
89
|
+
latex(t) { this._subs.push({ type: 'latex', text: t }); return this; }
|
|
90
|
+
/** table: proto tableMetadata mentah { title, rows: [...] } */
|
|
91
|
+
table(table) { this._subs.push({ type: 'table', table }); return this; }
|
|
92
|
+
raw(sub) { this._subs.push({ raw: sub }); return this; }
|
|
93
|
+
build() {
|
|
94
|
+
return { aiRich: { submessages: this._subs } };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Helper statis campuran. Alias: Tools. */
|
|
99
|
+
export class Toolkit {
|
|
100
|
+
static quickReply(id, text) { return nf('quick_reply', { display_text: text, id: String(id) }); }
|
|
101
|
+
static urlButton(text, url) { return nf('cta_url', { display_text: text, url, merchant_url: url }); }
|
|
102
|
+
static copyButton(text, code) { return nf('cta_copy', { display_text: text, copy_code: code, id: 'copy' }); }
|
|
103
|
+
static callButton(text, phoneNumber) { return nf('cta_call', { display_text: text, phone_number: phoneNumber }); }
|
|
104
|
+
static listSection(title, rows) {
|
|
105
|
+
return { title, rows: rows.map(r => ({ id: String(r.id), title: r.title, description: r.description || '' })) };
|
|
106
|
+
}
|
|
107
|
+
/** Ubah daftar {id, title, description} menjadi satu tombol single_select. */
|
|
108
|
+
static listButton(text, title, rows) {
|
|
109
|
+
return nf('single_select', { title: text, sections: [Toolkit.listSection(title, rows)] });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const Buttons = Button;
|
|
113
|
+
export const ButtonBuilder = Button;
|
|
114
|
+
export const ListButton = ButtonV2;
|
|
115
|
+
export const CarouselBuilder = Carousel;
|
|
116
|
+
export const AiRich = AIRich;
|
|
117
|
+
export const RichResponse = AIRich;
|
|
118
|
+
export const Tools = Toolkit;
|
package/lib/Utils/chat-utils.js
CHANGED
|
@@ -286,6 +286,7 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
|
|
|
286
286
|
};
|
|
287
287
|
const mutationMap = {};
|
|
288
288
|
for (const syncd of syncds) {
|
|
289
|
+
try {
|
|
289
290
|
const { version, keyId, snapshotMac } = syncd;
|
|
290
291
|
if (syncd.externalMutations) {
|
|
291
292
|
logger?.trace({ name, version }, 'downloading external patch');
|
|
@@ -318,6 +319,12 @@ export const decodePatches = async (name, syncds, initial, getAppStateSyncKey, o
|
|
|
318
319
|
}
|
|
319
320
|
// clear memory used up by the mutations
|
|
320
321
|
syncd.mutations = [];
|
|
322
|
+
}
|
|
323
|
+
catch (err) {
|
|
324
|
+
// AunuBaileys: satu patch rusak tidak membunuh seluruh sinkronisasi
|
|
325
|
+
logger?.warn({ err, name, version: syncd.version }, 'skipping undecodable app state patch');
|
|
326
|
+
syncd.mutations = [];
|
|
327
|
+
}
|
|
321
328
|
}
|
|
322
329
|
return { state: newState, mutationMap };
|
|
323
330
|
};
|
|
@@ -100,7 +100,21 @@ export const makeEventBuffer = (logger) => {
|
|
|
100
100
|
},
|
|
101
101
|
on: (...args) => ev.on(...args),
|
|
102
102
|
off: (...args) => ev.off(...args),
|
|
103
|
-
removeAllListeners: (...args) => ev.removeAllListeners(...args)
|
|
103
|
+
removeAllListeners: (...args) => ev.removeAllListeners(...args),
|
|
104
|
+
// AunuBaileys: destroy sungguhan — bersihkan buffer, cache & listener
|
|
105
|
+
destroy() {
|
|
106
|
+
try {
|
|
107
|
+
const fresh = makeBufferData();
|
|
108
|
+
for (const key of Object.keys(data)) {
|
|
109
|
+
delete data[key];
|
|
110
|
+
}
|
|
111
|
+
Object.assign(data, fresh);
|
|
112
|
+
}
|
|
113
|
+
catch { }
|
|
114
|
+
historyCache.clear();
|
|
115
|
+
isBuffering = false;
|
|
116
|
+
ev.removeAllListeners();
|
|
117
|
+
}
|
|
104
118
|
};
|
|
105
119
|
};
|
|
106
120
|
const makeBufferData = () => {
|
package/lib/Utils/generics.js
CHANGED
|
@@ -25,6 +25,8 @@ export const Browsers = {
|
|
|
25
25
|
macOS: browser => ['Mac OS', browser, '14.4.1'],
|
|
26
26
|
baileys: browser => ['Baileys', browser, '6.5.0'],
|
|
27
27
|
windows: browser => ['Windows', browser, '10.0.22631'],
|
|
28
|
+
/** AunuBaileys: sesi yang tampil sebagai Android */
|
|
29
|
+
android: browser => ['Android', browser, '14'],
|
|
28
30
|
/** The appropriate browser based on your OS & release */
|
|
29
31
|
appropriate: browser => [PLATFORM_MAP[platform()] || 'Ubuntu', browser, release()]
|
|
30
32
|
};
|
|
@@ -114,7 +114,8 @@ export const extractImageThumb = async (bufferOrFilePath, width = 32) => {
|
|
|
114
114
|
}
|
|
115
115
|
};
|
|
116
116
|
}
|
|
117
|
-
else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
|
|
117
|
+
else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) {
|
|
118
|
+
// AunuBaileys: jimp v1 mengekspos Jimp sebagai class (function), bukan object
|
|
118
119
|
const jimp = await lib.jimp.Jimp.read(bufferOrFilePath);
|
|
119
120
|
const dimensions = {
|
|
120
121
|
width: jimp.width,
|
|
@@ -156,10 +157,12 @@ export const generateProfilePicture = async (mediaUpload, dimensions) => {
|
|
|
156
157
|
})
|
|
157
158
|
.toBuffer();
|
|
158
159
|
}
|
|
159
|
-
else if ('jimp' in lib && typeof lib.jimp?.Jimp === 'object') {
|
|
160
|
+
else if ('jimp' in lib && (typeof lib.jimp?.Jimp === 'object' || typeof lib.jimp?.Jimp === 'function')) {
|
|
161
|
+
// AunuBaileys: jimp v1 mengekspos Jimp sebagai class (function), bukan object
|
|
160
162
|
const jimp = await lib.jimp.Jimp.read(buffer);
|
|
161
163
|
const min = Math.min(jimp.width, jimp.height);
|
|
162
|
-
|
|
164
|
+
// AunuBaileys: crop dari TENGAH (sebelumnya pojok kiri-atas memotong subjek)
|
|
165
|
+
const cropped = jimp.crop({ x: Math.floor((jimp.width - min) / 2), y: Math.floor((jimp.height - min) / 2), w: min, h: min });
|
|
163
166
|
img = cropped.resize({ w, h, mode: lib.jimp.ResizeStrategy.BILINEAR }).getBuffer('image/jpeg', { quality: 50 });
|
|
164
167
|
}
|
|
165
168
|
else {
|
|
@@ -386,8 +389,8 @@ const toSmallestChunkSize = (num) => {
|
|
|
386
389
|
};
|
|
387
390
|
export const getUrlFromDirectPath = (directPath) => `https://${DEF_HOST}${directPath}`;
|
|
388
391
|
export const downloadContentFromMessage = async ({ mediaKey, directPath, url }, type, opts = {}) => {
|
|
389
|
-
|
|
390
|
-
const downloadUrl =
|
|
392
|
+
// AunuBaileys: pakai host dari URL pesan bila ada (bukan selalu mmg.whatsapp.net)
|
|
393
|
+
const downloadUrl = url?.startsWith('https://') ? url : getUrlFromDirectPath(directPath);
|
|
391
394
|
if (!downloadUrl) {
|
|
392
395
|
throw new Boom('No valid media URL or directPath present in message', { statusCode: 400 });
|
|
393
396
|
}
|
package/lib/Utils/messages.js
CHANGED
|
@@ -643,6 +643,31 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
643
643
|
}
|
|
644
644
|
}
|
|
645
645
|
}
|
|
646
|
+
else if ('aiRich' in message) {
|
|
647
|
+
// AunuBaileys: Meta AI-style rich response (AIRichResponseMessage)
|
|
648
|
+
const subs = (message.aiRich.submessages || []).map((s) => {
|
|
649
|
+
if (s.type === 'text' || (!s.type && s.text)) {
|
|
650
|
+
return { messageText: { text: s.text || '' } };
|
|
651
|
+
}
|
|
652
|
+
if (s.type === 'code') {
|
|
653
|
+
return { codeMetadata: { codeContent: s.code || s.text || '', codeLanguage: s.language || 'text' } };
|
|
654
|
+
}
|
|
655
|
+
if (s.type === 'latex') {
|
|
656
|
+
return { latexMetadata: { text: s.text || '' } };
|
|
657
|
+
}
|
|
658
|
+
if (s.type === 'table') {
|
|
659
|
+
return { tableMetadata: s.table };
|
|
660
|
+
}
|
|
661
|
+
return s.raw || s;
|
|
662
|
+
});
|
|
663
|
+
m.richResponseMessage = {
|
|
664
|
+
messageType: proto.AIRichResponseMessage.AIRichResponseMessageType.AI_RICH_RESPONSE_TYPE_STANDARD,
|
|
665
|
+
submessages: subs
|
|
666
|
+
};
|
|
667
|
+
if ('text' in message) {
|
|
668
|
+
// teks pendamping opsional diabaikan oleh proto bila rich response ada
|
|
669
|
+
}
|
|
670
|
+
}
|
|
646
671
|
else if ('pollResult' in message) {
|
|
647
672
|
// AunuBaileys: poll result snapshot (dari @itsliaaa/baileys)
|
|
648
673
|
const pollResultSnapshotMessage = {
|
|
@@ -707,6 +732,40 @@ export const generateWAMessageContent = async (message, options) => {
|
|
|
707
732
|
else if ('requestPhoneNumber' in message) {
|
|
708
733
|
m.requestPhoneNumberMessage = {};
|
|
709
734
|
}
|
|
735
|
+
else if ('interactive' in message) {
|
|
736
|
+
// AunuBaileys: interactive / native-flow (pola @itsliaaa/baileys, disederhanakan)
|
|
737
|
+
const iMsg = message.interactive;
|
|
738
|
+
const interactiveMessage = {
|
|
739
|
+
body: iMsg.body ?? (iMsg.text ? { text: iMsg.text } : undefined),
|
|
740
|
+
footer: iMsg.footer ?? (iMsg.footerText ? { text: iMsg.footerText } : undefined),
|
|
741
|
+
header: iMsg.header,
|
|
742
|
+
contextInfo: iMsg.contextInfo
|
|
743
|
+
};
|
|
744
|
+
if (iMsg.carousel) {
|
|
745
|
+
interactiveMessage.carouselMessage = {
|
|
746
|
+
cards: iMsg.carousel.map(card => ({
|
|
747
|
+
header: card.header ?? (card.title ? { title: card.title, hasMediaAttachment: !!card.imageMessage, imageMessage: card.imageMessage } : undefined),
|
|
748
|
+
body: { text: card.body || '' },
|
|
749
|
+
footer: card.footer ? { text: card.footer } : undefined,
|
|
750
|
+
nativeFlowMessage: { buttons: (card.buttons || []).map(b => ({ name: b.name, buttonParamsJson: typeof b.buttonParamsJson === 'string' ? b.buttonParamsJson : JSON.stringify(b.buttonParamsJson ?? {}) })) }
|
|
751
|
+
})),
|
|
752
|
+
messageVersion: 1
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
interactiveMessage.nativeFlowMessage = {
|
|
757
|
+
buttons: (iMsg.buttons || []).map(b => ({
|
|
758
|
+
name: b.name,
|
|
759
|
+
buttonParamsJson: typeof b.buttonParamsJson === 'string' ? b.buttonParamsJson : JSON.stringify(b.buttonParamsJson ?? {})
|
|
760
|
+
})),
|
|
761
|
+
messageParamsJson: iMsg.messageParamsJson || ''
|
|
762
|
+
};
|
|
763
|
+
}
|
|
764
|
+
m = {
|
|
765
|
+
interactiveMessage,
|
|
766
|
+
messageContextInfo: { deviceListMetadata: {}, deviceListMetadataVersion: 2 }
|
|
767
|
+
};
|
|
768
|
+
}
|
|
710
769
|
else if ('album' in message) {
|
|
711
770
|
// AunuBaileys: album message (dari @itsliaaa/baileys)
|
|
712
771
|
if (!Array.isArray(message.album)) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import Boom from '@hapi/boom';
|
|
1
2
|
import { proto } from '../../WAProto/index.js';
|
|
2
3
|
import { WAMessageStubType } from '../Types/index.js';
|
|
3
4
|
import { getContentType, normalizeMessageContent } from '../Utils/messages.js';
|
|
@@ -61,10 +62,12 @@ export const shouldIncrementChatUnread = (message) => !message.key.fromMe && !me
|
|
|
61
62
|
* Typically -- that'll be the remoteJid, but for broadcasts, it'll be the participant
|
|
62
63
|
*/
|
|
63
64
|
export const getChatId = ({ remoteJid, participant, fromMe }) => {
|
|
64
|
-
|
|
65
|
-
|
|
65
|
+
// AunuBaileys: gagal dengan jelas, jangan diam-diam memakai undefined
|
|
66
|
+
const id = isJidBroadcast(remoteJid) && !isJidStatusBroadcast(remoteJid) && !fromMe ? participant : remoteJid;
|
|
67
|
+
if (!id) {
|
|
68
|
+
throw new Boom('getChatId: missing remoteJid/participant in message key', { statusCode: 400 });
|
|
66
69
|
}
|
|
67
|
-
return
|
|
70
|
+
return id;
|
|
68
71
|
};
|
|
69
72
|
/**
|
|
70
73
|
* Decrypt a poll vote
|
package/lib/index.d.ts
CHANGED
|
@@ -14,3 +14,5 @@ export * from './Store/index.js';
|
|
|
14
14
|
export { processedMessages, initInMemoryKeyStore } from './Utils/fork-compat.js';
|
|
15
15
|
export { PHONENUMBER_MCC } from './Utils/phonenumber-mcc.js';
|
|
16
16
|
export * from './Utils/aunu-handler';
|
|
17
|
+
export * from './Utils/builders';
|
|
18
|
+
export * from './Utils/antiban';
|
package/lib/index.js
CHANGED
|
@@ -13,3 +13,5 @@ export * from './Store/index.js';
|
|
|
13
13
|
export { processedMessages, initInMemoryKeyStore } from './Utils/fork-compat.js';
|
|
14
14
|
export { PHONENUMBER_MCC } from './Utils/phonenumber-mcc.js';
|
|
15
15
|
export * from './Utils/aunu-handler.js';
|
|
16
|
+
export * from './Utils/builders.js';
|
|
17
|
+
export * from './Utils/antiban.js';
|
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.5",
|
|
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",
|