media-dl 2.2.0 → 2.3.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 (2) hide show
  1. package/bin/cli.js +165 -59
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -9,11 +9,14 @@ const { C, printHeader, renderProgressBar, askQuestion, rl } = require('./ui');
9
9
  // --- KONFIGURASI VISUAL (ANSI COLORS) ---
10
10
 
11
11
  const TOOLS_DIR = path.join(os.homedir(), '.media-dl');
12
+
12
13
  const isWindows = process.platform === 'win32';
13
14
  const isMac = process.platform === 'darwin';
15
+ const isTermux =
16
+ process.env.PREFIX && process.env.PREFIX.includes('com.termux');
14
17
 
15
- const YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
16
- const FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
18
+ let YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
19
+ let FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
17
20
 
18
21
  // State Aplikasi
19
22
  let safeMode = true;
@@ -21,10 +24,40 @@ let safeMode = true;
21
24
  if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
22
25
 
23
26
  function checkTools() {
24
- return {
25
- ytExists: fs.existsSync(YTDLP_PATH),
26
- ffExists: fs.existsSync(FFMPEG_PATH),
27
- };
27
+ let ytExists = fs.existsSync(YTDLP_PATH);
28
+ let ffExists = fs.existsSync(FFMPEG_PATH);
29
+
30
+ // Cek Global yt-dlp
31
+ if (!ytExists) {
32
+ try {
33
+ const cmd = isWindows ? 'where yt-dlp' : 'which yt-dlp';
34
+ const pathFound = execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'] })
35
+ .toString()
36
+ .trim()
37
+ .split('\n')[0];
38
+ if (pathFound) {
39
+ YTDLP_PATH = pathFound; // UPDATE PATH KE GLOBAL
40
+ ytExists = true;
41
+ }
42
+ } catch (e) {}
43
+ }
44
+
45
+ // Cek Global ffmpeg
46
+ if (!ffExists) {
47
+ try {
48
+ const cmd = isWindows ? 'where ffmpeg' : 'which ffmpeg';
49
+ const globalPath = execSync(cmd, { stdio: ['ignore', 'pipe', 'ignore'] })
50
+ .toString()
51
+ .trim()
52
+ .split('\n')[0];
53
+ if (globalPath) {
54
+ FFMPEG_PATH = globalPath; // UPDATE PATH KE GLOBAL
55
+ ffExists = true;
56
+ }
57
+ } catch (e) {}
58
+ }
59
+
60
+ return { ytExists, ffExists, allReady: ytExists && ffExists };
28
61
  }
29
62
 
30
63
  // --- INSTALLERS ---
