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/index2.js ADDED
@@ -0,0 +1,3611 @@
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 } from "./func.js";
20
+ import { pathToFileURL } from 'url';
21
+
22
+ // ------------------------------------------------------------
23
+ // BAILEYS - dynamic import supaya export fork lebih fleksibel
24
+ // ------------------------------------------------------------
25
+
26
+ const baileys = await import('@vkazee/baileys');
27
+
28
+ const makeWASocket =
29
+ baileys.default ??
30
+ baileys.makeWASocket;
31
+
32
+ const delay = baileys.delay;
33
+
34
+ const useMultiFileAuthState =
35
+ baileys.useMultiFileAuthState;
36
+
37
+ const makeCacheableSignalKeyStore =
38
+ baileys.makeCacheableSignalKeyStore;
39
+
40
+ const fetchLatestWaWebVersion =
41
+ baileys.fetchLatestWaWebVersion;
42
+
43
+ const Browsers =
44
+ baileys.Browsers;
45
+
46
+ const {
47
+ BufferJSON,
48
+ initAuthCreds
49
+ } = baileys;
50
+
51
+ // ------------------------------------------------------------
52
+ // CONFIG
53
+ // ------------------------------------------------------------
54
+
55
+ const SESSION_DIR = path.resolve('./session');
56
+
57
+ const LOG_LEVEL =
58
+ process.env.LOG_LEVEL || 'info';
59
+
60
+ const BASE_RECONNECT_DELAY = 3000;
61
+ const MAX_RECONNECT_DELAY = 30000;
62
+
63
+ const ENABLE_DYNAMIC_VERSION = true;
64
+
65
+ // ------------------------------------------------------------
66
+ // GLOBAL STATE
67
+ // ------------------------------------------------------------
68
+ global.owner = "6285185667890";
69
+ global.ownerr = "6285185667890@s.whatsapp.net";
70
+ let loginMethod = null;
71
+ let sock = null;
72
+
73
+ let sessionType = 'unknown';
74
+ let sessionPath = null;
75
+
76
+ let connectionState = 'closed';
77
+
78
+ let reconnectTimer = null;
79
+ let reconnectAttempts = 0;
80
+
81
+ let shuttingDown = false;
82
+ let terminalStarted = false;
83
+ let lastDisconnectInfo = null;
84
+
85
+ let currentWaVersion = null;
86
+ let lastDisconnect = null;
87
+ let lastConnectionUpdate = null;
88
+
89
+ let messageCount = 0;
90
+
91
+ // ============================================================
92
+ // MESSAGE CACHE
93
+ // ============================================================
94
+
95
+ function getMessageType(message) {
96
+ if (!message) {
97
+ return 'unknown';
98
+ }
99
+
100
+ const keys =
101
+ Object.keys(
102
+ message
103
+ );
104
+
105
+ return (
106
+ keys[0] ||
107
+ 'unknown'
108
+ );
109
+ }
110
+
111
+
112
+ const messageStore = new Map();
113
+ const MAX_MESSAGES = 2000;
114
+
115
+ function saveMessage(message) {
116
+
117
+ if (
118
+ !message?.key?.id ||
119
+ !message?.key?.remoteJid
120
+ ) {
121
+ return;
122
+ }
123
+
124
+ const key =
125
+ `${message.key.remoteJid}:${message.key.id}`;
126
+
127
+ messageStore.set(
128
+ key,
129
+ message
130
+ );
131
+
132
+ // Hapus pesan paling lama jika cache penuh
133
+ if (
134
+ messageStore.size >
135
+ MAX_MESSAGES
136
+ ) {
137
+ const oldestKey =
138
+ messageStore
139
+ .keys()
140
+ .next()
141
+ .value;
142
+
143
+ messageStore.delete(
144
+ oldestKey
145
+ );
146
+ }
147
+ }
148
+
149
+
150
+ async function chooseLoginMethod() {
151
+ const response = await prompts({
152
+ type: 'select',
153
+ name: 'method',
154
+ message: 'Pilih method login:',
155
+ choices: [
156
+ {
157
+ title: 'Scan QR Code',
158
+ value: 'qr'
159
+ },
160
+ {
161
+ title: 'Pairing Code',
162
+ value: 'pairing'
163
+ }
164
+ ],
165
+ initial: 0
166
+ });
167
+
168
+ if (!response.method) {
169
+ console.log('\nOperasi dibatalkan.');
170
+ process.exit(0);
171
+ }
172
+
173
+ return response.method;
174
+ }
175
+
176
+ // ------------------------------------------------------------
177
+ // READLINE
178
+ // ------------------------------------------------------------
179
+
180
+ let rl = null;
181
+
182
+ function initReadline() {
183
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
184
+ process.stdin.setRawMode(false);
185
+ process.stdin.resume();
186
+ }
187
+
188
+ if (rl) {
189
+ try { rl.close(); } catch {}
190
+ }
191
+
192
+ rl = readline.createInterface({
193
+ input: process.stdin,
194
+ output: process.stdout,
195
+ terminal: true
196
+ });
197
+
198
+ rl.setPrompt('\x1b[32mroot@wa-cli\x1b[0m:\x1b[34m~\x1b[0m$ ');
199
+
200
+ rl.on('line', async input => {
201
+ if (shuttingDown || (loginMethod === 'pairing' && connectionState !== 'open')) {
202
+ return;
203
+ }
204
+
205
+ try {
206
+ await handleCommand(input);
207
+ } catch (err) {
208
+ console.error(err);
209
+ }
210
+
211
+ if (!shuttingDown && connectionState === 'open') {
212
+ rl.prompt();
213
+ } else {
214
+ terminalStarted = false;
215
+ }
216
+ });
217
+ }
218
+
219
+
220
+ // ------------------------------------------------------------
221
+ // COLORS / LOGGING
222
+ // ------------------------------------------------------------
223
+
224
+ function now() {
225
+ return new Date().toISOString();
226
+ }
227
+
228
+ function debug(label, value = '') {
229
+ const prefix =
230
+ `\x1b[36m[${now()}] [DEBUG] ${label}\x1b[0m`;
231
+
232
+ if (
233
+ typeof value === 'object' &&
234
+ value !== null
235
+ ) {
236
+ console.log(
237
+ prefix,
238
+ util.inspect(value, {
239
+ depth: 10,
240
+ colors: true,
241
+ compact: false
242
+ })
243
+ );
244
+ } else {
245
+ console.log(prefix, value);
246
+ }
247
+ }
248
+
249
+ function info(message) {
250
+ console.log(
251
+ `\x1b[32m[+] ${message}\x1b[0m`
252
+ );
253
+ }
254
+
255
+ function warn(message) {
256
+ console.log(
257
+ `\x1b[33m[!] ${message}\x1b[0m`
258
+ );
259
+ }
260
+
261
+ function errlog(message, error = null) {
262
+ console.log(
263
+ `\x1b[31m[ERROR] ${message}\x1b[0m`
264
+ );
265
+
266
+ if (error) {
267
+ console.error(
268
+ util.inspect(error, {
269
+ depth: 10,
270
+ colors: true,
271
+ compact: false
272
+ })
273
+ );
274
+ }
275
+ }
276
+
277
+ // ------------------------------------------------------------
278
+ // SESSION DETECTION
279
+ // ------------------------------------------------------------
280
+
281
+ function fileExists(file) {
282
+ try {
283
+ return fs.existsSync(file);
284
+ } catch {
285
+ return false;
286
+ }
287
+ }
288
+
289
+ function isDirectory(dir) {
290
+ try {
291
+ return fs.statSync(dir).isDirectory();
292
+ } catch {
293
+ return false;
294
+ }
295
+ }
296
+
297
+ function detectSQLiteAuth(dbPath) {
298
+ let db;
299
+
300
+ try {
301
+ db = new Database(
302
+ dbPath,
303
+ {
304
+ readonly: true,
305
+ fileMustExist: true
306
+ }
307
+ );
308
+
309
+ const rows =
310
+ db.prepare(`
311
+ SELECT name
312
+ FROM sqlite_master
313
+ WHERE type = 'table'
314
+ `).all();
315
+
316
+ const tables =
317
+ rows.map(x => x.name);
318
+
319
+ const hasBaileysState =
320
+ tables.includes('baileys_state');
321
+
322
+ let registered = false;
323
+
324
+ if (hasBaileysState) {
325
+ const row =
326
+ db.prepare(`
327
+ SELECT value
328
+ FROM baileys_state
329
+ WHERE key = 'creds'
330
+ LIMIT 1
331
+ `).get();
332
+
333
+ if (row?.value) {
334
+ try {
335
+ const creds =
336
+ JSON.parse(
337
+ Buffer.from(
338
+ row.value
339
+ ).toString(),
340
+ BufferJSON?.reviver
341
+ );
342
+
343
+ registered =
344
+ creds?.registered === true;
345
+ } catch {
346
+ registered = false;
347
+ }
348
+ }
349
+ }
350
+
351
+ return {
352
+ valid: hasBaileysState,
353
+ registered,
354
+ tables
355
+ };
356
+
357
+ } catch (error) {
358
+ debug(
359
+ `SQLite check gagal: ${dbPath}`,
360
+ error.message
361
+ );
362
+
363
+ return {
364
+ valid: false,
365
+ registered: false,
366
+ tables: []
367
+ };
368
+
369
+ } finally {
370
+ try {
371
+ db?.close();
372
+ } catch {}
373
+ }
374
+ }
375
+
376
+ function detectMultiFileAuth(dir) {
377
+ if (!isDirectory(dir)) {
378
+ return {
379
+ valid: false,
380
+ registered: false,
381
+ files: []
382
+ };
383
+ }
384
+
385
+ const files =
386
+ fs.readdirSync(dir);
387
+
388
+ const credsPath =
389
+ path.join(dir, 'creds.json');
390
+
391
+ if (!fileExists(credsPath)) {
392
+ return {
393
+ valid: false,
394
+ registered: false,
395
+ files
396
+ };
397
+ }
398
+
399
+ try {
400
+ const creds =
401
+ JSON.parse(
402
+ fs.readFileSync(
403
+ credsPath,
404
+ 'utf8'
405
+ )
406
+ );
407
+
408
+ return {
409
+ valid: true,
410
+ registered:
411
+ creds?.registered === true,
412
+ files
413
+ };
414
+
415
+ } catch {
416
+ return {
417
+ valid: false,
418
+ registered: false,
419
+ files
420
+ };
421
+ }
422
+ }
423
+
424
+ // Ubah signature fungsi menjadi menerima parameter dir
425
+ function detectSession(targetDir = SESSION_DIR) {
426
+ const result = {
427
+ type: 'none',
428
+ path: null,
429
+ registered: false,
430
+ details: {}
431
+ };
432
+
433
+ if (!isDirectory(targetDir)) {
434
+ return result;
435
+ }
436
+
437
+ const entries = fs.readdirSync(targetDir, { withFileTypes: true });
438
+
439
+ const sqliteCandidates = entries
440
+ .filter(entry => entry.isFile() && (entry.name.endsWith('.db') || entry.name.endsWith('.sqlite') || entry.name.endsWith('.sqlite3')))
441
+ .map(entry => path.join(targetDir, entry.name));
442
+
443
+ const sqliteResults = sqliteCandidates.map(dbPath => ({
444
+ dbPath,
445
+ ...detectSQLiteAuth(dbPath)
446
+ }));
447
+
448
+ const registeredSQLite = sqliteResults.find(x => x.valid && x.registered);
449
+ if (registeredSQLite) {
450
+ return { type: 'sqlite', path: registeredSQLite.dbPath, registered: true, details: registeredSQLite };
451
+ }
452
+
453
+ const validSQLite = sqliteResults.find(x => x.valid);
454
+ if (validSQLite) {
455
+ return { type: 'sqlite', path: validSQLite.dbPath, registered: validSQLite.registered, details: validSQLite };
456
+ }
457
+
458
+ const multi = detectMultiFileAuth(targetDir);
459
+ if (multi.valid) {
460
+ return { type: 'multifile', path: targetDir, registered: multi.registered, details: multi };
461
+ }
462
+
463
+ return result;
464
+ }
465
+
466
+
467
+ // ------------------------------------------------------------
468
+ // SQLITE AUTH LOADER
469
+ // Kompatibel dengan struktur:
470
+ // baileys_state(key TEXT PRIMARY KEY, value BLOB)
471
+ // ------------------------------------------------------------
472
+
473
+ async function useSQLiteAuthState(dbPath) {
474
+ const db =
475
+ new Database(dbPath);
476
+
477
+ db.pragma('journal_mode = WAL');
478
+
479
+ db.prepare(`
480
+ CREATE TABLE IF NOT EXISTS baileys_state (
481
+ key TEXT PRIMARY KEY,
482
+ value BLOB
483
+ )
484
+ `).run();
485
+
486
+ function load(key) {
487
+ const row =
488
+ db.prepare(`
489
+ SELECT value
490
+ FROM baileys_state
491
+ WHERE key = ?
492
+ LIMIT 1
493
+ `).get(key);
494
+
495
+ if (!row) {
496
+ return null;
497
+ }
498
+
499
+ try {
500
+ return JSON.parse(
501
+ Buffer.from(
502
+ row.value
503
+ ).toString(),
504
+ BufferJSON?.reviver
505
+ );
506
+ } catch {
507
+ return null;
508
+ }
509
+ }
510
+
511
+ function save(key, data) {
512
+ const json =
513
+ JSON.stringify(
514
+ data,
515
+ BufferJSON?.replacer
516
+ );
517
+
518
+ const buffer =
519
+ Buffer.from(
520
+ json,
521
+ 'utf8'
522
+ );
523
+
524
+ db.prepare(`
525
+ INSERT OR REPLACE INTO baileys_state
526
+ (key, value)
527
+ VALUES (?, ?)
528
+ `).run(
529
+ key,
530
+ buffer
531
+ );
532
+ }
533
+
534
+ function remove(key) {
535
+ db.prepare(`
536
+ DELETE FROM baileys_state
537
+ WHERE key = ?
538
+ `).run(key);
539
+ }
540
+
541
+ const creds =
542
+ load('creds') ||
543
+ initAuthCreds();
544
+
545
+ const keys = {};
546
+
547
+ const categories = [
548
+ 'pre-key',
549
+ 'session',
550
+ 'sender-key',
551
+ 'app-state-sync-key',
552
+ 'app-state-sync-version',
553
+ 'lid-mapping',
554
+ 'device-list'
555
+ ];
556
+
557
+ for (
558
+ const category of categories
559
+ ) {
560
+ keys[category] = {};
561
+
562
+ const rows =
563
+ db.prepare(`
564
+ SELECT key, value
565
+ FROM baileys_state
566
+ WHERE key LIKE ?
567
+ `).all(
568
+ `${category}:%`
569
+ );
570
+
571
+ for (
572
+ const row of rows
573
+ ) {
574
+ try {
575
+ const id =
576
+ row.key.slice(
577
+ category.length + 1
578
+ );
579
+
580
+ keys[category][id] =
581
+ JSON.parse(
582
+ Buffer.from(
583
+ row.value
584
+ ).toString(),
585
+ BufferJSON?.reviver
586
+ );
587
+
588
+ } catch {
589
+ // skip corrupt row
590
+ }
591
+ }
592
+ }
593
+
594
+ const state = {
595
+ creds,
596
+
597
+ keys: {
598
+
599
+ get: async (
600
+ type,
601
+ ids
602
+ ) => {
603
+
604
+ const data = {};
605
+
606
+ for (
607
+ const id of ids
608
+ ) {
609
+ const value =
610
+ load(
611
+ `${type}:${id}`
612
+ );
613
+
614
+ if (
615
+ value !== null &&
616
+ value !== undefined
617
+ ) {
618
+ data[id] = value;
619
+ }
620
+ }
621
+
622
+ return data;
623
+ },
624
+
625
+ set: async (
626
+ data
627
+ ) => {
628
+
629
+ for (
630
+ const category
631
+ in data
632
+ ) {
633
+
634
+ for (
635
+ const id
636
+ in data[category]
637
+ ) {
638
+
639
+ const value =
640
+ data[
641
+ category
642
+ ][id];
643
+
644
+ save(
645
+ `${category}:${id}`,
646
+ value
647
+ );
648
+ }
649
+ }
650
+ }
651
+ }
652
+ };
653
+
654
+ async function saveCreds() {
655
+ save(
656
+ 'creds',
657
+ creds
658
+ );
659
+ }
660
+
661
+ // Persist berkala
662
+ const interval =
663
+ setInterval(
664
+ () => {
665
+ try {
666
+ save(
667
+ 'creds',
668
+ creds
669
+ );
670
+ } catch {}
671
+ },
672
+ 30000
673
+ );
674
+
675
+ interval.unref?.();
676
+
677
+ return {
678
+ state,
679
+ saveCreds,
680
+ close: () => {
681
+ try {
682
+ clearInterval(
683
+ interval
684
+ );
685
+ } catch {}
686
+
687
+ try {
688
+ db.close();
689
+ } catch {}
690
+ },
691
+
692
+ // util internal
693
+ _db: db,
694
+ _remove: remove
695
+ };
696
+ }
697
+
698
+ // ------------------------------------------------------------
699
+ // WEB VERSION
700
+ // ------------------------------------------------------------
701
+
702
+ async function getLatestVersion() {
703
+ if (
704
+ !ENABLE_DYNAMIC_VERSION ||
705
+ typeof fetchLatestWaWebVersion !==
706
+ 'function'
707
+ ) {
708
+ debug(
709
+ 'fetchLatestWaWebVersion tidak tersedia'
710
+ );
711
+
712
+ return null;
713
+ }
714
+
715
+ try {
716
+ const result =
717
+ await fetchLatestWaWebVersion();
718
+
719
+ debug(
720
+ 'WA Web version result',
721
+ result
722
+ );
723
+
724
+ if (
725
+ result?.version &&
726
+ Array.isArray(
727
+ result.version
728
+ )
729
+ ) {
730
+
731
+ currentWaVersion =
732
+ result.version;
733
+
734
+ info(
735
+ `WA Web version: ${
736
+ result.version.join('.')
737
+ }`
738
+ );
739
+
740
+ return result.version;
741
+ }
742
+
743
+ } catch (error) {
744
+ warn(
745
+ `Gagal mengambil WA Web version: ${
746
+ error.message
747
+ }`
748
+ );
749
+ }
750
+
751
+ return null;
752
+ }
753
+
754
+ // ------------------------------------------------------------
755
+ // QR OPTIONAL RENDER
756
+ // Tidak wajib install qrcode-terminal.
757
+ // ------------------------------------------------------------
758
+
759
+ async function renderQR(qr) {
760
+ try {
761
+ const mod =
762
+ await import(
763
+ 'qrcode-terminal'
764
+ );
765
+
766
+ const qrTerminal =
767
+ mod.default ??
768
+ mod;
769
+
770
+ if (
771
+ typeof qrTerminal.generate ===
772
+ 'function'
773
+ ) {
774
+ qrTerminal.generate(
775
+ qr,
776
+ {
777
+ small: true
778
+ }
779
+ );
780
+
781
+ return true;
782
+ }
783
+
784
+ } catch {
785
+ // package tidak terpasang
786
+ }
787
+
788
+ console.log('');
789
+ console.log(
790
+ '[QR] qrcode-terminal tidak terpasang.'
791
+ );
792
+ console.log(
793
+ '[QR] QR tersedia pada event connection.update.'
794
+ );
795
+ console.log('');
796
+
797
+ return false;
798
+ }
799
+
800
+ // ------------------------------------------------------------
801
+ // MESSAGE TEXT EXTRACTION
802
+ // ------------------------------------------------------------
803
+
804
+ function extractMessageText(message) {
805
+ if (!message) {
806
+ return '';
807
+ }
808
+
809
+ return (
810
+ message.conversation ||
811
+
812
+ message.extendedTextMessage
813
+ ?.text ||
814
+
815
+ message.imageMessage
816
+ ?.caption ||
817
+
818
+ message.videoMessage
819
+ ?.caption ||
820
+
821
+ message.documentMessage
822
+ ?.caption ||
823
+
824
+ message.buttonsResponseMessage
825
+ ?.selectedDisplayText ||
826
+
827
+ message.listResponseMessage
828
+ ?.title ||
829
+
830
+ message.templateButtonReplyMessage
831
+ ?.selectedDisplayText ||
832
+
833
+ message.interactiveResponseMessage
834
+ ?.body
835
+ ?.text ||
836
+
837
+ ''
838
+ );
839
+ }
840
+
841
+ function setupCLIInput() {
842
+ if (process.stdin.isTTY && process.stdin.setRawMode) {
843
+ process.stdin.setRawMode(false);
844
+ }
845
+
846
+ if (rl) rl.close();
847
+
848
+ rl = readline.createInterface({
849
+ input: process.stdin,
850
+ output: process.stdout,
851
+ terminal: true
852
+ });
853
+
854
+ // SET PROMPT CUSTOM DENGAN WARNA HIJAU ANSI (\x1b[32m)
855
+ const greenPrompt = '\x1b[32mroot@bot-wa:~$\x1b[0m ';
856
+ rl.setPrompt(greenPrompt);
857
+
858
+ rl.on('line', async (line) => {
859
+ const input = line.trim();
860
+ if (input) {
861
+ await handleCommand(input);
862
+ }
863
+ // Munculkan lagi prompt hijau setelah command selesai dieksekusi
864
+ rl.prompt();
865
+ });
866
+
867
+ // Tampilkan prompt untuk pertama kali
868
+ rl.prompt();
869
+ }
870
+
871
+
872
+ // ------------------------------------------------------------
873
+ // JID HELPER
874
+ // ------------------------------------------------------------
875
+
876
+ function normalizeJid(input) {
877
+ if (!input) {
878
+ return null;
879
+ }
880
+
881
+ if (
882
+ input.includes('@')
883
+ ) {
884
+ return input;
885
+ }
886
+
887
+ const clean =
888
+ input
889
+ .replace(
890
+ /[^\d]/g,
891
+ ''
892
+ );
893
+
894
+ if (!clean) {
895
+ return null;
896
+ }
897
+
898
+ return (
899
+ `${clean}@s.whatsapp.net`
900
+ );
901
+ }
902
+
903
+ // ------------------------------------------------------------
904
+ // DISCONNECT
905
+ // ------------------------------------------------------------
906
+
907
+ function parseDisconnect(
908
+ lastDisconnect
909
+ ) {
910
+
911
+ const error =
912
+ lastDisconnect?.error;
913
+
914
+ return {
915
+ message:
916
+ error?.message ||
917
+ 'Unknown error',
918
+
919
+ statusCode:
920
+ error?.output?.statusCode ??
921
+ error?.statusCode ??
922
+ error?.data?.statusCode ??
923
+ null,
924
+
925
+ reason:
926
+ error?.data?.reason ??
927
+ null,
928
+
929
+ location:
930
+ error?.data?.location ??
931
+ null,
932
+
933
+ name:
934
+ error?.name ??
935
+ null,
936
+
937
+ stack:
938
+ error?.stack ??
939
+ null
940
+ };
941
+ }
942
+
943
+ // ------------------------------------------------------------
944
+ // RECONNECT
945
+ // ------------------------------------------------------------
946
+
947
+ function reconnectDelay() {
948
+ const delay =
949
+ BASE_RECONNECT_DELAY *
950
+ Math.pow(
951
+ 2,
952
+ Math.min(
953
+ reconnectAttempts,
954
+ 4
955
+ )
956
+ );
957
+
958
+ return Math.min(
959
+ delay,
960
+ MAX_RECONNECT_DELAY
961
+ );
962
+ }
963
+
964
+ function scheduleReconnect() {
965
+ if (
966
+ shuttingDown
967
+ ) {
968
+ return;
969
+ }
970
+
971
+ if (
972
+ reconnectTimer
973
+ ) {
974
+ return;
975
+ }
976
+
977
+ reconnectAttempts++;
978
+
979
+ const delay =
980
+ reconnectDelay();
981
+
982
+ warn(
983
+ `Reconnect #${reconnectAttempts} dalam ${delay} ms`
984
+ );
985
+
986
+ reconnectTimer =
987
+ setTimeout(
988
+ async () => {
989
+
990
+ reconnectTimer =
991
+ null;
992
+
993
+ try {
994
+ await connectWhatsApp(ACTIVE_SESSION_DIR);
995
+ } catch (error) {
996
+ errlog(
997
+ 'Reconnect error',
998
+ error
999
+ );
1000
+
1001
+ scheduleReconnect();
1002
+ }
1003
+ },
1004
+ delay
1005
+ );
1006
+ }
1007
+
1008
+ // ------------------------------------------------------------
1009
+ // TERMINAL PROMPT
1010
+ // ------------------------------------------------------------
1011
+
1012
+ function prompt() {
1013
+ if (terminalStarted || shuttingDown) {
1014
+ return;
1015
+ }
1016
+
1017
+ if (connectionState !== 'open') {
1018
+ return;
1019
+ }
1020
+
1021
+ terminalStarted = true;
1022
+
1023
+ console.log('');
1024
+ console.log('Ketik "help" untuk daftar command.');
1025
+ console.log('');
1026
+
1027
+ ask();
1028
+ }
1029
+
1030
+ function ask() {
1031
+ if (shuttingDown || connectionState !== 'open' || !rl) {
1032
+ terminalStarted = false;
1033
+ return;
1034
+ }
1035
+
1036
+ rl.prompt();
1037
+ }
1038
+
1039
+
1040
+ /* rl.on('line', async input => {
1041
+ if (
1042
+ shuttingDown ||
1043
+ loginMethod === 'pairing' &&
1044
+ connectionState !== 'open'
1045
+ ) {
1046
+ return;
1047
+ }
1048
+
1049
+ try {
1050
+ await handleCommand(input);
1051
+ } catch (err) {
1052
+ console.error(err);
1053
+ }
1054
+
1055
+ if (
1056
+ !shuttingDown &&
1057
+ connectionState === 'open'
1058
+ ) {
1059
+ rl.prompt();
1060
+ } else {
1061
+ terminalStarted = false;
1062
+ }
1063
+ }); */
1064
+
1065
+
1066
+ // ------------------------------------------------------------
1067
+ // COMMANDS
1068
+ // ------------------------------------------------------------
1069
+
1070
+ async function handleCommand(
1071
+ input
1072
+ ) {
1073
+
1074
+ const trimmed =
1075
+ input.trim();
1076
+
1077
+ if (!trimmed) {
1078
+ return;
1079
+ }
1080
+
1081
+ const args =
1082
+ trimmed.split(
1083
+ /\s+/g
1084
+ );
1085
+
1086
+ const command =
1087
+ args.shift()
1088
+ ?.toLowerCase();
1089
+
1090
+ const text =
1091
+ args.join(' ');
1092
+
1093
+ try {
1094
+
1095
+ switch (command) {
1096
+
1097
+ // ================================================
1098
+ // HELP (REALTIME AUTOMATIC)
1099
+ // ================================================
1100
+
1101
+ case 'help': {
1102
+ const commandsList = getDynamicHelpList();
1103
+
1104
+ console.log('\n\x1b[36m================ COMMAND LIST (REALTIME) ================\x1b[0m\n');
1105
+
1106
+ if (commandsList.length > 0) {
1107
+ // Tampilkan per 3-4 kolom agar rapi di terminal
1108
+ let formatted = '';
1109
+ commandsList.forEach((cmd, idx) => {
1110
+ formatted += ` • ${cmd.padEnd(12)}`;
1111
+ if ((idx + 1) % 3 === 0) formatted += '\n';
1112
+ });
1113
+ console.log(formatted);
1114
+ } else {
1115
+ console.log(' Gagal membaca daftar command.');
1116
+ }
1117
+
1118
+ console.log('\n\x1b[36m=========================================================\x1b[0m\n');
1119
+ break;
1120
+ }
1121
+
1122
+
1123
+
1124
+ // ================================================
1125
+ // SEND
1126
+ // ================================================
1127
+
1128
+ case 'send': {
1129
+
1130
+ if (
1131
+ args.length <
1132
+ 2
1133
+ ) {
1134
+ console.log(
1135
+ 'Gunakan: send <nomor> <pesan>'
1136
+ );
1137
+
1138
+ break;
1139
+ }
1140
+
1141
+ const target =
1142
+ normalizeJid(
1143
+ args.shift()
1144
+ );
1145
+
1146
+ const message =
1147
+ args.join(' ');
1148
+
1149
+ if (!target) {
1150
+ warn(
1151
+ 'Nomor/JID tidak valid.'
1152
+ );
1153
+
1154
+ break;
1155
+ }
1156
+
1157
+ debug(
1158
+ 'sendMessage',
1159
+ {
1160
+ target,
1161
+ message
1162
+ }
1163
+ );
1164
+
1165
+ await sock.sendMessage(
1166
+ target,
1167
+ {
1168
+ text: message
1169
+ }
1170
+ );
1171
+
1172
+ info(
1173
+ `Pesan terkirim ke ${target}`
1174
+ );
1175
+
1176
+ break;
1177
+ }
1178
+
1179
+ // ================================================
1180
+ // ME / PROFILE
1181
+ // ================================================
1182
+
1183
+ case 'me':
1184
+ case 'profile': {
1185
+
1186
+ console.log('');
1187
+ console.log(
1188
+ '\x1b[36m========== BOT PROFILE ==========\x1b[0m'
1189
+ );
1190
+
1191
+ debug(
1192
+ 'sock.user',
1193
+ sock?.user
1194
+ );
1195
+
1196
+ console.log(
1197
+ `ID : ${
1198
+ sock?.user?.id ??
1199
+ '-'
1200
+ }`
1201
+ );
1202
+
1203
+ console.log(
1204
+ `Name : ${
1205
+ sock?.user?.name ??
1206
+ '-'
1207
+ }`
1208
+ );
1209
+
1210
+ console.log(
1211
+ `Verified: ${
1212
+ sock?.user?.verifiedName ??
1213
+ '-'
1214
+ }`
1215
+ );
1216
+
1217
+ console.log(
1218
+ `Status : ${
1219
+ connectionState
1220
+ }`
1221
+ );
1222
+
1223
+ console.log(
1224
+ `Session : ${
1225
+ sessionType
1226
+ }`
1227
+ );
1228
+
1229
+ console.log(
1230
+ '\x1b[36m=================================\x1b[0m'
1231
+ );
1232
+
1233
+ break;
1234
+ }
1235
+
1236
+ // ================================================
1237
+ // STATUS
1238
+ // ================================================
1239
+
1240
+ case 'status': {
1241
+
1242
+ const memory =
1243
+ process.memoryUsage();
1244
+
1245
+ console.log('');
1246
+ console.log(
1247
+ '\x1b[36m========== STATUS ==========\x1b[0m'
1248
+ );
1249
+
1250
+ console.log(
1251
+ `Node : ${process.version}`
1252
+ );
1253
+
1254
+ console.log(
1255
+ `Platform : ${process.platform}`
1256
+ );
1257
+
1258
+ console.log(
1259
+ `Architecture: ${process.arch}`
1260
+ );
1261
+
1262
+ console.log(
1263
+ `PID : ${process.pid}`
1264
+ );
1265
+
1266
+ console.log(
1267
+ `Connection : ${connectionState}`
1268
+ );
1269
+
1270
+ console.log(
1271
+ `Session : ${sessionType}`
1272
+ );
1273
+
1274
+ console.log(
1275
+ `Session path: ${sessionPath ?? '-'}`
1276
+ );
1277
+
1278
+ console.log(
1279
+ `Reconnect : ${reconnectAttempts}`
1280
+ );
1281
+
1282
+ console.log(
1283
+ `Messages : ${messageCount}`
1284
+ );
1285
+
1286
+ console.log(
1287
+ `WA version : ${
1288
+ currentWaVersion
1289
+ ? currentWaVersion.join('.')
1290
+ : '-'
1291
+ }`
1292
+ );
1293
+
1294
+ console.log(
1295
+ `RSS : ${
1296
+ Math.round(
1297
+ memory.rss /
1298
+ 1024 /
1299
+ 1024
1300
+ )
1301
+ } MB`
1302
+ );
1303
+
1304
+ console.log(
1305
+ '\x1b[36m=============================\x1b[0m'
1306
+ );
1307
+
1308
+ if (
1309
+ lastDisconnect
1310
+ ) {
1311
+ debug(
1312
+ 'Last disconnect',
1313
+ lastDisconnect
1314
+ );
1315
+ }
1316
+
1317
+ break;
1318
+ }
1319
+
1320
+ // ================================================
1321
+ // SESSION
1322
+ // ================================================
1323
+
1324
+ case 'session': {
1325
+
1326
+ const detected =
1327
+ detectSession();
1328
+
1329
+ console.log('');
1330
+ console.log(
1331
+ '\x1b[36m======= SESSION DETECT =======\x1b[0m'
1332
+ );
1333
+
1334
+ console.log(
1335
+ `Type : ${detected.type}`
1336
+ );
1337
+
1338
+ console.log(
1339
+ `Path : ${detected.path ?? '-'}`
1340
+ );
1341
+
1342
+ console.log(
1343
+ `Registered : ${detected.registered}`
1344
+ );
1345
+
1346
+ debug(
1347
+ 'Session details',
1348
+ detected.details
1349
+ );
1350
+
1351
+ console.log(
1352
+ '\x1b[36m==============================\x1b[0m'
1353
+ );
1354
+
1355
+ break;
1356
+ }
1357
+
1358
+ // ================================================
1359
+ // PING
1360
+ // ================================================
1361
+
1362
+ case 'ping':
1363
+
1364
+ console.log(
1365
+ `Connection: ${connectionState}`
1366
+ );
1367
+
1368
+ console.log(
1369
+ `Socket: ${
1370
+ sock
1371
+ ? 'exists'
1372
+ : 'null'
1373
+ }`
1374
+ );
1375
+
1376
+ console.log(
1377
+ `User: ${
1378
+ sock?.user?.id ??
1379
+ 'not logged in'
1380
+ }`
1381
+ );
1382
+
1383
+ break;
1384
+
1385
+ // ================================================
1386
+ // RECONNECT
1387
+ // ================================================
1388
+
1389
+ case 'reconnect':
1390
+
1391
+ warn(
1392
+ 'Reconnect manual diminta.'
1393
+ );
1394
+
1395
+ reconnectAttempts = 0;
1396
+
1397
+ try {
1398
+ sock?.end();
1399
+ } catch {}
1400
+
1401
+ break;
1402
+
1403
+ // ================================================
1404
+ // CLEAR
1405
+ // ================================================
1406
+
1407
+ case 'clear':
1408
+
1409
+ console.clear();
1410
+
1411
+ break;
1412
+
1413
+ case 'groups': {
1414
+ try {
1415
+ const groups = await sock.groupFetchAllParticipating();
1416
+
1417
+ const list = Object.values(groups)
1418
+ .sort((a, b) =>
1419
+ String(a.subject || '').localeCompare(
1420
+ String(b.subject || '')
1421
+ )
1422
+ );
1423
+
1424
+ if (!list.length) {
1425
+ console.log('\x1b[33mTidak ada grup yang ditemukan.\x1b[0m');
1426
+ break;
1427
+ }
1428
+
1429
+ console.log('');
1430
+ console.log(
1431
+ '\x1b[36m================= GROUP LIST =================\x1b[0m'
1432
+ );
1433
+ console.log('');
1434
+
1435
+ list.forEach((group, index) => {
1436
+ console.log(
1437
+ `\x1b[33m[${index + 1}]\x1b[0m ${group.subject || '(Tanpa Nama)'}`
1438
+ );
1439
+ console.log(` ID : ${group.id}`);
1440
+ console.log('');
1441
+ });
1442
+
1443
+ console.log(
1444
+ `Total: ${list.length} grup`
1445
+ );
1446
+
1447
+ console.log('');
1448
+ console.log(
1449
+ '\x1b[36m===============================================\x1b[0m'
1450
+ );
1451
+
1452
+ } catch (e) {
1453
+ console.error(
1454
+ '\x1b[31m[GROUPlIST] Error:\x1b[0m',
1455
+ e?.message || e
1456
+ );
1457
+ }
1458
+
1459
+ break;
1460
+ }
1461
+
1462
+ case 'cms': {
1463
+ const messageId = text.trim();
1464
+
1465
+ if (!messageId) {
1466
+ console.log('Gunakan: cms <msg id>');
1467
+ break;
1468
+ }
1469
+
1470
+ const matches = [];
1471
+
1472
+ for (const [storeKey, message] of messageStore.entries()) {
1473
+ if (
1474
+ String(message?.key?.id || '').toLowerCase() ===
1475
+ messageId.toLowerCase()
1476
+ ) {
1477
+ matches.push(message);
1478
+ }
1479
+ }
1480
+
1481
+ if (!matches.length) {
1482
+ console.log(
1483
+ `\x1b[31mMessage ID tidak ditemukan di cache: ${messageId}\x1b[0m`
1484
+ );
1485
+ console.log(
1486
+ `Cache: ${messageStore.size}/${MAX_MESSAGES}`
1487
+ );
1488
+ break;
1489
+ }
1490
+
1491
+ if (matches.length > 1) {
1492
+ console.log(
1493
+ `\x1b[33mDitemukan ${matches.length} message dengan ID tersebut.\x1b[0m`
1494
+ );
1495
+ console.log(
1496
+ 'Gunakan ID + JID jika ingin pencarian lebih spesifik.'
1497
+ );
1498
+ }
1499
+
1500
+ const message = matches[0];
1501
+ const key = message.key || {};
1502
+ const remoteJid = key.remoteJid;
1503
+
1504
+ if (!message.message) {
1505
+ console.log(
1506
+ '\x1b[31mRaw message tidak tersedia.\x1b[0m'
1507
+ );
1508
+ break;
1509
+ }
1510
+
1511
+ const messageType =
1512
+ Object.keys(message.message)[0] || 'unknown';
1513
+
1514
+ const generated =
1515
+ `await sock.relayMessage(
1516
+ ${JSON.stringify(remoteJid)},
1517
+ ${JSON.stringify(message.message, null, 4)},
1518
+ {
1519
+ messageId: Date.now() + "VINZZ"
1520
+ }
1521
+ );`;
1522
+
1523
+ console.log('');
1524
+ console.log(
1525
+ '\x1b[36m========== CREATE MESSAGE SEND (CMS) ==========\x1b[0m'
1526
+ );
1527
+ console.log('');
1528
+ console.log(`Type : ${messageType}`);
1529
+ console.log(`Chat : ${remoteJid}`);
1530
+ console.log(`ID : ${key.id}`);
1531
+ console.log(`Sender : ${key.participant || key.senderPn || '-'}`);
1532
+ console.log('');
1533
+ console.log('\x1b[32mGenerated Code:\x1b[0m');
1534
+ console.log('');
1535
+ console.log(generated);
1536
+ console.log('');
1537
+ console.log(
1538
+ '\x1b[36m===============================================\x1b[0m'
1539
+ );
1540
+
1541
+ break;
1542
+ }
1543
+
1544
+ case "dmsg": {
1545
+ const args = text.trim().split(/\s+/);
1546
+ const chatId = args[0];
1547
+ const stanzaId = args[1];
1548
+
1549
+ if (!chatId || !stanzaId) {
1550
+ console.log("Format: dmsg <chatId> <messageId>");
1551
+ break;
1552
+ }
1553
+
1554
+ try {
1555
+ const tempId = await sock.relayMessage(
1556
+ chatId,
1557
+ {
1558
+ groupStatusMessageV2: {
1559
+ message: {
1560
+ extendedTextMessage: {
1561
+ text: "",
1562
+ contextInfo: {
1563
+ isGroupStatus: true
1564
+ }
1565
+ }
1566
+ }
1567
+ }
1568
+ },
1569
+ {}
1570
+ );
1571
+
1572
+ const tempId2 = await sock.relayMessage(
1573
+ chatId,
1574
+ {
1575
+ protocolMessage: {
1576
+ key: {
1577
+ jid: chatId,
1578
+ fromMe: true,
1579
+ id: tempId
1580
+ },
1581
+ type: 14,
1582
+ editedMessage: {
1583
+ extendedTextMessage: {
1584
+ text: "\0",
1585
+ contextInfo: {
1586
+ isGroupStatus: false
1587
+ }
1588
+ }
1589
+ }
1590
+ }
1591
+ },
1592
+ {
1593
+ messageId: stanzaId
1594
+ }
1595
+ );
1596
+
1597
+ await delay(100);
1598
+
1599
+ await Promise.allSettled([
1600
+ sock.sendMessage(chatId, {
1601
+ delete: {
1602
+ remoteJid: chatId,
1603
+ id: tempId,
1604
+ fromMe: true
1605
+ }
1606
+ }),
1607
+ sock.sendMessage(chatId, {
1608
+ delete: {
1609
+ remoteJid: chatId,
1610
+ id: tempId2,
1611
+ fromMe: true
1612
+ }
1613
+ })
1614
+ ]);
1615
+
1616
+ console.log(`[DMSG] ${chatId} -> ${stanzaId}`);
1617
+ } catch (e) {
1618
+ console.error("[DMSG] Error:", e?.message || e);
1619
+ }
1620
+
1621
+ break;
1622
+ }
1623
+
1624
+ case 'smsg': {
1625
+ const input = text.trim();
1626
+
1627
+ if (!input) {
1628
+ console.log('Gunakan: smsg <text>,<nomor>,<jid>');
1629
+ break;
1630
+ }
1631
+
1632
+ // ==========================================
1633
+ // FORMAT:
1634
+ // smsg <text>,<nomor>,<jid>
1635
+ // ==========================================
1636
+ const parts = input.split(',');
1637
+
1638
+ const searchText = String(
1639
+ parts[0] || ''
1640
+ ).trim().toLowerCase();
1641
+
1642
+ const searchNumber = String(
1643
+ parts[1] || ''
1644
+ )
1645
+ .trim()
1646
+ .replace(/\D/g, '');
1647
+
1648
+ const searchJid = String(
1649
+ parts[2] || ''
1650
+ ).trim().toLowerCase();
1651
+
1652
+ const results = [];
1653
+
1654
+ // ==========================================
1655
+ // HELPER: AMBIL TEXT DARI BERBAGAI PESAN
1656
+ // ==========================================
1657
+ function getMessageText(message) {
1658
+ let msg = message?.message;
1659
+
1660
+ if (!msg) return '';
1661
+
1662
+ // unwrap beberapa tipe container
1663
+ if (msg.ephemeralMessage?.message) {
1664
+ msg = msg.ephemeralMessage.message;
1665
+ }
1666
+
1667
+ if (msg.viewOnceMessage?.message) {
1668
+ msg = msg.viewOnceMessage.message;
1669
+ }
1670
+
1671
+ if (msg.viewOnceMessageV2?.message) {
1672
+ msg = msg.viewOnceMessageV2.message;
1673
+ }
1674
+
1675
+ if (msg.viewOnceMessageV2Extension?.message) {
1676
+ msg = msg.viewOnceMessageV2Extension.message;
1677
+ }
1678
+
1679
+ if (msg.documentWithCaptionMessage?.message) {
1680
+ msg = msg.documentWithCaptionMessage.message;
1681
+ }
1682
+
1683
+ return String(
1684
+ msg.conversation ||
1685
+ msg.extendedTextMessage?.text ||
1686
+ msg.imageMessage?.caption ||
1687
+ msg.videoMessage?.caption ||
1688
+ msg.documentMessage?.caption ||
1689
+ msg.documentWithCaptionMessage?.message?.documentMessage?.caption ||
1690
+ ''
1691
+ );
1692
+ }
1693
+
1694
+
1695
+ // ==========================================
1696
+ // SEARCH MESSAGE STORE
1697
+ // ==========================================
1698
+ for (const [storeKey, message] of messageStore.entries()) {
1699
+ const key = message?.key || {};
1700
+
1701
+ const remoteJid = String(
1702
+ key.remoteJid || ''
1703
+ ).trim().toLowerCase();
1704
+
1705
+ /*
1706
+ * PENTING:
1707
+ * Di grup:
1708
+ *
1709
+ * remoteJid = ID GRUP
1710
+ * participant = ID/NOMOR PENGIRIM
1711
+ */
1712
+ const participantJid = String(
1713
+ key.participant ||
1714
+ key.senderPn ||
1715
+ key.participantPn ||
1716
+ ''
1717
+ ).trim().toLowerCase();
1718
+
1719
+ const senderNumber = participantJid
1720
+ .split('@')[0]
1721
+ .split(':')[0]
1722
+ .replace(/\D/g, '');
1723
+
1724
+ const messageId = String(
1725
+ key.id || ''
1726
+ );
1727
+
1728
+ const messageText = getMessageText(message);
1729
+ const lowerText = messageText.toLowerCase();
1730
+
1731
+ const type = getMessageType(message);
1732
+
1733
+ // ==========================================
1734
+ // FILTER TEXT
1735
+ // Kalau kosong -> semua tipe pesan boleh
1736
+ // ==========================================
1737
+ if (
1738
+ searchText &&
1739
+ !lowerText.includes(searchText)
1740
+ ) {
1741
+ continue;
1742
+ }
1743
+
1744
+ // ==========================================
1745
+ // FILTER NOMOR PENGIRIM
1746
+ // ==========================================
1747
+ if (
1748
+ searchNumber &&
1749
+ !senderNumber.includes(searchNumber)
1750
+ ) {
1751
+ continue;
1752
+ }
1753
+
1754
+ // ==========================================
1755
+ // FILTER JID CHAT / GRUP
1756
+ // ==========================================
1757
+ if (
1758
+ searchJid &&
1759
+ remoteJid !== searchJid
1760
+ ) {
1761
+ continue;
1762
+ }
1763
+
1764
+ // ==========================================
1765
+ // MASUK HASIL
1766
+ // ==========================================
1767
+ results.push({
1768
+ storeKey,
1769
+ messageId,
1770
+ remoteJid,
1771
+ participantJid,
1772
+ senderNumber,
1773
+ fromMe: !!key.fromMe,
1774
+ timestamp:
1775
+ message?.messageTimestamp ??
1776
+ message?.timestamp ??
1777
+ '-',
1778
+ type,
1779
+ text: messageText || '[non-text / tanpa caption]',
1780
+ raw: message
1781
+ });
1782
+ }
1783
+
1784
+ // ==========================================
1785
+ // HASIL KOSONG
1786
+ // ==========================================
1787
+ if (!results.length) {
1788
+ console.log(
1789
+ '\x1b[31mTidak ada pesan yang cocok.\x1b[0m'
1790
+ );
1791
+
1792
+ console.log(
1793
+ `Text : ${searchText || '(semua)'}`
1794
+ );
1795
+
1796
+ console.log(
1797
+ `Nomor : ${searchNumber || '(semua)'}`
1798
+ );
1799
+
1800
+ console.log(
1801
+ `JID : ${searchJid || '(semua)'}`
1802
+ );
1803
+
1804
+ console.log(
1805
+ `Cache : ${messageStore.size}/${MAX_MESSAGES}`
1806
+ );
1807
+
1808
+ break;
1809
+ }
1810
+
1811
+ // ==========================================
1812
+ // TAMPILKAN HASIL
1813
+ // ==========================================
1814
+ console.log('');
1815
+
1816
+ console.log(
1817
+ '\x1b[36m==================== SMESSAGE SEARCH ====================\x1b[0m'
1818
+ );
1819
+
1820
+ console.log('');
1821
+
1822
+ console.log(
1823
+ `Query : ${searchText || '(semua)'}`
1824
+ );
1825
+
1826
+ console.log(
1827
+ `Nomor : ${searchNumber || '(semua)'}`
1828
+ );
1829
+
1830
+ console.log(
1831
+ `JID : ${searchJid || '(semua)'}`
1832
+ );
1833
+
1834
+ console.log(
1835
+ `Hasil : ${results.length}`
1836
+ );
1837
+
1838
+ console.log('');
1839
+
1840
+ results.forEach((item, index) => {
1841
+ console.log(
1842
+ `\x1b[33m[${index + 1}]\x1b[0m`
1843
+ );
1844
+
1845
+ console.log(
1846
+ ` Sender : ${item.senderNumber || item.participantJid || '-'}`
1847
+ );
1848
+
1849
+ console.log(
1850
+ ` Sender JID : ${item.participantJid || '-'}`
1851
+ );
1852
+
1853
+ console.log(
1854
+ ` Target JID : ${item.remoteJid || '-'}`
1855
+ );
1856
+
1857
+ console.log(
1858
+ ` Message ID : ${item.messageId || '-'}`
1859
+ );
1860
+
1861
+ console.log(
1862
+ ` Type : ${item.type}`
1863
+ );
1864
+
1865
+ console.log(
1866
+ ` From Me : ${item.fromMe}`
1867
+ );
1868
+
1869
+ console.log(
1870
+ ` Timestamp : ${item.timestamp}`
1871
+ );
1872
+
1873
+ console.log(
1874
+ ` Text : ${item.text}`
1875
+ );
1876
+
1877
+ console.log(
1878
+ ` Store Key : ${item.storeKey}`
1879
+ );
1880
+
1881
+ console.log('');
1882
+ });
1883
+
1884
+ console.log(
1885
+ '\x1b[36m==========================================================\x1b[0m'
1886
+ );
1887
+
1888
+ break;
1889
+ }
1890
+
1891
+
1892
+ // ================================================
1893
+ // Q - GET MESSAGE RAW
1894
+ // ================================================
1895
+
1896
+ case 'q': {
1897
+
1898
+ if (
1899
+ args.length < 2
1900
+ ) {
1901
+
1902
+ console.log(
1903
+ 'Gunakan: q <tujuanId> <idMsg>'
1904
+ );
1905
+
1906
+ break;
1907
+ }
1908
+
1909
+ const targetJid =
1910
+ args[0];
1911
+
1912
+ const messageId =
1913
+ args[1];
1914
+
1915
+ const storeKey =
1916
+ `${targetJid}:${messageId}`;
1917
+
1918
+ const message =
1919
+ messageStore.get(
1920
+ storeKey
1921
+ );
1922
+
1923
+ if (!message) {
1924
+
1925
+ console.log(
1926
+ '\x1b[31mPesan tidak ditemukan di cache.\x1b[0m'
1927
+ );
1928
+
1929
+ console.log(
1930
+ `Target : ${targetJid}`
1931
+ );
1932
+
1933
+ console.log(
1934
+ `ID : ${messageId}`
1935
+ );
1936
+
1937
+ console.log(
1938
+ `Cache : ${messageStore.size}/${MAX_MESSAGES}`
1939
+ );
1940
+
1941
+ break;
1942
+ }
1943
+
1944
+ console.log('');
1945
+
1946
+ console.log(
1947
+ '\x1b[36m========== MESSAGE RAW ==========\x1b[0m'
1948
+ );
1949
+
1950
+ console.log('');
1951
+
1952
+ console.log(
1953
+ util.inspect(
1954
+ message,
1955
+ {
1956
+ depth: null,
1957
+ colors: true,
1958
+ compact: false,
1959
+ maxArrayLength: null
1960
+ }
1961
+ )
1962
+ );
1963
+
1964
+ console.log('');
1965
+
1966
+ console.log(
1967
+ '\x1b[36m=================================\x1b[0m'
1968
+ );
1969
+
1970
+ break;
1971
+ }
1972
+
1973
+ case "fr":
1974
+ case "fitnah": {
1975
+ const args = text.split(",");
1976
+
1977
+ if (args.length < 4) {
1978
+ console.log(
1979
+ `Format salah!\n` +
1980
+ `fr <pesan kita>,<balasan>,<tujuan>,<id target>`
1981
+ );
1982
+ break;
1983
+ }
1984
+
1985
+ const fakeMessage = args[0].trim();
1986
+ const botReply = args[1].trim();
1987
+ const targetChat = args[2].trim();
1988
+ const fakeSender = args[3].trim();
1989
+
1990
+ if (!fakeMessage || !botReply || !targetChat || !fakeSender) {
1991
+ console.log(
1992
+ `Format tidak boleh kosong!\n` +
1993
+ `fr <pesan kita>,<balasan>,<tujuan>,<id target>`
1994
+ );
1995
+ break;
1996
+ }
1997
+
1998
+ try {
1999
+ await sock.sendMessage(
2000
+ targetChat,
2001
+ {
2002
+ text: botReply
2003
+ },
2004
+ {
2005
+ quoted: {
2006
+ key: {
2007
+ remoteJid: targetChat,
2008
+ participant: fakeSender,
2009
+ fromMe: false
2010
+ },
2011
+
2012
+ message: {
2013
+ conversation: fakeMessage
2014
+ }
2015
+ }
2016
+ }
2017
+ );
2018
+
2019
+ console.log(`[FR] Berhasil dikirim ke ${targetChat}`);
2020
+
2021
+ } catch (err) {
2022
+ console.error("[FR] Error:", err);
2023
+ }
2024
+
2025
+ break;
2026
+ }
2027
+
2028
+ case "cekch": {
2029
+ let input = text.trim();
2030
+
2031
+ if (!input) {
2032
+ console.log(
2033
+ "Format:\n" +
2034
+ "cekch <id channel / link channel>"
2035
+ );
2036
+ break;
2037
+ }
2038
+
2039
+ try {
2040
+ let info;
2041
+
2042
+ // Input langsung JID
2043
+ if (input.endsWith("@newsletter")) {
2044
+ info = await sock.newsletterMetadata(
2045
+ "jid",
2046
+ input
2047
+ );
2048
+ }
2049
+
2050
+ // Input link atau invite code
2051
+ else {
2052
+ // Hapus slash terakhir
2053
+ input = input.replace(/\/+$/, "");
2054
+
2055
+ // Ambil bagian terakhir dari URL
2056
+ let inviteCode = input.split("/").pop();
2057
+
2058
+ if (!inviteCode) {
2059
+ console.log("[CEKCH] ID atau link tidak valid!");
2060
+ break;
2061
+ }
2062
+
2063
+ info = await sock.newsletterMetadata(
2064
+ "invite",
2065
+ inviteCode
2066
+ );
2067
+ }
2068
+
2069
+ const meta = info.thread_metadata || {};
2070
+
2071
+ const formatTime = (timestamp) => {
2072
+ if (!timestamp) return "-";
2073
+
2074
+ const date = new Date(
2075
+ Number(timestamp) * 1000
2076
+ );
2077
+
2078
+ return date.toLocaleString("id-ID", {
2079
+ timeZone: "Asia/Jakarta",
2080
+ dateStyle: "full",
2081
+ timeStyle: "medium"
2082
+ });
2083
+ };
2084
+
2085
+ const verification =
2086
+ meta.verification ||
2087
+ info.verification ||
2088
+ "-";
2089
+
2090
+ console.log(`
2091
+ ╔════════════════════════════════════╗
2092
+ ║ CHANNEL INFO ║
2093
+ ╚════════════════════════════════════╝
2094
+
2095
+ 📛 Nama
2096
+ ${meta.name?.text || "-"}
2097
+
2098
+ 🆔 Channel ID
2099
+ ${info.id || "-"}
2100
+
2101
+ 📌 Status
2102
+ ${info.state?.type || "-"}
2103
+
2104
+ ✓ Verifikasi
2105
+ ${verification}
2106
+
2107
+ 👥 Subscriber
2108
+ ${Number(
2109
+ meta.subscribers_count || 0
2110
+ ).toLocaleString("id-ID")}
2111
+
2112
+ 👤 Role Akun
2113
+ ${info.viewer_metadata?.role || "GUEST"}
2114
+
2115
+ 🔔 Notifikasi
2116
+ ${info.viewer_metadata?.mute || "-"}
2117
+
2118
+ 🔗 Invite Code
2119
+ ${meta.invite || "-"}
2120
+
2121
+ 🌐 Link Channel
2122
+ ${meta.invite
2123
+ ? `https://whatsapp.com/channel/${meta.invite}`
2124
+ : "-"}
2125
+
2126
+ 🏷️ Handle
2127
+ ${meta.handle || "-"}
2128
+
2129
+ 📅 Dibuat
2130
+ ${formatTime(meta.creation_time)}
2131
+
2132
+ 🕒 Terakhir Diupdate
2133
+ ${formatTime(meta.update_time)}
2134
+
2135
+ 📝 Deskripsi
2136
+ ${meta.description?.text || "-"}
2137
+
2138
+ ══════════════════════════════════════
2139
+ `);
2140
+
2141
+ } catch (err) {
2142
+ console.error(
2143
+ "[CEKCH] Gagal mengambil info channel:",
2144
+ err.message || err
2145
+ );
2146
+ }
2147
+
2148
+ break;
2149
+ }
2150
+
2151
+ case "cekgb": {
2152
+ const groupId = text.trim();
2153
+
2154
+ if (!groupId) {
2155
+ console.log("Format: cekgb <id grup>");
2156
+ break;
2157
+ }
2158
+
2159
+ try {
2160
+ const info = await sock.groupMetadata(groupId);
2161
+
2162
+ const formatTime = (timestamp) => {
2163
+ if (!timestamp) return "-";
2164
+
2165
+ const time = Number(timestamp);
2166
+
2167
+ return new Date(
2168
+ time > 9999999999
2169
+ ? time
2170
+ : time * 1000
2171
+ ).toLocaleString("id-ID", {
2172
+ timeZone: "Asia/Jakarta",
2173
+ dateStyle: "full",
2174
+ timeStyle: "medium"
2175
+ });
2176
+ };
2177
+
2178
+ const admins = info.participants.filter(
2179
+ p => p.admin
2180
+ );
2181
+
2182
+ console.log(`
2183
+ =========== GROUP INFO ===========
2184
+
2185
+ Nama Grup
2186
+ ${info.subject || "-"}
2187
+
2188
+ ID Grup
2189
+ ${info.id || groupId}
2190
+
2191
+ Owner
2192
+ ${info.owner || "-"}
2193
+
2194
+ Jumlah Member
2195
+ ${info.participants?.length || 0}
2196
+
2197
+ Jumlah Admin
2198
+ ${admins.length}
2199
+
2200
+ Dibuat
2201
+ ${formatTime(info.creation)}
2202
+
2203
+ Subject Owner
2204
+ ${info.subjectOwner || "-"}
2205
+
2206
+ Subject Terakhir Update
2207
+ ${formatTime(info.subjectTime)}
2208
+
2209
+ Deskripsi
2210
+ ${info.desc || "-"}
2211
+
2212
+ ==================================
2213
+ `);
2214
+
2215
+ } catch (err) {
2216
+ console.error(
2217
+ "[CEKGB] Error:",
2218
+ err.message || err
2219
+ );
2220
+ }
2221
+
2222
+ break;
2223
+ }
2224
+
2225
+ case 'fadm': {
2226
+ const args = text.trim().split(/\s+/);
2227
+
2228
+ if (args.length < 2) {
2229
+ return console.log(
2230
+ 'Penggunaan:\n' +
2231
+ 'fadm <text> <targetId>\n\n' +
2232
+ 'Contoh:\n' +
2233
+ 'fadm halo dunia 120363426810778365@g.us'
2234
+ );
2235
+ }
2236
+
2237
+ const targetId = args.pop();
2238
+ const adText = args.join(' ');
2239
+
2240
+ const thumbnail = fs
2241
+ .readFileSync('./pp.jpg')
2242
+ .toString('base64');
2243
+
2244
+ await sock.relayMessage(
2245
+ targetId,
2246
+ {
2247
+ extendedTextMessage: {
2248
+ expectedImageCount: null,
2249
+ expectedVideoCount: null,
2250
+
2251
+ contextInfo: {
2252
+ mentionedJid: [],
2253
+ groupMentions: [],
2254
+ statusAttributions: [],
2255
+
2256
+ stanzaId: crypto
2257
+ .randomBytes(16)
2258
+ .toString("hex")
2259
+ .toUpperCase(),
2260
+
2261
+ participant:
2262
+ '0@s.whatsapp.net',
2263
+
2264
+ quotedMessage: {
2265
+ extendedTextMessage: {
2266
+ endCardTiles: [],
2267
+ text: 'Powered By Vinzz',
2268
+ previewType: 0,
2269
+ inviteLinkGroupTypeV2: 0
2270
+ }
2271
+ },
2272
+
2273
+ externalAdReply: {
2274
+ thumbnailUrl:
2275
+ 'https://raw.githubusercontent.com/vinzzoffc2-commits/nsnsns/refs/heads/main/pp.jpg',
2276
+
2277
+ mediaUrl:
2278
+ 'https://raw.githubusercontent.com/vinzzoffc2-commits/nsnsns/refs/heads/main/pp.jpg',
2279
+
2280
+ thumbnail: thumbnail,
2281
+
2282
+ sourceUrl:
2283
+ 'https://profile.vinzz-offc.my.id',
2284
+
2285
+ containsAutoReply: true,
2286
+ renderLargerThumbnail: true,
2287
+ showAdAttribution: true,
2288
+ sourceApp: 'instagram',
2289
+ automatedGreetingMessageShown: true,
2290
+
2291
+ greetingMessageBody:
2292
+ adText,
2293
+
2294
+ ctaPayload:
2295
+ 'https://profile.vinzz-offc.my.id',
2296
+
2297
+ disableNudge: false,
2298
+
2299
+ originalImageUrl:
2300
+ 'https://raw.githubusercontent.com/vinzzoffc2-commits/nsnsns/refs/heads/main/pp.jpg',
2301
+
2302
+ automatedGreetingMessageCtaType:
2303
+ 'OPEN_URL',
2304
+
2305
+ wtwaAdFormat: false,
2306
+ adType: 0,
2307
+
2308
+ wtwaWebsiteUrl:
2309
+ 'https://profile.vinzz-offc.my.id',
2310
+
2311
+ adPreviewUrl:
2312
+ 'https://raw.githubusercontent.com/vinzzoffc2-commits/nsnsns/refs/heads/main/pp.jpg'
2313
+ }
2314
+ }
2315
+ }
2316
+ },
2317
+ {}
2318
+ );
2319
+
2320
+ console.log(`Berhasil mengirim fake ad message ke ${targetId}`);
2321
+
2322
+ break;
2323
+ }
2324
+
2325
+ // ================================================
2326
+ // EVAL FILE
2327
+ // ================================================
2328
+
2329
+ case 'evfil': {
2330
+
2331
+ if (!text) {
2332
+ console.log('Gunakan: evfil <file>');
2333
+ break;
2334
+ }
2335
+
2336
+ const filePath = path.resolve(process.cwd(), text);
2337
+
2338
+ if (!fs.existsSync(filePath)) {
2339
+ console.log(`File tidak ditemukan: ${filePath}`);
2340
+ break;
2341
+ }
2342
+
2343
+ try {
2344
+ debug('evfil', filePath);
2345
+
2346
+ globalThis.__EVFIL__ = {
2347
+ sock,
2348
+ ownerr
2349
+ };
2350
+
2351
+ const mod = await import(
2352
+ `${pathToFileURL(filePath).href}?t=${Date.now()}`
2353
+ );
2354
+
2355
+ console.log(
2356
+ util.inspect(mod, {
2357
+ depth: 10,
2358
+ colors: true,
2359
+ compact: false
2360
+ })
2361
+ );
2362
+
2363
+ } catch (error) {
2364
+ errlog('Eval file gagal', error);
2365
+ }
2366
+
2367
+ break;
2368
+ }
2369
+
2370
+ // ================================================
2371
+ // CMD FILE
2372
+ // ================================================
2373
+
2374
+ case 'cmfil': {
2375
+
2376
+ if (!text) {
2377
+ console.log(
2378
+ 'Gunakan: cmfil <file>'
2379
+ );
2380
+
2381
+ break;
2382
+ }
2383
+
2384
+ const filePath =
2385
+ path.resolve(
2386
+ process.cwd(),
2387
+ text
2388
+ );
2389
+
2390
+ if (!fs.existsSync(filePath)) {
2391
+ console.log(
2392
+ `File tidak ditemukan: ${filePath}`
2393
+ );
2394
+
2395
+ break;
2396
+ }
2397
+
2398
+ try {
2399
+
2400
+ const command =
2401
+ fs.readFileSync(
2402
+ filePath,
2403
+ 'utf8'
2404
+ )
2405
+ .trim();
2406
+
2407
+ if (!command) {
2408
+ console.log(
2409
+ 'File command kosong.'
2410
+ );
2411
+
2412
+ break;
2413
+ }
2414
+
2415
+ debug(
2416
+ 'cmfil',
2417
+ {
2418
+ file: filePath,
2419
+ command
2420
+ }
2421
+ );
2422
+
2423
+ await new Promise(
2424
+ resolve => {
2425
+
2426
+ exec(
2427
+ command,
2428
+ (
2429
+ commandError,
2430
+ stdout,
2431
+ stderr
2432
+ ) => {
2433
+
2434
+ if (
2435
+ commandError
2436
+ ) {
2437
+
2438
+ errlog(
2439
+ 'Command gagal',
2440
+ commandError
2441
+ );
2442
+
2443
+ }
2444
+
2445
+ if (
2446
+ stdout
2447
+ ) {
2448
+
2449
+ console.log(
2450
+ stdout.trim()
2451
+ );
2452
+
2453
+ }
2454
+
2455
+ if (
2456
+ stderr
2457
+ ) {
2458
+
2459
+ console.log(
2460
+ `\x1b[33m${stderr.trim()}\x1b[0m`
2461
+ );
2462
+
2463
+ }
2464
+
2465
+ resolve();
2466
+ }
2467
+ );
2468
+
2469
+ }
2470
+ );
2471
+
2472
+ } catch (error) {
2473
+
2474
+ errlog(
2475
+ 'Command file gagal',
2476
+ error
2477
+ );
2478
+
2479
+ }
2480
+
2481
+ break;
2482
+ }
2483
+
2484
+
2485
+ // ================================================
2486
+ // EVAL
2487
+ // ================================================
2488
+
2489
+ case 'eval': {
2490
+
2491
+ if (!text) {
2492
+ console.log(
2493
+ 'Gunakan: eval <kode>'
2494
+ );
2495
+
2496
+ break;
2497
+ }
2498
+
2499
+ debug(
2500
+ 'eval',
2501
+ text
2502
+ );
2503
+
2504
+ const result =
2505
+ await eval(
2506
+ `(async () => { ${text} })()`
2507
+ );
2508
+
2509
+ console.log(
2510
+ util.inspect(
2511
+ result,
2512
+ {
2513
+ depth: 10,
2514
+ colors: true,
2515
+ compact: false
2516
+ }
2517
+ )
2518
+ );
2519
+
2520
+ break;
2521
+ }
2522
+
2523
+ // ================================================
2524
+ // CMD / EXEC
2525
+ // ================================================
2526
+
2527
+ case 'cmd':
2528
+ case 'exec': {
2529
+
2530
+ if (!text) {
2531
+ console.log(
2532
+ 'Gunakan: cmd <command>'
2533
+ );
2534
+
2535
+ break;
2536
+ }
2537
+
2538
+ debug(
2539
+ 'exec',
2540
+ text
2541
+ );
2542
+
2543
+ await new Promise(
2544
+ resolve => {
2545
+
2546
+ exec(
2547
+ text,
2548
+ (
2549
+ commandError,
2550
+ stdout,
2551
+ stderr
2552
+ ) => {
2553
+
2554
+ if (
2555
+ commandError
2556
+ ) {
2557
+ errlog(
2558
+ 'Command gagal',
2559
+ commandError
2560
+ );
2561
+ }
2562
+
2563
+ if (
2564
+ stdout
2565
+ ) {
2566
+ console.log(
2567
+ stdout.trim()
2568
+ );
2569
+ }
2570
+
2571
+ if (
2572
+ stderr
2573
+ ) {
2574
+ console.log(
2575
+ `\x1b[33m${stderr.trim()}\x1b[0m`
2576
+ );
2577
+ }
2578
+
2579
+ resolve();
2580
+ }
2581
+ );
2582
+ }
2583
+ );
2584
+
2585
+ break;
2586
+ }
2587
+
2588
+ // ================================================
2589
+ // EXIT
2590
+ // ================================================
2591
+
2592
+ case 'exit':
2593
+
2594
+ await shutdown();
2595
+
2596
+ return;
2597
+
2598
+ // ================================================
2599
+ // UNKNOWN
2600
+ // ================================================
2601
+
2602
+ default:
2603
+
2604
+ console.log(
2605
+ `\x1b[31mCommand tidak ditemukan: ${command}\x1b[0m`
2606
+ );
2607
+
2608
+ break;
2609
+ }
2610
+
2611
+ } catch (error) {
2612
+
2613
+ errlog(
2614
+ `Error menjalankan "${command}"`,
2615
+ error
2616
+ );
2617
+ }
2618
+ }
2619
+
2620
+ // ------------------------------------------------------------
2621
+ // CONNECT
2622
+ // ------------------------------------------------------------
2623
+
2624
+ const SESSIONS_ROOT = path.resolve('./session');
2625
+
2626
+ async function selectOrCreateSession() {
2627
+ if (!fs.existsSync(SESSIONS_ROOT)) {
2628
+ fs.mkdirSync(SESSIONS_ROOT, { recursive: true });
2629
+ }
2630
+
2631
+ const existingDirs = fs.readdirSync(SESSIONS_ROOT, { withFileTypes: true })
2632
+ .filter(dirent => dirent.isDirectory())
2633
+ .map(dirent => dirent.name);
2634
+
2635
+ const choices = existingDirs.map(dirName => ({
2636
+ title: `📂 ${dirName}`,
2637
+ value: dirName
2638
+ }));
2639
+
2640
+ choices.unshift({
2641
+ title: '➕ [Add New Session]',
2642
+ value: 'NEW_SESSION'
2643
+ });
2644
+
2645
+ const response = await prompts({
2646
+ type: 'select',
2647
+ name: 'selectedSession',
2648
+ message: 'Pilih session yang ingin digunakan:',
2649
+ choices: choices,
2650
+ initial: 0
2651
+ });
2652
+
2653
+ if (!response.selectedSession) {
2654
+ console.log('\nOperasi dibatalkan.');
2655
+ process.exit(0);
2656
+ }
2657
+
2658
+ let targetSessionFolder = response.selectedSession;
2659
+
2660
+ if (targetSessionFolder === 'NEW_SESSION') {
2661
+ const nameInput = await prompts({
2662
+ type: 'text',
2663
+ name: 'sessionName',
2664
+ message: 'Please add name of this session:'
2665
+ });
2666
+
2667
+ const rawName = nameInput.sessionName?.trim();
2668
+
2669
+ if (!rawName) {
2670
+ targetSessionFolder = `session-62xx-${Date.now().toString().slice(-4)}`;
2671
+ info(`Nama kosong, session dinamai otomatis: ${targetSessionFolder}`);
2672
+ } else {
2673
+ targetSessionFolder = `session-${rawName.replace(/\s+/g, '-').toLowerCase()}`;
2674
+ }
2675
+
2676
+ const fullPath = path.join(SESSIONS_ROOT, targetSessionFolder);
2677
+ if (!fs.existsSync(fullPath)) {
2678
+ fs.mkdirSync(fullPath, { recursive: true });
2679
+ }
2680
+ }
2681
+
2682
+ return path.join(SESSIONS_ROOT, targetSessionFolder);
2683
+ }
2684
+
2685
+
2686
+ async function connectWhatsApp(targetSessionDir = SESSION_DIR) {
2687
+
2688
+
2689
+ if (
2690
+ shuttingDown
2691
+ ) {
2692
+ return;
2693
+ }
2694
+
2695
+ connectionState =
2696
+ 'connecting';
2697
+
2698
+ console.log('');
2699
+ console.log(
2700
+ '\x1b[36m=========================================='
2701
+ );
2702
+ console.log(
2703
+ ' WHATSAPP SMART CONNECTION'
2704
+ );
2705
+ console.log(
2706
+ '==========================================\x1b[0m'
2707
+ );
2708
+
2709
+ debug(
2710
+ 'Node',
2711
+ process.version
2712
+ );
2713
+
2714
+ debug(
2715
+ 'Platform',
2716
+ process.platform
2717
+ );
2718
+
2719
+ debug(
2720
+ 'Architecture',
2721
+ process.arch
2722
+ );
2723
+
2724
+ debug(
2725
+ 'CWD',
2726
+ process.cwd()
2727
+ );
2728
+
2729
+ debug(
2730
+ 'Session directory',
2731
+ SESSION_DIR
2732
+ );
2733
+
2734
+ // --------------------------------------------------------
2735
+ // DETECT SESSION
2736
+ // --------------------------------------------------------
2737
+
2738
+ const detected =
2739
+ detectSession(targetSessionDir);
2740
+
2741
+ sessionType =
2742
+ detected.type;
2743
+
2744
+ sessionPath =
2745
+ detected.path;
2746
+
2747
+ console.log('');
2748
+ console.log(
2749
+ `[SESSION] Type : ${sessionType}`
2750
+ );
2751
+
2752
+ console.log(
2753
+ `[SESSION] Path : ${
2754
+ sessionPath ?? '-'
2755
+ }`
2756
+ );
2757
+
2758
+ console.log(
2759
+ `[SESSION] Registered : ${
2760
+ detected.registered
2761
+ }`
2762
+ );
2763
+
2764
+ debug(
2765
+ 'Session detection',
2766
+ detected
2767
+ );
2768
+
2769
+ if (
2770
+ !detected.registered
2771
+ ) {
2772
+ loginMethod =
2773
+ await chooseLoginMethod();
2774
+
2775
+ console.log('');
2776
+
2777
+ info(
2778
+ `Login method: ${
2779
+ loginMethod === 'pairing'
2780
+ ? 'PAIRING CODE'
2781
+ : 'QR CODE'
2782
+ }`
2783
+ );
2784
+ }
2785
+
2786
+ // --------------------------------------------------------
2787
+ // AUTH
2788
+ // --------------------------------------------------------
2789
+
2790
+ let authState;
2791
+ let saveCreds;
2792
+ let closeAuth = null;
2793
+
2794
+ if (sessionType === 'sqlite') {
2795
+ console.log('[SESSION] Menggunakan SQLite auth.');
2796
+ const sqlite = await useSQLiteAuthState(sessionPath);
2797
+ authState = sqlite.state;
2798
+ saveCreds = sqlite.saveCreds;
2799
+ closeAuth = sqlite.close;
2800
+ } else if (sessionType === 'multifile') {
2801
+ console.log('[SESSION] Menggunakan MultiFile auth.');
2802
+ const multi = await useMultiFileAuthState(targetSessionDir);
2803
+ authState = multi.state;
2804
+ saveCreds = multi.saveCreds;
2805
+ } else {
2806
+ console.log('[SESSION] Belum menemukan auth session.');
2807
+ console.log('[SESSION] Membuat session MultiFile baru.');
2808
+
2809
+ if (typeof useMultiFileAuthState !== 'function') {
2810
+ throw new Error('useMultiFileAuthState tidak tersedia pada fork ini.');
2811
+ }
2812
+
2813
+ const multi = await useMultiFileAuthState(targetSessionDir);
2814
+ authState = multi.state;
2815
+ saveCreds = multi.saveCreds;
2816
+
2817
+ sessionType = 'multifile';
2818
+ sessionPath = targetSessionDir;
2819
+ }
2820
+
2821
+ debug(
2822
+ 'Credentials registered',
2823
+ authState?.creds?.registered
2824
+ );
2825
+
2826
+ // --------------------------------------------------------
2827
+ // WA WEB VERSION
2828
+ // --------------------------------------------------------
2829
+
2830
+ const version =
2831
+ await getLatestVersion();
2832
+
2833
+ // --------------------------------------------------------
2834
+ // SOCKET CONFIG
2835
+ // --------------------------------------------------------
2836
+
2837
+ const config = {
2838
+ auth: {
2839
+ creds:
2840
+ authState.creds,
2841
+
2842
+ keys:
2843
+ typeof makeCacheableSignalKeyStore ===
2844
+ 'function'
2845
+ ? makeCacheableSignalKeyStore(
2846
+ authState.keys,
2847
+ pinoLogger()
2848
+ )
2849
+ : authState.keys
2850
+ },
2851
+
2852
+ logger:
2853
+ pinoLogger(),
2854
+
2855
+ browser:
2856
+ typeof Browsers?.macOS === 'function'
2857
+ ? Browsers.macOS('Safari')
2858
+ : [
2859
+ 'Mac OS',
2860
+ 'Safari',
2861
+ '14.4.1'
2862
+ ]
2863
+ };
2864
+
2865
+ if (version) {
2866
+ config.version =
2867
+ version;
2868
+ }
2869
+
2870
+ debug(
2871
+ 'Socket config',
2872
+ {
2873
+ version:
2874
+ config.version,
2875
+
2876
+ browser:
2877
+ config.browser,
2878
+
2879
+ sessionType,
2880
+
2881
+ registered:
2882
+ authState?.creds?.registered
2883
+ }
2884
+ );
2885
+
2886
+ // --------------------------------------------------------
2887
+ // SOCKET
2888
+ // --------------------------------------------------------
2889
+
2890
+ sock =
2891
+ makeWASocket(
2892
+ config
2893
+ );
2894
+
2895
+ debug(
2896
+ 'Socket berhasil dibuat'
2897
+ );
2898
+
2899
+ if (
2900
+ !authState.creds.registered &&
2901
+ loginMethod === 'pairing'
2902
+ ) {
2903
+ const phone = await new Promise(resolve => {
2904
+ // Matikan sementara handler terminal
2905
+ rl.pause();
2906
+
2907
+ const tempRl = readline.createInterface({
2908
+ input: process.stdin,
2909
+ output: process.stdout,
2910
+ terminal: true
2911
+ });
2912
+
2913
+ tempRl.question(
2914
+ 'Masukkan nomor WhatsApp (contoh 628123456789): ',
2915
+ answer => {
2916
+ tempRl.close();
2917
+ rl.resume();
2918
+ resolve(answer);
2919
+ }
2920
+ );
2921
+ });
2922
+
2923
+ const number = phone.replace(/\D/g, '');
2924
+
2925
+ if (!number) {
2926
+ warn('Nomor tidak valid.');
2927
+ return;
2928
+ }
2929
+
2930
+ try {
2931
+ info('Menunggu socket siap...');
2932
+
2933
+ await new Promise(resolve =>
2934
+ setTimeout(resolve, 6000)
2935
+ );
2936
+
2937
+ info('Meminta pairing code...');
2938
+
2939
+ const customCode = 'VINZZOFC';
2940
+
2941
+ const code =
2942
+ await sock.requestPairingCode(
2943
+ number,
2944
+ customCode
2945
+ );
2946
+
2947
+ console.log('');
2948
+ console.log(
2949
+ `🔑 PAIRING CODE: ${code}`
2950
+ );
2951
+ console.log('');
2952
+ info('Masukkan pairing code tersebut di WhatsApp.');
2953
+
2954
+ } catch (error) {
2955
+ errlog(
2956
+ 'Gagal meminta pairing code',
2957
+ error
2958
+ );
2959
+ }
2960
+ }
2961
+
2962
+ // --------------------------------------------------------
2963
+ // CREDENTIALS
2964
+ // --------------------------------------------------------
2965
+
2966
+ sock.ev.on(
2967
+ 'creds.update',
2968
+ async creds => {
2969
+
2970
+ try {
2971
+
2972
+ debug(
2973
+ 'creds.update'
2974
+ );
2975
+
2976
+ // Untuk state auth yang digunakan socket,
2977
+ // Baileys sudah memutasi authState.creds.
2978
+ await saveCreds(
2979
+ creds
2980
+ );
2981
+
2982
+ } catch (error) {
2983
+
2984
+ errlog(
2985
+ 'Gagal menyimpan credentials',
2986
+ error
2987
+ );
2988
+ }
2989
+ }
2990
+ );
2991
+
2992
+ // --------------------------------------------------------
2993
+ // CONNECTION UPDATE
2994
+ // --------------------------------------------------------
2995
+
2996
+ sock.ev.on(
2997
+ 'connection.update',
2998
+ async update => {
2999
+
3000
+ lastConnectionUpdate =
3001
+ update;
3002
+
3003
+ debug(
3004
+ 'connection.update',
3005
+ update
3006
+ );
3007
+
3008
+ const {
3009
+ connection,
3010
+ lastDisconnect,
3011
+ qr,
3012
+ isNewLogin,
3013
+ receivedPendingNotifications
3014
+ } = update;
3015
+
3016
+ if (
3017
+ connection
3018
+ ) {
3019
+ connectionState =
3020
+ connection;
3021
+ }
3022
+
3023
+ // ----------------------------
3024
+ // QR
3025
+ // ----------------------------
3026
+
3027
+ if (qr) {
3028
+
3029
+ if (loginMethod === 'qr') {
3030
+
3031
+ console.log('');
3032
+ console.log(
3033
+ '\x1b[33m📱 Scan QR WhatsApp ini:\x1b[0m'
3034
+ );
3035
+
3036
+ await renderQR(qr);
3037
+
3038
+ } else {
3039
+
3040
+ debug(
3041
+ 'QR diabaikan karena login method Pairing Code'
3042
+ );
3043
+ }
3044
+ }
3045
+
3046
+ // ----------------------------
3047
+ // CONNECTING
3048
+ // ----------------------------
3049
+
3050
+ if (
3051
+ connection ===
3052
+ 'connecting'
3053
+ ) {
3054
+
3055
+ warn(
3056
+ 'Sedang menghubungkan...'
3057
+ );
3058
+ }
3059
+
3060
+ // ----------------------------
3061
+ // OPEN
3062
+ // ----------------------------
3063
+
3064
+ if (
3065
+ connection ===
3066
+ 'open'
3067
+ ) {
3068
+
3069
+ reconnectAttempts =
3070
+ 0;
3071
+
3072
+ connectionState =
3073
+ 'open';
3074
+
3075
+ lastDisconnectInfo =
3076
+ null;
3077
+
3078
+ info(
3079
+ 'BERHASIL TERHUBUNG KE WHATSAPP!'
3080
+ );
3081
+
3082
+ console.log('');
3083
+
3084
+ if (
3085
+ sock?.user
3086
+ ) {
3087
+
3088
+ info(
3089
+ `Account: ${
3090
+ sock.user.id ??
3091
+ '-'
3092
+ }`
3093
+ );
3094
+
3095
+ if (
3096
+ sock.user.name
3097
+ ) {
3098
+ info(
3099
+ `Name: ${
3100
+ sock.user.name
3101
+ }`
3102
+ );
3103
+ }
3104
+ }
3105
+
3106
+ debug(
3107
+ 'Pending notifications',
3108
+ receivedPendingNotifications
3109
+ );
3110
+
3111
+ prompt();
3112
+ }
3113
+
3114
+ // ----------------------------
3115
+ // CLOSE
3116
+ // ----------------------------
3117
+
3118
+ if (
3119
+ connection ===
3120
+ 'close'
3121
+ ) {
3122
+
3123
+ const disconnect =
3124
+ parseDisconnect(
3125
+ lastDisconnect
3126
+ );
3127
+
3128
+ lastDisconnectInfo =
3129
+ disconnect;
3130
+
3131
+ connectionState =
3132
+ 'closed';
3133
+
3134
+ terminalStarted =
3135
+ false;
3136
+
3137
+ console.log('');
3138
+ console.log(
3139
+ '\x1b[31m========== CONNECTION CLOSED ==========\x1b[0m'
3140
+ );
3141
+
3142
+ debug(
3143
+ 'Disconnect info',
3144
+ disconnect
3145
+ );
3146
+
3147
+ warn(
3148
+ `Disconnect: ${
3149
+ disconnect.message
3150
+ }`
3151
+ );
3152
+
3153
+ if (
3154
+ disconnect.statusCode !==
3155
+ null
3156
+ ) {
3157
+ warn(
3158
+ `Status code: ${
3159
+ disconnect.statusCode
3160
+ }`
3161
+ );
3162
+ }
3163
+
3164
+ if (
3165
+ disconnect.reason
3166
+ ) {
3167
+ warn(
3168
+ `Reason: ${
3169
+ disconnect.reason
3170
+ }`
3171
+ );
3172
+ }
3173
+
3174
+ if (
3175
+ disconnect.location
3176
+ ) {
3177
+ warn(
3178
+ `Location: ${
3179
+ disconnect.location
3180
+ }`
3181
+ );
3182
+ }
3183
+
3184
+ // Tutup DB handle kalau SQLite
3185
+ try {
3186
+ closeAuth?.();
3187
+ } catch {}
3188
+
3189
+ sock =
3190
+ null;
3191
+
3192
+ if (!shuttingDown) {
3193
+ if (disconnect.statusCode === 401) {
3194
+ warn('Login ditolak / session tidak valid.');
3195
+ warn('Hapus session lalu jalankan ulang.');
3196
+
3197
+ return;
3198
+ }
3199
+
3200
+ scheduleReconnect();
3201
+ }
3202
+ }
3203
+ }
3204
+ );
3205
+
3206
+ // --------------------------------------------------------
3207
+ // INCOMING MESSAGES
3208
+ // --------------------------------------------------------
3209
+
3210
+ sock.ev.on(
3211
+ 'messages.upsert',
3212
+ async ({ messages, type }) => {
3213
+
3214
+ if (!Array.isArray(messages)) {
3215
+ return;
3216
+ }
3217
+
3218
+ for (
3219
+ const message
3220
+ of messages
3221
+ ) {
3222
+
3223
+ if (!message) {
3224
+ continue;
3225
+ }
3226
+ saveMessage(message);
3227
+ const key =
3228
+ message.key;
3229
+
3230
+ const remoteJid =
3231
+ key?.remoteJid ??
3232
+ null;
3233
+
3234
+ const sender =
3235
+ key?.participant ??
3236
+ remoteJid ??
3237
+ null;
3238
+
3239
+ const fromMe =
3240
+ key?.fromMe === true;
3241
+
3242
+ const isGroup =
3243
+ typeof remoteJid ===
3244
+ 'string' &&
3245
+ remoteJid.endsWith(
3246
+ '@g.us'
3247
+ );
3248
+
3249
+ const status =
3250
+ isGroup
3251
+ ? 'GROUP'
3252
+ : 'PRIVATE';
3253
+
3254
+ const text =
3255
+ extractMessageText(
3256
+ message.message
3257
+ ) ||
3258
+ null;
3259
+
3260
+ const messageType =
3261
+ getMessageType(
3262
+ message.message
3263
+ ) ??
3264
+ 'unknown';
3265
+
3266
+ const senderName =
3267
+ message.pushName ??
3268
+ null;
3269
+
3270
+ let groupName =
3271
+ null;
3272
+
3273
+ // Ambil nama grup kalau pesan dari grup
3274
+ if (isGroup) {
3275
+
3276
+ try {
3277
+
3278
+ const metadata =
3279
+ await sock.groupMetadata(
3280
+ remoteJid
3281
+ );
3282
+
3283
+ groupName =
3284
+ metadata?.subject ??
3285
+ null;
3286
+
3287
+ } catch {
3288
+
3289
+ groupName =
3290
+ null;
3291
+
3292
+ }
3293
+ }
3294
+
3295
+ // Jangan tampilkan message kosong
3296
+ if (
3297
+ !message.message
3298
+ ) {
3299
+ continue;
3300
+ }
3301
+
3302
+ messageCount++;
3303
+
3304
+ console.log('');
3305
+ console.log(
3306
+ '\x1b[35m╔══════════════════════════════════════════════════╗\x1b[0m'
3307
+ );
3308
+
3309
+ console.log(
3310
+ '\x1b[35m║ 📩 PESAN MASUK BARU ║\x1b[0m'
3311
+ );
3312
+
3313
+ console.log(
3314
+ '\x1b[35m╚══════════════════════════════════════════════════╝\x1b[0m'
3315
+ );
3316
+
3317
+ console.log('');
3318
+
3319
+ console.log(
3320
+ `📌 STATUS : ${
3321
+ status
3322
+ }`
3323
+ );
3324
+
3325
+ console.log(
3326
+ `📨 EVENT : ${
3327
+ type ??
3328
+ 'unknown'
3329
+ }`
3330
+ );
3331
+
3332
+ console.log(
3333
+ `👤 PENGIRIM : ${
3334
+ sender ??
3335
+ 'null'
3336
+ }`
3337
+ );
3338
+
3339
+ console.log(
3340
+ `📛 NAMA : ${
3341
+ senderName ??
3342
+ 'null'
3343
+ }`
3344
+ );
3345
+
3346
+ console.log(
3347
+ `💬 CHAT : ${
3348
+ remoteJid ??
3349
+ 'null'
3350
+ }`
3351
+ );
3352
+
3353
+ console.log(
3354
+ `👥 GRUP : ${
3355
+ groupName ??
3356
+ 'null'
3357
+ }`
3358
+ );
3359
+
3360
+ console.log(
3361
+ `📦 JENIS PESAN : ${
3362
+ messageType
3363
+ }`
3364
+ );
3365
+
3366
+ console.log(
3367
+ `↩️ DARI SAYA : ${
3368
+ fromMe
3369
+ }`
3370
+ );
3371
+
3372
+ console.log('');
3373
+
3374
+ console.log(
3375
+ '📝 ISI PESAN'
3376
+ );
3377
+
3378
+ console.log(
3379
+ '──────────────────────────────────────────────────'
3380
+ );
3381
+
3382
+ console.log(
3383
+ text ??
3384
+ '[Tidak ada teks]'
3385
+ );
3386
+
3387
+ console.log(
3388
+ '──────────────────────────────────────────────────'
3389
+ );
3390
+
3391
+ console.log('');
3392
+
3393
+ console.log(
3394
+ `🆔 MESSAGE ID : ${
3395
+ key?.id ??
3396
+ 'null'
3397
+ }`
3398
+ );
3399
+
3400
+ console.log(
3401
+ `🕒 WAKTU : ${
3402
+ message.messageTimestamp
3403
+ ? new Date(
3404
+ Number(
3405
+ message.messageTimestamp
3406
+ ) * 1000
3407
+ ).toISOString()
3408
+ : 'null'
3409
+ }`
3410
+ );
3411
+
3412
+ console.log('');
3413
+
3414
+ console.log(
3415
+ '\x1b[35m════════════════════════════════════════════════════\x1b[0m'
3416
+ );
3417
+
3418
+ console.log('');
3419
+ }
3420
+ }
3421
+ );
3422
+
3423
+ // --------------------------------------------------------
3424
+ // GROUP / PRESENCE DEBUG
3425
+ // --------------------------------------------------------
3426
+
3427
+ sock.ev.on(
3428
+ 'presence.update',
3429
+ update => {
3430
+ debug(
3431
+ 'presence.update',
3432
+ update
3433
+ );
3434
+ }
3435
+ );
3436
+
3437
+ if (
3438
+ typeof closeAuth ===
3439
+ 'function'
3440
+ ) {
3441
+ // closeAuth dikelola saat connection close
3442
+ }
3443
+ }
3444
+
3445
+ // ------------------------------------------------------------
3446
+ // Pino lazy logger
3447
+ // ------------------------------------------------------------
3448
+
3449
+ function pinoLogger() {
3450
+
3451
+ // Import synchronous sudah tersedia lewat dynamic import?
3452
+ // Pino belum di-import di atas supaya script fleksibel.
3453
+ // Gunakan require tidak bisa pada ESM, jadi ambil dari cache
3454
+ // menggunakan createRequire.
3455
+ return loggerInstance;
3456
+ }
3457
+
3458
+ let loggerInstance;
3459
+
3460
+ {
3461
+ const {
3462
+ createRequire
3463
+ } = await import(
3464
+ 'module'
3465
+ );
3466
+
3467
+ const require =
3468
+ createRequire(
3469
+ import.meta.url
3470
+ );
3471
+
3472
+ const pino =
3473
+ require('pino');
3474
+
3475
+ loggerInstance =
3476
+ pino({
3477
+ level: LOG_LEVEL
3478
+ });
3479
+ }
3480
+
3481
+ // ------------------------------------------------------------
3482
+ // SHUTDOWN
3483
+ // ------------------------------------------------------------
3484
+
3485
+ async function shutdown() {
3486
+
3487
+ if (
3488
+ shuttingDown
3489
+ ) {
3490
+ return;
3491
+ }
3492
+
3493
+ shuttingDown =
3494
+ true;
3495
+
3496
+ console.log('');
3497
+ warn(
3498
+ 'Shutdown dimulai...'
3499
+ );
3500
+
3501
+ if (
3502
+ reconnectTimer
3503
+ ) {
3504
+ clearTimeout(
3505
+ reconnectTimer
3506
+ );
3507
+
3508
+ reconnectTimer =
3509
+ null;
3510
+ }
3511
+
3512
+ try {
3513
+ if (sock) {
3514
+ sock.end();
3515
+ }
3516
+ } catch {}
3517
+
3518
+ try {
3519
+ rl.close();
3520
+ } catch {}
3521
+
3522
+ info(
3523
+ 'Bot dihentikan.'
3524
+ );
3525
+
3526
+ process.exit(0);
3527
+ }
3528
+
3529
+ // ------------------------------------------------------------
3530
+ // GLOBAL ERRORS
3531
+ // ------------------------------------------------------------
3532
+
3533
+ process.on(
3534
+ 'uncaughtException',
3535
+ error => {
3536
+
3537
+ errlog(
3538
+ 'UNCAUGHT EXCEPTION',
3539
+ error
3540
+ );
3541
+ }
3542
+ );
3543
+
3544
+ process.on(
3545
+ 'unhandledRejection',
3546
+ reason => {
3547
+
3548
+ errlog(
3549
+ 'UNHANDLED REJECTION',
3550
+ reason
3551
+ );
3552
+ }
3553
+ );
3554
+
3555
+ process.on(
3556
+ 'SIGINT',
3557
+ async () => {
3558
+ await shutdown();
3559
+ }
3560
+ );
3561
+
3562
+ process.on(
3563
+ 'SIGTERM',
3564
+ async () => {
3565
+ await shutdown();
3566
+ }
3567
+ );
3568
+
3569
+ // ------------------------------------------------------------
3570
+ // START
3571
+ // ------------------------------------------------------------
3572
+
3573
+ console.clear();
3574
+
3575
+ console.log(
3576
+ '\x1b[36m=========================================='
3577
+ );
3578
+
3579
+ console.log(
3580
+ ' SMART WHATSAPP CLI'
3581
+ );
3582
+
3583
+ console.log(
3584
+ '==========================================\x1b[0m'
3585
+ );
3586
+
3587
+ console.log('');
3588
+
3589
+ let ACTIVE_SESSION_DIR = null;
3590
+
3591
+ async function startApp() {
3592
+ console.clear();
3593
+ console.log('\x1b[36m==========================================');
3594
+ console.log(' SMART WHATSAPP CLI');
3595
+ console.log('==========================================\x1b[0m\n');
3596
+
3597
+ // 1. Pilih session saat readline CLI belum aktif (Joystick/Panah Lancar)
3598
+ ACTIVE_SESSION_DIR = await selectOrCreateSession();
3599
+
3600
+ // 2. Inisialisasi Readline & Prompt HIJAU setelah selection selesai
3601
+ initReadline();
3602
+
3603
+ info(`Session aktif: ${path.basename(ACTIVE_SESSION_DIR)}`);
3604
+ console.log('');
3605
+
3606
+ // 3. Connect ke WhatsApp
3607
+ await connectWhatsApp(ACTIVE_SESSION_DIR);
3608
+ }
3609
+
3610
+ startApp();
3611
+