vinzzsync-wacli 1.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.
Files changed (49) hide show
  1. package/c.js +2 -0
  2. package/func.js +3263 -0
  3. package/index.js +2416 -0
  4. package/index2.js +3611 -0
  5. package/lib/sqlAuth.js +109 -0
  6. package/package.json +21 -0
  7. package/plugins/_loader.js +191 -0
  8. package/plugins/addplugins.js +93 -0
  9. package/plugins/backup.js +91 -0
  10. package/plugins/cekch.js +157 -0
  11. package/plugins/cekgb.js +105 -0
  12. package/plugins/clear.js +12 -0
  13. package/plugins/cmd.js +69 -0
  14. package/plugins/cmfil.js +110 -0
  15. package/plugins/cms.js +474 -0
  16. package/plugins/delplugins.js +57 -0
  17. package/plugins/dmsg.js +112 -0
  18. package/plugins/eval.js +175 -0
  19. package/plugins/evfil.js +86 -0
  20. package/plugins/exit.js +11 -0
  21. package/plugins/fadm.js +107 -0
  22. package/plugins/fakemsg.js +154 -0
  23. package/plugins/fakesize.js +204 -0
  24. package/plugins/fclick.js +111 -0
  25. package/plugins/fitnah.js +87 -0
  26. package/plugins/getfile.js +169 -0
  27. package/plugins/getplugins.js +71 -0
  28. package/plugins/getquoted.js +76 -0
  29. package/plugins/getusn.js +92 -0
  30. package/plugins/groups.js +62 -0
  31. package/plugins/help.js +19 -0
  32. package/plugins/ht.js +45 -0
  33. package/plugins/ht2.js +59 -0
  34. package/plugins/ht3.js +66 -0
  35. package/plugins/isbot.js +177 -0
  36. package/plugins/listplugins.js +103 -0
  37. package/plugins/me.js +95 -0
  38. package/plugins/minigames.js +144 -0
  39. package/plugins/ping.js +124 -0
  40. package/plugins/quoted.js +102 -0
  41. package/plugins/rvo.js +88 -0
  42. package/plugins/savefile.js +334 -0
  43. package/plugins/send.js +52 -0
  44. package/plugins/session.js +18 -0
  45. package/plugins/smsg.js +204 -0
  46. package/plugins/status.js +18 -0
  47. package/plugins/typing_troll.js +91 -0
  48. package/pp.jpg +0 -0
  49. package/vkazee-send-message.js +1238 -0
