@isaxn/bailyes 1.0.0 → 2.1.3
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 +271 -23
- package/package.json +24 -12
- package/src/core/baileys-loader.js +16 -0
- package/src/core/client.js +155 -35
- package/src/core/context.js +95 -35
- package/src/core/events.js +14 -2
- package/src/core/handler.js +36 -19
- package/src/core/store.js +209 -0
- package/src/index.js +101 -23
- package/src/message/ai-rich.js +457 -0
- package/src/message/base-builder.js +54 -0
- package/src/message/button-v2.js +104 -0
- package/src/message/button.js +221 -0
- package/src/message/carousel.js +57 -0
- package/src/message/index.js +34 -0
- package/src/message/inline-entities.js +168 -0
- package/src/message/link-preview.js +56 -0
- package/src/message/live-photo.js +59 -0
- package/src/message/live-thumbnail.js +56 -0
- package/src/message/native-flow.js +28 -0
- package/src/message/quick.js +117 -0
- package/src/message/table.js +39 -0
- package/src/message/tokenizer.js +207 -0
- package/src/message/toolkit.js +233 -0
- package/dist/core/client.cjs +0 -65
- package/dist/index.cjs +0 -6
package/README.md
CHANGED
|
@@ -1,33 +1,281 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @isaxn/bailyes
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[`@whiskeysockets/baileys`](https://github.com/WhiskeySockets/Baileys)
|
|
3
|
+
Framework WhatsApp bot berbasis [`@whiskeysockets/baileys`](https://github.com/WhiskeySockets/Baileys), dengan:
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
5
|
+
- Login QR **dan** pairing code
|
|
6
|
+
- Auto-reconnect yang tidak kehilangan event setelah socket diganti
|
|
7
|
+
- `Store` bawaan (kontak, chat, pesan, metadata grup) — pengganti `makeInMemoryStore` yang sudah dihapus dari Baileys
|
|
8
|
+
- Message builder modular: `Button`, `ButtonV2`, `Carousel`, `AIRich`
|
|
9
|
+
- 100% bisa dipakai lewat `require(...)`, tidak perlu ESM
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
## Instalasi
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
```bash
|
|
14
|
+
npm install @isaxn/bailyes
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Paket ini memuat `@whiskeysockets/baileys` secara dinamis di dalam (Baileys versi terbaru murni ESM), jadi kamu tetap bisa `require("@isaxn/bailyes")` seperti biasa walaupun dependensinya ESM.
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
const button = require("@isaxn/bailyes");
|
|
21
|
+
const { Bailyes, ButtonV2, Store } = button;
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Quick start — bot sederhana
|
|
26
|
+
|
|
27
|
+
```js
|
|
28
|
+
const { Bailyes } = require("@isaxn/bailyes");
|
|
29
|
+
|
|
30
|
+
const bot = new Bailyes({
|
|
31
|
+
auth: "auth",
|
|
32
|
+
prefix: ["!", "."]
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
bot.command("ping", async (ctx) => {
|
|
36
|
+
await ctx.reply("pong");
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
bot.onFramework("ready", () => console.log("Bot connected"));
|
|
40
|
+
|
|
41
|
+
bot.start();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Menjalankan `node bot.js` akan menampilkan QR code di terminal secara default.
|
|
45
|
+
|
|
46
|
+
## Login dengan pairing code
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const bot = new Bailyes({
|
|
50
|
+
auth: "auth",
|
|
51
|
+
pairingCode: true,
|
|
52
|
+
phoneNumber: "6281234567890"
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
bot.onFramework("pairing-code", (code) => {
|
|
56
|
+
console.log("Masukkan kode ini di WhatsApp:", code);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
bot.start();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`phoneNumber` wajib diisi format internasional tanpa `+` atau spasi (`62...`). Kalau `pairingCode: true` tapi sesi sudah pernah login sebelumnya (`creds.registered`), kode tidak akan diminta lagi.
|
|
63
|
+
|
|
64
|
+
## Reconnect
|
|
65
|
+
|
|
66
|
+
`WAClient` menyimpan status koneksi lewat `EventEmitter` sendiri (`open`, `close`, `qr`, `pairing-code`, `logged-out`, `error`), bukan langsung dari `sock.ev`. Jadi walaupun socket internal diganti saat reconnect, listener command/hear kamu **tidak hilang** — beda dari versi sebelumnya yang mendaftarkan `messages.upsert` langsung ke `sock.ev` sekali saja di awal.
|
|
67
|
+
|
|
68
|
+
Reconnect otomatis dengan backoff (`reconnectDelay * attempt`, dibatasi `maxReconnectDelay`), berhenti otomatis kalau `DisconnectReason` adalah `loggedOut`.
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
const bot = new Bailyes({
|
|
72
|
+
reconnectDelay: 3000,
|
|
73
|
+
maxReconnectDelay: 30000,
|
|
74
|
+
maxReconnectAttempts: 0 // 0 = tidak dibatasi
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Store — memory kontak, chat, dan pesan
|
|
79
|
+
|
|
80
|
+
Baileys versi baru tidak lagi menyediakan `makeInMemoryStore` bawaan, jadi harus dibuat manual. `Store` di paket ini otomatis mendengarkan event `contacts.*`, `chats.*`, `messages.*`, `groups.*`, `group-participants.update`.
|
|
81
|
+
|
|
82
|
+
```js
|
|
83
|
+
const { Store } = require("@isaxn/bailyes");
|
|
84
|
+
|
|
85
|
+
const store = new Store({
|
|
86
|
+
file: "store.json", // opsional: simpan/baca otomatis dari file
|
|
87
|
+
autosaveInterval: 15000, // interval autosave (ms)
|
|
88
|
+
maxMessagesPerChat: 200 // batas pesan yang disimpan per chat
|
|
89
|
+
});
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Dipakai lewat opsi `store` saat membuat `Bailyes`/`WAClient` (otomatis dibind ke `sock.ev`):
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
const bot = new Bailyes({
|
|
96
|
+
store: { file: "store.json", autosaveInterval: 15000 }
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Atau matikan store sama sekali dengan `store: false`.
|
|
101
|
+
|
|
102
|
+
API `Store`:
|
|
103
|
+
|
|
104
|
+
- `store.getName(jid)` — nama kontak (fallback ke nomor)
|
|
105
|
+
- `store.getContact(jid)`, `store.getChat(jid)`, `store.getGroupMetadata(jid)`
|
|
106
|
+
- `store.loadMessage(jid, id)` — ambil pesan lama dari memory (berguna untuk quoted/anti-delete)
|
|
107
|
+
- `store.writeToFile(path)` / `store.readFromFile(path)` — simpan/baca manual
|
|
108
|
+
- `store.toJSON()` / `store.fromJSON(data)`
|
|
109
|
+
|
|
110
|
+
Di dalam `ctx` (Context pesan masuk), tinggal panggil `ctx.getName()` — otomatis pakai store yang sama.
|
|
111
|
+
|
|
112
|
+
## Message builder
|
|
113
|
+
|
|
114
|
+
### ButtonV2 (buttonsMessage)
|
|
115
|
+
|
|
116
|
+
```js
|
|
117
|
+
const { ButtonV2 } = require("@isaxn/bailyes");
|
|
118
|
+
|
|
119
|
+
const msg = new ButtonV2(sock)
|
|
120
|
+
.setBody("Pilih salah satu")
|
|
121
|
+
.setFooter("Bailyes Bot")
|
|
122
|
+
.addButton("Menu 1", "menu1")
|
|
123
|
+
.addButton("Menu 2", "menu2");
|
|
124
|
+
|
|
125
|
+
await msg.send(jid, { quoted: ctx.msg });
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`ButtonV2` **wajib** minimal satu `.addButton()` sebelum `.send()` — kalau tidak, akan langsung melempar `Error`, bukan diam-diam fallback jadi teks biasa seperti sebelumnya.
|
|
15
129
|
|
|
16
|
-
|
|
17
|
-
- 🔐 **QR Login only** (aman & stabil)
|
|
18
|
-
- ⚡ Fast & lightweight
|
|
19
|
-
- 🧩 Easy to integrate into any bot
|
|
20
|
-
- 📦 Ready for **ESM & CommonJS**
|
|
21
|
-
- 🔁 Auto reconnect
|
|
22
|
-
- 🧠 Clean & minimal API
|
|
23
|
-
- 🚫 No forced pairing / aggressive login
|
|
130
|
+
### Button (interactiveMessage / native flow list)
|
|
24
131
|
|
|
25
|
-
|
|
132
|
+
```js
|
|
133
|
+
const { Button } = require("@isaxn/bailyes");
|
|
26
134
|
|
|
27
|
-
|
|
28
|
-
|
|
135
|
+
const msg = new Button(sock)
|
|
136
|
+
.setTitle("Judul")
|
|
137
|
+
.setBody("Isi pesan")
|
|
138
|
+
.addSelection("Pilih menu")
|
|
139
|
+
.makeSection("Kategori A")
|
|
140
|
+
.makeRow("", "Item 1", "Deskripsi item 1", "item1")
|
|
141
|
+
.makeRow("", "Item 2", "Deskripsi item 2", "item2");
|
|
29
142
|
|
|
30
|
-
|
|
143
|
+
await msg.send(jid);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Carousel
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
const { Carousel, Button } = require("@isaxn/bailyes");
|
|
150
|
+
|
|
151
|
+
const card1 = await new Button(sock).setImage("https://...").setTitle("Kartu 1").toCard();
|
|
152
|
+
const card2 = await new Button(sock).setImage("https://...").setTitle("Kartu 2").toCard();
|
|
153
|
+
|
|
154
|
+
const carousel = new Carousel(sock).setBody("Lihat pilihan berikut").addCard([card1, card2]);
|
|
155
|
+
|
|
156
|
+
await carousel.send(jid);
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### AIRich (rich response ala AI)
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
const { AIRich } = require("@isaxn/bailyes");
|
|
163
|
+
|
|
164
|
+
const rich = new AIRich(sock)
|
|
165
|
+
.setTitle("Asisten")
|
|
166
|
+
.addText("Ini contoh **teks** dengan [tautan](https://example.com)")
|
|
167
|
+
.addCode("javascript", "console.log('halo')")
|
|
168
|
+
.addTip("Ketik !menu untuk melihat semua perintah");
|
|
169
|
+
|
|
170
|
+
await rich.send(jid);
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Toolkit
|
|
174
|
+
|
|
175
|
+
Kumpulan util murni tanpa perlu instance socket kecuali fungsi yang upload media (`toUrl`, `resolveMedia`):
|
|
176
|
+
|
|
177
|
+
```js
|
|
178
|
+
const { Toolkit } = require("@isaxn/bailyes");
|
|
179
|
+
|
|
180
|
+
const buffer = await Toolkit.fetchBuffer("https://example.com/gambar.jpg");
|
|
181
|
+
const resized = await Toolkit.resize(buffer, 300, 300);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## v2.1.0 — cara panggil baru: `bx`
|
|
185
|
+
|
|
186
|
+
Selain builder chain (`new ButtonV2(sock).setBody(...).addButton(...)`), sekarang ada `bx` — satu fungsi sekali panggil, tanpa chaining, untuk kasus-kasus umum:
|
|
187
|
+
|
|
188
|
+
```js
|
|
189
|
+
const { bx } = require("@isaxn/bailyes");
|
|
190
|
+
|
|
191
|
+
await bx.button(sock, jid, {
|
|
192
|
+
body: "Pilih menu",
|
|
193
|
+
footer: "Bailyes Bot",
|
|
194
|
+
buttons: [
|
|
195
|
+
{ text: "Menu 1", id: "menu1" },
|
|
196
|
+
{ text: "Menu 2", id: "menu2" }
|
|
197
|
+
]
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
await bx.list(sock, jid, {
|
|
201
|
+
title: "Daftar Produk",
|
|
202
|
+
sections: [
|
|
203
|
+
{ title: "Kategori A", rows: [{ title: "Item 1", description: "Deskripsi", id: "item1" }] }
|
|
204
|
+
]
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
await bx.card(sock, jid, {
|
|
208
|
+
body: "Lihat produk kami",
|
|
209
|
+
cards: [
|
|
210
|
+
{ title: "Produk 1", image: "https://...", buttons: [{ type: "url", text: "Beli", value: "https://..." }] },
|
|
211
|
+
{ title: "Produk 2", image: "https://...", buttons: [{ type: "reply", text: "Tanya", value: "tanya1" }] }
|
|
212
|
+
]
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await bx.rich(sock, jid, { title: "Asisten", text: "Halo!", tip: "Ketik !menu" });
|
|
216
|
+
|
|
217
|
+
await bx.linkPreview(sock, jid, { text: "Cek ini", link: "https://example.com", title: "Judul", description: "Deskripsi" });
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### `bx.livePhoto` — kirim foto + video terpasang jadi satu (Live Photo)
|
|
221
|
+
|
|
222
|
+
Fitur baru: kirim gambar yang otomatis "hidup" jadi video singkat saat ditekan, mirip Live Photo di iPhone. Ini memakai `messageAssociation` bawaan WhatsApp (video di-relay terpisah tapi terikat ke `parentMessageKey` pesan gambar).
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
await bx.livePhoto(sock, jid, {
|
|
226
|
+
image: "https://example.com/foto.jpg", // url, path lokal, atau Buffer
|
|
227
|
+
video: "https://example.com/klip.mp4" // url, path lokal, atau Buffer
|
|
228
|
+
});
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### `bx.liveThumbnail` — animasi thumbnail link preview
|
|
232
|
+
|
|
233
|
+
Edit pesan berulang kali dengan thumbnail link-preview yang berganti-ganti, jadi efek "gambar bergerak" di dalam satu bubble teks/link.
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
const { key } = await sock.sendMessage(jid, { text: "Menyiapkan..." });
|
|
237
|
+
|
|
238
|
+
await bx.liveThumbnail(sock, jid, {
|
|
239
|
+
key,
|
|
240
|
+
text: "Cek promo ini",
|
|
241
|
+
link: "https://example.com/promo",
|
|
242
|
+
title: "Promo",
|
|
243
|
+
description: "Terbatas hari ini",
|
|
244
|
+
images: ["https://.../1.jpg", "https://.../2.jpg", "https://.../3.jpg"],
|
|
245
|
+
interval: 1500,
|
|
246
|
+
loops: 2
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Perbaikan dari versi sebelumnya
|
|
251
|
+
|
|
252
|
+
- **`require()` sekarang benar-benar berfungsi** tanpa perlu build step terpisah (`dist/`), karena Baileys (ESM) di-load lewat `import()` dinamis yang di-cache, dipanggil dari dalam fungsi `async` — bukan `import` statis yang bikin `require()` gagal.
|
|
253
|
+
- **Pairing code** ditambahkan (`pairingCode: true` + `phoneNumber`), sebelumnya hanya QR.
|
|
254
|
+
- **Reconnect tidak lagi memutus listener command** — sebelumnya `sock.ev.on("messages.upsert", ...)` didaftarkan sekali di `start()`, jadi setelah reconnect (socket baru), listener lama menunjuk ke socket basi. Sekarang event diteruskan lewat `WAClient` (EventEmitter sendiri) yang stabil lintas reconnect.
|
|
255
|
+
- **`ButtonV2` tanpa `.addButton()` sekarang melempar error jelas**, bukan diam-diam fallback jadi pesan teks biasa.
|
|
256
|
+
- **Store/memory kontak dibuat manual** (`src/core/store.js`) karena Baileys tidak lagi menyediakan `makeInMemoryStore` bawaan.
|
|
257
|
+
- **`@lid` dan JID lain yang gagal diambil foto profilnya** tidak lagi melempar exception — `ctx.getProfilePicture()` mengembalikan `null` dengan aman lewat try/catch.
|
|
258
|
+
- **`@whiskeysockets/baileys` dinaikkan ke `^6.7.23`** (versi `6.7.9` yang dipakai sebelumnya kena advisory keamanan spoofing pesan).
|
|
259
|
+
- File tunggal 2600+ baris (`build-message.js`) dipecah jadi modul-modul kecil per tanggung jawab (`toolkit`, `base-builder`, `button`, `button-v2`, `carousel`, `ai-rich`, `tokenizer`, `table`, `inline-entities`, `native-flow`, `link-preview`) supaya gampang di-maintain dan tidak saling tabrak saat diedit.
|
|
260
|
+
|
|
261
|
+
## Saran update selanjutnya
|
|
262
|
+
|
|
263
|
+
- **Plugin loader otomatis** — folder `plugins/` di-scan, tiap file otomatis jadi command (mirip pola yang sudah kamu pakai di bot lamamu), supaya `bot.command()` tidak ditulis manual satu-satu.
|
|
264
|
+
- **Anti-delete berbasis `Store`** — karena `store.loadMessage(jid, id)` sudah ada, tinggal dengarkan event `messages.delete` dan kirim ulang isi pesan lama sebelum dihapus.
|
|
265
|
+
- **Rate limiter per-command** — `cooldown` sekarang masih per-chat global; bisa dibuat per-command per-user biar lebih presisi (misal command berat dibatasi lebih ketat dari command ringan).
|
|
266
|
+
- **Poll message builder** — builder khusus untuk `pollCreationMessage`, senada gaya `bx` (`bx.poll(sock, jid, { question, options })`).
|
|
267
|
+
- **Session multi-akun** — satu proses Node bisa jalankan banyak `Bailyes` instance sekaligus (multi-nomor), tinggal kasih `auth` folder beda-beda; bisa dibungkus jadi `SessionManager` biar gampang.
|
|
268
|
+
- **Backend `Store` opsional ke SQLite** — saat ini `Store` murni in-memory + file JSON; untuk bot dengan banyak chat/pesan, opsi simpan ke SQLite (`better-sqlite3`) bakal lebih tahan crash dan tidak makan RAM.
|
|
269
|
+
- **Webhook/HTTP bridge** — endpoint HTTP kecil biar bot bisa dikontrol dari luar (kirim pesan lewat API, cek status koneksi, dsb), cocok buat kamu yang juga suka bikin REST API (NexRay API).
|
|
270
|
+
|
|
271
|
+
## Testing
|
|
31
272
|
|
|
32
273
|
```bash
|
|
33
|
-
npm
|
|
274
|
+
npm test
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
`test/basic.test.js` menguji Handler, Store, Toolkit, dan proses `build()` dari `ButtonV2`/`AIRich` tanpa perlu koneksi WhatsApp asli.
|
|
278
|
+
|
|
279
|
+
## Lisensi
|
|
280
|
+
|
|
281
|
+
MIT — lihat `LICENSE`.
|
package/package.json
CHANGED
|
@@ -1,33 +1,45 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@isaxn/bailyes",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "WhatsApp Bot Framework built on Baileys",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
3
|
+
"version": "2.1.3",
|
|
4
|
+
"description": "WhatsApp Bot Framework built on Baileys, with QR and pairing code login, a built-in contact/chat/message store, and a modular interactive message builder (Button, ButtonV2, Carousel, AIRich).",
|
|
5
|
+
"main": "./src/index.js",
|
|
6
|
+
"exports": "./src/index.js",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=18"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node test/basic.test.js"
|
|
11
12
|
},
|
|
12
13
|
"files": [
|
|
13
14
|
"src",
|
|
14
|
-
"dist",
|
|
15
15
|
"README.md",
|
|
16
16
|
"LICENSE"
|
|
17
17
|
],
|
|
18
18
|
"author": "isan",
|
|
19
19
|
"license": "MIT",
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/USERNAME/bailyes.git"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://github.com/USERNAME/bailyes#readme",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/USERNAME/bailyes/issues"
|
|
27
|
+
},
|
|
20
28
|
"keywords": [
|
|
21
29
|
"whatsapp",
|
|
22
30
|
"bot",
|
|
23
31
|
"baileys",
|
|
24
32
|
"whatsapp-bot",
|
|
25
|
-
"framework"
|
|
33
|
+
"framework",
|
|
34
|
+
"pairing-code",
|
|
35
|
+
"store"
|
|
26
36
|
],
|
|
27
37
|
"dependencies": {
|
|
28
|
-
"@whiskeysockets/baileys": "^6.7.
|
|
38
|
+
"@whiskeysockets/baileys": "^6.7.23",
|
|
29
39
|
"pino": "^9.0.0",
|
|
30
|
-
"qrcode-terminal": "^0.12.0"
|
|
40
|
+
"qrcode-terminal": "^0.12.0",
|
|
41
|
+
"sharp": "^0.33.0",
|
|
42
|
+
"fluent-ffmpeg": "^2.1.2"
|
|
31
43
|
},
|
|
32
44
|
"publishConfig": {
|
|
33
45
|
"access": "public"
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
let pending = null;
|
|
4
|
+
|
|
5
|
+
function getBaileys() {
|
|
6
|
+
if (!pending) {
|
|
7
|
+
pending = import("@whiskeysockets/baileys").then((mod) => {
|
|
8
|
+
return mod && mod.default && !mod.makeWASocket
|
|
9
|
+
? Object.assign({ makeWASocket: mod.default }, mod)
|
|
10
|
+
: mod;
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
return pending;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { getBaileys };
|
package/src/core/client.js
CHANGED
|
@@ -1,55 +1,175 @@
|
|
|
1
|
-
|
|
2
|
-
import makeWASocket, {
|
|
3
|
-
useMultiFileAuthState,
|
|
4
|
-
DisconnectReason,
|
|
5
|
-
fetchLatestBaileysVersion
|
|
6
|
-
} from "@whiskeysockets/baileys";
|
|
1
|
+
"use strict";
|
|
7
2
|
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
const { EventEmitter } = require("events");
|
|
4
|
+
const pino = require("pino");
|
|
5
|
+
const qrcode = require("qrcode-terminal");
|
|
6
|
+
const { getBaileys } = require("./baileys-loader.js");
|
|
7
|
+
const { Store } = require("./store.js");
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
const PASSTHROUGH_EVENTS = [
|
|
10
|
+
"messages.upsert",
|
|
11
|
+
"messages.update",
|
|
12
|
+
"messages.delete",
|
|
13
|
+
"messages.reaction",
|
|
14
|
+
"message-receipt.update",
|
|
15
|
+
"chats.upsert",
|
|
16
|
+
"chats.update",
|
|
17
|
+
"chats.delete",
|
|
18
|
+
"contacts.upsert",
|
|
19
|
+
"contacts.update",
|
|
20
|
+
"groups.upsert",
|
|
21
|
+
"groups.update",
|
|
22
|
+
"group-participants.update",
|
|
23
|
+
"presence.update",
|
|
24
|
+
"call",
|
|
25
|
+
"blocklist.set",
|
|
26
|
+
"blocklist.update"
|
|
27
|
+
];
|
|
15
28
|
|
|
16
|
-
|
|
17
|
-
|
|
29
|
+
class WAClient extends EventEmitter {
|
|
30
|
+
constructor(options = {}) {
|
|
31
|
+
super();
|
|
32
|
+
|
|
33
|
+
this.authFolder = options.auth || "auth";
|
|
34
|
+
this.usePairingCode = !!options.pairingCode;
|
|
35
|
+
this.phoneNumber = options.phoneNumber || null;
|
|
36
|
+
this.browser = options.browser || ["Bailyes", "Chrome", "2.0.0"];
|
|
37
|
+
this.logger = options.logger || pino({ level: "silent" });
|
|
38
|
+
this.reconnectDelay = options.reconnectDelay ?? 3000;
|
|
39
|
+
this.maxReconnectDelay = options.maxReconnectDelay ?? 30000;
|
|
40
|
+
this.maxReconnectAttempts = options.maxReconnectAttempts ?? 0;
|
|
41
|
+
this.socketOptions = options.socketOptions || {};
|
|
42
|
+
|
|
43
|
+
this.store =
|
|
44
|
+
options.store === false
|
|
45
|
+
? null
|
|
46
|
+
: options.store instanceof Store
|
|
47
|
+
? options.store
|
|
48
|
+
: new Store(typeof options.store === "object" ? options.store : {});
|
|
49
|
+
|
|
50
|
+
this.sock = null;
|
|
51
|
+
this._reconnectAttempts = 0;
|
|
52
|
+
this._stopped = false;
|
|
53
|
+
this._pairingRequested = false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async connect() {
|
|
57
|
+
this._stopped = false;
|
|
58
|
+
|
|
59
|
+
const baileys = await getBaileys();
|
|
60
|
+
const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion } = baileys;
|
|
61
|
+
|
|
62
|
+
const { state, saveCreds } = await useMultiFileAuthState(this.authFolder);
|
|
63
|
+
const { version } = await fetchLatestBaileysVersion();
|
|
18
64
|
|
|
19
65
|
const sock = makeWASocket({
|
|
20
66
|
version,
|
|
21
67
|
auth: state,
|
|
22
68
|
printQRInTerminal: false,
|
|
23
|
-
logger:
|
|
24
|
-
browser: ["
|
|
69
|
+
logger: this.logger,
|
|
70
|
+
browser: this.usePairingCode ? ["Ubuntu", "Chrome", "22.04.4"] : this.browser,
|
|
71
|
+
syncFullHistory: false,
|
|
72
|
+
...this.socketOptions
|
|
25
73
|
});
|
|
26
74
|
|
|
75
|
+
this.sock = sock;
|
|
76
|
+
this._pairingRequested = false;
|
|
77
|
+
|
|
78
|
+
if (this.store) {
|
|
79
|
+
this.store.bind(sock.ev);
|
|
80
|
+
}
|
|
81
|
+
|
|
27
82
|
sock.ev.on("creds.update", saveCreds);
|
|
83
|
+
sock.ev.on("connection.update", (update) => this._handleConnectionUpdate(update, baileys));
|
|
28
84
|
|
|
29
|
-
|
|
30
|
-
const { connection, lastDisconnect, qr } = update;
|
|
85
|
+
this._bindPassthroughEvents(sock);
|
|
31
86
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
87
|
+
if (this.usePairingCode && !sock.authState.creds.registered) {
|
|
88
|
+
await this._requestPairingCode(sock);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return sock;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async _requestPairingCode(sock) {
|
|
95
|
+
if (this._pairingRequested) return;
|
|
96
|
+
this._pairingRequested = true;
|
|
36
97
|
|
|
37
|
-
|
|
38
|
-
|
|
98
|
+
if (!this.phoneNumber) {
|
|
99
|
+
throw new Error("phoneNumber is required when pairingCode is enabled");
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await new Promise((resolve) => setTimeout(resolve, 3000));
|
|
103
|
+
|
|
104
|
+
const cleanNumber = String(this.phoneNumber).replace(/[^0-9]/g, "");
|
|
105
|
+
const code = await sock.requestPairingCode(cleanNumber);
|
|
106
|
+
|
|
107
|
+
this.emit("pairing-code", code);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
_handleConnectionUpdate(update, baileys) {
|
|
111
|
+
const { connection, lastDisconnect, qr } = update;
|
|
112
|
+
|
|
113
|
+
if (qr && !this.usePairingCode) {
|
|
114
|
+
qrcode.generate(qr, { small: true });
|
|
115
|
+
this.emit("qr", qr);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (connection === "connecting") {
|
|
119
|
+
this.emit("connecting");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (connection === "open") {
|
|
123
|
+
this._reconnectAttempts = 0;
|
|
124
|
+
this.emit("open", this.sock);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (connection === "close") {
|
|
128
|
+
const statusCode = lastDisconnect?.error?.output?.statusCode;
|
|
129
|
+
const reason = this._resolveDisconnectReason(statusCode, baileys);
|
|
130
|
+
|
|
131
|
+
this.emit("close", { statusCode, reason, error: lastDisconnect?.error });
|
|
132
|
+
|
|
133
|
+
if (reason === "loggedOut") {
|
|
134
|
+
this._stopped = true;
|
|
135
|
+
this.emit("logged-out");
|
|
136
|
+
return;
|
|
39
137
|
}
|
|
40
138
|
|
|
41
|
-
if (
|
|
42
|
-
const code =
|
|
43
|
-
lastDisconnect?.error?.output?.statusCode;
|
|
139
|
+
if (this._stopped) return;
|
|
44
140
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}, 3000);
|
|
49
|
-
}
|
|
141
|
+
if (this.maxReconnectAttempts && this._reconnectAttempts >= this.maxReconnectAttempts) {
|
|
142
|
+
this.emit("reconnect-failed");
|
|
143
|
+
return;
|
|
50
144
|
}
|
|
51
|
-
});
|
|
52
145
|
|
|
53
|
-
|
|
146
|
+
this._reconnectAttempts++;
|
|
147
|
+
const delay = Math.min(this.reconnectDelay * this._reconnectAttempts, this.maxReconnectDelay);
|
|
148
|
+
|
|
149
|
+
setTimeout(() => {
|
|
150
|
+
this.connect().catch((error) => this.emit("error", error));
|
|
151
|
+
}, delay);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
_resolveDisconnectReason(statusCode, baileys) {
|
|
156
|
+
const { DisconnectReason } = baileys;
|
|
157
|
+
const entries = Object.entries(DisconnectReason || {});
|
|
158
|
+
const found = entries.find(([, value]) => value === statusCode);
|
|
159
|
+
return found ? found[0] : "unknown";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_bindPassthroughEvents(sock) {
|
|
163
|
+
for (const event of PASSTHROUGH_EVENTS) {
|
|
164
|
+
sock.ev.on(event, (data) => this.emit(event, data));
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
stop() {
|
|
169
|
+
this._stopped = true;
|
|
170
|
+
if (this.store) this.store.stop();
|
|
171
|
+
this.sock?.end?.(undefined);
|
|
54
172
|
}
|
|
55
|
-
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = { WAClient };
|