@vanzxy/baileys 1.6.8 → 1.7.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/NOTICE.md +111 -0
- package/README.md +138 -9
- package/lib/Framework/Bot.js +209 -0
- package/lib/Framework/Context.js +90 -0
- package/lib/Framework/MediaManager.js +152 -0
- package/lib/Framework/SessionManager.js +34 -0
- package/lib/Framework/StatsManager.js +120 -0
- package/lib/Framework/Store/SQLiteStore.js +74 -0
- package/lib/Framework/index.js +11 -0
- package/lib/Socket/index.js +4 -2
- package/lib/Socket/username.js +153 -0
- package/lib/Utils/MessageBuilder.js +67 -1
- package/lib/Utils/MessageBuilder_d.ts +133 -1
- package/lib/VoIP/worker-bootstrap.js +8 -8
- package/lib/index.js +3 -0
- package/package.json +39 -35
package/NOTICE.md
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# NOTICE
|
|
2
|
+
|
|
3
|
+
`@vanzxy/baileys` is MIT-licensed (see the root `LICENSE`/`license` field in
|
|
4
|
+
`package.json`). It's a fork built on top of, and incorporating ported code
|
|
5
|
+
from, several other MIT-licensed WhatsApp libraries. This file lists each
|
|
6
|
+
third-party component, its original author/source, and where it lives in
|
|
7
|
+
this package — so credit stays attached to the actual code, not just a
|
|
8
|
+
generic "thanks" in the README.
|
|
9
|
+
|
|
10
|
+
MIT only requires the original copyright notice to travel with the code; it
|
|
11
|
+
does not require original authors to endorse this fork. Every component
|
|
12
|
+
below was MIT-licensed at its source at the time it was ported.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Upstream base
|
|
17
|
+
|
|
18
|
+
- **WhiskeySockets/Baileys** — original library this entire tree descends from.
|
|
19
|
+
- **@itsliaaa/baileys** — intermediate fork this package was branched from.
|
|
20
|
+
|
|
21
|
+
## Ported components
|
|
22
|
+
|
|
23
|
+
### MessageBuilder / AIRich / Button / Carousel / Toolkit — base implementation
|
|
24
|
+
- **Source:** Nixel, "NIXCODE — Advanced WhatsApp Interactive Message Builder", with contributions from Ahmad tumbuh kembang.
|
|
25
|
+
- **Location in this package:** `lib/Utils/MessageBuilder.js` and the `AIRich`/`AIVanzxy`/`LeafRich` exports.
|
|
26
|
+
- **⚠ Needs your confirmation:** the in-file header as of 1.7.0 now says this
|
|
27
|
+
was "ported into @vanzxy/baileys 1.3.9 from the `@blurose/baileys` 1.1.13
|
|
28
|
+
fork (base, feature-complete), with perf defaults and message-authenticity
|
|
29
|
+
fields backported from `arslan-baileys` 1.1.0." That doesn't name Nixel/
|
|
30
|
+
NIXCODE or Ahmad tumbuh kembang at all. I didn't overwrite the existing
|
|
31
|
+
line since I can't tell from the code alone whether blurose/arslan are
|
|
32
|
+
themselves downstream of NIXCODE (in which case both chains should be
|
|
33
|
+
listed) or whether this is a separate lineage that should replace the old
|
|
34
|
+
entry. Let me know which and I'll finalize it.
|
|
35
|
+
|
|
36
|
+
### MessageBuilder v4.7 additions (setResponseId/setBotResponseId/hasId/getIds/peek/delete)
|
|
37
|
+
- **Source:** credited in-code only as "temen's MessageBuilderV4.7" (`temen` = Indonesian for "friend/buddy") — not a resolvable handle or repo.
|
|
38
|
+
- **Location in this package:** `lib/Utils/MessageBuilder.js` (`~line 2120, 2885, 2916`).
|
|
39
|
+
- **⚠ Needs your input:** I can't attribute this properly without the actual author/repo name. Let me know who this refers to.
|
|
40
|
+
|
|
41
|
+
### Button/list addon-kind resolution logic
|
|
42
|
+
- **Source:** vinikjkkj, [zapo](https://github.com/vinikjkkj/zapo) (MIT).
|
|
43
|
+
- **Location in this package:** `lib/Utils/message-kind.js` (`resolveButtonAddonKind`, ported from `zapo-js` `src/message/encode/content.ts`).
|
|
44
|
+
- *(Corrected from the previous NOTICE, which pointed this entry at
|
|
45
|
+
`button-helper-utils.js`/`button-sender.js` — those two files are actually
|
|
46
|
+
sourced from `@queenanya/baileys`, see below. The zapo attribution belongs
|
|
47
|
+
here instead.)*
|
|
48
|
+
- Also referenced "in spirit" (not a direct port) for migration-tracking
|
|
49
|
+
design in `lib/Utils/use-sqlite-auth-state.js`.
|
|
50
|
+
|
|
51
|
+
### Button helper / button-sender runtime layer
|
|
52
|
+
- **Source:** QueenAnya, [`@queenanya/baileys`](https://github.com/QueenAnya/Bail) (MIT) `src/addons/message-utils.ts` and `src/addons/button-sender.ts` — both of which that fork's own header credits onward to **`@ryuu-reinzz/button-helper` v2.2.5** as the original implementation.
|
|
53
|
+
- **Location in this package:** `lib/Utils/button-helper-utils.js`, `lib/Utils/button-sender.js`.
|
|
54
|
+
|
|
55
|
+
### Interactive/native-flow button send layer
|
|
56
|
+
- **Source:** QueenAnya, [`@queenanya/baileys`](https://github.com/QueenAnya/Bail) (MIT, Copyright (c) 2025 Rajeh Taher/WhiskeySockets).
|
|
57
|
+
- **Location in this package:** `lib/Utils/button-sender.js` (send-layer portions).
|
|
58
|
+
|
|
59
|
+
### Username management (check/set/pin/recommend/find)
|
|
60
|
+
- **Source:** QueenAnya, [`@queenanya/baileys`](https://github.com/QueenAnya/Bail) `lib/Socket/username.js` (MIT), itself ported from `@innovatorssoft/baileys`.
|
|
61
|
+
- **Location in this package:** `lib/Socket/username.js`.
|
|
62
|
+
|
|
63
|
+
### Chat Control utilities (TypingIndicator + related presence helpers)
|
|
64
|
+
- **Source:** `@innovatorssoft/baileys` `chat-control.js` (direct, per this file's own header — not routed through @queenanya/baileys).
|
|
65
|
+
- **Location in this package:** `lib/Utils/chat-control.js` (and its `.d.ts`).
|
|
66
|
+
|
|
67
|
+
### Bail-master Utils addons batch
|
|
68
|
+
- **Source:** QueenAnya's `Bail` repository, master branch ("Bail-master"), `src/addons/*.ts` — same upstream as the `@queenanya/baileys` package above.
|
|
69
|
+
- **Location in this package** (TS → ESM JS conversions, type-only annotations dropped, behavior unchanged unless noted):
|
|
70
|
+
- `lib/Utils/vcard.js` ← `addons/vcard.ts`
|
|
71
|
+
- `lib/Utils/scheduling.js` ← `addons/scheduling.ts` + `addons/message-scheduler.ts` (merged into one class-based scheduler)
|
|
72
|
+
- `lib/Utils/message-search.js` ← `addons/message-search.ts`
|
|
73
|
+
- `lib/Utils/auto-reply.js` ← `addons/auto-reply.ts`
|
|
74
|
+
- `lib/Utils/stickerpack.js` ← `addons/stickerpack.ts`
|
|
75
|
+
- `lib/Utils/anti-delete.js` ← `addons/anti-delete.ts`
|
|
76
|
+
- `lib/Utils/chat-history-helpers.js` ← `addons/chat-history-helpers.ts` (adapted to this fork's own `makeInMemoryStore`)
|
|
77
|
+
- `lib/Utils/media-messages.js` ← `addons/media-messages.ts`
|
|
78
|
+
- `lib/Utils/media-set.js` ← `addons/media-set.ts`
|
|
79
|
+
- `lib/Utils/status.js` ← `addons/status-helpers.ts`
|
|
80
|
+
- `lib/Utils/templates.js` ← `addons/templates.ts`
|
|
81
|
+
- `lib/Utils/baileys-event-stream.js` ← `addons/baileys-event-stream.ts`
|
|
82
|
+
- `lib/Utils/past-participants.js` ← `addons/past-participants.ts`
|
|
83
|
+
- `lib/Utils/use-cache-manager-auth-state.js` ← `addons/use-cache-manager-auth-state.ts` (that file itself credits `@innovatorssoft/baileys` `make-cache-manager-store.js`)
|
|
84
|
+
|
|
85
|
+
### Bot Framework (Bot / Context / SessionManager / StatsManager / MediaManager / SQLiteStore)
|
|
86
|
+
- **Source:** Originally submitted as WhiskeySockets/Baileys PR #2710 by **LuferOS**; the P0–P3 bugfix pass and TypeScript rewrite referenced in this package's code comments were done by QueenAnya in `@queenanya/baileys` `lib/Framework/` (MIT). Adapted further for this fork (async `better-sqlite3`/`fluent-ffmpeg` lazy-loading, no `ffmpeg-static` dependency) — see the `Vanz@Port`/`Vanz@Fix` comments at the top of each file for exactly what changed.
|
|
87
|
+
- **Location in this package:** `lib/Framework/`.
|
|
88
|
+
|
|
89
|
+
### ourin-baileys ports
|
|
90
|
+
- **Source:** ourin-baileys (version 9.0.11 for the VoIP stack, per this package's `lib/index.js`).
|
|
91
|
+
- **Location in this package:**
|
|
92
|
+
- `lib/VoIP/`, `lib/assets/wasm/` — audio call WASM stack + WebRTC relay.
|
|
93
|
+
- `lib/Socket/newsletter.js` — AutoFollow feature.
|
|
94
|
+
- `lib/Utils/MessageBuilder.js` — the `ORich` no-op subclass of `AIRich`, kept only for drop-in compatibility with code originally written against ourin-baileys.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Referenced for verification only (no code incorporated)
|
|
99
|
+
|
|
100
|
+
A few bug-fix comments in `lib/Utils/MessageBuilder.js` cite other
|
|
101
|
+
implementations to confirm expected wire behavior, without copying code from
|
|
102
|
+
them: `zqdevelopers/zq_baileys_helper`, `@chatunity/baileys`, `@neoxr/wb`,
|
|
103
|
+
and `WhiskeySockets/Baileys` issue/PR `#2626`. Listed here for transparency
|
|
104
|
+
only — not attribution-bearing ports, so no third-party notice entry is
|
|
105
|
+
needed for them.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
If a source or attribution above is inaccurate or a component was missed,
|
|
110
|
+
please open an issue at the repository linked in `package.json` so it can be
|
|
111
|
+
corrected — misattribution here is a mistake to fix, not a dispute to argue.
|
package/README.md
CHANGED
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
<img src="https://capsule-render.vercel.app/api?type=waving&color=0:1d6a23,100:2ecc71&height=180§ion=header&text=%40vanzxy%2Fbaileys&fontSize=42&fontColor=ffffff&animation=fadeIn&fontAlignY=38&desc=Next-generation%20Baileys%20fork%20—%20built%20different,%20built%20by%20Vanzxy&descAlignY=58&descSize=16" width="100%"/>
|
|
4
4
|
|
|
5
|
-
<img src="https://files.catbox.moe/l72xji.svg" width="100%" alt="Sakura night banner"/>
|
|
6
|
-
|
|
7
5
|
<p>
|
|
8
6
|
<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&size=18&pause=1200&color=2ECC71¢er=true&vCenter=true&width=650&lines=Extended+interactive+messages+%26+native+flow;Status+tools+%2B+quality-of-life+fixes;Built+on+top+of+WhiskeySockets%2FBaileys" alt="Typing SVG" />
|
|
9
7
|
</p>
|
|
@@ -41,11 +39,11 @@
|
|
|
41
39
|
<summary>📖 Full table of contents</summary>
|
|
42
40
|
<br/>
|
|
43
41
|
|
|
44
|
-
- [
|
|
42
|
+
- [✨ Why @vanzxy/baileys](#-why-vanzxybaileys)
|
|
45
43
|
- [🆚 Comparison](#-comparison)
|
|
46
44
|
- [🧰 Built With](#-built-with)
|
|
47
45
|
- [🔥 Features](#-features)
|
|
48
|
-
- [
|
|
46
|
+
- [🌟 Exclusive Vanzxy Enhancements](#-exclusive-vanzxy-enhancements)
|
|
49
47
|
- [📦 Installation](#-installation)
|
|
50
48
|
- [🚀 Quick Start](#-quick-start)
|
|
51
49
|
- [🔐 Authentication](#-authentication)
|
|
@@ -57,6 +55,8 @@
|
|
|
57
55
|
- [AIRich — rich response cards](#airich--rich-response-cards)
|
|
58
56
|
- [📞 Voice & Video Calls](#-voice--video-calls)
|
|
59
57
|
- [🔎 User Sync Queries](#-user-sync-queries)
|
|
58
|
+
- [👤 Username Management](#-username-management)
|
|
59
|
+
- [🤖 Bot Framework](#-bot-framework)
|
|
60
60
|
- [🧩 Utility Modules](#-utility-modules)
|
|
61
61
|
- [🛎️ System Notification Filter](#-system-notification-filter)
|
|
62
62
|
- [🛠 Recommended Environment](#-recommended-environment)
|
|
@@ -65,10 +65,11 @@
|
|
|
65
65
|
- [❓ FAQ & Troubleshooting](#-faq--troubleshooting)
|
|
66
66
|
- [🤝 Contributing](#-contributing)
|
|
67
67
|
- [🙏 Credits](#-credits)
|
|
68
|
+
- [📈 Repo Activity](#-repo-activity)
|
|
68
69
|
- [💖 Support](#-support)
|
|
69
70
|
- [👑 Maintainer](#-maintainer)
|
|
70
71
|
- [⚠️ Disclaimer](#-disclaimer)
|
|
71
|
-
- [📝 Patch Notes](#-
|
|
72
|
+
- [📝 Patch Notes](#-v5-patch-notes)
|
|
72
73
|
|
|
73
74
|
</details>
|
|
74
75
|
|
|
@@ -76,7 +77,7 @@
|
|
|
76
77
|
|
|
77
78
|
---
|
|
78
79
|
|
|
79
|
-
##
|
|
80
|
+
## ✨ Why @vanzxy/baileys?
|
|
80
81
|
|
|
81
82
|
This fork focuses on:
|
|
82
83
|
|
|
@@ -107,6 +108,8 @@ How `@vanzxy/baileys` stacks up against its own lineage and the wider Baileys fo
|
|
|
107
108
|
| Multi-backend store (SQL/Mongo/Redis) | ✅ | ⚠️ file/SQLite only | ⚠️ in-memory only |
|
|
108
109
|
| Full `.d.ts` TypeScript definitions | ✅ | ⚠️ partial | ⚠️ partial |
|
|
109
110
|
| User sync queries (WAUSync) | ✅ | ✅ | ✅ |
|
|
111
|
+
| Username management (check/set/pin/recommend) | ✅ | ❌ | ❌ |
|
|
112
|
+
| Bot framework (middleware, session, stats) | ✅ | ❌ | ❌ |
|
|
110
113
|
|
|
111
114
|
> Tabel ini menggambarkan fitur di level package, bukan benchmark performa. PR untuk memperbarui/mengoreksi tabel ini dipersilakan lewat [Contributing](#-contributing).
|
|
112
115
|
|
|
@@ -218,7 +221,7 @@ How `@vanzxy/baileys` stacks up against its own lineage and the wider Baileys fo
|
|
|
218
221
|
|
|
219
222
|
---
|
|
220
223
|
|
|
221
|
-
##
|
|
224
|
+
## 🌟 Exclusive Vanzxy Enhancements
|
|
222
225
|
|
|
223
226
|
### Audio Group Status Fix
|
|
224
227
|
Audio status uses a more compatible implementation to avoid unsupported-version errors on older WhatsApp clients.
|
|
@@ -315,7 +318,8 @@ Everything below is **optional** — the socket works without any of them. Insta
|
|
|
315
318
|
| `fluent-ffmpeg` | Audio/video conversion for media messages |
|
|
316
319
|
| `audio-decode` | Audio waveform/duration extraction (voice notes, VoIP capture) |
|
|
317
320
|
| `link-preview-js` | Rich link previews for URLs in outgoing text messages |
|
|
318
|
-
| `better-sqlite3` | SQLite auth state **and**
|
|
321
|
+
| `better-sqlite3` | SQLite auth state, SQLite store adapter, **and** the [Bot Framework](#-bot-framework)'s `SQLiteStore`/`StatsManager` |
|
|
322
|
+
| `node-webpmux` | Packname/author EXIF metadata on stickers made via `MediaManager.convertToSticker()` — not needed for plain sticker conversion |
|
|
319
323
|
| `mongodb` | MongoDB store adapter |
|
|
320
324
|
| `mysql2` | MySQL store adapter |
|
|
321
325
|
| `pg` | PostgreSQL store adapter |
|
|
@@ -535,6 +539,84 @@ Available protocols: `USyncContactProtocol`, `USyncDeviceProtocol`, `USyncStatus
|
|
|
535
539
|
|
|
536
540
|
---
|
|
537
541
|
|
|
542
|
+
## 👤 Username Management
|
|
543
|
+
|
|
544
|
+
High-level wrappers around WhatsApp's username feature (the `@username` handle you can set instead of exposing your phone number), sitting on top of `USyncUsernameProtocol`. Query IDs are captured from live WA Web sessions — if a call starts throwing `unexpected response structure`, WA has rotated them and they need re-capturing.
|
|
545
|
+
|
|
546
|
+
```js
|
|
547
|
+
// Check availability + get suggestions if taken
|
|
548
|
+
const check = await sock.checkUsername('vanzxy')
|
|
549
|
+
// { available: true, username: 'vanzxy' }
|
|
550
|
+
// or: { available: false, suggestions: [...], rejectionReasons: [...] }
|
|
551
|
+
|
|
552
|
+
// Claim a username
|
|
553
|
+
await sock.setUsername('vanzxy', { source: sock.USERNAME_SOURCE.USER_INPUT })
|
|
554
|
+
|
|
555
|
+
// Lock it behind a PIN so it can't be changed without one
|
|
556
|
+
await sock.setUsernamePin('123456')
|
|
557
|
+
|
|
558
|
+
// Read your own username / drop it
|
|
559
|
+
const mine = await sock.getMyUsername()
|
|
560
|
+
await sock.deleteUsername()
|
|
561
|
+
|
|
562
|
+
// Resolve a username to a JID (USync-based, like onWhatsApp() but by username)
|
|
563
|
+
const user = await sock.findUserByUsername('someone')
|
|
564
|
+
// { jid: '628...@s.whatsapp.net', contact: false }
|
|
565
|
+
|
|
566
|
+
// Batch-resolve usernames for a list of known contacts
|
|
567
|
+
const usernames = await sock.fetchContactUsernames(jid1, jid2, jid3)
|
|
568
|
+
|
|
569
|
+
// Get WA's own suggestions (e.g. for onboarding flows)
|
|
570
|
+
const recs = await sock.getUsernameRecommendations()
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
> Requires a Community/Contact-tier account in good standing — accounts under WA's usual restrictions for new/unverified numbers may see `INVALID` or empty suggestions regardless of the username's actual availability.
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## 🤖 Bot Framework
|
|
578
|
+
|
|
579
|
+
An optional, higher-level layer on top of the raw socket: middleware routing, a `!command` dispatcher, a message queue that survives disconnects, exponential-backoff auto-reconnect, per-JID session storage, group activity stats, and media (sticker/voice-note) conversion helpers — so a new bot project doesn't have to hand-roll session/context management every time.
|
|
580
|
+
|
|
581
|
+
Needs the `better-sqlite3` peer dependency (session + stats storage) and `fluent-ffmpeg` (sticker/voice-note conversion, already listed above) — both fail with an install hint rather than crashing if you use a Framework feature that needs them without installing them first.
|
|
582
|
+
|
|
583
|
+
```js
|
|
584
|
+
import { Bot } from '@vanzxy/baileys'
|
|
585
|
+
|
|
586
|
+
const bot = new Bot({
|
|
587
|
+
socketConfig: { printQRInTerminal: true },
|
|
588
|
+
dbPath: './bot.db', // sessions + stats, defaults to 'baileys_store.db'
|
|
589
|
+
enableStats: true // group message/sticker leaderboards + ghost detection
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
bot.command('!ping', async (ctx) => {
|
|
593
|
+
await ctx.reply({ text: 'pong' })
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
bot.command('!sticker', async (ctx) => {
|
|
597
|
+
if (!ctx.quoted?.imageMessage) return ctx.reply({ text: 'Reply to an image with !sticker' })
|
|
598
|
+
// ctx.replySticker() handles the WebP conversion for you
|
|
599
|
+
await ctx.replySticker(imageBuffer, { packname: 'Vanzxy Pack', author: 'you' })
|
|
600
|
+
})
|
|
601
|
+
|
|
602
|
+
bot.onText(async (ctx) => {
|
|
603
|
+
// ctx.session() / ctx.setSession() / ctx.updateSession() / ctx.clearSession()
|
|
604
|
+
// persist small per-chat state (e.g. multi-step flows) to SQLite automatically
|
|
605
|
+
const state = ctx.session()
|
|
606
|
+
if (state?.awaitingReply) {
|
|
607
|
+
ctx.updateSession((s) => ({ ...s, awaitingReply: false }))
|
|
608
|
+
}
|
|
609
|
+
})
|
|
610
|
+
|
|
611
|
+
await bot.start()
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
`Context`, `SessionManager`, `StatsManager`, `MediaManager`, and `SQLiteStore` are also exported individually if you only need one piece rather than the full `Bot` class.
|
|
615
|
+
|
|
616
|
+
> These files ship as plain `.js` for now — hand-written `.d.ts` declarations for the Framework module haven't been added yet, unlike the rest of this fork's fully-typed surface.
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
538
620
|
## 🧩 Utility Modules
|
|
539
621
|
|
|
540
622
|
A sample of the utilities exported from `lib/Utils` beyond the message builders above:
|
|
@@ -656,6 +738,41 @@ Full details and license terms per component: see [`NOTICE.md`](./NOTICE.md).
|
|
|
656
738
|
|
|
657
739
|
---
|
|
658
740
|
|
|
741
|
+
## 📈 Repo Activity
|
|
742
|
+
|
|
743
|
+
<div align="center">
|
|
744
|
+
|
|
745
|
+
<a href="https://star-history.com/#vanzxysenpai/vanzxybaileys&Date">
|
|
746
|
+
<img src="https://api.star-history.com/svg?repos=vanzxysenpai/vanzxybaileys&type=Date" width="70%" alt="Star History Chart"/>
|
|
747
|
+
</a>
|
|
748
|
+
|
|
749
|
+
</div>
|
|
750
|
+
|
|
751
|
+
> Star history di atas otomatis mengikuti data repo — tidak perlu setup tambahan.
|
|
752
|
+
|
|
753
|
+
<details>
|
|
754
|
+
<summary>🎮 Want a 3D contribution graph on your own profile?</summary>
|
|
755
|
+
<br/>
|
|
756
|
+
|
|
757
|
+
That's a separate, opt-in GitHub Action for your **profile** repo (`username/username`), not something this package ships — see [github-profile-3d-contrib](https://github.com/yoshi389111/github-profile-3d-contrib) if you want to set it up for yourself.
|
|
758
|
+
|
|
759
|
+
</details>
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## 💖 Support
|
|
764
|
+
|
|
765
|
+
Kalau package ini kepakai buat project kamu, dukungan kecil sangat membantu perawatannya:
|
|
766
|
+
|
|
767
|
+
<p>
|
|
768
|
+
<a href="https://github.com/sponsors/vanzxysenpai" target="_blank"><img src="https://img.shields.io/badge/GitHub%20Sponsors-EA4AAA?style=for-the-badge&logo=githubsponsors&logoColor=white" alt="GitHub Sponsors"/></a>
|
|
769
|
+
<a href="#" target="_blank"><img src="https://img.shields.io/badge/Ko--fi-FF5E5B?style=for-the-badge&logo=kofi&logoColor=white" alt="Ko-fi"/></a>
|
|
770
|
+
</p>
|
|
771
|
+
|
|
772
|
+
> Ganti link Ko-fi/Sponsors di atas dengan akun kamu, atau hapus baris ini kalau belum dipakai.
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
659
776
|
## 👑 Maintainer
|
|
660
777
|
|
|
661
778
|
**Vanzxy**
|
|
@@ -672,6 +789,18 @@ Use responsibly and follow WhatsApp Terms of Service.
|
|
|
672
789
|
|
|
673
790
|
---
|
|
674
791
|
|
|
792
|
+
## 📝 V5 Patch Notes
|
|
793
|
+
|
|
794
|
+
- Added [Username Management](#-username-management): `checkUsername`, `checkUsernameMulti`, `setUsername`, `deleteUsername`, `getMyUsername`, `setUsernamePin`, `findUserByUsername`, `fetchContactUsernames`, `getUsernameRecommendations` — ported from `@queenanya/baileys`, layered onto this fork's existing `USyncUsernameProtocol` support.
|
|
795
|
+
- Added the [Bot Framework](#-bot-framework) (`Bot`, `Context`, `SessionManager`, `StatsManager`, `MediaManager`, `SQLiteStore`) — ported from `@queenanya/baileys` (originally WhiskeySockets PR #2710 by LuferOS). Adapted on the way in:
|
|
796
|
+
- `SQLiteStore`/`StatsManager` construction moved behind an async `.create()` factory so `better-sqlite3` stays a lazily-loaded optional peer dep instead of a hard top-level import that would crash the Framework module for anyone without it installed.
|
|
797
|
+
- `MediaManager`'s sticker/voice-note conversion now reuses this fork's existing lazy `fluent-ffmpeg` loader (see `Utils/MessageBuilder.js`) instead of adding `ffmpeg-static` + a second ffmpeg dependency; `node-webpmux` (sticker EXIF metadata) is lazy-loaded the same way and only when packname/author is actually requested.
|
|
798
|
+
- `Bot`'s default logger now falls back to this fork's own pino instance instead of a silent no-op stub.
|
|
799
|
+
- No `.d.ts` files were written for the new Framework module yet — see the note in that section.
|
|
800
|
+
- Bumped to `1.7.0`.
|
|
801
|
+
|
|
802
|
+
---
|
|
803
|
+
|
|
675
804
|
## 📝 V4 Patch Notes
|
|
676
805
|
|
|
677
806
|
- Kept the existing Vanzxy custom MessageBuilder classes and AIRich implementation intact.
|
|
@@ -679,7 +808,7 @@ Use responsibly and follow WhatsApp Terms of Service.
|
|
|
679
808
|
- Existing guarded fallbacks for platforms where the native bridge cannot load remain in place.
|
|
680
809
|
- ESM-only package metadata is preserved; no CommonJS build is included.
|
|
681
810
|
|
|
682
|
-
Reference audit: the supplied Ourin, Elaina, ItsLiaaa, and Noxleyss packages were compared before this patch. Their broader core changes were not copied wholesale because the current Vanzxy tree already contains additional LID, retry, media, interactive,
|
|
811
|
+
Reference audit: the supplied Ourin, Elaina, ItsLiaaa, and Noxleyss packages were compared before this patch. Their broader core changes were not copied wholesale because the current Vanzxy tree already contains additional LID, retry, media, interactive, and AIRich fixes that would risk regressions if replaced.
|
|
683
812
|
|
|
684
813
|
---
|
|
685
814
|
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { Boom } from '@hapi/boom';
|
|
2
|
+
import makeWASocket from '../Socket/index.js';
|
|
3
|
+
import { DisconnectReason } from '../Types/index.js';
|
|
4
|
+
import { isJidGroup } from '../WABinary/index.js';
|
|
5
|
+
import defaultLogger from '../Utils/logger.js';
|
|
6
|
+
import { SQLiteStore } from './Store/SQLiteStore.js';
|
|
7
|
+
import { Context } from './Context.js';
|
|
8
|
+
import { SessionManager } from './SessionManager.js';
|
|
9
|
+
import { StatsManager } from './StatsManager.js';
|
|
10
|
+
|
|
11
|
+
// Vanz@Port (from @queenanya/baileys Framework, originally Baileys PR #2710
|
|
12
|
+
// by LuferOS) --- high-level Bot class: middleware routing (Express/Koa-style
|
|
13
|
+
// `use()`/`next()`), a `!command` dispatcher, an outgoing message queue that
|
|
14
|
+
// survives disconnects, and exponential-backoff auto-reconnect.
|
|
15
|
+
//
|
|
16
|
+
// Kept from upstream's fix pass:
|
|
17
|
+
// - command boundary check: `!sticker` no longer matches `!stickerSpam`
|
|
18
|
+
// - queue is rejected (not left hanging) on DisconnectReason.loggedOut
|
|
19
|
+
// - stats.observeMessage runs inside the per-message try block, so a
|
|
20
|
+
// SQLite error can't silently skip middleware for that message
|
|
21
|
+
// - reconnect timer handle is stored so stop()/restart() can cancel it
|
|
22
|
+
//
|
|
23
|
+
// Vanz@Fix vs upstream: SQLiteStore/StatsManager are now created through an
|
|
24
|
+
// async factory (see Store/SQLiteStore.js) instead of a synchronous
|
|
25
|
+
// constructor, since better-sqlite3 is lazy-loaded as an optional peer dep
|
|
26
|
+
// in this fork. That means store/session/stats setup moved out of the Bot
|
|
27
|
+
// constructor and into start() — the socket is only created *after* the
|
|
28
|
+
// store is ready, so no message can arrive before sessions/stats exist.
|
|
29
|
+
export class Bot {
|
|
30
|
+
constructor(config) {
|
|
31
|
+
this.middlewares = [];
|
|
32
|
+
this.messageQueue = [];
|
|
33
|
+
this.isConnected = false;
|
|
34
|
+
this.reconnectAttempts = 0;
|
|
35
|
+
this.reconnectTimer = null;
|
|
36
|
+
this.BASE_RECONNECT_DELAY = 1000;
|
|
37
|
+
this.MAX_RECONNECT_DELAY = 30000;
|
|
38
|
+
this.socket = null;
|
|
39
|
+
this.sessions = null;
|
|
40
|
+
this.stats = null;
|
|
41
|
+
this.store = null;
|
|
42
|
+
this.config = config;
|
|
43
|
+
this.logger = config.logger ?? defaultLogger.child({ module: 'Framework.Bot' });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Register a middleware function */
|
|
47
|
+
use(middleware) {
|
|
48
|
+
this.middlewares.push(middleware);
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Register a command handler.
|
|
54
|
+
* Uses exact match OR prefix + whitespace so '!sticker' does NOT
|
|
55
|
+
* match '!stickerSpam' or '!stickerset'.
|
|
56
|
+
*/
|
|
57
|
+
command(cmd, handler) {
|
|
58
|
+
this.use(async (ctx, next) => {
|
|
59
|
+
const text = ctx.text;
|
|
60
|
+
if (text === cmd || text?.startsWith(cmd + ' ')) {
|
|
61
|
+
await handler(ctx);
|
|
62
|
+
}
|
|
63
|
+
await next();
|
|
64
|
+
});
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Register a handler that fires on any non-empty text */
|
|
69
|
+
onText(handler) {
|
|
70
|
+
this.use(async (ctx, next) => {
|
|
71
|
+
if (ctx.text) {
|
|
72
|
+
await handler(ctx);
|
|
73
|
+
}
|
|
74
|
+
await next();
|
|
75
|
+
});
|
|
76
|
+
return this;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Send a message — queues automatically when socket is disconnected */
|
|
80
|
+
async sendMessage(jid, content, options = {}) {
|
|
81
|
+
if (this.isConnected && this.socket) {
|
|
82
|
+
return this.socket.sendMessage(jid, content, options);
|
|
83
|
+
}
|
|
84
|
+
return new Promise((resolve, reject) => {
|
|
85
|
+
this.messageQueue.push({ jid, content, options, resolve, reject });
|
|
86
|
+
this.logger.info({ queueLength: this.messageQueue.length }, 'socket not connected — message queued');
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Drain queued messages after reconnect. */
|
|
91
|
+
drainQueue() {
|
|
92
|
+
if (!this.isConnected || !this.socket || this.messageQueue.length === 0)
|
|
93
|
+
return;
|
|
94
|
+
this.logger.info({ pending: this.messageQueue.length }, 'draining message queue');
|
|
95
|
+
const queue = [...this.messageQueue];
|
|
96
|
+
this.messageQueue = [];
|
|
97
|
+
for (const msg of queue) {
|
|
98
|
+
this.socket.sendMessage(msg.jid, msg.content, msg.options).then(msg.resolve).catch(msg.reject);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Reject and clear the message queue. Called on loggedOut so callers don't hang forever. */
|
|
103
|
+
rejectQueue(reason) {
|
|
104
|
+
const queue = [...this.messageQueue];
|
|
105
|
+
this.messageQueue = [];
|
|
106
|
+
for (const msg of queue) {
|
|
107
|
+
msg.reject(new Boom(reason, { statusCode: 401 }));
|
|
108
|
+
}
|
|
109
|
+
if (queue.length > 0) {
|
|
110
|
+
this.logger.warn({ rejected: queue.length }, 'message queue rejected — session terminated');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Start the socket and wire all event handlers */
|
|
115
|
+
async start() {
|
|
116
|
+
if (this.reconnectTimer) {
|
|
117
|
+
clearTimeout(this.reconnectTimer);
|
|
118
|
+
this.reconnectTimer = null;
|
|
119
|
+
}
|
|
120
|
+
// Vanz@Fix: store/sessions must exist before the socket can receive
|
|
121
|
+
// any message, so this now awaits creation instead of doing it
|
|
122
|
+
// synchronously in the constructor.
|
|
123
|
+
if (!this.store) {
|
|
124
|
+
const dbPath = this.config.dbPath ?? 'baileys_store.db';
|
|
125
|
+
this.store = await SQLiteStore.create(dbPath);
|
|
126
|
+
this.sessions = new SessionManager(this.store);
|
|
127
|
+
}
|
|
128
|
+
this.socket = makeWASocket(this.config.socketConfig);
|
|
129
|
+
if (this.config.enableStats && !this.stats) {
|
|
130
|
+
this.stats = await StatsManager.create(this.config.dbPath ?? 'baileys_store.db', jid => this.socket.groupMetadata(jid));
|
|
131
|
+
}
|
|
132
|
+
this.socket.ev.on('messages.upsert', async ({ messages, type }) => {
|
|
133
|
+
if (type !== 'notify')
|
|
134
|
+
return;
|
|
135
|
+
for (const msg of messages) {
|
|
136
|
+
const ctx = new Context(this, msg);
|
|
137
|
+
try {
|
|
138
|
+
if (this.stats && ctx.remoteJid && isJidGroup(ctx.remoteJid)) {
|
|
139
|
+
const participant = msg.key.participant || msg.participant;
|
|
140
|
+
if (participant) {
|
|
141
|
+
const isSticker = !!msg.message?.stickerMessage;
|
|
142
|
+
this.stats.observeMessage(ctx.remoteJid, participant, isSticker);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
await this.executeMiddlewares(ctx);
|
|
146
|
+
}
|
|
147
|
+
catch (err) {
|
|
148
|
+
this.logger.error({ err }, 'error executing middleware');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
this.socket.ev.on('connection.update', update => {
|
|
153
|
+
const { connection, lastDisconnect } = update;
|
|
154
|
+
if (connection === 'open') {
|
|
155
|
+
this.logger.info('bot connected');
|
|
156
|
+
this.isConnected = true;
|
|
157
|
+
this.reconnectAttempts = 0;
|
|
158
|
+
this.drainQueue();
|
|
159
|
+
}
|
|
160
|
+
if (connection === 'close') {
|
|
161
|
+
this.isConnected = false;
|
|
162
|
+
const error = lastDisconnect?.error;
|
|
163
|
+
const statusCode = error?.output?.statusCode;
|
|
164
|
+
const isLoggedOut = statusCode === DisconnectReason.loggedOut;
|
|
165
|
+
this.logger.warn({ statusCode }, 'connection closed');
|
|
166
|
+
if (isLoggedOut) {
|
|
167
|
+
this.rejectQueue('session terminated (logged out)');
|
|
168
|
+
this.logger.info('session logged out — not reconnecting');
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
this.reconnectAttempts++;
|
|
172
|
+
const delay = Math.min(this.MAX_RECONNECT_DELAY, this.BASE_RECONNECT_DELAY * Math.pow(2, this.reconnectAttempts - 1));
|
|
173
|
+
this.logger.info({ delay, attempt: this.reconnectAttempts }, 'scheduling reconnect');
|
|
174
|
+
this.reconnectTimer = setTimeout(() => {
|
|
175
|
+
this.reconnectTimer = null;
|
|
176
|
+
this.start().catch(err => {
|
|
177
|
+
this.logger.error({ err }, 'reconnect failed');
|
|
178
|
+
});
|
|
179
|
+
}, delay);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async executeMiddlewares(ctx) {
|
|
186
|
+
let index = -1;
|
|
187
|
+
const dispatch = async (i) => {
|
|
188
|
+
if (i <= index)
|
|
189
|
+
throw new Error('next() called multiple times');
|
|
190
|
+
index = i;
|
|
191
|
+
const middleware = this.middlewares[i];
|
|
192
|
+
if (middleware) {
|
|
193
|
+
await middleware(ctx, () => dispatch(i + 1));
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
await dispatch(0);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** Gracefully stop the bot */
|
|
200
|
+
stop() {
|
|
201
|
+
if (this.reconnectTimer) {
|
|
202
|
+
clearTimeout(this.reconnectTimer);
|
|
203
|
+
this.reconnectTimer = null;
|
|
204
|
+
}
|
|
205
|
+
this.rejectQueue('bot stopped');
|
|
206
|
+
this.store?.close();
|
|
207
|
+
this.stats?.close();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { MediaManager } from './MediaManager.js';
|
|
2
|
+
|
|
3
|
+
// Vanz@Port (from @queenanya/baileys Framework, originally Baileys PR #2710
|
|
4
|
+
// by LuferOS) --- message context helper: wraps a WAMessage with reply,
|
|
5
|
+
// react, session, and media helpers so command handlers don't have to touch
|
|
6
|
+
// the raw socket API.
|
|
7
|
+
//
|
|
8
|
+
// Kept from upstream's fix pass:
|
|
9
|
+
// - text getter also reads imageMessage/videoMessage/documentMessage
|
|
10
|
+
// captions, so a command sent as an image caption still matches
|
|
11
|
+
// - reply()/react()/replySticker()/replyVoiceNote() throw when remoteJid is
|
|
12
|
+
// missing instead of silently no-op'ing, so a handler can't believe a
|
|
13
|
+
// reply went out when it never did
|
|
14
|
+
export class Context {
|
|
15
|
+
constructor(bot, message) {
|
|
16
|
+
this.bot = bot;
|
|
17
|
+
this.message = message;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get remoteJid() {
|
|
21
|
+
return this.message.key.remoteJid;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Text content, including image/video/document captions so caption-only commands still match. */
|
|
25
|
+
get text() {
|
|
26
|
+
return (this.message.message?.conversation ||
|
|
27
|
+
this.message.message?.extendedTextMessage?.text ||
|
|
28
|
+
this.message.message?.imageMessage?.caption ||
|
|
29
|
+
this.message.message?.videoMessage?.caption ||
|
|
30
|
+
this.message.message?.documentMessage?.caption ||
|
|
31
|
+
undefined);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get quoted() {
|
|
35
|
+
return this.message.message?.extendedTextMessage?.contextInfo?.quotedMessage;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
session() {
|
|
39
|
+
return this.bot.sessions?.get(this.remoteJid || '');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setSession(data) {
|
|
43
|
+
this.bot.sessions?.set(this.remoteJid || '', data);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
updateSession(updater) {
|
|
47
|
+
this.bot.sessions?.update(this.remoteJid || '', updater);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
clearSession() {
|
|
51
|
+
this.bot.sessions?.delete(this.remoteJid || '');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async reply(content, options) {
|
|
55
|
+
if (!this.remoteJid)
|
|
56
|
+
throw new Error('Cannot reply: remoteJid is undefined');
|
|
57
|
+
await this.bot.sendMessage(this.remoteJid, {
|
|
58
|
+
...content
|
|
59
|
+
}, {
|
|
60
|
+
quoted: this.message,
|
|
61
|
+
...options
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async react(emoji) {
|
|
66
|
+
if (!this.remoteJid)
|
|
67
|
+
throw new Error('Cannot react: remoteJid is undefined');
|
|
68
|
+
await this.bot.sendMessage(this.remoteJid, {
|
|
69
|
+
react: { text: emoji, key: this.message.key }
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async replySticker(inputPathOrBuffer, metadata) {
|
|
74
|
+
if (!this.remoteJid)
|
|
75
|
+
throw new Error('Cannot reply: remoteJid is undefined');
|
|
76
|
+
const buffer = await MediaManager.convertToSticker(inputPathOrBuffer, metadata);
|
|
77
|
+
await this.reply({ sticker: buffer });
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async replyVoiceNote(inputPathOrBuffer) {
|
|
81
|
+
if (!this.remoteJid)
|
|
82
|
+
throw new Error('Cannot reply: remoteJid is undefined');
|
|
83
|
+
const buffer = await MediaManager.convertToVoiceNote(inputPathOrBuffer);
|
|
84
|
+
await this.reply({
|
|
85
|
+
audio: buffer,
|
|
86
|
+
mimetype: 'audio/ogg; codecs=opus',
|
|
87
|
+
ptt: true
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|