package/index.js ADDED
@@ -0,0 +1,2416 @@
1
+ // ============================================================
2
+ // SMART WA CLI
3
+ // - Auto detect MultiFile / SQLite session
4
+ // - Auto reconnect
5
+ // - Dynamic WA Web version when supported
6
+ // - Incoming message callback/debug
7
+ // - send / eval / cmd / exec / me / profile / status
8
+ // ============================================================
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import util from 'util';
13
+ import crypto from "crypto";
14
+ import qrcode from 'qrcode-terminal'
15
+ import readline from 'readline';
16
+ import { exec } from 'child_process';
17
+ import prompts from 'prompts';
18
+ import Database from 'better-sqlite3';
19
+ import { VERSION, Button, ButtonV2, Carousel, AIRich, Toolkit, copyNForward} from "./func.js";
20
+ import { pathToFileURL } from 'url';
21
+ import {
22
+ loadPlugins,
23
+ getPlugin,
24
+ getPluginCommands,
25
+ watchPlugins
26
+ } from './plugins/_loader.js';
27
+ // ------------------------------------------------------------
28
+ // BAILEYS - dynamic import supaya export fork lebih fleksibel
29
+ // ------------------------------------------------------------
30
+
31
+ const baileys = await import('@vkazee/baileys');
32
+
33
+ const makeWASocket =
34
+ baileys.default ??
35
+ baileys.makeWASocket;
36
+
37
+ const delay = baileys.delay;
38
+
39
+ const useMultiFileAuthState =
40
+ baileys.useMultiFileAuthState;
41
+
42
+ const makeCacheableSignalKeyStore =
43
+ baileys.makeCacheableSignalKeyStore;
44
+
45
+ const fetchLatestWaWebVersion =
46
+ baileys.fetchLatestWaWebVersion;
47
+
48
+ const Browsers =
49
+ baileys.Browsers;
50
+
51
+ const {
52
+ BufferJSON,
53
+ initAuthCreds
54
+ } = baileys;
55
+
56
+ // ------------------------------------------------------------
57
+ // CONFIG
58
+ // ------------------------------------------------------------
59
+
60
+ const SESSION_DIR = path.resolve('./session');
61
+
62
+ const LOG_LEVEL =
63
+ process.env.LOG_LEVEL || 'info';
64
+
65
+ const BASE_RECONNECT_DELAY = 3000;
66
+ const MAX_RECONNECT_DELAY = 30000;
67
+
68
+ const ENABLE_DYNAMIC_VERSION = true;
69
+
70
+ // ------------------------------------------------------------
71
+ // GLOBAL STATE
72
+ // ------------------------------------------------------------
73
+ global.owner = "6285185667890";
74
+ global.ownerr = "6285185667890@s.whatsapp.net";
75
+ global.v = {
76
+ key: {
77
+ participant: '13135550002@s.whatsapp.net',
78
+ remoteJid: '17608914335-1615035634@g.us',
79
+ },
80
+ message: {
81
+ conversation: `_Powered By Vinzz_`
82
+ }
83
+ };
84
+ global.prefix = ".";
85
+ let loginMethod = null;
86
+ let sock = null;
87
+
88
+ let sessionType = 'unknown';
89
+ let sessionPath = null;
90
+
91
+ let connectionState = 'closed';
92
+
93
+ let reconnectTimer = null;
94
+ let reconnectAttempts = 0;
95
+
96
+ let shuttingDown = false;
97
+ let terminalStarted = false;
98
+ let lastDisconnectInfo = null;
99
+
100
+ let currentWaVersion = null;
101
+ let lastDisconnect = null;
102
+ let lastConnectionUpdate = null;
103
+
104
+ let messageCount = 0;
105
+
106
+ // ============================================================
107
+ // MESSAGE CACHE
108
+ // ============================================================
109
+
110
+ function getMessageType(message) {
111
+ if (!message) {
112
+ return 'unknown';
113
+ }
114
+
115
+ const keys =
116
+ Object.keys(
117
+ message
118
+ );
119
+
120
+ return (
121
+ keys[0] ||
122
+ 'unknown'
123
+ );
124
+ }
125
+
126
+
127
+ const messageStore = new Map();
128
+ const MAX_MESSAGES = 2000;
129
+
130
+ function saveMessage(message) {
131
+
132
+ if (
133
+ !message?.key?.id ||
134
+ !message?.key?.remoteJid
135
+ ) {
136
+ return;
137
+ }
138
+
139
+ const key =
140
+ `${message.key.remoteJid}:${message.key.id}`;
141
+
142
+ messageStore.set(
143
+ key,
144
+ message
145
+ );
146
+
147
+ // Hapus pesan paling lama jika cache penuh
148
+ if (
149
+ messageStore.size >
150
+ MAX_MESSAGES
151
+ ) {
152
+ const oldestKey =
153
+ messageStore
154
+ .keys()
155
+ .next()
156
+ .value;
157
+
158
+ messageStore.delete(
159
+ oldestKey
160
+ );
161
+ }
162
+ }
163
+
164
+ function getDynamicHelpList() {
165
+ const source = handleCommand.toString();
166
+
167
+ const commands = [
168
+ ...source.matchAll(/\bcase\s+['"]([^'"]+)['"]\s*:/g)
169
+ ]
170
+ .map(match => match[1])
171
+ .filter(Boolean);
172
+
173
+ return [...new Set(commands)];
174
+ }
175
+
176
+ async function chooseLoginMethod() {
177
+ const response = await prompts({
178
+ type: 'select',
179
+ name: 'method',
180
+ message: 'Pilih method login:',
181
+ choices: [
182
+ {
183
+ title: 'Scan QR Code',
184
+ value: 'qr'
185
+ },
186
+ {
187
+ title: 'Pairing Code',
188
+ value: 'pairing'
189
+ }
190
+ ],
191
+ initial: 0
192
+ });
193
+
194
+ if (!response.method) {
195
+ console.log('\nOperasi dibatalkan.');
196
+ process.exit(0);
197
+ }
198
+
199
+ return response.method;
200
+ }
201
+
202
+ // ------------------------------------------------------------
203
+ // READLINE
204
+ // ------------------------------------------------------------
205
+
206
+ let rl = null;
207
+
208
+ function initReadline() {
209
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
210
+ process.stdin.setRawMode(false);
211
+ process.stdin.resume();
212
+ }
213
+
214
+ if (rl) {
215
+ try { rl.close(); } catch {}
216
+ }
217
+
218
+ rl = readline.createInterface({
219
+ input: process.stdin,
220
+ output: process.stdout,
221
+ terminal: true
222
+ });
223
+
224
+ rl.setPrompt('\x1b[32mroot@wa-cli\x1b[0m:\x1b[34m~\x1b[0m$ ');
225
+
226
+ rl.on('line', async input => {
227
+ if (shuttingDown || (loginMethod === 'pairing' && connectionState !== 'open')) {
228
+ return;
229
+ }
230
+
231
+ try {
232
+ await handleCommand(input);
233
+ } catch (err) {
234
+ console.error(err);
235
+ }
236
+
237
+ if (!shuttingDown && connectionState === 'open') {
238
+ rl.prompt();
239
+ } else {
240
+ terminalStarted = false;
241
+ }
242
+ });
243
+ }
244
+
245
+
246
+ // ------------------------------------------------------------
247
+ // COLORS / LOGGING
248
+ // ------------------------------------------------------------
249
+
250
+ function now() {
251
+ return new Date().toISOString();
252
+ }
253
+
254
+ function debug(label, value = '') {
255
+ const prefix =
256
+ `\x1b[36m[${now()}] [DEBUG] ${label}\x1b[0m`;
257
+
258
+ if (
259
+ typeof value === 'object' &&
260
+ value !== null
261
+ ) {
262
+ console.log(
263
+ prefix,
264
+ util.inspect(value, {
265
+ depth: 10,
266
+ colors: true,
267
+ compact: false
268
+ })
269
+ );
270
+ } else {
271
+ console.log(prefix, value);
272
+ }
273
+ }
274
+
275
+ function info(message) {
276
+ console.log(
277
+ `\x1b[32m[+] ${message}\x1b[0m`
278
+ );
279
+ }
280
+
281
+ function warn(message) {
282
+ console.log(
283
+ `\x1b[33m[!] ${message}\x1b[0m`
284
+ );
285
+ }
286
+
287
+ function errlog(message, error = null) {
288
+ console.log(
289
+ `\x1b[31m[ERROR] ${message}\x1b[0m`
290
+ );
291
+
292
+ if (error) {
293
+ console.error(
294
+ util.inspect(error, {
295
+ depth: 10,
296
+ colors: true,
297
+ compact: false
298
+ })
299
+ );
300
+ }
301
+ }
302
+
303
+ // ------------------------------------------------------------
304
+ // SESSION DETECTION
305
+ // ------------------------------------------------------------
306
+
307
+ function fileExists(file) {
308
+ try {
309
+ return fs.existsSync(file);
310
+ } catch {
311
+ return false;
312
+ }
313
+ }
314
+
315
+ function isDirectory(dir) {
316
+ try {
317
+ return fs.statSync(dir).isDirectory();
318
+ } catch {
319
+ return false;
320
+ }
321
+ }
322
+
323
+ function detectSQLiteAuth(dbPath) {
324
+ let db;
325
+
326
+ try {
327
+ db = new Database(
328
+ dbPath,
329
+ {
330
+ readonly: true,
331
+ fileMustExist: true
332
+ }
333
+ );
334
+
335
+ const rows =
336
+ db.prepare(`
337
+ SELECT name
338
+ FROM sqlite_master
339
+ WHERE type = 'table'
340
+ `).all();
341
+
342
+ const tables =
343
+ rows.map(x => x.name);
344
+
345
+ const hasBaileysState =
346
+ tables.includes('baileys_state');
347
+
348
+ let registered = false;
349
+
350
+ if (hasBaileysState) {
351
+ const row =
352
+ db.prepare(`
353
+ SELECT value
354
+ FROM baileys_state
355
+ WHERE key = 'creds'
356
+ LIMIT 1
357
+ `).get();
358
+
359
+ if (row?.value) {
360
+ try {
361
+ const creds =
362
+ JSON.parse(
363
+ Buffer.from(
364
+ row.value
365
+ ).toString(),
366
+ BufferJSON?.reviver
367
+ );
368
+
369
+ registered =
370
+ creds?.registered === true;
371
+ } catch {
372
+ registered = false;
373
+ }
374
+ }
375
+ }
376
+
377
+ return {
378
+ valid: hasBaileysState,
379
+ registered,
380
+ tables
381
+ };
382
+
383
+ } catch (error) {
384
+ debug(
385
+ `SQLite check gagal: ${dbPath}`,
386
+ error.message
387
+ );
388
+
389
+ return {
390
+ valid: false,
391
+ registered: false,
392
+ tables: []
393
+ };
394
+
395
+ } finally {
396
+ try {
397
+ db?.close();
398
+ } catch {}
399
+ }
400
+ }
401
+
402
+ function detectMultiFileAuth(dir) {
403
+ if (!isDirectory(dir)) {
404
+ return {
405
+ valid: false,
406
+ registered: false,
407
+ files: []
408
+ };
409
+ }
410
+
411
+ const files =
412
+ fs.readdirSync(dir);
413
+
414
+ const credsPath =
415
+ path.join(dir, 'creds.json');
416
+
417
+ if (!fileExists(credsPath)) {
418
+ return {
419
+ valid: false,
420
+ registered: false,
421
+ files
422
+ };
423
+ }
424
+
425
+ try {
426
+ const creds =
427
+ JSON.parse(
428
+ fs.readFileSync(
429
+ credsPath,
430
+ 'utf8'
431
+ )
432
+ );
433
+
434
+ return {
435
+ valid: true,
436
+ registered:
437
+ creds?.registered === true,
438
+ files
439
+ };
440
+
441
+ } catch {
442
+ return {
443
+ valid: false,
444
+ registered: false,
445
+ files
446
+ };
447
+ }
448
+ }
449
+
450
+ // Ubah signature fungsi menjadi menerima parameter dir
451
+ function detectSession(targetDir = SESSION_DIR) {
452
+ const result = {
453
+ type: 'none',
454
+ path: null,
455
+ registered: false,
456
+ details: {}
457
+ };
458
+
459
+ if (!isDirectory(targetDir)) {
460
+ return result;
461
+ }
462
+
463
+ const entries = fs.readdirSync(targetDir, { withFileTypes: true });
464
+
465
+ const sqliteCandidates = entries
466
+ .filter(entry => entry.isFile() && (entry.name.endsWith('.db') || entry.name.endsWith('.sqlite') || entry.name.endsWith('.sqlite3')))
467
+ .map(entry => path.join(targetDir, entry.name));
468
+
469
+ const sqliteResults = sqliteCandidates.map(dbPath => ({
470
+ dbPath,
471
+ ...detectSQLiteAuth(dbPath)
472
+ }));
473
+
474
+ const registeredSQLite = sqliteResults.find(x => x.valid && x.registered);
475
+ if (registeredSQLite) {
476
+ return { type: 'sqlite', path: registeredSQLite.dbPath, registered: true, details: registeredSQLite };
477
+ }
478
+
479
+ const validSQLite = sqliteResults.find(x => x.valid);
480
+ if (validSQLite) {
481
+ return { type: 'sqlite', path: validSQLite.dbPath, registered: validSQLite.registered, details: validSQLite };
482
+ }
483
+
484
+ const multi = detectMultiFileAuth(targetDir);
485
+ if (multi.valid) {
486
+ return { type: 'multifile', path: targetDir, registered: multi.registered, details: multi };
487
+ }
488
+
489
+ return result;
490
+ }
491
+
492
+
493
+ // ------------------------------------------------------------
494
+ // SQLITE AUTH LOADER
495
+ // Kompatibel dengan struktur:
496
+ // baileys_state(key TEXT PRIMARY KEY, value BLOB)
497
+ // ------------------------------------------------------------
498
+
499
+ async function useSQLiteAuthState(dbPath) {
500
+ const db =
501
+ new Database(dbPath);
502
+
503
+ db.pragma('journal_mode = WAL');
504
+
505
+ db.prepare(`
506
+ CREATE TABLE IF NOT EXISTS baileys_state (
507
+ key TEXT PRIMARY KEY,
508
+ value BLOB
509
+ )
510
+ `).run();
511
+
512
+ function load(key) {
513
+ const row =
514
+ db.prepare(`
515
+ SELECT value
516
+ FROM baileys_state
517
+ WHERE key = ?
518
+ LIMIT 1
519
+ `).get(key);
520
+
521
+ if (!row) {
522
+ return null;
523
+ }
524
+
525
+ try {
526
+ return JSON.parse(
527
+ Buffer.from(
528
+ row.value
529
+ ).toString(),
530
+ BufferJSON?.reviver
531
+ );
532
+ } catch {
533
+ return null;
534
+ }
535
+ }
536
+
537
+ function save(key, data) {
538
+ const json =
539
+ JSON.stringify(
540
+ data,
541
+ BufferJSON?.replacer
542
+ );
543
+
544
+ const buffer =
545
+ Buffer.from(
546
+ json,
547
+ 'utf8'
548
+ );
549
+
550
+ db.prepare(`
551
+ INSERT OR REPLACE INTO baileys_state
552
+ (key, value)
553
+ VALUES (?, ?)
554
+ `).run(
555
+ key,
556
+ buffer
557
+ );
558
+ }
559
+
560
+ function remove(key) {
561
+ db.prepare(`
562
+ DELETE FROM baileys_state
563
+ WHERE key = ?
564
+ `).run(key);
565
+ }
566
+
567
+ const creds =
568
+ load('creds') ||
569
+ initAuthCreds();
570
+
571
+ const keys = {};
572
+
573
+ const categories = [
574
+ 'pre-key',
575
+ 'session',
576
+ 'sender-key',
577
+ 'app-state-sync-key',
578
+ 'app-state-sync-version',
579
+ 'lid-mapping',
580
+ 'device-list'
581
+ ];
582
+
583
+ for (
584
+ const category of categories
585
+ ) {
586
+ keys[category] = {};
587
+
588
+ const rows =
589
+ db.prepare(`
590
+ SELECT key, value
591
+ FROM baileys_state
592
+ WHERE key LIKE ?
593
+ `).all(
594
+ `${category}:%`
595
+ );
596
+
597
+ for (
598
+ const row of rows
599
+ ) {
600
+ try {
601
+ const id =
602
+ row.key.slice(
603
+ category.length + 1
604
+ );
605
+
606
+ keys[category][id] =
607
+ JSON.parse(
608
+ Buffer.from(
609
+ row.value
610
+ ).toString(),
611
+ BufferJSON?.reviver
612
+ );
613
+
614
+ } catch {
615
+ // skip corrupt row
616
+ }
617
+ }
618
+ }
619
+
620
+ const state = {
621
+ creds,
622
+
623
+ keys: {
624
+
625
+ get: async (
626
+ type,
627
+ ids
628
+ ) => {
629
+
630
+ const data = {};
631
+
632
+ for (
633
+ const id of ids
634
+ ) {
635
+ const value =
636
+ load(
637
+ `${type}:${id}`
638
+ );
639
+
640
+ if (
641
+ value !== null &&
642
+ value !== undefined
643
+ ) {
644
+ data[id] = value;
645
+ }
646
+ }
647
+
648
+ return data;
649
+ },
650
+
651
+ set: async (
652
+ data
653
+ ) => {
654
+
655
+ for (
656
+ const category
657
+ in data
658
+ ) {
659
+
660
+ for (
661
+ const id
662
+ in data[category]
663
+ ) {
664
+
665
+ const value =
666
+ data[
667
+ category
668
+ ][id];
669
+
670
+ save(
671
+ `${category}:${id}`,
672
+ value
673
+ );
674
+ }
675
+ }
676
+ }
677
+ }
678
+ };
679
+
680
+ async function saveCreds() {
681
+ save(
682
+ 'creds',
683
+ creds
684
+ );
685
+ }
686
+
687
+ // Persist berkala
688
+ const interval =
689
+ setInterval(
690
+ () => {
691
+ try {
692
+ save(
693
+ 'creds',
694
+ creds
695
+ );
696
+ } catch {}
697
+ },
698
+ 30000
699
+ );
700
+
701
+ interval.unref?.();
702
+
703
+ return {
704
+ state,
705
+ saveCreds,
706
+ close: () => {
707
+ try {
708
+ clearInterval(
709
+ interval
710
+ );
711
+ } catch {}
712
+
713
+ try {
714
+ db.close();
715
+ } catch {}
716
+ },
717
+
718
+ // util internal
719
+ _db: db,
720
+ _remove: remove
721
+ };
722
+ }
723
+
724
+ // ------------------------------------------------------------
725
+ // WEB VERSION
726
+ // ------------------------------------------------------------
727
+
728
+ async function getLatestVersion() {
729
+ if (
730
+ !ENABLE_DYNAMIC_VERSION ||
731
+ typeof fetchLatestWaWebVersion !==
732
+ 'function'
733
+ ) {
734
+ debug(
735
+ 'fetchLatestWaWebVersion tidak tersedia'
736
+ );
737
+
738
+ return null;
739
+ }
740
+
741
+ try {
742
+ const result =
743
+ await fetchLatestWaWebVersion();
744
+
745
+ debug(
746
+ 'WA Web version result',
747
+ result
748
+ );
749
+
750
+ if (
751
+ result?.version &&
752
+ Array.isArray(
753
+ result.version
754
+ )
755
+ ) {
756
+
757
+ currentWaVersion =
758
+ result.version;
759
+
760
+ info(
761
+ `WA Web version: ${
762
+ result.version.join('.')
763
+ }`
764
+ );
765
+
766
+ return result.version;
767
+ }
768
+
769
+ } catch (error) {
770
+ warn(
771
+ `Gagal mengambil WA Web version: ${
772
+ error.message
773
+ }`
774
+ );
775
+ }
776
+
777
+ return null;
778
+ }
779
+
780
+ // ------------------------------------------------------------
781
+ // QR OPTIONAL RENDER
782
+ // Tidak wajib install qrcode-terminal.
783
+ // ------------------------------------------------------------
784
+
785
+ async function renderQR(qr) {
786
+ try {
787
+ const mod =
788
+ await import(
789
+ 'qrcode-terminal'
790
+ );
791
+
792
+ const qrTerminal =
793
+ mod.default ??
794
+ mod;
795
+
796
+ if (
797
+ typeof qrTerminal.generate ===
798
+ 'function'
799
+ ) {
800
+ qrTerminal.generate(
801
+ qr,
802
+ {
803
+ small: true
804
+ }
805
+ );
806
+
807
+ return true;
808
+ }
809
+
810
+ } catch {
811
+ // package tidak terpasang
812
+ }
813
+
814
+ console.log('');
815
+ console.log(
816
+ '[QR] qrcode-terminal tidak terpasang.'
817
+ );
818
+ console.log(
819
+ '[QR] QR tersedia pada event connection.update.'
820
+ );
821
+ console.log('');
822
+
823
+ return false;
824
+ }
825
+
826
+ // ------------------------------------------------------------
827
+ // MESSAGE TEXT EXTRACTION
828
+ // ------------------------------------------------------------
829
+
830
+ function extractMessageText(message) {
831
+ if (!message) {
832
+ return '';
833
+ }
834
+
835
+ return (
836
+ message.conversation ||
837
+
838
+ message.extendedTextMessage
839
+ ?.text ||
840
+
841
+ message.imageMessage
842
+ ?.caption ||
843
+
844
+ message.videoMessage
845
+ ?.caption ||
846
+
847
+ message.documentMessage
848
+ ?.caption ||
849
+
850
+ message.buttonsResponseMessage
851
+ ?.selectedDisplayText ||
852
+
853
+ message.listResponseMessage
854
+ ?.title ||
855
+
856
+ message.templateButtonReplyMessage
857
+ ?.selectedDisplayText ||
858
+
859
+ message.interactiveResponseMessage
860
+ ?.body
861
+ ?.text ||
862
+
863
+ ''
864
+ );
865
+ }
866
+
867
+ function setupCLIInput() {
868
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
869
+ process.stdin.setRawMode(false);
870
+ }
871
+
872
+ if (rl) rl.close();
873
+
874
+ rl = readline.createInterface({
875
+ input: process.stdin,
876
+ output: process.stdout,
877
+ terminal: true
878
+ });
879
+
880
+ // SET PROMPT CUSTOM DENGAN WARNA HIJAU ANSI (\x1b[32m)
881
+ const greenPrompt = '\x1b[32mroot@bot-wa:~$\x1b[0m ';
882
+ rl.setPrompt(greenPrompt);
883
+
884
+ rl.on('line', async (line) => {
885
+ const input = line.trim();
886
+ if (input) {
887
+ await handleCommand(input);
888
+ }
889
+ // Munculkan lagi prompt hijau setelah command selesai dieksekusi
890
+ rl.prompt();
891
+ });
892
+
893
+ // Tampilkan prompt untuk pertama kali
894
+ rl.prompt();
895
+ }
896
+
897
+
898
+ // ------------------------------------------------------------
899
+ // JID HELPER
900
+ // ------------------------------------------------------------
901
+
902
+ function normalizeJid(input) {
903
+ if (!input) {
904
+ return null;
905
+ }
906
+
907
+ if (
908
+ input.includes('@')
909
+ ) {
910
+ return input;
911
+ }
912
+
913
+ const clean =
914
+ input
915
+ .replace(
916
+ /[^\d]/g,
917
+ ''
918
+ );
919
+
920
+ if (!clean) {
921
+ return null;
922
+ }
923
+
924
+ return (
925
+ `${clean}@s.whatsapp.net`
926
+ );
927
+ }
928
+
929
+ // ------------------------------------------------------------
930
+ // DISCONNECT
931
+ // ------------------------------------------------------------
932
+
933
+ function parseDisconnect(
934
+ lastDisconnect
935
+ ) {
936
+
937
+ const error =
938
+ lastDisconnect?.error;
939
+
940
+ return {
941
+ message:
942
+ error?.message ||
943
+ 'Unknown error',
944
+
945
+ statusCode:
946
+ error?.output?.statusCode ??
947
+ error?.statusCode ??
948
+ error?.data?.statusCode ??
949
+ null,
950
+
951
+ reason:
952
+ error?.data?.reason ??
953
+ null,
954
+
955
+ location:
956
+ error?.data?.location ??
957
+ null,
958
+
959
+ name:
960
+ error?.name ??
961
+ null,
962
+
963
+ stack:
964
+ error?.stack ??
965
+ null
966
+ };
967
+ }
968
+
969
+ // ------------------------------------------------------------
970
+ // RECONNECT
971
+ // ------------------------------------------------------------
972
+
973
+ function reconnectDelay() {
974
+ const delay =
975
+ BASE_RECONNECT_DELAY *
976
+ Math.pow(
977
+ 2,
978
+ Math.min(
979
+ reconnectAttempts,
980
+ 4
981
+ )
982
+ );
983
+
984
+ return Math.min(
985
+ delay,
986
+ MAX_RECONNECT_DELAY
987
+ );
988
+ }
989
+
990
+ function scheduleReconnect() {
991
+ if (
992
+ shuttingDown
993
+ ) {
994
+ return;
995
+ }
996
+
997
+ if (
998
+ reconnectTimer
999
+ ) {
1000
+ return;
1001
+ }
1002
+
1003
+ reconnectAttempts++;
1004
+
1005
+ const delay =
1006
+ reconnectDelay();
1007
+
1008
+ warn(
1009
+ `Reconnect #${reconnectAttempts} dalam ${delay} ms`
1010
+ );
1011
+
1012
+ reconnectTimer =
1013
+ setTimeout(
1014
+ async () => {
1015
+
1016
+ reconnectTimer =
1017
+ null;
1018
+
1019
+ try {
1020
+ await connectWhatsApp(ACTIVE_SESSION_DIR);
1021
+ } catch (error) {
1022
+ errlog(
1023
+ 'Reconnect error',
1024
+ error
1025
+ );
1026
+
1027
+ scheduleReconnect();
1028
+ }
1029
+ },
1030
+ delay
1031
+ );
1032
+ }
1033
+
1034
+ // ------------------------------------------------------------
1035
+ // TERMINAL PROMPT
1036
+ // ------------------------------------------------------------
1037
+
1038
+ function prompt() {
1039
+ if (terminalStarted || shuttingDown) {
1040
+ return;
1041
+ }
1042
+
1043
+ if (connectionState !== 'open') {
1044
+ return;
1045
+ }
1046
+
1047
+ terminalStarted = true;
1048
+
1049
+ console.log('');
1050
+ console.log('Ketik "help" untuk daftar command.');
1051
+ console.log('');
1052
+
1053
+ ask();
1054
+ }
1055
+
1056
+ function ask() {
1057
+ if (shuttingDown || connectionState !== 'open' || !rl) {
1058
+ terminalStarted = false;
1059
+ return;
1060
+ }
1061
+
1062
+ rl.prompt();
1063
+ }
1064
+
1065
+
1066
+ /* rl.on('line', async input => {
1067
+ if (
1068
+ shuttingDown ||
1069
+ loginMethod === 'pairing' &&
1070
+ connectionState !== 'open'
1071
+ ) {
1072
+ return;
1073
+ }
1074
+
1075
+ try {
1076
+ await handleCommand(input);
1077
+ } catch (err) {
1078
+ console.error(err);
1079
+ }
1080
+
1081
+ if (
1082
+ !shuttingDown &&
1083
+ connectionState === 'open'
1084
+ ) {
1085
+ rl.prompt();
1086
+ } else {
1087
+ terminalStarted = false;
1088
+ }
1089
+ }); */
1090
+
1091
+
1092
+ // ------------------------------------------------------------
1093
+ // MESSAGE CONTEXT / BOT STYLE HANDLER
1094
+ // ------------------------------------------------------------
1095
+
1096
+ function jidNumber(jid) {
1097
+ return String(jid || '')
1098
+ .split('@')[0]
1099
+ .split(':')[0]
1100
+ .replace(/\D/g, '');
1101
+ }
1102
+
1103
+ function isSameJid(a, b) {
1104
+ const aa = jidNumber(a);
1105
+ const bb = jidNumber(b);
1106
+ return !!aa && !!bb && aa === bb;
1107
+ }
1108
+
1109
+ function unwrapMessage(message) {
1110
+ let msg = message || {};
1111
+
1112
+ for (let i = 0; i < 8; i++) {
1113
+ const next =
1114
+ msg?.ephemeralMessage?.message ||
1115
+ msg?.viewOnceMessage?.message ||
1116
+ msg?.viewOnceMessageV2?.message ||
1117
+ msg?.viewOnceMessageV2Extension?.message ||
1118
+ msg?.documentWithCaptionMessage?.message ||
1119
+ msg;
1120
+
1121
+ if (next === msg) break;
1122
+ msg = next;
1123
+ }
1124
+
1125
+ return msg;
1126
+ }
1127
+
1128
+ function getContextInfo(message) {
1129
+ const msg = unwrapMessage(message);
1130
+ for (const value of Object.values(msg || {})) {
1131
+ if (value?.contextInfo) return value.contextInfo;
1132
+ }
1133
+ return msg?.contextInfo || null;
1134
+ }
1135
+
1136
+ function getQuotedMessage(message, chat) {
1137
+ const contextInfo = getContextInfo(message);
1138
+ const quotedMessage = contextInfo?.quotedMessage;
1139
+ const quotedId = contextInfo?.stanzaId;
1140
+
1141
+ if (!quotedMessage || !quotedId) return null;
1142
+
1143
+ const participant =
1144
+ contextInfo?.participant ||
1145
+ contextInfo?.remoteJid ||
1146
+ chat ||
1147
+ null;
1148
+
1149
+ const stored =
1150
+ messageStore.get(`${chat}:${quotedId}`) ||
1151
+ [...messageStore.values()].find(x =>
1152
+ x?.key?.id === quotedId &&
1153
+ x?.key?.remoteJid === chat
1154
+ );
1155
+
1156
+ const key = stored?.key || {
1157
+ remoteJid: chat,
1158
+ id: quotedId,
1159
+ participant,
1160
+ fromMe: false
1161
+ };
1162
+
1163
+ return {
1164
+ key,
1165
+ message: stored?.message || quotedMessage,
1166
+ id: key.id || quotedId,
1167
+ chat: key.remoteJid || chat,
1168
+ sender: key.participant || key.senderPn || participant,
1169
+ fromMe: key.fromMe === true
1170
+ };
1171
+ }
1172
+
1173
+ function createMessageContext(message) {
1174
+ const key = message?.key || {};
1175
+ const chat = key.remoteJid || null;
1176
+
1177
+ const sender =
1178
+ key.participant ||
1179
+ key.senderPn ||
1180
+ key.participantPn ||
1181
+ chat ||
1182
+ null;
1183
+
1184
+ const fromMe = key.fromMe === true;
1185
+
1186
+ const ownerJid = global.ownerr;
1187
+
1188
+ const isOwner =
1189
+ fromMe ||
1190
+ isSameJid(sender, ownerJid) ||
1191
+ isSameJid(key.senderPn, ownerJid);
1192
+
1193
+ const rawText =
1194
+ extractMessageText(message?.message) || '';
1195
+
1196
+ global.vv = {
1197
+ key: {
1198
+ participant: '13135550002@s.whatsapp.net',
1199
+ remoteJid: '17608914335-1615035634@g.us',
1200
+ },
1201
+ message: {
1202
+ conversation: rawText
1203
+ }
1204
+ };
1205
+
1206
+ const prefix = global.prefix || '.';
1207
+
1208
+ const trimmed = rawText.trim();
1209
+
1210
+ const hasPrefix =
1211
+ trimmed.startsWith(prefix);
1212
+
1213
+ const commandInput =
1214
+ hasPrefix
1215
+ ? trimmed.slice(prefix.length).trim()
1216
+ : trimmed;
1217
+
1218
+ const parts =
1219
+ commandInput
1220
+ ? commandInput.split(/\s+/g)
1221
+ : [];
1222
+
1223
+ const command =
1224
+ parts.shift()?.toLowerCase() || '';
1225
+
1226
+ const args = parts;
1227
+
1228
+ const text =
1229
+ args.join(' ');
1230
+
1231
+ return {
1232
+ ...message,
1233
+ key,
1234
+ chat,
1235
+ sender,
1236
+ fromMe,
1237
+ isOwner,
1238
+ isGroup:
1239
+ typeof chat === 'string' &&
1240
+ chat.endsWith('@g.us'),
1241
+ text: rawText,
1242
+ body: rawText,
1243
+ command,
1244
+ args,
1245
+ textCommand: text,
1246
+ prefix: hasPrefix ? prefix : '',
1247
+ quoted:
1248
+ getQuotedMessage(
1249
+ message?.message,
1250
+ chat
1251
+ ),
1252
+ reply: async (
1253
+ text,
1254
+ chatId,
1255
+ options
1256
+ ) => {
1257
+ if (!sock) return null;
1258
+
1259
+ const target =
1260
+ chatId || chat;
1261
+
1262
+ if (!target) return null;
1263
+
1264
+ const content =
1265
+ typeof text === 'string'
1266
+ ? { text }
1267
+ : text;
1268
+
1269
+ const sendOptions = {
1270
+ contextInfo: {
1271
+ mentionedJid: [],
1272
+ groupMentions: [],
1273
+ isForwarded: false,
1274
+ forwardedNewsletterMessageInfo: {
1275
+ newsletterJid:
1276
+ '120363252742621904@newsletter',
1277
+ newsletterName:
1278
+ global.title || 'Vinzz',
1279
+ serverMessageId: -1
1280
+ }
1281
+ },
1282
+ quoted: global.vv, onTarget: true,
1283
+ ...(options || {})
1284
+ };
1285
+
1286
+ return sock.sendMessage(
1287
+ target,
1288
+ content,
1289
+ sendOptions
1290
+ );
1291
+ },
1292
+ send: async (
1293
+ content,
1294
+ options = {}
1295
+ ) => {
1296
+ if (!sock || !chat) {
1297
+ return null;
1298
+ }
1299
+
1300
+ return sock.sendMessage(
1301
+ chat,
1302
+ content,
1303
+ options
1304
+ );
1305
+ }
1306
+ };
1307
+ }
1308
+
1309
+ function getCommandInputFromMessage(m) {
1310
+ if (!m?.isOwner) return null;
1311
+
1312
+ const input = String(m.text || '').trim();
1313
+ if (!input) return null;
1314
+
1315
+ const prefix = String(global.prefix || '.');
1316
+
1317
+ if (!input.startsWith(prefix)) {
1318
+ return null;
1319
+ }
1320
+
1321
+ const commandInput = input.slice(prefix.length).trim();
1322
+ if (!commandInput) return null;
1323
+
1324
+ const firstToken = commandInput.split(/\s+/g)[0] || '';
1325
+
1326
+ if (!/^[a-z0-9_]+$/i.test(firstToken)) {
1327
+ return null;
1328
+ }
1329
+
1330
+ return commandInput;
1331
+ }
1332
+
1333
+ // ------------------------------------------------------------
1334
+ // COMMANDS
1335
+ // ------------------------------------------------------------
1336
+
1337
+ async function handleCommand(
1338
+ input,
1339
+ m = null
1340
+ ) {
1341
+ const trimmed = input.trim();
1342
+
1343
+ if (!trimmed) {
1344
+ return;
1345
+ }
1346
+
1347
+ const args = trimmed.split(/\s+/g);
1348
+ const command = args.shift()?.toLowerCase();
1349
+ const text = args.join(' ');
1350
+
1351
+ try {
1352
+ const plugin = getPlugin(command);
1353
+
1354
+ if (!plugin) {
1355
+ console.log(
1356
+ `\x1b[31mCommand tidak ditemukan: ${command}\x1b[0m`
1357
+ );
1358
+ return;
1359
+ }
1360
+
1361
+ await plugin.run({
1362
+ sock,
1363
+ m,
1364
+ args,
1365
+ text,
1366
+ command,
1367
+ messageStore,
1368
+ MAX_MESSAGES,
1369
+ delay,
1370
+ fs,
1371
+ path,
1372
+ util,
1373
+ crypto,
1374
+ normalizeJid,
1375
+ getMessageType,
1376
+ extractMessageText,
1377
+ unwrapMessage,
1378
+ getContextInfo,
1379
+ getQuotedMessage,
1380
+ createMessageContext,
1381
+ getPluginCommands,
1382
+ detectSession,
1383
+ connectionState,
1384
+ sessionType,
1385
+ sessionPath,
1386
+ reconnectAttempts,
1387
+ currentWaVersion,
1388
+ messageCount,
1389
+ lastDisconnect,
1390
+ ownerr: global.ownerr,
1391
+ shutdown,
1392
+ connectWhatsApp,
1393
+ ACTIVE_SESSION_DIR,
1394
+ debug,
1395
+ info,
1396
+ warn,
1397
+ errlog,
1398
+ exec
1399
+ });
1400
+ } catch (error) {
1401
+ errlog(
1402
+ `Error menjalankan "${command}"`,
1403
+ error
1404
+ );
1405
+ }
1406
+ }
1407
+
1408
+ // ------------------------------------------------------------
1409
+ // CONNECT
1410
+ // ------------------------------------------------------------
1411
+
1412
+ const SESSIONS_ROOT = path.resolve('./session');
1413
+
1414
+ async function selectOrCreateSession() {
1415
+ if (!fs.existsSync(SESSIONS_ROOT)) {
1416
+ fs.mkdirSync(SESSIONS_ROOT, { recursive: true });
1417
+ }
1418
+
1419
+ const existingDirs = fs.readdirSync(SESSIONS_ROOT, { withFileTypes: true })
1420
+ .filter(dirent => dirent.isDirectory())
1421
+ .map(dirent => dirent.name);
1422
+
1423
+ const choices = existingDirs.map(dirName => ({
1424
+ title: `📂 ${dirName}`,
1425
+ value: dirName
1426
+ }));
1427
+
1428
+ choices.unshift({
1429
+ title: '➕ [Add New Session]',
1430
+ value: 'NEW_SESSION'
1431
+ });
1432
+
1433
+ const response = await prompts({
1434
+ type: 'select',
1435
+ name: 'selectedSession',
1436
+ message: 'Pilih session yang ingin digunakan:',
1437
+ choices: choices,
1438
+ initial: 0
1439
+ });
1440
+
1441
+ if (!response.selectedSession) {
1442
+ console.log('\nOperasi dibatalkan.');
1443
+ process.exit(0);
1444
+ }
1445
+
1446
+ let targetSessionFolder = response.selectedSession;
1447
+
1448
+ if (targetSessionFolder === 'NEW_SESSION') {
1449
+ const nameInput = await prompts({
1450
+ type: 'text',
1451
+ name: 'sessionName',
1452
+ message: 'Please add name of this session:'
1453
+ });
1454
+
1455
+ const rawName = nameInput.sessionName?.trim();
1456
+
1457
+ if (!rawName) {
1458
+ targetSessionFolder = `session-62xx-${Date.now().toString().slice(-4)}`;
1459
+ info(`Nama kosong, session dinamai otomatis: ${targetSessionFolder}`);
1460
+ } else {
1461
+ targetSessionFolder = `session-${rawName.replace(/\s+/g, '-').toLowerCase()}`;
1462
+ }
1463
+
1464
+ const fullPath = path.join(SESSIONS_ROOT, targetSessionFolder);
1465
+ if (!fs.existsSync(fullPath)) {
1466
+ fs.mkdirSync(fullPath, { recursive: true });
1467
+ }
1468
+ }
1469
+
1470
+ return path.join(SESSIONS_ROOT, targetSessionFolder);
1471
+ }
1472
+
1473
+
1474
+ async function connectWhatsApp(targetSessionDir = SESSION_DIR) {
1475
+
1476
+
1477
+ if (
1478
+ shuttingDown
1479
+ ) {
1480
+ return;
1481
+ }
1482
+
1483
+ connectionState =
1484
+ 'connecting';
1485
+
1486
+ console.log('');
1487
+ console.log(
1488
+ '\x1b[36m=========================================='
1489
+ );
1490
+ console.log(
1491
+ ' WHATSAPP SMART CONNECTION'
1492
+ );
1493
+ console.log(
1494
+ '==========================================\x1b[0m'
1495
+ );
1496
+
1497
+ debug(
1498
+ 'Node',
1499
+ process.version
1500
+ );
1501
+
1502
+ debug(
1503
+ 'Platform',
1504
+ process.platform
1505
+ );
1506
+
1507
+ debug(
1508
+ 'Architecture',
1509
+ process.arch
1510
+ );
1511
+
1512
+ debug(
1513
+ 'CWD',
1514
+ process.cwd()
1515
+ );
1516
+
1517
+ debug(
1518
+ 'Session directory',
1519
+ SESSION_DIR
1520
+ );
1521
+
1522
+ // --------------------------------------------------------
1523
+ // DETECT SESSION
1524
+ // --------------------------------------------------------
1525
+
1526
+ const detected =
1527
+ detectSession(targetSessionDir);
1528
+
1529
+ sessionType =
1530
+ detected.type;
1531
+
1532
+ sessionPath =
1533
+ detected.path;
1534
+
1535
+ console.log('');
1536
+ console.log(
1537
+ `[SESSION] Type : ${sessionType}`
1538
+ );
1539
+
1540
+ console.log(
1541
+ `[SESSION] Path : ${
1542
+ sessionPath ?? '-'
1543
+ }`
1544
+ );
1545
+
1546
+ console.log(
1547
+ `[SESSION] Registered : ${
1548
+ detected.registered
1549
+ }`
1550
+ );
1551
+
1552
+ debug(
1553
+ 'Session detection',
1554
+ detected
1555
+ );
1556
+
1557
+ if (
1558
+ !detected.registered
1559
+ ) {
1560
+ loginMethod =
1561
+ await chooseLoginMethod();
1562
+
1563
+ console.log('');
1564
+
1565
+ info(
1566
+ `Login method: ${
1567
+ loginMethod === 'pairing'
1568
+ ? 'PAIRING CODE'
1569
+ : 'QR CODE'
1570
+ }`
1571
+ );
1572
+ }
1573
+
1574
+ // --------------------------------------------------------
1575
+ // AUTH
1576
+ // --------------------------------------------------------
1577
+
1578
+ let authState;
1579
+ let saveCreds;
1580
+ let closeAuth = null;
1581
+
1582
+ if (sessionType === 'sqlite') {
1583
+ console.log('[SESSION] Menggunakan SQLite auth.');
1584
+ const sqlite = await useSQLiteAuthState(sessionPath);
1585
+ authState = sqlite.state;
1586
+ saveCreds = sqlite.saveCreds;
1587
+ closeAuth = sqlite.close;
1588
+ } else if (sessionType === 'multifile') {
1589
+ console.log('[SESSION] Menggunakan MultiFile auth.');
1590
+ const multi = await useMultiFileAuthState(targetSessionDir);
1591
+ authState = multi.state;
1592
+ saveCreds = multi.saveCreds;
1593
+ } else {
1594
+ console.log('[SESSION] Belum menemukan auth session.');
1595
+ console.log('[SESSION] Membuat session MultiFile baru.');
1596
+
1597
+ if (typeof useMultiFileAuthState !== 'function') {
1598
+ throw new Error('useMultiFileAuthState tidak tersedia pada fork ini.');
1599
+ }
1600
+
1601
+ const multi = await useMultiFileAuthState(targetSessionDir);
1602
+ authState = multi.state;
1603
+ saveCreds = multi.saveCreds;
1604
+
1605
+ sessionType = 'multifile';
1606
+ sessionPath = targetSessionDir;
1607
+ }
1608
+
1609
+ debug(
1610
+ 'Credentials registered',
1611
+ authState?.creds?.registered
1612
+ );
1613
+
1614
+ // --------------------------------------------------------
1615
+ // WA WEB VERSION
1616
+ // --------------------------------------------------------
1617
+
1618
+ const version =
1619
+ await getLatestVersion();
1620
+
1621
+ // --------------------------------------------------------
1622
+ // SOCKET CONFIG
1623
+ // --------------------------------------------------------
1624
+
1625
+ const config = {
1626
+ auth: {
1627
+ creds:
1628
+ authState.creds,
1629
+
1630
+ keys:
1631
+ typeof makeCacheableSignalKeyStore ===
1632
+ 'function'
1633
+ ? makeCacheableSignalKeyStore(
1634
+ authState.keys,
1635
+ pinoLogger()
1636
+ )
1637
+ : authState.keys
1638
+ },
1639
+
1640
+ logger:
1641
+ pinoLogger(),
1642
+
1643
+ browser:
1644
+ typeof Browsers?.macOS === 'function'
1645
+ ? Browsers.macOS('Safari')
1646
+ : [
1647
+ 'Mac OS',
1648
+ 'Safari',
1649
+ '14.4.1'
1650
+ ]
1651
+ };
1652
+
1653
+ if (version) {
1654
+ config.version =
1655
+ version;
1656
+ }
1657
+
1658
+ debug(
1659
+ 'Socket config',
1660
+ {
1661
+ version:
1662
+ config.version,
1663
+
1664
+ browser:
1665
+ config.browser,
1666
+
1667
+ sessionType,
1668
+
1669
+ registered:
1670
+ authState?.creds?.registered
1671
+ }
1672
+ );
1673
+
1674
+ // --------------------------------------------------------
1675
+ // SOCKET
1676
+ // --------------------------------------------------------
1677
+
1678
+ sock =
1679
+ makeWASocket(
1680
+ config
1681
+ );
1682
+
1683
+ debug(
1684
+ 'Socket berhasil dibuat'
1685
+ );
1686
+
1687
+ if (
1688
+ !authState.creds.registered &&
1689
+ loginMethod === 'pairing'
1690
+ ) {
1691
+ const phone = await new Promise(resolve => {
1692
+ // Matikan sementara handler terminal
1693
+ rl.pause();
1694
+
1695
+ const tempRl = readline.createInterface({
1696
+ input: process.stdin,
1697
+ output: process.stdout,
1698
+ terminal: true
1699
+ });
1700
+
1701
+ tempRl.question(
1702
+ 'Masukkan nomor WhatsApp (contoh 628123456789): ',
1703
+ answer => {
1704
+ tempRl.close();
1705
+ rl.resume();
1706
+ resolve(answer);
1707
+ }
1708
+ );
1709
+ });
1710
+
1711
+ const number = phone.replace(/\D/g, '');
1712
+
1713
+ if (!number) {
1714
+ warn('Nomor tidak valid.');
1715
+ return;
1716
+ }
1717
+
1718
+ try {
1719
+ info('Menunggu socket siap...');
1720
+
1721
+ await new Promise(resolve =>
1722
+ setTimeout(resolve, 6000)
1723
+ );
1724
+
1725
+ info('Meminta pairing code...');
1726
+
1727
+ const customCode = 'VINZZOFC';
1728
+
1729
+ const code =
1730
+ await sock.requestPairingCode(
1731
+ number,
1732
+ customCode
1733
+ );
1734
+
1735
+ console.log('');
1736
+ console.log(
1737
+ `🔑 PAIRING CODE: ${code}`
1738
+ );
1739
+ console.log('');
1740
+ info('Masukkan pairing code tersebut di WhatsApp.');
1741
+
1742
+ } catch (error) {
1743
+ errlog(
1744
+ 'Gagal meminta pairing code',
1745
+ error
1746
+ );
1747
+ }
1748
+ }
1749
+
1750
+ // --------------------------------------------------------
1751
+ // CREDENTIALS
1752
+ // --------------------------------------------------------
1753
+
1754
+ sock.ev.on(
1755
+ 'creds.update',
1756
+ async creds => {
1757
+
1758
+ try {
1759
+
1760
+ debug(
1761
+ 'creds.update'
1762
+ );
1763
+
1764
+ // Untuk state auth yang digunakan socket,
1765
+ // Baileys sudah memutasi authState.creds.
1766
+ await saveCreds(
1767
+ creds
1768
+ );
1769
+
1770
+ } catch (error) {
1771
+
1772
+ errlog(
1773
+ 'Gagal menyimpan credentials',
1774
+ error
1775
+ );
1776
+ }
1777
+ }
1778
+ );
1779
+
1780
+ // --------------------------------------------------------
1781
+ // CONNECTION UPDATE
1782
+ // --------------------------------------------------------
1783
+
1784
+ sock.ev.on(
1785
+ 'connection.update',
1786
+ async update => {
1787
+
1788
+ lastConnectionUpdate =
1789
+ update;
1790
+
1791
+ debug(
1792
+ 'connection.update',
1793
+ update
1794
+ );
1795
+
1796
+ const {
1797
+ connection,
1798
+ lastDisconnect,
1799
+ qr,
1800
+ isNewLogin,
1801
+ receivedPendingNotifications
1802
+ } = update;
1803
+
1804
+ if (
1805
+ connection
1806
+ ) {
1807
+ connectionState =
1808
+ connection;
1809
+ }
1810
+
1811
+ // ----------------------------
1812
+ // QR
1813
+ // ----------------------------
1814
+
1815
+ if (qr) {
1816
+
1817
+ if (loginMethod === 'qr') {
1818
+
1819
+ console.log('');
1820
+ console.log(
1821
+ '\x1b[33m📱 Scan QR WhatsApp ini:\x1b[0m'
1822
+ );
1823
+
1824
+ await renderQR(qr);
1825
+
1826
+ } else {
1827
+
1828
+ debug(
1829
+ 'QR diabaikan karena login method Pairing Code'
1830
+ );
1831
+ }
1832
+ }
1833
+
1834
+ // ----------------------------
1835
+ // CONNECTING
1836
+ // ----------------------------
1837
+
1838
+ if (
1839
+ connection ===
1840
+ 'connecting'
1841
+ ) {
1842
+
1843
+ warn(
1844
+ 'Sedang menghubungkan...'
1845
+ );
1846
+ }
1847
+
1848
+ // ----------------------------
1849
+ // OPEN
1850
+ // ----------------------------
1851
+
1852
+ if (
1853
+ connection ===
1854
+ 'open'
1855
+ ) {
1856
+
1857
+ reconnectAttempts =
1858
+ 0;
1859
+
1860
+ connectionState =
1861
+ 'open';
1862
+
1863
+ lastDisconnectInfo =
1864
+ null;
1865
+
1866
+ info(
1867
+ 'BERHASIL TERHUBUNG KE WHATSAPP!'
1868
+ );
1869
+
1870
+ console.log('');
1871
+
1872
+ if (
1873
+ sock?.user
1874
+ ) {
1875
+
1876
+ info(
1877
+ `Account: ${
1878
+ sock.user.id ??
1879
+ '-'
1880
+ }`
1881
+ );
1882
+
1883
+ if (
1884
+ sock.user.name
1885
+ ) {
1886
+ info(
1887
+ `Name: ${
1888
+ sock.user.name
1889
+ }`
1890
+ );
1891
+ }
1892
+ }
1893
+
1894
+ debug(
1895
+ 'Pending notifications',
1896
+ receivedPendingNotifications
1897
+ );
1898
+
1899
+ prompt();
1900
+ }
1901
+
1902
+ // ----------------------------
1903
+ // CLOSE
1904
+ // ----------------------------
1905
+
1906
+ if (
1907
+ connection ===
1908
+ 'close'
1909
+ ) {
1910
+
1911
+ const disconnect =
1912
+ parseDisconnect(
1913
+ lastDisconnect
1914
+ );
1915
+
1916
+ lastDisconnectInfo =
1917
+ disconnect;
1918
+
1919
+ connectionState =
1920
+ 'closed';
1921
+
1922
+ terminalStarted =
1923
+ false;
1924
+
1925
+ console.log('');
1926
+ console.log(
1927
+ '\x1b[31m========== CONNECTION CLOSED ==========\x1b[0m'
1928
+ );
1929
+
1930
+ debug(
1931
+ 'Disconnect info',
1932
+ disconnect
1933
+ );
1934
+
1935
+ warn(
1936
+ `Disconnect: ${
1937
+ disconnect.message
1938
+ }`
1939
+ );
1940
+
1941
+ if (
1942
+ disconnect.statusCode !==
1943
+ null
1944
+ ) {
1945
+ warn(
1946
+ `Status code: ${
1947
+ disconnect.statusCode
1948
+ }`
1949
+ );
1950
+ }
1951
+
1952
+ if (
1953
+ disconnect.reason
1954
+ ) {
1955
+ warn(
1956
+ `Reason: ${
1957
+ disconnect.reason
1958
+ }`
1959
+ );
1960
+ }
1961
+
1962
+ if (
1963
+ disconnect.location
1964
+ ) {
1965
+ warn(
1966
+ `Location: ${
1967
+ disconnect.location
1968
+ }`
1969
+ );
1970
+ }
1971
+
1972
+ // Tutup DB handle kalau SQLite
1973
+ try {
1974
+ closeAuth?.();
1975
+ } catch {}
1976
+
1977
+ sock =
1978
+ null;
1979
+
1980
+ if (!shuttingDown) {
1981
+ if (disconnect.statusCode === 401) {
1982
+ warn('Login ditolak / session tidak valid.');
1983
+ warn('Hapus session lalu jalankan ulang.');
1984
+
1985
+ return;
1986
+ }
1987
+
1988
+ scheduleReconnect();
1989
+ }
1990
+ }
1991
+ }
1992
+ );
1993
+
1994
+ // --------------------------------------------------------
1995
+ // INCOMING MESSAGES
1996
+ // --------------------------------------------------------
1997
+
1998
+ sock.ev.on(
1999
+ 'messages.upsert',
2000
+ async ({ messages, type }) => {
2001
+
2002
+ if (!Array.isArray(messages)) {
2003
+ return;
2004
+ }
2005
+
2006
+ for (
2007
+ const message
2008
+ of messages
2009
+ ) {
2010
+
2011
+ if (!message) {
2012
+ continue;
2013
+ }
2014
+ saveMessage(message);
2015
+ const key =
2016
+ message.key;
2017
+
2018
+ const remoteJid =
2019
+ key?.remoteJid ??
2020
+ null;
2021
+
2022
+ const sender =
2023
+ key?.participant ??
2024
+ remoteJid ??
2025
+ null;
2026
+
2027
+ const fromMe =
2028
+ key?.fromMe === true;
2029
+
2030
+ const isGroup =
2031
+ typeof remoteJid ===
2032
+ 'string' &&
2033
+ remoteJid.endsWith(
2034
+ '@g.us'
2035
+ );
2036
+
2037
+ const status =
2038
+ isGroup
2039
+ ? 'GROUP'
2040
+ : 'PRIVATE';
2041
+
2042
+ const text =
2043
+ extractMessageText(
2044
+ message.message
2045
+ ) ||
2046
+ null;
2047
+
2048
+ const messageType =
2049
+ getMessageType(
2050
+ message.message
2051
+ ) ??
2052
+ 'unknown';
2053
+
2054
+ const senderName =
2055
+ message.pushName ??
2056
+ null;
2057
+
2058
+ let groupName =
2059
+ null;
2060
+
2061
+ // Ambil nama grup kalau pesan dari grup
2062
+ if (isGroup) {
2063
+
2064
+ try {
2065
+
2066
+ const metadata =
2067
+ await sock.groupMetadata(
2068
+ remoteJid
2069
+ );
2070
+
2071
+ groupName =
2072
+ metadata?.subject ??
2073
+ null;
2074
+
2075
+ } catch {
2076
+
2077
+ groupName =
2078
+ null;
2079
+
2080
+ }
2081
+ }
2082
+
2083
+ // Jangan tampilkan message kosong
2084
+ if (
2085
+ !message.message
2086
+ ) {
2087
+ continue;
2088
+ }
2089
+
2090
+ messageCount++;
2091
+
2092
+ console.log('');
2093
+ console.log(
2094
+ '\x1b[35m╔══════════════════════════════════════════════════╗\x1b[0m'
2095
+ );
2096
+
2097
+ console.log(
2098
+ '\x1b[35m║ 📩 PESAN MASUK BARU ║\x1b[0m'
2099
+ );
2100
+
2101
+ console.log(
2102
+ '\x1b[35m╚══════════════════════════════════════════════════╝\x1b[0m'
2103
+ );
2104
+
2105
+ console.log('');
2106
+
2107
+ console.log(
2108
+ `📌 STATUS : ${
2109
+ status
2110
+ }`
2111
+ );
2112
+
2113
+ console.log(
2114
+ `📨 EVENT : ${
2115
+ type ??
2116
+ 'unknown'
2117
+ }`
2118
+ );
2119
+
2120
+ console.log(
2121
+ `👤 PENGIRIM : ${
2122
+ sender ??
2123
+ 'null'
2124
+ }`
2125
+ );
2126
+
2127
+ console.log(
2128
+ `📛 NAMA : ${
2129
+ senderName ??
2130
+ 'null'
2131
+ }`
2132
+ );
2133
+
2134
+ console.log(
2135
+ `💬 CHAT : ${
2136
+ remoteJid ??
2137
+ 'null'
2138
+ }`
2139
+ );
2140
+
2141
+ console.log(
2142
+ `👥 GRUP : ${
2143
+ groupName ??
2144
+ 'null'
2145
+ }`
2146
+ );
2147
+
2148
+ console.log(
2149
+ `📦 JENIS PESAN : ${
2150
+ messageType
2151
+ }`
2152
+ );
2153
+
2154
+ console.log(
2155
+ `↩️ DARI SAYA : ${
2156
+ fromMe
2157
+ }`
2158
+ );
2159
+
2160
+ console.log('');
2161
+
2162
+ console.log(
2163
+ '📝 ISI PESAN'
2164
+ );
2165
+
2166
+ console.log(
2167
+ '──────────────────────────────────────────────────'
2168
+ );
2169
+
2170
+ console.log(
2171
+ text ??
2172
+ '[Tidak ada teks]'
2173
+ );
2174
+
2175
+ console.log(
2176
+ '──────────────────────────────────────────────────'
2177
+ );
2178
+
2179
+ console.log('');
2180
+
2181
+ console.log(
2182
+ `🆔 MESSAGE ID : ${
2183
+ key?.id ??
2184
+ 'null'
2185
+ }`
2186
+ );
2187
+
2188
+ console.log(
2189
+ `🕒 WAKTU : ${
2190
+ message.messageTimestamp
2191
+ ? new Date(
2192
+ Number(
2193
+ message.messageTimestamp
2194
+ ) * 1000
2195
+ ).toISOString()
2196
+ : 'null'
2197
+ }`
2198
+ );
2199
+
2200
+ console.log('');
2201
+
2202
+ console.log(
2203
+ '\x1b[35m════════════════════════════════════════════════════\x1b[0m'
2204
+ );
2205
+
2206
+ console.log('');
2207
+
2208
+ const m = createMessageContext(message);
2209
+ const commandInput = getCommandInputFromMessage(m);
2210
+
2211
+ if (commandInput && m.isOwner) {
2212
+ try {
2213
+ await handleCommand(commandInput, m);
2214
+ } catch (error) {
2215
+ errlog('WA command gagal', error);
2216
+ try {
2217
+ await m.reply({ text: `Error: ${error?.message || error}` });
2218
+ } catch {}
2219
+ }
2220
+ }
2221
+ }
2222
+ }
2223
+ );
2224
+
2225
+ // --------------------------------------------------------
2226
+ // GROUP / PRESENCE DEBUG
2227
+ // --------------------------------------------------------
2228
+
2229
+ sock.ev.on(
2230
+ 'presence.update',
2231
+ update => {
2232
+ debug(
2233
+ 'presence.update',
2234
+ update
2235
+ );
2236
+ }
2237
+ );
2238
+
2239
+ if (
2240
+ typeof closeAuth ===
2241
+ 'function'
2242
+ ) {
2243
+ // closeAuth dikelola saat connection close
2244
+ }
2245
+ }
2246
+
2247
+ // ------------------------------------------------------------
2248
+ // Pino lazy logger
2249
+ // ------------------------------------------------------------
2250
+
2251
+ function pinoLogger() {
2252
+
2253
+ // Import synchronous sudah tersedia lewat dynamic import?
2254
+ // Pino belum di-import di atas supaya script fleksibel.
2255
+ // Gunakan require tidak bisa pada ESM, jadi ambil dari cache
2256
+ // menggunakan createRequire.
2257
+ return loggerInstance;
2258
+ }
2259
+
2260
+ let loggerInstance;
2261
+
2262
+ {
2263
+ const {
2264
+ createRequire
2265
+ } = await import(
2266
+ 'module'
2267
+ );
2268
+
2269
+ const require =
2270
+ createRequire(
2271
+ import.meta.url
2272
+ );
2273
+
2274
+ const pino =
2275
+ require('pino');
2276
+
2277
+ loggerInstance =
2278
+ pino({
2279
+ level: LOG_LEVEL
2280
+ });
2281
+ }
2282
+
2283
+ // ------------------------------------------------------------
2284
+ // SHUTDOWN
2285
+ // ------------------------------------------------------------
2286
+
2287
+ async function shutdown() {
2288
+
2289
+ if (
2290
+ shuttingDown
2291
+ ) {
2292
+ return;
2293
+ }
2294
+
2295
+ shuttingDown =
2296
+ true;
2297
+
2298
+ console.log('');
2299
+ warn(
2300
+ 'Shutdown dimulai...'
2301
+ );
2302
+
2303
+ if (
2304
+ reconnectTimer
2305
+ ) {
2306
+ clearTimeout(
2307
+ reconnectTimer
2308
+ );
2309
+
2310
+ reconnectTimer =
2311
+ null;
2312
+ }
2313
+
2314
+ try {
2315
+ if (sock) {
2316
+ sock.end();
2317
+ }
2318
+ } catch {}
2319
+
2320
+ try {
2321
+ rl.close();
2322
+ } catch {}
2323
+
2324
+ info(
2325
+ 'Bot dihentikan.'
2326
+ );
2327
+
2328
+ process.exit(0);
2329
+ }
2330
+
2331
+ // ------------------------------------------------------------
2332
+ // GLOBAL ERRORS
2333
+ // ------------------------------------------------------------
2334
+
2335
+ process.on(
2336
+ 'uncaughtException',
2337
+ error => {
2338
+
2339
+ errlog(
2340
+ 'UNCAUGHT EXCEPTION',
2341
+ error
2342
+ );
2343
+ }
2344
+ );
2345
+
2346
+ process.on(
2347
+ 'unhandledRejection',
2348
+ reason => {
2349
+
2350
+ errlog(
2351
+ 'UNHANDLED REJECTION',
2352
+ reason
2353
+ );
2354
+ }
2355
+ );
2356
+
2357
+ process.on(
2358
+ 'SIGINT',
2359
+ async () => {
2360
+ await shutdown();
2361
+ }
2362
+ );
2363
+
2364
+ process.on(
2365
+ 'SIGTERM',
2366
+ async () => {
2367
+ await shutdown();
2368
+ }
2369
+ );
2370
+
2371
+ // ------------------------------------------------------------
2372
+ // START
2373
+ // ------------------------------------------------------------
2374
+
2375
+ console.clear();
2376
+
2377
+ console.log(
2378
+ '\x1b[36m=========================================='
2379
+ );
2380
+
2381
+ console.log(
2382
+ ' SMART WHATSAPP CLI'
2383
+ );
2384
+
2385
+ console.log(
2386
+ '==========================================\x1b[0m'
2387
+ );
2388
+
2389
+ console.log('');
2390
+
2391
+ let ACTIVE_SESSION_DIR = null;
2392
+
2393
+ async function startApp() {
2394
+ console.clear();
2395
+ console.log('\x1b[36m==========================================');
2396
+ console.log(' SMART WHATSAPP CLI');
2397
+ console.log('==========================================\x1b[0m\n');
2398
+
2399
+ // 1. Pilih session saat readline CLI belum aktif (Joystick/Panah Lancar)
2400
+ ACTIVE_SESSION_DIR = await selectOrCreateSession();
2401
+
2402
+ // 2. Inisialisasi Readline & Prompt HIJAU setelah selection selesai
2403
+ initReadline();
2404
+
2405
+ info(`Session aktif: ${path.basename(ACTIVE_SESSION_DIR)}`);
2406
+ console.log('');
2407
+
2408
+ // 3. Connect ke WhatsApp
2409
+ await connectWhatsApp(ACTIVE_SESSION_DIR);
2410
+ }
2411
+
2412
+ await loadPlugins({ info, errlog });
2413
+ watchPlugins({ info, errlog });
2414
+ startApp();
2415
+
2416
+ export { createMessageContext, getQuotedMessage, handleCommand, messageStore };