lyxe-baileys 1.0.2 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <div align="center">
2
2
 
3
- <img src="https://raw.githubusercontent.com/noxXza/data/refs/heads/main/lyxe-baileys.png" width="400" alt="Lyxe Baileys Banner" />
3
+ <img src="https://i.ibb.co.com/fz44Z9Nz/images.jpg" width="400" alt="Lyxe Baileys Banner" />
4
4
  <br><br>
5
5
 
6
6
  <h1>lyxe-baileys</h1>
@@ -113,6 +113,172 @@ Baru di sini? Ini seluruh library dalam satu pandangan. Tiap baris menaut ke bag
113
113
 
114
114
  ---
115
115
 
116
+ ## 🚀 Fitur Eksklusif Lyxe Baileys
117
+
118
+ ### 1. 🛡️ Smart Anti-Ban & Humanizer
119
+ Hindari pemblokiran akun dengan jeda acak manusiawi dan simulasi presence:
120
+ ```javascript
121
+ import { smartDelay } from 'lyxe-baileys'
122
+
123
+ // Jeda acak (misal 1000 - 2500ms)
124
+ await sock.smartDelay(1000, 2500)
125
+
126
+ // Simulasi mengetik otomatis sebelum pesan terkirim
127
+ await sock.simulateTyping(jid, 2000)
128
+
129
+ // Atau langsung kirim dengan opsi simulateTyping
130
+ await sock.sendMessage(jid, { text: 'Halo! Ada yang bisa dibantu?' }, { simulateTyping: true })
131
+ ```
132
+
133
+ ### 2. 👁️ View-Once & Anti-Delete Retriever
134
+ Bongkar media pesan 1x lihat (foto/video/audio) atau pulihkan pesan yang ditarik:
135
+ ```javascript
136
+ // Download pesan View Once secara instan
137
+ const { buffer, mediaType, caption } = await sock.downloadViewOnce(m)
138
+ console.log(`Media ${mediaType} berhasil diunduh! Ukuran: ${buffer.length} bytes`)
139
+
140
+ // Tangkap pesan yang dihapus pengirim dari store
141
+ const original = sock.extractRevokedMessage(store, revocationMsg)
142
+ if (original) {
143
+ console.log('Pesan yang ditarik:', original.message)
144
+ }
145
+ ```
146
+
147
+ ### 3. 📢 WhatsApp Status / Story Supercharger
148
+ Otomatis baca story, beri reaksi emoji, buat status mention, atau unggah voice note ke story:
149
+ ```javascript
150
+ // Otomatis tandai status sudah dibaca dengan jeda alami
151
+ await sock.readStatus(statusMessage, 1500)
152
+
153
+ // Beri reaksi emoji ke story kontak
154
+ await sock.reactStatus(statusMessage, '❤️')
155
+
156
+ // Buat status teks/media dengan mention kontak tertentu
157
+ await sock.sendStatusMention('Halo semua!', ['628123456789@s.whatsapp.net'])
158
+
159
+ // Unggah Voice Note (VN) ke Status WhatsApp
160
+ await sock.sendVoiceStatus(audioBuffer, { backgroundColor: '#7F5AF0' })
161
+ ```
162
+
163
+ ### 4. 🎙️ Media & Audio Helpers
164
+ Kirim Voice Note (PTT) asli dengan animasi waveform, stiker, dan media HD:
165
+ ```javascript
166
+ // Kirim rekaman suara (Voice Note PTT) lengkap dengan waveform
167
+ await sock.sendVoiceNote(jid, './suara.mp3', quoted)
168
+
169
+ // Kirim stiker instan
170
+ await sock.sendImageAsSticker(jid, stickerBuffer, quoted)
171
+
172
+ // Kirim foto/video kualitas HD resmi
173
+ await sock.sendHDMedia(jid, { image: { url: './foto.jpg' }, caption: 'Kualitas HD' })
174
+ ```
175
+
176
+ ### 5. 🤖 AI Streaming Message
177
+ Tampilkan pesan bertahap dengan animasi ketik langsung seperti ChatGPT/Gemini di WhatsApp:
178
+ ```javascript
179
+ // Mengalirkan teks bertahap (AsyncIterable, generator, array, atau string)
180
+ await sock.streamMessage(jid, aiTextStream, {
181
+ throttleMs: 350,
182
+ initialPlaceholder: '⏳ Sedang merangkai jawaban...',
183
+ quoted: m
184
+ })
185
+ ```
186
+
187
+ ### 6. 📱 Multi-Session Manager
188
+ Kelola banyak nomor bot WhatsApp sekaligus dalam satu script:
189
+ ```javascript
190
+ import makeWASocket, { LyxeSessionManager, useMultiFileAuthState } from 'lyxe-baileys'
191
+
192
+ const manager = new LyxeSessionManager({
193
+ createSocketFn: makeWASocket
194
+ })
195
+
196
+ // Buat dan jalankan sesi bot 1
197
+ const { state, saveCreds } = await useMultiFileAuthState('./session-bot1')
198
+ const sock1 = await manager.createSession('bot-1', { auth: state })
199
+
200
+ // Tangkap pesan dari semua sesi sekaligus
201
+ manager.on('messages.upsert', ({ sessionId, upsert, sock }) => {
202
+ console.log(`Pesan masuk di ${sessionId}`)
203
+ })
204
+ ```
205
+
206
+ ### 7. 🛡️ Built-in Proxy & TLS Chrome Spoofer (Anti-Banned Connection Shield)
207
+ Hubungkan bot melalui proxy HTTP, HTTPS, atau SOCKS5 dengan fingerprint TLS Chrome 131+ asli untuk meminimalkan deteksi bot dari server WhatsApp:
208
+ ```javascript
209
+ import makeWASocket, { useMultiFileAuthState } from 'lyxe-baileys'
210
+
211
+ const { state, saveCreds } = await useMultiFileAuthState('./session')
212
+
213
+ // Mendukung HTTP, HTTPS, atau SOCKS5 Proxy
214
+ const sock = makeWASocket({
215
+ auth: state,
216
+ proxy: 'socks5://user:password@127.0.0.1:1080' // atau 'http://proxyhost:8080'
217
+ })
218
+ ```
219
+
220
+ ### 8. 🏷️ Bypass Pembatasan Privasi Grup (Privacy 403 Bypass Engine)
221
+ Menambahkan peserta ke grup meskipun nomor target mengunci privasi grup (*Error 403*). Engine otomatis mengambil tiket server resmi dan mengirimkan kartu undangan GroupInviteMessage (V4) 1-klik langsung ke chat pribadinya:
222
+ ```javascript
223
+ // Otomatis bypass jika nomor target mengunci privasi grupnya
224
+ const results = await sock.groupAddWithBypass(groupJid, ['6281234567890@s.whatsapp.net'], {
225
+ caption: 'Undangan resmi bergabung ke grup kami!'
226
+ })
227
+
228
+ console.log(results)
229
+ // Output: [{ jid: '...', status: '403_bypassed_with_invite', bypassed: true, inviteCode: '...' }]
230
+ ```
231
+
232
+ ### 9. 🗄️ Immortal Media & Message Vault (Anti-Delete Otomatis)
233
+ Simpan semua pesan, media (gambar, video, audio, VN), dan pesan View-Once yang masuk secara otomatis ke brankas lokal sebelum sempat ditarik pengirim:
234
+ ```javascript
235
+ // Mengambil statistik dari vault
236
+ const stats = sock.vault.getStats()
237
+ console.log(`Vault menyimpan ${stats.totalMessages} pesan dan ${stats.totalMedia} berkas media.`)
238
+
239
+ // Ambil riwayat chat tertentu dari brankas
240
+ const chatHistory = sock.vault.getMessages('628123456789@s.whatsapp.net', 20)
241
+ ```
242
+
243
+ ### 10. 🚀 Built-in Web Cockpit & Live Dashboard
244
+ Pantau status koneksi bot, statistik memori, uptime, kirim pesan uji coba, dan lihat pesan masuk secara live real-time via browser tanpa dependensi tambahan (zero-dependency native HTTP):
245
+ ```javascript
246
+ // Nyalakan Web Cockpit Dashboard di port pilihan
247
+ sock.startDashboard(3000)
248
+ // Buka browser Anda: http://localhost:3000
249
+ ```
250
+
251
+ ### 11. ✈️ Telegram to WhatsApp Auto-Bridge
252
+ Hubungkan bot Telegram langsung ke WhatsApp! Pesan teks, foto, video, dokumen, dan voice note dari Telegram otomatis diteruskan ke nomor atau grup WhatsApp yang ditentukan:
253
+ ```javascript
254
+ const bridge = sock.bridgeTelegram({
255
+ botToken: '123456789:ABCdefGHIjklMNOpqrSTUvwxYZ',
256
+ targetWaJid: '120363023123456789@g.us', // JID WhatsApp tujuan
257
+ allowedTelegramChatIds: ['-1001234567890'], // (Opsional) filter ID Telegram
258
+ autoDownloadMedia: true
259
+ })
260
+
261
+ bridge.start()
262
+ ```
263
+
264
+ ### 12. 🛰️ Presence & Online Stalker Radar (Stealth Subscriptions)
265
+ Lacak status online / offline target secara diam-diam tanpa perlu membuka obrolan mereka menggunakan stanza presence WhatsApp tingkat rendah:
266
+ ```javascript
267
+ // Pantau target secara real-time
268
+ await sock.trackPresence('628123456789@s.whatsapp.net', (presence) => {
269
+ console.log(`[RADAR] ${presence.jid} sekarang: ${presence.status} pada ${new Date(presence.lastSeen).toLocaleTimeString()}`)
270
+ })
271
+ ```
272
+
273
+ ### 13. ⚪ Circular Video Note PTV (WhatsApp Video Bulat)
274
+ Kirim video note berbentuk bulat layaknya pesan video instan di Telegram (Push-To-Video / PTV):
275
+ ```javascript
276
+ // Kirim video note bulat instan
277
+ await sock.sendVideoNote(jid, './video.mp4')
278
+ ```
279
+
280
+ ---
281
+
116
282
  ## 📚 Daftar Isi
