@vanzxy/baileys 1.6.7 → 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 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&section=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&center=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
- - [🍃 Why @vanzxy/baileys](#-why-vanzxybaileys)
42
+ - [ Why @vanzxy/baileys](#-why-vanzxybaileys)
45
43
  - [🆚 Comparison](#-comparison)
46
44
  - [🧰 Built With](#-built-with)
47
45
  - [🔥 Features](#-features)
48
- - [ Exclusive Vanzxy Enhancements](#-exclusive-vanzxy-enhancements)
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](#-v4-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
- ## 🍃 Why @vanzxy/baileys?
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
- ## Exclusive Vanzxy Enhancements
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** SQLite store adapter |
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, angd AIRich fixes that would risk regressions if replaced.
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
+ }