@xbibzlibrary/whatsbibz 1.3.1 → 1.4.0

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.id.md ADDED
@@ -0,0 +1,604 @@
1
+ <p align="center">
2
+ <a href="https://www.npmjs.com/package/@xbibzlibrary/whatsbibz"><img src="https://cdn.jsdelivr.net/gh/XbibzOfficial777/whatsbibz@main/assets/logo/preview.webp" alt="WhatsBibz" width="640"></a>
3
+ </p>
4
+
5
+ <h1 align="center">WhatsBibz</h1>
6
+
7
+ <p align="center">
8
+ Library WhatsApp Web multi-device untuk Node.js — fork Baileys yang dirawat, dengan klien tingkat tinggi,
9
+ pairing code kustom, sesi yang memulihkan diri sendiri, dan identitas perangkat tertaut yang bisa diatur atau dipilih otomatis.
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://www.npmjs.com/package/@xbibzlibrary/whatsbibz"><img src="https://img.shields.io/npm/v/%40xbibzlibrary%2Fwhatsbibz?label=npm&color=0a6958" alt="versi npm"></a>
14
+ <a href="https://www.npmjs.com/package/@xbibzlibrary/whatsbibz"><img src="https://img.shields.io/npm/dm/%40xbibzlibrary%2Fwhatsbibz?color=0a6958" alt="unduhan npm"></a>
15
+ <a href="https://github.com/XbibzOfficial777/whatsbibz/actions/workflows/ci.yml"><img src="https://github.com/XbibzOfficial777/whatsbibz/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
16
+ <a href="#persyaratan"><img src="https://img.shields.io/badge/node-%E2%89%A5%2020-0a6958" alt="Node.js >= 20"></a>
17
+ <a href="LICENSE"><img src="https://img.shields.io/badge/lisensi-MIT-0a6958" alt="MIT"></a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="README.md">English</a> · <b>Bahasa Indonesia</b> · <a href="README.cn.md">简体中文</a>
22
+ </p>
23
+
24
+ ---
25
+
26
+ ## Daftar isi
27
+
28
+ - [Mengapa WhatsBibz](#mengapa-whatsbibz)
29
+ - [Persyaratan](#persyaratan)
30
+ - [Instalasi](#instalasi)
31
+ - [Mulai cepat](#mulai-cepat)
32
+ - [Klien tingkat tinggi: `createBibzWhats()`](#klien-tingkat-tinggi-createbibzwhats)
33
+ - [Opsi](#opsi)
34
+ - [Event](#event)
35
+ - [Properti dan metode](#properti-dan-metode)
36
+ - [Siklus hidup koneksi](#siklus-hidup-koneksi)
37
+ - [Identitas perangkat tertaut](#identitas-perangkat-tertaut)
38
+ - [Mode otomatis (bawaan)](#mode-otomatis-bawaan)
39
+ - [Mode kustom](#mode-kustom)
40
+ - [Apa yang diterima WhatsApp](#apa-yang-diterima-whatsapp)
41
+ - [Mengirim dan membaca pesan](#mengirim-dan-membaca-pesan)
42
+ - [Format teks dan markup chat](#format-teks-dan-markup-chat)
43
+ - [Tombol interaktif (Button)](#tombol-interaktif-button)
44
+ - [Pesan kaya bergaya AI](#pesan-kaya-bergaya-ai)
45
+ - [API tingkat rendah (kompatibel Baileys)](#api-tingkat-rendah-kompatibel-baileys)
46
+ - [TypeScript](#typescript)
47
+ - [Migrasi dari Baileys / ourin-baileys](#migrasi-dari-baileys--ourin-baileys)
48
+ - [Pemecahan masalah](#pemecahan-masalah)
49
+ - [Pengujian](#pengujian)
50
+ - [Struktur proyek](#struktur-proyek)
51
+ - [Versi dan kompatibilitas](#versi-dan-kompatibilitas)
52
+ - [Berkontribusi](#berkontribusi)
53
+ - [Keamanan](#keamanan)
54
+ - [Lisensi](#lisensi)
55
+ - [Dukungan dan komunitas](#dukungan-dan-komunitas)
56
+
57
+ ## Mengapa WhatsBibz
58
+
59
+ WhatsBibz adalah fork penuh dari [Baileys](https://github.com/WhiskeySockets/Baileys) v7 (lewat `ourin-baileys@9.0.21`), sudah di-patch sampai `master` upstream saat ini, ditambah lapisan bawaan yang menangani koneksi, identitas, dan sesi:
60
+
61
+ | Masalah | Yang dilakukan WhatsBibz |
62
+ |---|---|
63
+ | Pairing "berhasil" tetapi HP tidak pernah menampilkan notifikasi | `requestPairingCode()` menunggu pengakuan server. Penolakan (`400`, `429`) dan timeout muncul sebagai error, bukan sukses palsu. |
64
+ | Nama perangkat tertaut ditolak server (`428` sebelum QR, `400` saat pairing) | Identitas **bisa diatur** (`identity: 'archLinux:Chrome'`) atau **otomatis**: profil paling stabil dipakai lebih dulu dan klien berpindah ke profil berikutnya hanya bila server menolak. Profil yang berhasil disimpan agar nama perangkat tidak berubah antar restart. |
65
+ | Sesi korup atau ter-logout berputar tanpa henti | `401 / 500 / 411 / korup` → folder sesi dihapus, kredensial baru, pairing ulang — dibatasi `maxSessionWipes`. |
66
+ | Notifikasi `companion_reg_refresh` mematikan sesi yang belum tertaut | Ditangani di dalam socket (tanpa patch `postinstall`). |
67
+ | Timer bocor setelah `close()`, reconnect "hantu" | `close()` membatalkan semua reconnect terjadwal; diverifikasi 40 siklus create→close. |
68
+ | Pengguna TypeScript mendapat `WAProto/index.d.ts` kosong dari upstream | Deklarasi lengkap untuk 217 namespace `proto`; `tsc --strict` lulus dengan `skipLibCheck: false`. |
69
+
70
+ Semua ekspor Baileys tetap ada — kode lama tetap berjalan, cukup ganti specifier import-nya.
71
+
72
+ ## Persyaratan
73
+
74
+ - **Node.js ≥ 20** (diuji di 20, 22, 24, dan 26)
75
+ - Hanya ESM (`"type": "module"` atau `import()` dinamis)
76
+ - Peer opsional: `sharp` (thumbnail media), `qrcode-terminal` (QR ASCII), `link-preview-js`, `jimp`, `audio-decode`, `@roamhq/wrtc` (VoIP)
77
+
78
+ ## Instalasi
79
+
80
+ ```bash
81
+ npm install @xbibzlibrary/whatsbibz
82
+ ```
83
+
84
+ ## Mulai cepat
85
+
86
+ ```js
87
+ import { createBibzWhats, extractMessage, sendText } from '@xbibzlibrary/whatsbibz';
88
+
89
+ const client = await createBibzWhats({
90
+ phone: '6281234567890', // nomor bot, digit dengan kode negara
91
+ pairingCode: 'XBIBZPRO', // opsional: kode kustom 8 karakter (A–Z, 0–9)
92
+ authDir: 'whatsbibz-session',
93
+ });
94
+
95
+ client.on('pairing-code', (code) => console.log('Masukkan kode ini di HP:', code));
96
+
97
+ client.on('ready', (sock) => {
98
+ sock.ev.on('messages.upsert', async ({ messages, type }) => {
99
+ if (type !== 'notify') return;
100
+ for (const m of messages) {
101
+ if (m.key.fromMe) continue;
102
+ const item = extractMessage(m);
103
+ if (item?.type === 'text' && /^ping$/i.test(item.text)) {
104
+ await sendText(sock, m.key.remoteJid, 'pong', { quoted: m });
105
+ }
106
+ }
107
+ });
108
+ });
109
+ ```
110
+
111
+ Di HP: **Setelan → Perangkat tertaut → Tautkan perangkat → Tautkan dengan nomor telepon**, lalu ketik kodenya. Kosongkan `phone` untuk memakai QR (`client.on('qr', ...)`, atau `printQR: true` untuk mencetaknya di terminal).
112
+
113
+ Contoh yang bisa langsung dijalankan ada di [`example/ping-bot.js`](example/ping-bot.js).
114
+
115
+ ## Klien tingkat tinggi: `createBibzWhats()`
116
+
117
+ `createBibzWhats(options)` mengembalikan Promise berisi klien berbasis `EventEmitter` yang mengelola socket untukmu: mengambil versi WhatsApp Web terkini, menentukan identitas perangkat, meminta pairing code, menyambung ulang dengan back-off, menghapus sesi rusak, dan pairing ulang.
118
+
119
+ ### Opsi
120
+
121
+ | Opsi | Tipe | Bawaan | Keterangan |
122
+ |---|---|---|---|
123
+ | `phone` | `string` | — | Nomor bot, digit dengan kode negara. Wajib untuk pairing code; kosongkan untuk QR. |
124
+ | `pairingCode` | `string` | acak | Kode kustom, tepat 8 karakter `A–Z0–9`. Bila server menolak kode kustom, klien memakai kode acak sekali. |
125
+ | `authDir` | `string` | `'bibzwhats-session'` | Folder kredensial, kunci signal, dan `identity.json`. |
126
+ | `identity` | `'auto' \| string \| [os, browser, versi]` | `'auto'` | Identitas perangkat tertaut. Lihat [Identitas perangkat tertaut](#identitas-perangkat-tertaut). |
127
+ | `browser` | `[os, browser, versi]` | `null` | Alias lama `identity` (tuple eksplisit). |
128
+ | `maxIdentityRotations` | `number` | `4` | Mode otomatis: jumlah profil alternatif yang dicoba sebelum menyerah. |
129
+ | `companionPlatformDisplay` | `string \| null` | `null` | Override `companion_platform_display` saat pairing. Isi hanya bila kamu yakin nilainya ada di allow-list WhatsApp. |
130
+ | `logger` | `{ info, warn, error, debug, ok }` | console | Objek apa pun dengan metode tersebut (semua opsional). |
131
+ | `printQR` | `boolean` | `false` | Cetak QR sebagai ASCII saat QR ditampilkan (butuh `qrcode-terminal`). |
132
+ | `banner` | `boolean` | `auto` | Cetak banner ASCII *"WhatsBibz."* (huruf Small Slant, warna pelangi) di terminal saat client dibuat. Bawaan hanya di terminal interaktif; `true` memaksa, `false` mematikan. |
133
+ | `socketConfig` | `Partial<SocketConfig>` | `{}` | Opsi mentah `makeWASocket`, digabung paling akhir — pintu keluar penuh. |
134
+ | `fetchLatestVersion` | `boolean` | `true` | Ambil versi WhatsApp Web terkini di setiap connect. Versi bawaan tidak pernah diturunkan. |
135
+ | `forceIPv4` | `boolean` | `true` | Paksa IPv4 untuk unggah media. |
136
+ | `readyOnEveryConnect` | `boolean` | `true` | Pancarkan `ready` untuk setiap socket baru (connect pertama dan tiap reconnect). |
137
+ | `maxReconnectAttempts` | `number` | `10` | Reconnect gagal berturut-turut sebelum `give-up`. |
138
+ | `reconnectStepMs` / `reconnectMaxMs` | `number` | `10000` / `60000` | Back-off linear: `step × percobaan`, maksimum `max`. |
139
+ | `restartDelayMs` | `number` | `2000` | Jeda setelah `515 restartRequired` (normal setelah pairing) dan setelah rotasi identitas. |
140
+ | `maxSessionWipes` | `number` | `3` | Penghapusan sesi (401/500/411/korup) sebelum `give-up`. |
141
+ | `wipeReconnectDelayMs` | `number` | `10000` | Jeda sebelum menyambung ulang dengan kredensial baru. |
142
+ | `qrFallbackAfterMs` | `number` | `90000` | Dengan `phone` terisi, tampilkan QR bila tidak ada pairing code yang diterima dalam waktu ini. |
143
+ | `pairingRequestDelayMs` | `number` | `20000` | Bila server tidak mengirim QR sama sekali, minta pairing code langsung setelah jeda ini. |
144
+ | `groupMetadataTtlMs` | `number` | `300000` | TTL cache metadata grup saat mengirim ke grup. |
145
+
146
+ Nilai bawaan diekspor sebagai `BIBZWHATS_DEFAULTS`.
147
+
148
+ ### Event
149
+
150
+ | Event | Payload | Kapan |
151
+ |---|---|---|
152
+ | `pairing-code` | `(code, { custom, fallback })` | Server menerima permintaan pairing code. `custom` = kodemu dipakai; `fallback` = server menolak kode kustom dan menerbitkan kode acak. |
153
+ | `qr` | `(qr)` | String QR tersedia (langsung tanpa `phone`, atau setelah `qrFallbackAfterMs`). |
154
+ | `ready` | `(sock)` | **Socket baru** siap. Pasang handler pesan di sini — socket diganti saat reconnect dan listener lama ikut mati. |
155
+ | `first-ready` | `(sock)` | Seperti `ready`, tetapi hanya sekali seumur klien. |
156
+ | `open` | `(sock)` | Koneksi terbuka (setiap kali). |
157
+ | `user` | `(digits)` | Nomor bot sendiri diketahui. |
158
+ | `close` | `({ status, error })` | Koneksi tertutup. `status` adalah kode `DisconnectReason`. |
159
+ | `reconnecting` | `({ delay, attempt, fresh, identity?, pairingPending? })` | Reconnect dijadwalkan. `fresh` = dengan kredensial baru; `identity` = profil setelah rotasi; `pairingPending` = pairing sebelumnya masih tertunda di server (lihat siklus hidup). |
160
+ | `identity-changed` | `({ browser, linkedDeviceName, pairingDisplay, pairingDisplayAccepted, profileId, reason })` | Hanya mode otomatis: server menolak profil sebelumnya dan profil berikutnya dipakai. |
161
+ | `session-wiped` | `(reason)` | Folder sesi dihapus (`'401 loggedOut'`, `'500 badSession'`, `'411'`, `'sesi korup'`, `'401 pairing tertunda'`). |
162
+ | `give-up` | `(message)` | Klien berhenti mencoba. Perlu campur tangan manusia (nomor salah, IP diblokir, …). |
163
+ | `connection.update` | `(update)` | Update koneksi mentah Baileys, diteruskan apa adanya. |
164
+
165
+ ### Properti dan metode
166
+
167
+ | Anggota | Keterangan |
168
+ |---|---|
169
+ | `client.sock` | Socket saat ini (`null` sebelum connect pertama; berubah saat reconnect). |
170
+ | `client.identity` | `{ mode: 'auto' \| 'custom', source, profileId, browser, linkedDeviceName, pairingDisplay, pairingDisplayAccepted, tried: [{ id, reason }] }` |
171
+ | `client.options` | Opsi efektif setelah nilai bawaan. |
172
+ | `client.isConnected()` | `true` selama socket terbuka. |
173
+ | `client.close()` | Tutup socket dan batalkan reconnect terjadwal. Tidak meninggalkan timer. |
174
+ | `client.logout()` | Logout perangkat di server dan hapus folder sesi. |
175
+
176
+ ### Siklus hidup koneksi
177
+
178
+ ```
179
+ connect ──► QR / pairing code ──► 515 restartRequired ──► reconnect (2 dtk) ──► open ──► ready
180
+ │ │
181
+ │ 428 sebelum QR / 405 (identitas ditolak) auto → profil berikutnya, 2 dtk │ putus jaringan → 1 dtk, lalu back-off
182
+ │ 400 saat pairing (identitas ditolak) auto → profil berikutnya, 2 dtk │ 401 sudah terdaftar → hapus sesi, pairing ulang
183
+ │ 401 dengan pairing tertunda kredensial baru, 2 dtk │
184
+ └─ kode close lain ── back-off 10 dtk × percobaan (maks 60 dtk, 10 percobaan) ── give-up
185
+ ```
186
+
187
+ Yang perlu diketahui:
188
+
189
+ - **515 setelah pairing itu normal.** Server meminta klien restart; klien menyambung ulang setelah `restartDelayMs`.
190
+ - **401 dengan pairing tertunda.** Bila koneksi putus tepat setelah pairing code terbit, server menjawab connect berikutnya dengan `401`. Ini bukan nomor salah: klien mengganti kredensial dan meminta kode baru dalam 2 detik. Jalur ini tidak dihitung ke `maxSessionWipes`.
191
+ - **Rotasi identitas hanya terjadi sebelum perangkat terdaftar.** Setelah tertaut, identitas dikunci — mengubahnya akan terlihat sebagai perangkat lain di HP.
192
+ - **Penghapusan sesi mempertahankan `identity.json`** bila identitasnya sudah diterima (`qr-received`, `pairing-accepted`, `open`), sehingga nama perangkat tetap sama setelah pairing ulang.
193
+
194
+ ## Identitas perangkat tertaut
195
+
196
+ Tuple `browser = [os, browser, versi]` menentukan apa yang tampil di HP pada **Perangkat tertaut**, dan sebagiannya divalidasi WhatsApp saat pairing.
197
+
198
+ ### Mode otomatis (bawaan)
199
+
200
+ ```js
201
+ const client = await createBibzWhats({ phone }); // identity: 'auto'
202
+ client.on('identity-changed', (i) => console.log('pindah ke', i.linkedDeviceName, 'karena', i.reason));
203
+ console.log(client.identity);
204
+ // { mode: 'auto', profileId: 'macos-chrome', linkedDeviceName: 'Chrome (Mac OS)',
205
+ // pairingDisplay: 'Chrome (Mac OS)', pairingDisplayAccepted: true, tried: [] }
206
+ ```
207
+
208
+ Klien mulai dari profil dengan rekam jejak terbaik dan berpindah hanya pada sinyal penolakan (`428` sebelum QR, `400 bad-request` saat pairing, `405`):
209
+
210
+ | Urutan | Profil | Tampil di HP | Dikirim saat pairing |
211
+ |---|---|---|---|
212
+ | 1 | `macos-chrome` | Chrome (Mac OS) | Chrome (Mac OS) |
213
+ | 2 | `macos-safari` | Safari (Mac OS) | Safari (Mac OS) |
214
+ | 3 | `windows-chrome` | Chrome (Windows) | Chrome (Windows) |
215
+ | 4 | `linux-chrome` | Chrome (Linux) | Chrome (Linux) |
216
+ | 5 | `ubuntu-chrome` | Chrome (Ubuntu) | Chrome (Ubuntu) |
217
+ | 6 | `macos-firefox` | Firefox (Mac OS) | Firefox (Mac OS) |
218
+ | 7 | `windows-edge` | Edge (Windows) | Edge (Windows) |
219
+ | 8 | `archlinux-chrome` | Chrome (Arch Linux) | Chrome (Linux) |
220
+
221
+ Profil yang berhasil ditulis ke `<authDir>/identity.json` dan dipakai lagi pada start berikutnya. Hapus file itu (atau seluruh `authDir`) untuk memulai dari awal.
222
+
223
+ ### Mode kustom
224
+
225
+ Identitas kustom dipakai **apa adanya** dan tidak pernah diganti diam-diam. Bila server menolaknya, kamu mendapat error yang jelas di log, bukan nama perangkat yang berbeda.
226
+
227
+ ```js
228
+ createBibzWhats({ phone, identity: Browsers.archLinux('Chrome') }); // tuple preset
229
+ createBibzWhats({ phone, identity: ['Arch Linux', 'Firefox', '6.16.4'] }); // tuple bebas
230
+ createBibzWhats({ phone, identity: 'archLinux:Firefox' }); // "preset:Browser"
231
+ createBibzWhats({ phone, identity: 'Mac OS/Safari/15.6.1' }); // "OS/Browser/Versi"
232
+ createBibzWhats({ phone, identity: 'linux-chrome' }); // id profil
233
+ createBibzWhats({ phone, browser: ['Mac OS', 'Chrome', '14.4.1'] }); // alias lama
234
+ ```
235
+
236
+ Variabel lingkungan bekerja tanpa mengubah kode — prioritas: opsi → env → otomatis:
237
+
238
+ ```bash
239
+ BIBZ_BROWSER="archLinux:Chrome" # atau "Arch Linux/Firefox/6.16", atau "auto"
240
+ # atau trio
241
+ BIBZ_DEVICE_OS="Arch Linux" BIBZ_DEVICE_BROWSER="Chrome" BIBZ_DEVICE_VERSION="6.16"
242
+ ```
243
+
244
+ Versi kosong diisi per OS (Mac OS `15.6.1`, Windows `10.0.22631`, Linux `6.12.44`, lainnya `1.0.0`).
245
+
246
+ Preset di `Browsers`: `macOS`, `windows`, `ubuntu`, `linux`, `archLinux`, `android` (eksperimental), `appropriate` (mengikuti OS host, mengenali distro lewat `/etc/os-release`), `whatsbibz`.
247
+
248
+ ### Apa yang diterima WhatsApp
249
+
250
+ Identitas dipakai di tiga tempat dengan aturan berbeda. Diukur terhadap server produksi pada 2026-09-03:
251
+
252
+ | Tempat | Dipakai untuk | Aturan server | Perilaku WhatsBibz |
253
+ |---|---|---|---|
254
+ | `DeviceProps.os` (`browser[0]`) | Teks di *Perangkat tertaut* | Teks bebas | Dikirim apa adanya — "Arch Linux" tampil sebagai "Arch Linux" |
255
+ | `companion_platform_display` (pairing code) | Validasi permintaan pairing | **Allow-list**; nilai tak dikenal → `400`, HP tidak pernah dapat notifikasi | Diturunkan otomatis ke nilai valid (`derivePairingDisplay`): Arch Linux → `Chrome (Linux)` |
256
+ | `WebInfo.webSubPlatform` (`Desktop` + `syncFullHistory`) | Jenis klien desktop | `DARWIN` / `APP_STORE` ditutup (`428` sebelum QR); `WIN_HYBRID` / `WEB_BROWSER` berfungsi | Mac OS Desktop → `WEB_BROWSER`, riwayat penuh tetap diminta |
257
+
258
+ | Diterima | Ditolak (`400`) |
259
+ |---|---|
260
+ | OS: Mac OS, macOS, Windows, Linux, Ubuntu, Debian, Fedora, CentOS, Gentoo, Manjaro, Chromium OS, Android, iOS | OS: Arch Linux, Arch, GNU/Linux, Mac OS X, Linux Mint, openSUSE, Kali, NixOS, Pop!_OS, FreeBSD, Chrome OS, nama produk apa pun |
261
+ | Browser: Chrome, Chromium, Firefox, Safari, Edge, Opera, Brave, Vivaldi, Arc | Browser: Desktop, nama produk apa pun |
262
+
263
+ Helper: `derivePairingDisplay(browser)`, `isPairingDisplayAccepted(str)`, `lintIdentity({ browser, companionPlatformDisplay })`, `resolveDeviceIdentity(opts)`, `parseBrowserSpec(spec)`, `identityFromEnv(env)`, `describeIdentity(browser)`, `IDENTITY_PROFILES`.
264
+
265
+ ## Mengirim dan membaca pesan
266
+
267
+ ```js
268
+ import {
269
+ sendText, sendMedia, react, presence, sendWithRetry, splitText, whatsappify,
270
+ extractMessage, unwrapMessage, messageTimestampMs,
271
+ LidMap, digitsOf, pnJid, lidJid, isGroupJid, isLidJid, sameUser, normalizeJid,
272
+ } from '@xbibzlibrary/whatsbibz';
273
+ ```
274
+
275
+ | Helper | Fungsi |
276
+ |---|---|
277
+ | `sendText(sock, jid, text, { quoted, format, maxLen })` | Memecah teks panjang (≤ 4000 karakter per pesan), retry 3×, mengubah Markdown ke format WhatsApp bila `format: true`. Mengembalikan `{ ok, ids, error? }`. |
278
+ | `sendMedia(sock, jid, content, { quoted, fallbackToDocument })` | Mengirim gambar/video/audio/dokumen; bila media ditolak, dikirim ulang sebagai dokumen. |
279
+ | `react(sock, jid, key, emoji)` | Memberi reaksi pada pesan. |
280
+ | `presence(sock, jid, state)` | `composing`, `recording`, `paused`, `available`, `unavailable`. |
281
+ | `sendWithRetry(sock, jid, content, options, { attempts })` | `sendMessage` generik dengan retry. |
282
+ | `extractMessage(m)` | Menormalkan pesan masuk apa pun menjadi `{ type, text, participant, mentions, quoted, imageMsg \| videoMsg \| … , buttonId, pollName, … }`. Membuka pembungkus ephemeral, view-once, dan pesan yang diedit. |
283
+ | `LidMap` | Memetakan JID LID ↔ nomor: `learnFromMessage(m)`, `canonical(jid)`, `variants(jid)`, `toJSON()` / `fromJSON()`. |
284
+
285
+ `sock.sendMessage(jid, content)` mentah mendukung semua yang didukung Baileys: teks, gambar, video, audio, dokumen, stiker, paket stiker, lokasi, kontak, polling, reaksi, edit, hapus, pin, teruskan, view-once, tombol interaktif, pesan produk dan event, newsletter. Builder `Button`, `Carousel`, dan `AIRich` diekspor.
286
+
287
+ ## Format teks dan markup chat
288
+
289
+ ### 1) Format asli WhatsApp (tanpa konversi)
290
+
291
+ `sendText(..., { format: false })` dan `sock.sendMessage(jid, { text })` mengirim teks **apa adanya** — WhatsApp sendiri yang merender markup berikut di HP penerima:
292
+
293
+ | Kamu ketik | Hasil di WhatsApp |
294
+ |---|---|
295
+ | `*teks*` | **teks tebal** |
296
+ | `_teks_` | *teks miring* |
297
+ | `~teks~` | ~~teks coret~~ |
298
+ | `` `teks` `` | `teks monospace` |
299
+ | `*_kombinasi_*` / `_*kombinasi*_` | **_tebal dan miring_** |
300
+ | `` ```baris\nteks``` `` | blok kode monospace |
301
+
302
+ ```js
303
+ await sock.sendMessage(jid, { text: '*Halo* _dunia_ ~semua~ `kode`' });
304
+ // sendText dengan format:false juga meneruskannya apa adanya
305
+ await sendText(sock, jid, '*bold* _italic_ ~strike~ `mono`', { format: false });
306
+ ```
307
+
308
+ ### 2) Konversi Markdown (gaya ChatGPT/AI) otomatis
309
+
310
+ `sendText(..., { format: true })` (bawaan) menjalankan `whatsappify()` agar teks Markdown yang biasa keluar dari LLM tampil rapi sebagai format WhatsApp:
311
+
312
+ | Markdown sumber | Hasil WhatsApp |
313
+ |---|---|
314
+ | `**tebal**` atau `__tebal__` | `*tebal*` |
315
+ | `***tebal-miring***` | `*tebal-miring*` |
316
+ | `~~coret~~` | `~coret~` |
317
+ | `_miring_` | `_miring_` (dibiarkan, sudah format WA) |
318
+ | `# / ## / ### …` di awal baris | dihapus (tidak tampil) |
319
+ | `[label](https://url)` | `label (https://url)` |
320
+ | sisa `***`/`___` berlebih | dirapikan jadi `*`/`_` |
321
+
322
+ ```js
323
+ const jawabanAI = '## Ringkasan\n\n**Cuaca** hari ini *cerah*, ~~hujan~~ sudah berlalu.\nLihat [prakiraan](https://bmkg.go.id).';
324
+ await sendText(sock, jid, jawabanAI); // format: true secara default
325
+ // hasil: "Ringkasan" (tanpa #) + *Cuaca* + _cerah_ + ~hujan~ + "prakiraan (https://bmkg.go.id)"
326
+ ```
327
+
328
+ `splitText(teks, maxLen)` memecah teks > 4000 karakter (potong di baris/spasi, jeda 250 ms antar bagian) — `sendText` melakukannya otomatis.
329
+
330
+ ### 3) Mention & sebut semua
331
+
332
+ ```js
333
+ import { pnJid } from '@xbibzlibrary/whatsbibz';
334
+
335
+ await sock.sendMessage(jid, {
336
+ text: 'Halo @6281234567890, cek ini!',
337
+ mentions: [pnJid('6281234567890')], // [] array JID
338
+ });
339
+ await sock.sendMessage(groupJid, {
340
+ text: 'Perhatian semua anggota',
341
+ mentions: [], // atau isi [jidA, jidB, ...]
342
+ mentionAll: true, // menandai seluruh chat (broadcast mention)
343
+ });
344
+ ```
345
+
346
+ ## Tombol interaktif (Button)
347
+
348
+ Semua tombol modern WhatsApp memakai format **native flow**: konten tombol berbentuk markup `{ name, buttonParamsJson }`. WhatsBibz meneruskannya lewat `sock.sendMessage(jid, { interactiveMessage: {...} })` atau builder `ButtonV2`.
349
+
350
+ ### Cara 1 — Native flow (disarankan) lewat `sendMessage`
351
+
352
+ ```js
353
+ await sock.sendMessage(jid, {
354
+ interactiveMessage: {
355
+ title: 'Silakan pilih menu di bawah:', // → teks badan pesan
356
+ footer: 'WhatsBibz Bot', // teks kecil di bawah (opsional)
357
+ header: 'MENU MAKAN', // judul besar (opsional)
358
+ buttons: [
359
+ { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Makanan', id: 'food' }) },
360
+ { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Minuman', id: 'drink' }) },
361
+ { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Batal', id: 'cancel' }) },
362
+ ],
363
+ },
364
+ });
365
+ ```
366
+
367
+ **Markup tombol native flow** (`name` + isi `buttonParamsJson`):
368
+
369
+ | `name` | Tampilan | `buttonParamsJson` (contoh) |
370
+ |---|---|---|
371
+ | `quick_reply` | tombol balas biasa | `{"display_text":"Ya","id":"yes"}` |
372
+ | `single_select` | dropdown daftar pilihan | `{"title":"Pilih Kota","sections":[{...}]}` (lihat Cara 3) |
373
+ | `cta_url` | tombol buka tautan | `{"display_text":"Kunjungi","url":"https://…","webview_interaction":false}` |
374
+ | `cta_call` | tombol telepon | `{"display_text":"Hubungi","id":"+628…"}` |
375
+ | `cta_copy` | tombol salin | `{"display_text":"Salin kode","copy_code":"ABC123"}` |
376
+ | `cta_reminder` / `cta_cancel_reminder` | atur / batalkan pengingat | `{"display_text":"Ingatkan saya","id":"…"}` |
377
+ | `address_message` | minta alamat | `{"display_text":"Kirim alamat","id":"…"}` |
378
+ | `send_location` | minta lokasi | `{"display_text":"…"}` |
379
+ | `limited_time_offer` | penawaran terbatas | `{"text":"…","url":"…","copy_code":"…","expiration_time":0}` |
380
+
381
+ Kunci yang dikenal: `display_text`, `id`, `url`, `copy_code`, `webview_interaction`, `expiration_time`, `sections`, `title`, `rows`. Tombol maksimal yang nyaman: 3 `quick_reply`, atau 1 `single_select` per pesan.
382
+
383
+ ### Cara 2 — Builder `ButtonV2` (format lama `buttonsMessage`)
384
+
385
+ ```js
386
+ import { ButtonV2 } from '@xbibzlibrary/whatsbibz';
387
+
388
+ const btn = new ButtonV2(sock)
389
+ .text('Pilih jawabanmu') // .title()/.text()/.footer()/.image(url)
390
+ .footer('Kuis WhatsBibz')
391
+ .addButton('Sangat Puas', 'a')
392
+ .addButton('Cukup', 'b')
393
+ .addButton('Kurang', 'c');
394
+
395
+ const msg = await btn.send(jid); // otomatis menyisipkan node <biz> yang diperlukan
396
+ // msg.key.id → id pesan terkirim
397
+ ```
398
+
399
+ ### Cara 3 — Dropdown pilihan (single_select)
400
+
401
+ ```js
402
+ await sock.sendMessage(jid, {
403
+ interactiveMessage: {
404
+ title: 'Pilih kota tujuan:',
405
+ buttons: [{
406
+ name: 'single_select',
407
+ buttonParamsJson: JSON.stringify({
408
+ title: 'Kota',
409
+ sections: [{
410
+ title: 'Pulau Jawa',
411
+ rows: [
412
+ { title: 'Jakarta', description: 'DKI Jakarta', id: 'jkt' },
413
+ { title: 'Bandung', description: 'Jawa Barat', id: 'bdg' },
414
+ { title: 'Surabaya', description: 'Jawa Timur', id: 'sby' },
415
+ ],
416
+ }],
417
+ }),
418
+ }],
419
+ },
420
+ });
421
+ ```
422
+
423
+ ### Menangkap klik / balasan tombol (inbound)
424
+
425
+ Ketika user menekan tombol (native flow maupun legacy), bot menerima `messages.upsert` dengan tipe balasan; `extractMessage` menormalkannya menjadi `type: 'button'`:
426
+
427
+ ```js
428
+ sock.ev.on('messages.upsert', async ({ messages, type }) => {
429
+ if (type !== 'notify') return;
430
+ for (const m of messages) {
431
+ if (m.key.fromMe || !m.message) continue;
432
+ const item = extractMessage(m); // unwrap otomatis
433
+ if (item?.type !== 'button') continue;
434
+
435
+ console.log('Tombol ditekan:', item.buttonId, '|', item.buttonText);
436
+ switch (item.buttonId) {
437
+ case 'food': return sendText(sock, m.key.remoteJid, 'Anda memilih *Makanan*', { quoted: m });
438
+ case 'drink': return sendText(sock, m.key.remoteJid, 'Anda memilih *Minuman*', { quoted: m });
439
+ case 'jkt': return sendText(sock, m.key.remoteJid, 'Tujuan: *Jakarta*', { quoted: m });
440
+ case 'cancel': return sendText(sock, m.key.remoteJid, 'Dibatalkan');
441
+ }
442
+ }
443
+ });
444
+ ```
445
+
446
+ Klik **quick_reply** juga bisa dibaca mentah: `m.message.interactiveResponseMessage.nativeFlowResponseMessage` berisi `name` (mis. `quick_reply`) dan `paramsJson` = string JSON `{"id":"food","text":"Makanan"}`. Balasan `listResponseMessage`, `buttonsResponseMessage`, dan `templateButtonReplyMessage` (legacy) juga masuk akun `extractMessage`.
447
+
448
+ ## Pesan kaya bergaya AI
449
+
450
+ WhatsBibz ikut menyediakan pembuat **rich message** (format yang sama dengan balasan Meta AI di WhatsApp: `botForwardedMessage`/`richResponseMessage`) sehingga bot bisa menampilkan tabel, blok kode ber-highlight, daftar tautan, dan saran lanjutan dalam satu pesan terformat.
451
+
452
+ ### Metode bawaan socket
453
+
454
+ ```js
455
+ // Tabel
456
+ await sock.sendTable(jid, 'Daftar Harga', ['Item', 'Harga'], [['Nasi Goreng', 'Rp15rb'], ['Es Teh', 'Rp5rb']], undefined, { footer: 'berlaku hari ini' });
457
+
458
+ // Blok kode dengan highlight sintaks
459
+ await sock.sendCodeBlock(jid, 'const x = 1;\nconsole.log(x);', undefined, { language: 'javascript', title: 'Contoh JS' });
460
+
461
+ // Daftar tautan terformat — links: string URL atau { url, displayName }
462
+ await sock.sendLink(jid, 'Sumber terpercaya:', [
463
+ { displayName: 'BMKG — Info Cuaca & Gempa', url: 'https://www.bmkg.go.id' },
464
+ 'https://www.bmkg.go.id/cuaca',
465
+ ], undefined);
466
+
467
+ // Pesan rich penuh dari submessage mentah
468
+ await sock.sendRichMessage(jid, [
469
+ { messageType: 2, messageText: 'Halo dari rich message' },
470
+ { messageType: 4, tableMetadata: { title: 'Skor', rows: [{ items: ['A', '1'], isHeading: true }] } },
471
+ ], undefined);
472
+ ```
473
+
474
+ Metode lain: `sendTableV2`, `sendList`, `sendCodeBlockV2`, `sendLinkV2`, `sendLatex`, `sendLatexImage`, `sendUnifiedResponse`, `captureUnifiedResponse` (lihat `Socket/messages-send.js`).
475
+
476
+ ### Builder `AIRich` (fluent)
477
+
478
+ ```js
479
+ import { AIRich } from '@xbibzlibrary/whatsbibz';
480
+
481
+ const rich = new AIRich(sock)
482
+ .addText('Berikut ringkasan cuaca hari ini untuk **Jakarta**:')
483
+ .addTable([
484
+ ['Waktu', 'Cuaca', 'Suhu'],
485
+ ['Pagi', 'Cerah', '26°C'],
486
+ ['Siang', 'Berawan','32°C'],
487
+ ['Malam', 'Hujan', '28°C'],
488
+ ])
489
+ .addCode('javascript', `async function cuaca(kota) {\n return await fetch('https://api/weather?q=' + kota);\n}`)
490
+ .addSuggest(['Bagaimana cuaca besok?', 'Cari prakiraan mingguan']);
491
+
492
+ await rich.send(jid, { forwarded: true, includesUnifiedResponse: true });
493
+ ```
494
+
495
+ Metode `AIRich` (semua mengembalikan `this`, bisa dirantai): `addText(text)`, `addTable(array2dString)`, `addCode(lang, code)`, `addSource([[ikon, url, teks], ...])`, `addImage(url/buffer | array)`, `addVideo(...)`, `addProduct({...}|[...])`, `addPost(...)`, `addReels(...)`, `addTip(text)`, `addSuggest(string | string[])`, `addSubmessage(...)`, `addSection(...)`. `send(jid, { forwarded, notification, includesUnifiedResponse, includesSubmessages })` — semua opsi bernilai default `true` kecuali `notification` (`false`). `ORich` adalah alias `AIRich`; utilitas statis: `AIRich.tokenizer(code, lang)`, `AIRich.toTableMetadata(arr)`, `AIRich.newLayout(...)`.
496
+
497
+ ### Catatan penggunaan yang etis
498
+
499
+ Pesan rich/AI ini tampil di HP penerima **seolah-olah berasal dari asisten AI resmi Meta** (memakai `forwardedAiBotMessageInfo`, `GenAI*` view-model, dll.). Gunakan hanya untuk bot yang memang meniru asisten AI secara transparan — jangan dipakai untuk menyamar, menipu, atau mengelabui pengguna. Demikian pula: library ini adalah klien WhatsApp **tidak resmi**; pakai di nomor cadangan dan sadari risiko pembatasan akun oleh WhatsApp.
500
+
501
+ ## API tingkat rendah (kompatibel Baileys)
502
+
503
+ ```js
504
+ import makeWASocket, {
505
+ useMultiFileAuthState, makeCacheableSignalKeyStore, fetchLatestWaWebVersion,
506
+ DisconnectReason, Browsers, downloadMediaMessage, proto, jidNormalizedUser,
507
+ } from '@xbibzlibrary/whatsbibz';
508
+
509
+ const { state, saveCreds } = await useMultiFileAuthState('session');
510
+ const { version } = await fetchLatestWaWebVersion();
511
+ const sock = makeWASocket({ version, auth: state, browser: Browsers.macOS('Chrome') });
512
+ sock.ev.on('creds.update', saveCreds);
513
+ ```
514
+
515
+ Semua yang ada di Baileys v7 / ourin-baileys diekspor dengan nama yang sama. Penggantian nama: `Dugong` → `BibzWhatsEngine`, `sock.ourin` → `sock.bibz` (`sock.ourin` dipertahankan sebagai alias). Opsi socket tambahan: `companionPlatformDisplay`, `companionPlatformId`, `webSubPlatform`.
516
+
517
+ Tambahan di atas `master` upstream (sumber ada di [CHANGELOG.md](CHANGELOG.md)): `WIN32 → WIN_HYBRID` untuk Windows Desktop, `requestPairingCode` lewat `query()` dengan `creds.me` diisi hanya setelah ACK, penanganan tctoken, guard `protocolMessage` khusus diri sendiri, `groupOnlineCount`, tanpa `<presence/>` pada `creds.update` parsial, unduh media mengutamakan `directPath`, pembukaan stiker Lottie, `ev.destroy()` / `signalRepository.close()` saat socket berakhir, `whatsapp-rust-bridge` 0.5.5.
518
+
519
+ ## TypeScript
520
+
521
+ Deklarasi tipe ikut dalam paket, termasuk `WAProto/index.d.ts` yang lengkap. Diverifikasi dengan `tsc --strict`, `moduleResolution: NodeNext`, dan `skipLibCheck: false`; satu-satunya tambahan yang dibutuhkan adalah `@types/node`.
522
+
523
+ ```ts
524
+ import { createBibzWhats, type BibzWhatsClient, type ResolvedIdentity } from '@xbibzlibrary/whatsbibz';
525
+
526
+ const client: BibzWhatsClient = await createBibzWhats({ phone: '628…', identity: 'archLinux:Chrome' });
527
+ client.on('identity-changed', (i) => console.log(i.linkedDeviceName));
528
+ ```
529
+
530
+ ## Migrasi dari Baileys / ourin-baileys
531
+
532
+ 1. `npm uninstall baileys ourin-baileys && npm install @xbibzlibrary/whatsbibz`
533
+ 2. Ganti specifier import. Semua ekspor mempertahankan namanya.
534
+ 3. Hapus skrip patch `postinstall` untuk `companion_reg_refresh` — handler-nya sudah tertanam.
535
+ 4. Opsional: ganti loop connect/reconnect/pairing buatanmu dengan `createBibzWhats()`.
536
+
537
+ ## Pemecahan masalah
538
+
539
+ | Gejala | Penyebab | Solusi |
540
+ |---|---|---|
541
+ | Pairing code tercetak, HP tidak menampilkan apa pun | Library lama mencetak kode sebelum server mengonfirmasi. WhatsBibz hanya memancarkan `pairing-code` setelah ACK — bila tetap tidak muncul, nomornya salah atau IP dibatasi. | Periksa digitnya (kode negara, tanpa `+`, tanpa `0` di depan). Tunggu 15 menit bila kamu mencoba berkali-kali (`429`). |
542
+ | `428` seketika, tanpa QR | Identitas yang diiklankan ditolak (mis. sub-platform `DARWIN`). | Biarkan `identity` kosong (otomatis berputar), atau pilih profil dari tabel di atas. |
543
+ | `400 bad-request` saat pairing | `companion_platform_display` tidak ada di allow-list. | Jangan override `companionPlatformDisplay`; nilai turunannya selalu valid. |
544
+ | `401` tepat setelah reconnect saat pairing | Pairing sebelumnya masih tertunda di server. | Ditangani otomatis (`reconnecting` dengan `pairingPending: true`). |
545
+ | Loop `408` | Versi WhatsApp Web terlalu lama. | Pertahankan `fetchLatestVersion: true` (bawaan) atau perbarui paket. |
546
+ | `give-up` setelah 3 kali hapus sesi | Bukan masalah sesi: nomor tidak terdaftar, digit salah, atau IP diblokir. | Pastikan nomor aktif di WhatsApp; coba jaringan lain. |
547
+ | Handler berhenti bekerja setelah reconnect | Handler dipasang di socket lama. | Pasang di dalam `client.on('ready', sock => …)`. |
548
+ | Proses tidak mau keluar | Ada hal lain yang menahan event loop — klien tidak meninggalkan timer setelah `close()`. | Periksa interval buatanmu; panggil `client.close()` sebelum `process.exit`. |
549
+
550
+ ## Pengujian
551
+
552
+ ```bash
553
+ npm test # 55 test offline (protokol, identitas, pairing controller, siklus hidup) — tanpa jaringan
554
+ npm run check # pemeriksaan permukaan ekspor
555
+ npm run test:live # 11 test ke server WhatsApp sungguhan (tanpa HP; memakai nomor fiktif Ofcom)
556
+ ```
557
+
558
+ Suite live mencakup QR, pengakuan pairing, rotasi otomatis pada `428` dan `400`, identitas kustom, kontrol negatif, dan pemutusan jaringan paksa. Hasil lengkap: [VERIFIKASI-IDENTITAS-2026-09-03.md](VERIFIKASI-IDENTITAS-2026-09-03.md).
559
+
560
+ ## Struktur proyek
561
+
562
+ ```
563
+ lib/
564
+ BibzWhats/ client.js (createBibzWhats), device-identity.js, pairing.js, send.js, extract.js, jid.js
565
+ Socket/ socket.js, engine.js, messages-send.js, messages-recv.js, groups.js, newsletter.js, ...
566
+ Utils/ platform-identity.js, browser-utils.js, messages-media.js, use-multi-file-auth-state.js, ...
567
+ Defaults/ Signal/ Types/ WABinary/ WAM/ WAUSync/ VoIP/ Modded/
568
+ WAProto/ definisi protobuf (index.js + index.d.ts lengkap)
569
+ assets/logo/ logo, ikon, favicon
570
+ example/ ping-bot.js
571
+ test/ suite node:test (offline + live)
572
+ ```
573
+
574
+ ## Versi dan kompatibilitas
575
+
576
+ - Semantic versioning. Perubahan yang memutus kompatibilitas hanya di rilis mayor.
577
+ - Versi WhatsApp Web bawaan diperbarui di setiap rilis dan diambil langsung di setiap connect.
578
+ - Node.js yang didukung: jalur LTS saat ini dan rilis terbaru (20 / 22 / 24 / 26 saat ini ditulis).
579
+ - Perubahan dicatat di [CHANGELOG.md](CHANGELOG.md).
580
+
581
+ ## Berkontribusi
582
+
583
+ Laporan bug, hasil pengukuran terhadap server WhatsApp, dan pull request diterima. Baca [CONTRIBUTING.md](CONTRIBUTING.md) lebih dulu — di sana dijelaskan syarat pengujian dan cara menjalankan suite live dengan aman. Setiap orang yang berpartisipasi diharapkan mengikuti [Kode Etik](CODE_OF_CONDUCT.md).
584
+
585
+ ## Keamanan
586
+
587
+ Jangan buka issue publik untuk kerentanan. Lihat [SECURITY.md](SECURITY.md).
588
+
589
+ ## Lisensi
590
+
591
+ MIT — © 2026 Xbibz Developer. Berbasis Baileys, MIT © Rajeh Taher / WhiskeySockets — lihat [LICENSE](LICENSE) dan [LICENSE.upstream](LICENSE.upstream).
592
+
593
+ WhatsBibz tidak berafiliasi dengan, didukung oleh, atau disokong oleh WhatsApp maupun Meta. Gunakan nomor khusus, patuhi Ketentuan Layanan WhatsApp, dan jangan mengirim pesan yang tidak diminta.
594
+
595
+ ## Dukungan dan komunitas
596
+
597
+ <p align="center">
598
+ <a href="https://ko-fi.com/xbibzofficial"><img src="https://img.shields.io/badge/Ko--Fi-Donate-FF5E5B?style=for-the-badge&amp;logo=ko-fi&amp;logoColor=white" alt="Ko-Fi"></a>
599
+ <a href="https://saweria.co/XbibzOfficial"><img src="https://img.shields.io/badge/Saweria-Support-FFAA00?style=for-the-badge" alt="Saweria"></a>
600
+ <a href="https://tiktok.com/@xbibzofficial"><img src="https://img.shields.io/badge/TikTok-@xbibzofficial-000000?style=for-the-badge&amp;logo=tiktok&amp;logoColor=white" alt="TikTok"></a>
601
+ <a href="https://t.me/xbibzofc"><img src="https://img.shields.io/badge/Telegram-@xbibzofc-26A5E4?style=for-the-badge&amp;logo=telegram&amp;logoColor=white" alt="Telegram"></a>
602
+ </p>
603
+
604
+ [Ko-Fi](https://ko-fi.com/xbibzofficial) · [Saweria](https://saweria.co/XbibzOfficial) · [TikTok](https://tiktok.com/@xbibzofficial) · [Telegram](https://t.me/xbibzofc)