117
283
 
118
284
  - [Ringkasan](#-ringkasan)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lyxe-baileys",
3
- "version": "1.0.2",
3
+ "version": "2.0.0",
4
4
  "description": "Custom WhatsApp Multi-Device library by lyxe (forked from luna-baileys by noxXza)",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -0,0 +1,72 @@
1
+ import { delay } from '../Utils/generics.js';
2
+
3
+ /**
4
+ * Mengirim pesan dengan efek streaming (seperti animasi mengetik AI ChatGPT/Gemini).
5
+ * Pesan dikirim sekali lalu di-edit bertahap seiring potongan teks baru tiba.
6
+ *
7
+ * @param {any} sock Socket Baileys
8
+ * @param {string} jid Target chat
9
+ * @param {AsyncIterable<string>|string[]|Iterable<string>} stream Iterable teks/chunk
10
+ * @param {object} [options={}] Opsi
11
+ * @param {number} [options.throttleMs=350] Interval minimum antar edit agar tidak terkena rate-limit WA
12
+ * @param {string} [options.initialPlaceholder='⏳ Sedang berpikir...'] Teks awal saat proses mulai
13
+ * @param {any} [options.quoted] Pesan rujukan (quoted message)
14
+ * @returns {Promise<any>} Objek pesan terakhir yang dikirim
15
+ */
16
+ export const streamMessage = async (sock, jid, stream, options = {}) => {
17
+ const throttleMs = options.throttleMs || 350;
18
+ const placeholder = options.initialPlaceholder || '⏳ Sedang berpikir...';
19
+
20
+ // Kirim pesan pertama
21
+ const sent = await sock.sendMessage(jid, { text: placeholder }, { quoted: options.quoted });
22
+ if (!sent?.key) return sent;
23
+
24
+ let accumulatedText = '';
25
+ let lastEditTime = Date.now();
26
+
27
+ const doEdit = async (text) => {
28
+ try {
29
+ await sock.sendMessage(jid, {
30
+ text: text,
31
+ edit: sent.key
32
+ });
33
+ lastEditTime = Date.now();
34
+ } catch {
35
+ // Abaikan error transien saat streaming edit
36
+ }
37
+ };
38
+
39
+ if (stream && typeof stream[Symbol.asyncIterator] === 'function') {
40
+ for await (const chunk of stream) {
41
+ accumulatedText += chunk;
42
+ const now = Date.now();
43
+ if (now - lastEditTime >= throttleMs) {
44
+ await doEdit(accumulatedText);
45
+ }
46
+ }
47
+ } else if (stream && typeof stream[Symbol.iterator] === 'function') {
48
+ for (const chunk of stream) {
49
+ accumulatedText += chunk;
50
+ const now = Date.now();
51
+ if (now - lastEditTime >= throttleMs) {
52
+ await doEdit(accumulatedText);
53
+ await delay(throttleMs);
54
+ }
55
+ }
56
+ } else if (typeof stream === 'string') {
57
+ // Jika diberikan full string, simulasikan streaming dengan memecah per kata
58
+ const words = stream.split(' ');
59
+ for (let i = 0; i < words.length; i += 3) {
60
+ accumulatedText = words.slice(0, i + 3).join(' ');
61
+ await doEdit(accumulatedText);
62
+ await delay(throttleMs);
63
+ }
64
+ }
65
+
66
+ // Pastikan teks akhir terkirim utuh
67
+ if (accumulatedText && accumulatedText !== placeholder) {
68
+ await doEdit(accumulatedText);
69
+ }
70
+
71
+ return sent;
72
+ };
@@ -0,0 +1,301 @@
1
+ import http from 'http';
2
+ import os from 'os';
3
+
4
+ /**
5
+ * Built-in Web Cockpit & Live Monitor Dashboard (Zero Dependency).
6
+ * Membuka antarmuka grafis di browser untuk memantau status bot, chat masuk, dan kontrol bot.
7
+ */
8
+ export class LyxeDashboard {
9
+ constructor(sock, options = {}) {
10
+ this.sock = sock;
11
+ this.port = Number(options.port) || 3000;
12
+ this.recentMessages = [];
13
+ this.maxMessages = 40;
14
+ this.server = null;
15
+ this.qrCode = null;
16
+ this.connectionStatus = 'connecting';
17
+ this.startTime = Date.now();
18
+
19
+ this.bindEvents();
20
+ }
21
+
22
+ bindEvents() {
23
+ if (!this.sock) return;
24
+
25
+ this.sock.ev.on('connection.update', (update) => {
26
+ if (update.qr) {
27
+ this.qrCode = update.qr;
28
+ }
29
+ if (update.connection) {
30
+ this.connectionStatus = update.connection;
31
+ if (update.connection === 'open') {
32
+ this.qrCode = null;
33
+ }
34
+ }
35
+ });
36
+
37
+ this.sock.ev.on('messages.upsert', ({ messages }) => {
38
+ for (const m of messages) {
39
+ if (!m.message) continue;
40
+ const text = m.message.conversation ||
41
+ m.message.extendedTextMessage?.text ||
42
+ m.message.imageMessage?.caption ||
43
+ (m.message.imageMessage ? '[Foto]' : m.message.videoMessage ? '[Video]' : '[Media]');
44
+ const from = m.key.remoteJid;
45
+ const sender = m.key.participant || from;
46
+ this.recentMessages.unshift({
47
+ id: m.key.id,
48
+ from,
49
+ sender: sender ? sender.split('@')[0] : 'Unknown',
50
+ fromMe: Boolean(m.key.fromMe),
51
+ text,
52
+ time: new Date().toLocaleTimeString('id-ID')
53
+ });
54
+ if (this.recentMessages.length > this.maxMessages) {
55
+ this.recentMessages.pop();
56
+ }
57
+ }
58
+ });
59
+ }
60
+
61
+ start() {
62
+ if (this.server) return;
63
+
64
+ this.server = http.createServer((req, res) => {
65
+ const url = new URL(req.url, `http://${req.headers.host}`);
66
+
67
+ // CORS headers
68
+ res.setHeader('Access-Control-Allow-Origin', '*');
69
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
70
+ res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
71
+
72
+ if (req.method === 'OPTIONS') {
73
+ res.writeHead(204);
74
+ res.end();
75
+ return;
76
+ }
77
+
78
+ if (url.pathname === '/api/status') {
79
+ res.writeHead(200, { 'Content-Type': 'application/json' });
80
+ res.end(JSON.stringify({
81
+ status: this.connectionStatus,
82
+ user: this.sock?.user || null,
83
+ uptime: Math.floor((Date.now() - this.startTime) / 1000),
84
+ memory: {
85
+ totalMB: Math.round(os.totalmem() / 1024 / 1024),
86
+ freeMB: Math.round(os.freemem() / 1024 / 1024),
87
+ processMB: Math.round(process.memoryUsage().rss / 1024 / 1024)
88
+ },
89
+ qrCode: this.qrCode
90
+ }));
91
+ return;
92
+ }
93
+
94
+ if (url.pathname === '/api/messages') {
95
+ res.writeHead(200, { 'Content-Type': 'application/json' });
96
+ res.end(JSON.stringify(this.recentMessages));
97
+ return;
98
+ }
99
+
100
+ if (url.pathname === '/api/send' && req.method === 'POST') {
101
+ let body = '';
102
+ req.on('data', chunk => body += chunk);
103
+ req.on('end', async () => {
104
+ try {
105
+ const { jid, text } = JSON.parse(body);
106
+ if (!jid || !text) {
107
+ res.writeHead(400, { 'Content-Type': 'application/json' });
108
+ return res.end(JSON.stringify({ error: 'JID dan Text wajib diisi' }));
109
+ }
110
+ const formattedJid = jid.includes('@') ? jid : `${jid.replace(/[^0-9]/g, '')}@s.whatsapp.net`;
111
+ const sent = await this.sock.sendMessage(formattedJid, { text });
112
+ res.writeHead(200, { 'Content-Type': 'application/json' });
113
+ res.end(JSON.stringify({ success: true, id: sent?.key?.id }));
114
+ } catch (e) {
115
+ res.writeHead(500, { 'Content-Type': 'application/json' });
116
+ res.end(JSON.stringify({ error: e.message }));
117
+ }
118
+ });
119
+ return;
120
+ }
121
+
122
+ // HTML Frontend Dashboard
123
+ if (url.pathname === '/' || url.pathname === '/index.html') {
124
+ res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
125
+ res.end(this.renderHTML());
126
+ return;
127
+ }
128
+
129
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
130
+ res.end('Not Found');
131
+ });
132
+
133
+ this.server.listen(this.port, () => {
134
+ console.log(`\n🎛️ [Lyxe Cockpit] Web Dashboard aktif di: http://localhost:${this.port}\n`);
135
+ });
136
+ }
137
+
138
+ stop() {
139
+ if (this.server) {
140
+ this.server.close();
141
+ this.server = null;
142
+ }
143
+ }
144
+
145
+ renderHTML() {
146
+ return `<!DOCTYPE html>
147
+ <html lang="id">
148
+ <head>
149
+ <meta charset="UTF-8">
150
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
151
+ <title>Lyxe Baileys - Live Cockpit Dashboard</title>
152
+ <style>
153
+ :root {
154
+ --bg: #0f172a;
155
+ --card: #1e293b;
156
+ --border: #334155;
157
+ --primary: #8b5cf6;
158
+ --accent: #06b6d4;
159
+ --text: #f8fafc;
160
+ --muted: #94a3b8;
161
+ --green: #10b981;
162
+ --red: #ef4444;
163
+ }
164
+ * { box-sizing: border-box; margin: 0; padding: 0; }
165
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: var(--bg); color: var(--text); padding: 24px; }
166
+ .header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; padding-bottom: 16px; border-bottom: 1px solid var(--border); }
167
+ .header h1 { font-size: 24px; font-weight: 700; background: linear-gradient(135deg, var(--primary), var(--accent)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
168
+ .grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; margin-bottom: 24px; }
169
+ .card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.2); }
170
+ .card h3 { font-size: 14px; text-transform: uppercase; color: var(--muted); margin-bottom: 8px; letter-spacing: 0.5px; }
171
+ .card .val { font-size: 24px; font-weight: bold; }
172
+ .badge { display: inline-block; padding: 4px 10px; border-radius: 9999px; font-size: 12px; font-weight: 600; text-transform: uppercase; }
173
+ .badge-open { background: rgba(16, 185, 129, 0.2); color: var(--green); }
174
+ .badge-connecting { background: rgba(6, 182, 212, 0.2); color: var(--accent); }
175
+ .badge-close { background: rgba(239, 68, 68, 0.2); color: var(--red); }
176
+ .chat-container { display: grid; grid-template-columns: 1fr 340px; gap: 20px; }
177
+ .chat-box { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 20px; height: 500px; display: flex; flex-direction: column; }
178
+ .messages-list { flex: 1; overflow-y: auto; padding-right: 8px; }
179
+ .msg-item { padding: 10px; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 14px; }
180
+ .msg-item .meta { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); margin-bottom: 4px; }
181
+ .send-box { display: flex; flex-direction: column; gap: 12px; }
182
+ input, textarea, button { width: 100%; padding: 10px 14px; background: #0f172a; border: 1px solid var(--border); border-radius: 8px; color: #fff; font-size: 14px; }
183
+ input:focus, textarea:focus { outline: none; border-color: var(--primary); }
184
+ button { background: var(--primary); color: #fff; font-weight: 600; border: none; cursor: pointer; transition: 0.2s; }
185
+ button:hover { background: #7c3aed; }
186
+ @media (max-width: 800px) { .chat-container { grid-template-columns: 1fr; } }
187
+ </style>
188
+ </head>
189
+ <body>
190
+ <div class="header">
191
+ <div>
192
+ <h1>⚡ LYXE BAILEYS COCKPIT</h1>
193
+ <p style="color: var(--muted); font-size: 13px;">Realtime Bot Engine & Protocol Telemetry</p>
194
+ </div>
195
+ <div id="badge-status" class="badge badge-connecting">Connecting...</div>
196
+ </div>
197
+
198
+ <div class="grid">
199
+ <div class="card">
200
+ <h3>Nomor WhatsApp</h3>
201
+ <div id="val-user" class="val">-</div>
202
+ </div>
203
+ <div class="card">
204
+ <h3>Penggunaan RAM</h3>
205
+ <div id="val-ram" class="val">-</div>
206
+ </div>
207
+ <div class="card">
208
+ <h3>Uptime Bot</h3>
209
+ <div id="val-uptime" class="val">-</div>
210
+ </div>
211
+ </div>
212
+
213
+ <div class="chat-container">
214
+ <div class="chat-box">
215
+ <h3 style="margin-bottom: 12px;">📡 LIVE MESSAGE STREAM</h3>
216
+ <div id="messages-list" class="messages-list">
217
+ <p style="color: var(--muted); text-align: center; margin-top: 40px;">Menunggu pesan masuk...</p>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="card send-box">
222
+ <h3>🚀 TEST SEND MESSAGE</h3>
223
+ <input id="input-jid" placeholder="Nomor WA (contoh: 628123xxx)" />
224
+ <textarea id="input-text" rows="5" placeholder="Ketik pesan yang ingin dikirim..."></textarea>
225
+ <button onclick="sendMessage()">KIRIM PESAN</button>
226
+ <p id="send-status" style="font-size: 12px; text-align: center; color: var(--muted);"></p>
227
+ </div>
228
+ </div>
229
+
230
+ <script>
231
+ async function updateStatus() {
232
+ try {
233
+ const res = await fetch('/api/status');
234
+ const data = await res.json();
235
+
236
+ const badge = document.getElementById('badge-status');
237
+ badge.innerText = data.status || 'unknown';
238
+ badge.className = 'badge badge-' + (data.status || 'connecting');
239
+
240
+ document.getElementById('val-user').innerText = data.user?.id ? data.user.id.split('@')[0] : 'Belum Login';
241
+ document.getElementById('val-ram').innerText = data.memory ? data.memory.processMB + ' MB' : '-';
242
+
243
+ const uptime = data.uptime || 0;
244
+ const mins = Math.floor(uptime / 60);
245
+ const secs = uptime % 60;
246
+ document.getElementById('val-uptime').innerText = mins + 'm ' + secs + 's';
247
+ } catch (e) {}
248
+ }
249
+
250
+ async function updateMessages() {
251
+ try {
252
+ const res = await fetch('/api/messages');
253
+ const list = await res.json();
254
+ const container = document.getElementById('messages-list');
255
+ if (!list || !list.length) return;
256
+
257
+ container.innerHTML = list.map(m => \`
258
+ <div class="msg-item">
259
+ <div class="meta">
260
+ <span>\${m.fromMe ? '🟢 OUT' : '🔵 IN'} • @\${m.sender}</span>
261
+ <span>\${m.time}</span>
262
+ </div>
263
+ <div>\${m.text}</div>
264
+ </div>
265
+ \`).join('');
266
+ } catch (e) {}
267
+ }
268
+
269
+ async function sendMessage() {
270
+ const jid = document.getElementById('input-jid').value.trim();
271
+ const text = document.getElementById('input-text').value.trim();
272
+ const status = document.getElementById('send-status');
273
+ if (!jid || !text) return alert('Isi nomor dan pesan terlebih dahulu!');
274
+ status.innerText = 'Mengirim...';
275
+ try {
276
+ const res = await fetch('/api/send', {
277
+ method: 'POST',
278
+ headers: { 'Content-Type': 'application/json' },
279
+ body: JSON.stringify({ jid, text })
280
+ });
281
+ const data = await res.json();
282
+ if (data.success) {
283
+ status.innerText = '✅ Terkirim! ID: ' + data.id;
284
+ document.getElementById('input-text').value = '';
285
+ } else {
286
+ status.innerText = '❌ Gagal: ' + data.error;
287
+ }
288
+ } catch (err) {
289
+ status.innerText = '❌ Error: ' + err.message;
290
+ }
291
+ }
292
+
293
+ setInterval(updateStatus, 2000);
294
+ setInterval(updateMessages, 1500);
295
+ updateStatus();
296
+ updateMessages();
297
+ </script>
298
+ </body>
299
+ </html>`;
300
+ }
301
+ }
@@ -0,0 +1,87 @@
1
+ import { getBinaryNodeChild } from '../WABinary/index.js';
2
+
3
+ /**
4
+ * Menambahkan peserta ke grup dengan engine bypass pembatasan privasi (Privacy 403 Bypass).
5
+ * Jika peserta menyetel privasi grup ke "Kontak Saya" atau "Tidak Ada" (Error 403),
6
+ * engine otomatis mengambil kode undangan resmi yang ditandatangani server WhatsApp,
7
+ * lalu mengirim kartu undangan GroupInviteMessage (V4) langsung ke chat pribadi target.
8
+ *
9
+ * @param {any} sock Socket Baileys
10
+ * @param {string} groupJid JID grup tujuan
11
+ * @param {string[]|string} participants JID nomor yang ingin ditambahkan
12
+ * @param {object} [options={}] Opsi tambahan
13
+ * @param {boolean} [options.autoInvite=true] Otomatis kirim kartu undangan jika terblokir privasi 403
14
+ * @param {string} [options.caption] Teks pengantar undangan
15
+ * @returns {Promise<Array<{ jid: string, status: string, bypassed: boolean, inviteCode?: string, inviteExpiration?: number }>>}
16
+ */
17
+ export const groupAddWithBypass = async (sock, groupJid, participants, options = {}) => {
18
+ const list = Array.isArray(participants) ? participants : [participants];
19
+ const autoInvite = options.autoInvite !== false;
20
+
21
+ // 1. Eksekusi penambahan langsung via WhatsApp protocol
22
+ const updateResults = await sock.groupParticipantsUpdate(groupJid, list, 'add');
23
+ const finalResults = [];
24
+
25
+ let groupMeta = null;
26
+ const getMeta = async () => {
27
+ if (!groupMeta) {
28
+ try {
29
+ groupMeta = await sock.groupMetadata(groupJid);
30
+ } catch {
31
+ groupMeta = { subject: 'Grup WhatsApp' };
32
+ }
33
+ }
34
+ return groupMeta;
35
+ };
36
+
37
+ for (const res of updateResults) {
38
+ if (res.status === '403') {
39
+ // Server WhatsApp menolak add langsung karena privasi target, tapi memberikan add_request token resmi
40
+ const addRequestNode = getBinaryNodeChild(res.content, 'add_request');
41
+ const code = addRequestNode?.attrs?.code;
42
+ const expiration = addRequestNode?.attrs?.expiration;
43
+
44
+ if (code && expiration && autoInvite) {
45
+ try {
46
+ const meta = await getMeta();
47
+ await sock.sendMessage(res.jid, {
48
+ groupInviteMessage: {
49
+ groupJid: groupJid,
50
+ groupName: meta.subject || 'Grup WhatsApp',
51
+ caption: options.caption || `Halo! Kamu diundang bergabung ke grup *${meta.subject || 'Grup'}*. Klik tombol di bawah untuk bergabung langsung.`,
52
+ inviteCode: code,
53
+ inviteExpiration: Number(expiration)
54
+ }
55
+ });
56
+
57
+ finalResults.push({
58
+ jid: res.jid,
59
+ status: '403_bypassed_with_invite',
60
+ bypassed: true,
61
+ inviteCode: code,
62
+ inviteExpiration: Number(expiration)
63
+ });
64
+ continue;
65
+ } catch (inviteErr) {
66
+ sock.logger?.warn?.({ err: inviteErr }, 'groupAddWithBypass: Gagal mengirim V4 group invite card');
67
+ }
68
+ }
69
+
70
+ finalResults.push({
71
+ jid: res.jid,
72
+ status: '403',
73
+ bypassed: false,
74
+ inviteCode: code,
75
+ inviteExpiration: expiration ? Number(expiration) : undefined
76
+ });
77
+ } else {
78
+ finalResults.push({
79
+ jid: res.jid,
80
+ status: res.status || '200',
81
+ bypassed: res.status === '200'
82
+ });
83
+ }
84
+ }
85
+
86
+ return finalResults;
87
+ };