@@ -36,20 +69,34 @@ async function installYtdlp() {
36
69
  const url = isWindows
37
70
  ? 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe'
38
71
  : 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
72
+
39
73
  try {
40
- if (isWindows) {
74
+ if (isTermux) {
75
+ // Di Termux, lebih disarankan menggunakan python/pip untuk stabilitas
76
+ console.log(`${C.dim}Menginstall yt-dlp via python...${C.reset}`);
77
+ execSync('pkg update && pkg install python ffmpeg -y', {
78
+ stdio: 'inherit',
79
+ });
80
+ execSync('pip install -U "yt-dlp[default]"', { stdio: 'inherit' });
81
+ console.log(
82
+ `\n${C.green}✅ yt-dlp berhasil diinstal di Termux!${C.reset}`,
83
+ );
84
+ } else if (isWindows) {
41
85
  execSync(
42
86
  `powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${YTDLP_PATH}'"`,
43
- { stdio: 'inherit' }
87
+ { stdio: 'inherit' },
44
88
  );
45
89
  } else {
90
+ // Linux (Ubuntu) & macOS
46
91
  execSync(`curl -L -# "${url}" -o "${YTDLP_PATH}"`, { stdio: 'inherit' });
47
92
  execSync(`chmod a+rx "${YTDLP_PATH}"`);
48
93
  }
49
- console.log(`\n${C.green}✅ yt-dlp berhasil dikonfigurasi!${C.reset}`);
94
+ if (!isTermux) {
95
+ console.log(`\n${C.green}✅ yt-dlp berhasil dikonfigurasi!${C.reset}`);
96
+ }
50
97
  } catch (e) {
51
98
  console.error(
52
- `\n${C.red}❌ Gagal mengunduh. Periksa koneksi internet Anda.${C.reset}`
99
+ `\n${C.red}❌ Gagal mengunduh. Periksa koneksi internet Anda.${C.reset}`,
53
100
  );
54
101
  }
55
102
  }
@@ -57,16 +104,25 @@ async function installYtdlp() {
57
104
  async function installFfmpeg() {
58
105
  printHeader('INSTALL FFmpeg');
59
106
  console.log(
60
- `${C.dim}FFmpeg diperlukan untuk kualitas 1080p+ dan konversi MP3.${C.reset}\n`
107
+ `${C.dim}FFmpeg diperlukan untuk kualitas 1080p+ dan konversi MP3.${C.reset}\n`,
61
108
  );
62
109
 
63
110
  try {
64
- if (isMac) {
111
+ if (isTermux) {
112
+ console.log(
113
+ `${C.blue}⏳ Mendeteksi Termux: Menginstall via pkg...${C.reset}`,
114
+ );
115
+ execSync('pkg update && pkg install ffmpeg -y', { stdio: 'inherit' });
116
+ console.log(
117
+ `\n${C.green}✅ FFmpeg berhasil diinstal di Termux!${C.reset}`,
118
+ );
119
+ } else if (isMac) {
120
+ // ... (Kode macOS Anda sudah benar)
65
121
  console.log(`${C.blue}⏳ Mengunduh FFmpeg untuk macOS...${C.reset}`);
66
122
  const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
67
123
  execSync(
68
124
  `curl -L -# "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" -o "${zipPath}"`,
69
- { stdio: 'inherit' }
125
+ { stdio: 'inherit' },
70
126
  );
71
127
  execSync(`unzip -o "${zipPath}" -d "${TOOLS_DIR}"`, { stdio: 'inherit' });
72
128
  execSync(`rm "${zipPath}"`);
@@ -74,14 +130,53 @@ async function installFfmpeg() {
74
130
  console.log(`\n${C.green}✅ FFmpeg aktif di macOS.${C.reset}`);
75
131
  } else if (isWindows) {
76
132
  console.log(
77
- `${C.yellow}ℹ️ Untuk Windows, disarankan mengunduh 'ffmpeg.exe' secara manual`
133
+ `${C.blue} Mengunduh FFmpeg untuk Windows (Essentials)...${C.reset}`,
134
+ );
135
+
136
+ const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
137
+ // Link direct ke build essentials agar file tidak terlalu besar
138
+ const url =
139
+ 'https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip';
140
+
141
+ // 1. Download menggunakan PowerShell
142
+ execSync(
143
+ `powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${zipPath}'"`,
144
+ { stdio: 'inherit' },
145
+ );
146
+
147
+ console.log(`${C.yellow}📦 Mengekstrak FFmpeg...${C.reset}`);
148
+
149
+ // 2. Ekstrak menggunakan perintah 'tar' (Bawaan Windows 10+)
150
+ // Kita hanya mengambil ffmpeg.exe dari dalam folder bin di zip tersebut
151
+ execSync(
152
+ `tar -xf "${zipPath}" -C "${TOOLS_DIR}" --strip-components 2 "*/bin/ffmpeg.exe" "*/bin/ffprobe.exe"`,
153
+ { stdio: 'inherit' },
154
+ );
155
+
156
+ // 3. Bersihkan file zip
157
+ if (fs.existsSync(zipPath)) fs.unlinkSync(zipPath);
158
+
159
+ console.log(
160
+ `\n${C.green}✅ FFmpeg berhasil diinstal di Windows!${C.reset}`,
161
+ );
162
+ } else {
163
+ // Asumsi Linux (Ubuntu/Debian)
164
+ console.log(
165
+ `${C.blue}⏳ Mendeteksi Linux: Menginstall via apt...${C.reset}`,
166
+ );
167
+ console.log(`${C.dim}Mungkin memerlukan password sudo.${C.reset}`);
168
+ execSync('sudo apt update && sudo apt install ffmpeg -y', {
169
+ stdio: 'inherit',
170
+ });
171
+ console.log(
172
+ `\n${C.green}✅ FFmpeg berhasil diinstal di Linux!${C.reset}`,
78
173
  );
79
- console.log(`dan letakkan di: ${C.white}${TOOLS_DIR}${C.reset}`);
80
174
  }
81
175
  } catch (e) {
82
176
  console.error(
83
- `${C.red}❌ Gagal menginstal FFmpeg secara otomatis.${C.reset}`
177
+ `${C.red}❌ Gagal menginstal FFmpeg secara otomatis.${C.reset}`,
84
178
  );
179
+ console.log(`${C.dim}Error: ${e.message}${C.reset}`);
85
180
  }
86
181
  }
87
182
 
@@ -94,7 +189,7 @@ function runSpawn(command, args) {
94
189
  const output = data.toString();
95
190
  // Regex untuk menangkap progress dari yt-dlp
96
191
  const progressMatch = output.match(
97
- /\[download\]\s+(\d+\.\d+)%\s+of\s+.*\s+at\s+([\d\w\./s]+)\s+ETA\s+([\d:]+)/
192
+ /\[download\]\s+(\d+\.\d+)%\s+of\s+.*\s+at\s+([\d\w\./s]+)\s+ETA\s+([\d:]+)/,
98
193
  );
99
194
 
100
195
  if (progressMatch) {
@@ -127,7 +222,7 @@ async function startDownload(videoURLFromArgs = null) {
127
222
  let { ytExists, ffExists } = checkTools();
128
223
  if (!ytExists) {
129
224
  console.log(
130
- `\n${C.red}❌ Engine yt-dlp tidak ditemukan. Silakan pilih menu Update/Install.${C.reset}`
225
+ `\n${C.red}❌ Engine yt-dlp tidak ditemukan. Silakan pilih menu Update/Install.${C.reset}`,
131
226
  );
132
227
  await askQuestion('Tekan Enter...');
133
228
  return mainMenu();
@@ -135,7 +230,7 @@ async function startDownload(videoURLFromArgs = null) {
135
230
 
136
231
  if (!ffExists) {
137
232
  console.log(
138
- `${C.yellow}⚠️ Peringatan: FFmpeg tidak ditemukan. Video mungkin tidak tergabung dengan audio.${C.reset}`
233
+ `${C.yellow}⚠️ Peringatan: FFmpeg tidak ditemukan. Video mungkin tidak tergabung dengan audio.${C.reset}`,
139
234
  );
140
235
  const cont = await askQuestion('Lanjutkan saja? (y/n): ');
141
236
  if (cont.toLowerCase() !== 'y') return mainMenu();
@@ -150,8 +245,12 @@ async function startDownload(videoURLFromArgs = null) {
150
245
 
151
246
  try {
152
247
  const rawInfo = execSync(
153
- `"${YTDLP_PATH}" --flat-playlist --print "%(playlist_title)s|%(title)s" "${videoURL}"`,
154
- { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
248
+ `"${YTDLP_PATH}" --flat-playlist --no-warnings --print "%(playlist_title)s|%(title)s" "${videoURL}"`,
249
+ {
250
+ encoding: 'utf-8',
251
+ stdio: ['ignore', 'pipe', 'ignore'],
252
+ timeout: 20000,
253
+ },
155
254
  );
156
255
  const lines = rawInfo.trim().split('\n');
157
256
  if (lines.length > 1 || videoURL.includes('playlist?list=')) {
@@ -159,20 +258,35 @@ async function startDownload(videoURLFromArgs = null) {
159
258
  playlistInfo.title = lines[0].split('|')[0] || 'Unduhan Playlist';
160
259
  playlistInfo.items = lines.map((l) => l.split('|')[1]).filter(Boolean);
161
260
  }
162
- } catch (e) {}
261
+ } catch (e) {
262
+ console.log(`\n${C.red}❌ Gagal menganalisa tautan.${C.reset}`);
263
+
264
+ if (e.message.includes('ETIMEDOUT')) {
265
+ console.log(
266
+ `${C.yellow}⚠️ Waktu analisa habis. Periksa koneksi internet Anda.${C.reset}`,
267
+ );
268
+ } else {
269
+ console.log(
270
+ `${C.yellow}⚠️ Pastikan link valid atau tidak diprivat/dihapus.${C.reset}`,
271
+ );
272
+ }
273
+
274
+ await askQuestion('\nTekan Enter untuk kembali ke menu...');
275
+ return mainMenu(); // Keluar dari fungsi dan balik ke menu utama
276
+ }
163
277
 
164
278
  let playlistSelection = null;
165
279
  if (playlistInfo.isPlaylist) {
166
280
  console.log(
167
- `\n${C.bgBlue}${C.bright} 📂 PLAYLIST TERDETEKSI: ${playlistInfo.title} ${C.reset}`
281
+ `\n${C.bgBlue}${C.bright} 📂 PLAYLIST TERDETEKSI: ${playlistInfo.title} ${C.reset}`,
168
282
  );
169
283
  playlistInfo.items.forEach((item, index) => {
170
284
  console.log(
171
- `${C.cyan}${(index + 1).toString().padStart(3, ' ')}.${C.reset} ${item}`
285
+ `${C.cyan}${(index + 1).toString().padStart(3, ' ')}.${C.reset} ${item}`,
172
286
  );
173
287
  });
174
288
  console.log(
175
- `\n${C.dim}Contoh pilih nomor: 1,3,5-10 atau biarkan kosong untuk semua.${C.reset}`
289
+ `\n${C.dim}Contoh pilih nomor: 1,3,5-10 atau biarkan kosong untuk semua.${C.reset}`,
176
290
  );
177
291
  const selectionInput = await askQuestion('Pilih nomor: ');
178
292
 
@@ -198,7 +312,7 @@ async function startDownload(videoURLFromArgs = null) {
198
312
  console.log(
199
313
  ` ${C.green}2.${C.reset} Audio Only (MP3) ${
200
314
  ffExists ? C.green + '✅' : C.red + '❌ (Butuh FFmpeg)'
201
- }`
315
+ }`,
202
316
  );
203
317
  const mode = await askQuestion('Pilihan: ');
204
318
 
@@ -208,7 +322,7 @@ async function startDownload(videoURLFromArgs = null) {
208
322
  if (playlistInfo.isPlaylist)
209
323
  outputDir = path.join(
210
324
  outputDir,
211
- playlistInfo.title.replace(/[\\/:"*?<>|]/g, '_')
325
+ playlistInfo.title.replace(/[\\/:"*?<>|]/g, '_'),
212
326
  );
213
327
  if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
214
328
 
@@ -230,7 +344,7 @@ async function startDownload(videoURLFromArgs = null) {
230
344
  '--max-sleep-interval',
231
345
  '10',
232
346
  '--user-agent',
233
- 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36'
347
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
234
348
  );
235
349
  }
236
350
 
@@ -240,7 +354,7 @@ async function startDownload(videoURLFromArgs = null) {
240
354
  if (mode === '2') {
241
355
  if (!ffExists) {
242
356
  console.log(
243
- `${C.red}❌ Error: Anda wajib menginstal FFmpeg untuk mengunduh audio.${C.reset}`
357
+ `${C.red}❌ Error: Anda wajib menginstal FFmpeg untuk mengunduh audio.${C.reset}`,
244
358
  );
245
359
  await askQuestion('Tekan Enter untuk kembali ke Menu Utama...');
246
360
  mainMenu();
@@ -249,7 +363,7 @@ async function startDownload(videoURLFromArgs = null) {
249
363
  } else {
250
364
  args.unshift(
251
365
  '-f',
252
- 'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best'
366
+ 'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best',
253
367
  );
254
368
  if (ffExists) args.unshift('--recode-video', 'mp4');
255
369
  }
@@ -268,8 +382,8 @@ async function startDownload(videoURLFromArgs = null) {
268
382
  isWindows
269
383
  ? `explorer "${outputDir}"`
270
384
  : isMac
271
- ? `open "${outputDir}"`
272
- : `xdg-open "${outputDir}"`
385
+ ? `open "${outputDir}"`
386
+ : `xdg-open "${outputDir}"`,
273
387
  );
274
388
  } catch (e) {}
275
389
  } else {
@@ -316,7 +430,7 @@ async function showSupport() {
316
430
  console.log(
317
431
  ` ${C.green}${f.icon}${C.reset} ${C.white}${f.title.padEnd(15)}${
318
432
  C.reset
319
- } ${C.dim}• ${f.desc}${C.reset}`
433
+ } ${C.dim}• ${f.desc}${C.reset}`,
320
434
  );
321
435
  });
322
436
 
@@ -335,7 +449,7 @@ async function showSupport() {
335
449
 
336
450
  links.forEach((l) => {
337
451
  console.log(
338
- ` ${C.bgBlue}${C.white}${l.label}${C.reset} ${C.blue}➜${C.reset} ${C.dim}${l.url}${C.reset}`
452
+ ` ${C.bgBlue}${C.white}${l.label}${C.reset} ${C.blue}➜${C.reset} ${C.dim}${l.url}${C.reset}`,
339
453
  );
340
454
  });
341
455
 
@@ -371,20 +485,20 @@ async function mainMenu() {
371
485
  // --- SEKSI NAVIGASI ---
372
486
  console.log(` ${C.bright}MAIN SERVICES${C.reset}`);
373
487
  console.log(
374
- ` ${C.cyan}1.${C.reset} 📥 Download Media ${C.dim}(Video, Music, Playlist)${C.reset}`
488
+ ` ${C.cyan}1.${C.reset} 📥 Download Media ${C.dim}(Video, Music, Playlist)${C.reset}`,
375
489
  );
376
490
  console.log(
377
491
  ` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(Sekarang: ${
378
492
  safeMode ? 'Aktif' : 'Nonaktif'
379
- })${C.reset}`
493
+ })${C.reset}`,
380
494
  );
381
495
 
382
496
  console.log(`\n ${C.bright}SYSTEM & INFO${C.reset}`);
383
497
  console.log(
384
- ` ${C.cyan}3.${C.reset} ⚙️ Maintenance & Update ${C.dim}(Update engine / Cleanup)${C.reset}`
498
+ ` ${C.cyan}3.${C.reset} ⚙️ Maintenance & Update ${C.dim}(Update engine / Cleanup)${C.reset}`,
385
499
  );
386
500
  console.log(
387
- ` ${C.cyan}4.${C.reset} ❤️ Tentang Aplikasi ${C.dim}(Dukungan & Fitur)${C.reset}`
501
+ ` ${C.cyan}4.${C.reset} ❤️ Tentang Aplikasi ${C.dim}(Dukungan & Fitur)${C.reset}`,
388
502
  );
389
503
  console.log(` ${C.cyan}0.${C.reset} 🚪 Keluar`);
390
504
 
@@ -402,7 +516,7 @@ async function mainMenu() {
402
516
  console.log(
403
517
  `\n${C.yellow} 🛡️ Safe Mode telah ${
404
518
  safeMode ? 'DIAKTIFKAN' : 'DINONAKTIFKAN'
405
- }${C.reset}`
519
+ }${C.reset}`,
406
520
  );
407
521
  setTimeout(() => mainMenu(), 800);
408
522
  break;
@@ -415,10 +529,10 @@ async function mainMenu() {
415
529
  case '0':
416
530
  console.log(`\n${C.cyan}━${'━'.repeat(48)}${C.reset}`);
417
531
  console.log(
418
- ` ${C.bright}${C.white}Terima kasih telah menggunakan MEDIA-DL!${C.reset}`
532
+ ` ${C.bright}${C.white}Terima kasih telah menggunakan MEDIA-DL!${C.reset}`,
419
533
  );
420
534
  console.log(
421
- ` ${C.green}✨ Semoga Anda sukses, jaya, dan sehat selalu! ✨${C.reset}`
535
+ ` ${C.green}✨ Semoga Anda sukses, jaya, dan sehat selalu! ✨${C.reset}`,
422
536
  );
423
537
  console.log(`${C.cyan}━${'━'.repeat(48)}${C.reset}\n`);
424
538
 
@@ -442,36 +556,28 @@ async function cleanUp() {
442
556
  fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
443
557
  }
444
558
 
445
- function checkTools() {
446
- return {
447
- ytExists: fs.existsSync(YTDLP_PATH),
448
- ffExists: fs.existsSync(FFMPEG_PATH),
449
- allReady: fs.existsSync(YTDLP_PATH) && fs.existsSync(FFMPEG_PATH),
450
- };
451
- }
452
-
453
559
  async function firstTimeSetup() {
454
560
  while (true) {
455
561
  const { ytExists, ffExists } = checkTools();
456
562
  printHeader(
457
563
  'FIRST-TIME SETUP',
458
- 'Komponen diperlukan untuk menjalankan aplikasi'
564
+ 'Komponen diperlukan untuk menjalankan aplikasi',
459
565
  );
460
566
 
461
567
  console.log(`${C.white}Status Instalasi:${C.reset}`);
462
568
  console.log(
463
569
  ` [${ytExists ? C.green + '✓' : C.red + '✗'}${
464
570
  C.reset
465
- }] Engine yt-dlp (Wajib)`
571
+ }] Engine yt-dlp (Wajib)`,
466
572
  );
467
573
  console.log(
468
574
  ` [${ffExists ? C.green + '✓' : C.red + '✗'}${
469
575
  C.reset
470
- }] FFmpeg (Direkomendasikan)`
576
+ }] FFmpeg (Direkomendasikan)`,
471
577
  );
472
578
 
473
579
  console.log(
474
- `\n${C.yellow}Aplikasi belum siap digunakan. Pilih opsi:${C.reset}`
580
+ `\n${C.yellow}Aplikasi belum siap digunakan. Pilih opsi:${C.reset}`,
475
581
  );
476
582
  console.log(` ${C.cyan}1.${C.reset} Install Semua Komponen Otomatis`);
477
583
  console.log(` ${C.cyan}0.${C.reset} Keluar dari Aplikasi`);
@@ -485,7 +591,7 @@ async function firstTimeSetup() {
485
591
  const status = checkTools();
486
592
  if (status.ytExists) {
487
593
  console.log(
488
- `\n${C.green}✨ Setup Selesai! Membuka Menu Utama...${C.reset}`
594
+ `\n${C.green}✨ Setup Selesai! Membuka Menu Utama...${C.reset}`,
489
595
  );
490
596
  await new Promise((r) => setTimeout(r, 1500));
491
597
  return mainMenu(); // Berhasil, lanjut ke menu utama
@@ -504,19 +610,19 @@ async function systemMaintenance() {
504
610
  const { ytExists, ffExists } = checkTools();
505
611
  printHeader(
506
612
  'SYSTEM MAINTENANCE',
507
- 'Update engine atau bersihkan file sistem'
613
+ 'Update engine atau bersihkan file sistem',
508
614
  );
509
615
 
510
616
  console.log(`${C.white}Versi Terinstal:${C.reset}`);
511
617
  console.log(
512
618
  ` • yt-dlp : ${ytExists ? C.green + 'Ready' : C.red + 'Not Found'}${
513
619
  C.reset
514
- }`
620
+ }`,
515
621
  );
516
622
  console.log(
517
623
  ` • FFmpeg : ${ffExists ? C.green + 'Ready' : C.red + 'Not Found'}${
518
624
  C.reset
519
- }`
625
+ }`,
520
626
  );
521
627
 
522
628
  console.log(`\n${C.bright}Opsi Pemeliharaan:${C.reset}`);
@@ -535,12 +641,12 @@ async function systemMaintenance() {
535
641
 
536
642
  case '2':
537
643
  const confirm = await askQuestion(
538
- `${C.bgRed}${C.white} KONFIRMASI ${C.reset} Hapus semua tools? (y/n): `
644
+ `${C.bgRed}${C.white} KONFIRMASI ${C.reset} Hapus semua tools? (y/n): `,
539
645
  );
540
646
  if (confirm.toLowerCase() === 'y') {
541
647
  await cleanUp(); // Panggil fungsi penghapusan folder
542
648
  console.log(
543
- `${C.yellow}Sistem dibersihkan. Anda akan diarahkan ke Setup Wizard.${C.reset}`
649
+ `${C.yellow}Sistem dibersihkan. Anda akan diarahkan ke Setup Wizard.${C.reset}`,
544
650
  );
545
651
  await askQuestion('Tekan Enter...');
546
652
  return bootstrap(); // Kembali ke pengecekan awal
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "media-dl",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "description": "CLI Downloader video/audio lintas platform menggunakan yt-dlp.",
5
5
  "main": "bin/cli.js",
6
6
  "scripts": {