media-dl 2.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,17 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const { spawn, execSync } = require('child_process');
4
- const readline = require('readline');
5
4
  const path = require('path');
6
5
  const fs = require('fs');
7
6
  const os = require('os');
7
+ const { C, printHeader, renderProgressBar, askQuestion, rl } = require('./ui');
8
8
 
9
- const rl = readline.createInterface({
10
- input: process.stdin,
11
- output: process.stdout,
12
- });
9
+ // --- KONFIGURASI VISUAL (ANSI COLORS) ---
13
10
 
14
- // Penentuan folder Tools berdasarkan OS
15
11
  const TOOLS_DIR = path.join(os.homedir(), '.media-dl');
16
12
  const isWindows = process.platform === 'win32';
17
13
  const isMac = process.platform === 'darwin';
@@ -19,11 +15,10 @@ const isMac = process.platform === 'darwin';
19
15
  const YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
20
16
  const FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
21
17
 
22
- if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
18
+ // State Aplikasi
19
+ let safeMode = true;
23
20
 
24
- function askQuestion(query) {
25
- return new Promise((resolve) => rl.question(query, resolve));
26
- }
21
+ if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
27
22
 
28
23
  function checkTools() {
29
24
  return {
@@ -32,8 +27,13 @@ function checkTools() {
32
27
  };
33
28
  }
34
29
 
30
+ // --- INSTALLERS ---
35
31
  async function installYtdlp() {
36
- console.log('\n⏳ Mengunduh yt-dlp...');
32
+
33
+ if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
34
+
35
+ printHeader('INSTALL / UPDATE YT-DLP');
36
+ console.log(`${C.blue}⏳ Sedang mengunduh engine terbaru...${C.reset}`);
37
37
  const url = isWindows
38
38
  ? 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe'
39
39
  : 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
@@ -47,16 +47,23 @@ async function installYtdlp() {
47
47
  execSync(`curl -L -# "${url}" -o "${YTDLP_PATH}"`, { stdio: 'inherit' });
48
48
  execSync(`chmod a+rx "${YTDLP_PATH}"`);
49
49
  }
50
- console.log('✅ yt-dlp berhasil diinstal.');
50
+ console.log(`\n${C.green}✅ yt-dlp berhasil dikonfigurasi!${C.reset}`);
51
51
  } catch (e) {
52
- console.error('❌ Gagal mengunduh yt-dlp.');
52
+ console.error(
53
+ `\n${C.red}❌ Gagal mengunduh. Periksa koneksi internet Anda.${C.reset}`
54
+ );
53
55
  }
54
56
  }
55
57
 
56
58
  async function installFfmpeg() {
57
- console.log('\n⏳ Mengunduh FFmpeg (Diperlukan untuk konversi)...');
59
+ printHeader('INSTALL FFmpeg');
60
+ console.log(
61
+ `${C.dim}FFmpeg diperlukan untuk kualitas 1080p+ dan konversi MP3.${C.reset}\n`
62
+ );
63
+
58
64
  try {
59
65
  if (isMac) {
66
+ console.log(`${C.blue}⏳ Mengunduh FFmpeg untuk macOS...${C.reset}`);
60
67
  const zipPath = path.join(TOOLS_DIR, 'ffmpeg.zip');
61
68
  execSync(
62
69
  `curl -L -# "https://evermeet.cx/ffmpeg/getrelease/ffmpeg/zip" -o "${zipPath}"`,
@@ -65,47 +72,86 @@ async function installFfmpeg() {
65
72
  execSync(`unzip -o "${zipPath}" -d "${TOOLS_DIR}"`, { stdio: 'inherit' });
66
73
  execSync(`rm "${zipPath}"`);
67
74
  execSync(`chmod a+rx "${FFMPEG_PATH}"`);
68
- console.log('✅ FFmpeg berhasil disiapkan.');
75
+ console.log(`\n${C.green}✅ FFmpeg aktif di macOS.${C.reset}`);
76
+ } else if (isWindows) {
77
+ console.log(
78
+ `${C.yellow}ℹ️ Untuk Windows, disarankan mengunduh 'ffmpeg.exe' secara manual`
79
+ );
80
+ console.log(`dan letakkan di: ${C.white}${TOOLS_DIR}${C.reset}`);
69
81
  }
70
82
  } catch (e) {
71
- console.error('❌ Gagal instal FFmpeg.');
83
+ console.error(
84
+ `${C.red}❌ Gagal menginstal FFmpeg secara otomatis.${C.reset}`
85
+ );
72
86
  }
73
87
  }
74
88
 
75
- function parseSelection(input, max) {
76
- if (!input || input.toLowerCase() === 'all') return null;
77
- const selected = new Set();
78
- const parts = input.split(',');
79
- parts.forEach((part) => {
80
- if (part.includes('-')) {
81
- const [start, end] = part.split('-').map(Number);
82
- for (let i = start; i <= end; i++) if (i > 0 && i <= max) selected.add(i);
83
- } else {
84
- const num = parseInt(part.trim());
85
- if (num > 0 && num <= max) selected.add(num);
86
- }
89
+ function runSpawn(command, args) {
90
+ return new Promise((resolve) => {
91
+ const proc = spawn(command, args);
92
+ let lastOutput = '';
93
+
94
+ proc.stdout.on('data', (data) => {
95
+ const output = data.toString();
96
+ // Regex untuk menangkap progress dari yt-dlp
97
+ const progressMatch = output.match(
98
+ /\[download\]\s+(\d+\.\d+)%\s+of\s+.*\s+at\s+([\d\w\./s]+)\s+ETA\s+([\d:]+)/
99
+ );
100
+
101
+ if (progressMatch) {
102
+ const [_, percent, speed, eta] = progressMatch;
103
+ renderProgressBar(parseFloat(percent), speed, eta);
104
+ } else {
105
+ // Jika bukan bar, print normal (misal: info merging/ffmpeg)
106
+ if (output.trim() && !output.includes('[download]')) {
107
+ process.stdout.write(`\n${C.dim}${output.trim()}${C.reset}\n`);
108
+ }
109
+ }
110
+ });
111
+
112
+ proc.stderr.on('data', (data) => {
113
+ const err = data.toString();
114
+ if (!err.includes('WARNING')) {
115
+ process.stdout.write(`\n${C.red}⚠️ ${err}${C.reset}`);
116
+ }
117
+ });
118
+
119
+ proc.on('close', (code) => {
120
+ process.stdout.write('\n'); // Baris baru setelah selesai
121
+ resolve(code);
122
+ });
87
123
  });
88
- return Array.from(selected).join(',');
89
124
  }
90
125
 
126
+ // --- DOWNLOAD ENGINE ---
91
127
  async function startDownload() {
92
128
  let { ytExists, ffExists } = checkTools();
93
129
  if (!ytExists) {
94
- console.log('\n⚠️ yt-dlp belum terpasang.');
95
- const ans = await askQuestion('Instal sekarang? (y/n): ');
96
- if (ans.toLowerCase() === 'y') await installYtdlp();
97
- if (!fs.existsSync(YTDLP_PATH)) return mainMenu();
130
+ console.log(
131
+ `\n${C.red}❌ Engine yt-dlp tidak ditemukan. Silakan pilih menu Update/Install.${C.reset}`
132
+ );
133
+ await askQuestion('Tekan Enter...');
134
+ return mainMenu();
135
+ }
136
+
137
+ if (!ffExists) {
138
+ console.log(
139
+ `${C.yellow}⚠️ Peringatan: FFmpeg tidak ditemukan. Video mungkin tidak tergabung dengan audio.${C.reset}`
140
+ );
141
+ const cont = await askQuestion('Lanjutkan saja? (y/n): ');
142
+ if (cont.toLowerCase() !== 'y') return mainMenu();
98
143
  }
99
144
 
100
- const videoURL = await askQuestion('\n🔗 Masukkan Link (Video/Playlist): ');
145
+ const videoURL = await askQuestion('Masukkan Link (Video/Playlist): ');
146
+ if (!videoURL) return mainMenu();
101
147
 
102
- console.log('Mengecek link...');
148
+ console.log(`${C.dim}Menganalisa tautan...${C.reset}`);
103
149
  let playlistInfo = { isPlaylist: false, title: '', items: [] };
104
150
 
105
151
  try {
106
152
  const rawInfo = execSync(
107
- `"${YTDLP_PATH}" --flat-playlist --print "%(playlist_title)s|%(index)s. %(title)s" "${videoURL}"`,
108
- { encoding: 'utf-8' }
153
+ `"${YTDLP_PATH}" --flat-playlist --print "%(playlist_title)s|%(title)s" "${videoURL}"`,
154
+ { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
109
155
  );
110
156
  const lines = rawInfo.trim().split('\n');
111
157
  if (lines.length > 1 || videoURL.includes('playlist?list=')) {
@@ -117,149 +163,410 @@ async function startDownload() {
117
163
 
118
164
  let playlistSelection = null;
119
165
  if (playlistInfo.isPlaylist) {
120
- console.log(`\n📂 PLAYLIST TERDETEKSI: ${playlistInfo.title}`);
121
- playlistInfo.items.forEach((item) => console.log(item));
122
- const selectionInput = await askQuestion(
123
- '\nPilih nomor video (contoh: 1,3,5-10) atau tekan Enter untuk semua: '
166
+ console.log(
167
+ `\n${C.bgBlue}${C.bright} 📂 PLAYLIST TERDETEKSI: ${playlistInfo.title} ${C.reset}`
124
168
  );
125
- playlistSelection = parseSelection(
126
- selectionInput,
127
- playlistInfo.items.length
169
+ playlistInfo.items.forEach((item, index) => {
170
+ console.log(
171
+ `${C.cyan}${(index + 1).toString().padStart(3, ' ')}.${C.reset} ${item}`
172
+ );
173
+ });
174
+ console.log(
175
+ `\n${C.dim}Contoh pilih nomor: 1,3,5-10 atau biarkan kosong untuk semua.${C.reset}`
128
176
  );
177
+ const selectionInput = await askQuestion('Pilih nomor: ');
178
+
179
+ // Parsing nomor playlist
180
+ if (selectionInput) {
181
+ const selected = new Set();
182
+ selectionInput.split(',').forEach((p) => {
183
+ if (p.includes('-')) {
184
+ const [s, e] = p.split('-').map(Number);
185
+ for (let i = s; i <= e; i++)
186
+ if (i > 0 && i <= playlistInfo.items.length) selected.add(i);
187
+ } else {
188
+ const n = parseInt(p.trim());
189
+ if (n > 0 && n <= playlistInfo.items.length) selected.add(n);
190
+ }
191
+ });
192
+ playlistSelection = Array.from(selected).join(',');
193
+ }
129
194
  }
130
195
 
131
- console.log('\n[PILIH FORMAT]');
132
- console.log('1. Video MP4 (Sangat Kompatibel Mac/QuickTime)');
133
- console.log('2. Audio (MP3) ' + (ffExists ? '✅' : '❌ (Butuh FFmpeg)'));
196
+ console.log(`\n${C.bright} [ PILIH FORMAT ]${C.reset}`);
197
+ console.log(` ${C.green}1.${C.reset} Video (Kualitas Terbaik/MP4)`);
198
+ console.log(
199
+ ` ${C.green}2.${C.reset} Audio Only (MP3) ${
200
+ ffExists ? C.green + '✅' : C.red + '❌ (Butuh FFmpeg)'
201
+ }`
202
+ );
134
203
  const mode = await askQuestion('Pilihan: ');
135
204
 
136
205
  const baseDir = path.join(os.homedir(), 'Downloads', 'media-dl');
137
206
  const subFolder = mode === '2' ? 'audio' : 'video';
138
- let finalOutputDir = path.join(baseDir, subFolder);
139
-
140
- if (playlistInfo.isPlaylist) {
141
- const folderName = playlistInfo.title.replace(/[\\/:"*?<>|]/g, '_');
142
- finalOutputDir = path.join(finalOutputDir, folderName);
143
- }
144
-
145
- if (!fs.existsSync(finalOutputDir))
146
- fs.mkdirSync(finalOutputDir, { recursive: true });
207
+ let outputDir = path.join(baseDir, subFolder);
208
+ if (playlistInfo.isPlaylist)
209
+ outputDir = path.join(
210
+ outputDir,
211
+ playlistInfo.title.replace(/[\\/:"*?<>|]/g, '_')
212
+ );
213
+ if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
147
214
 
148
- const qtFormat =
149
- 'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best';
150
215
  let args = [
151
216
  '--ffmpeg-location',
152
217
  FFMPEG_PATH,
153
218
  '-o',
154
- `${finalOutputDir}/%(title).100s.%(ext)s`,
219
+ `${outputDir}/%(title).100s.%(ext)s`,
155
220
  videoURL,
156
221
  ];
157
222
 
158
- if (playlistSelection) {
159
- args.push('--playlist-items', playlistSelection);
160
- } else if (!playlistInfo.isPlaylist) {
161
- args.push('--no-playlist');
223
+ // Integrasi Safe Mode
224
+ if (safeMode) {
225
+ args.push(
226
+ '--ratelimit',
227
+ '5M',
228
+ '--sleep-interval',
229
+ '3',
230
+ '--max-sleep-interval',
231
+ '10',
232
+ '--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'
234
+ );
162
235
  }
163
236
 
237
+ if (playlistSelection) args.push('--playlist-items', playlistSelection);
238
+ else if (!playlistInfo.isPlaylist) args.push('--no-playlist');
239
+
164
240
  if (mode === '2') {
165
241
  if (!ffExists) {
166
- console.log('❌ Butuh FFmpeg.');
242
+ console.log(
243
+ `${C.red}❌ Error: Anda wajib menginstal FFmpeg untuk mengunduh audio.${C.reset}`
244
+ );
245
+ await askQuestion('Kembali...');
167
246
  return mainMenu();
168
247
  }
169
248
  args.unshift('-x', '--audio-format', 'mp3');
170
249
  } else {
171
- console.log('\n[PILIH KUALITAS]');
172
- console.log('1. Terbaik (Auto-Conversion ke MP4)');
173
- console.log('2. 1080p');
174
- console.log('3. 720p');
175
- const res = await askQuestion('Pilih: ');
176
-
177
- let fCode = qtFormat;
178
- if (res === '2')
179
- fCode =
180
- 'bestvideo[height<=1080][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=1080]/best';
181
- if (res === '3')
182
- fCode =
183
- 'bestvideo[height<=720][vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[height<=720]/best';
184
-
185
- args.unshift('-f', fCode);
250
+ args.unshift(
251
+ '-f',
252
+ 'bestvideo[vcodec^=avc1]+bestaudio[acodec^=mp4a]/best[vcodec^=avc1]/best'
253
+ );
186
254
  if (ffExists) args.unshift('--recode-video', 'mp4');
187
255
  }
188
256
 
189
- console.log('\n🚀 Memulai proses unduhan...');
190
- const download = spawn(YTDLP_PATH, args);
257
+ args.push('--no-mtime');
191
258
 
192
- download.stdout.on('data', (data) => process.stdout.write(data));
193
- download.stderr.on('data', (data) => process.stderr.write(data));
259
+ console.log(`\n${C.bgBlue}${C.bright} 🚀 MEMULAI PROSES... ${C.reset}\n`);
194
260
 
195
- download.on('close', (code) => {
196
- if (code === 0) {
197
- console.log(`\n✅ BERHASIL! File disimpan di: ${finalOutputDir}`);
261
+ const code = await runSpawn(YTDLP_PATH, args); // Menunggu sampai benar-benar selesai
262
+
263
+ if (code === 0) {
264
+ console.log(`\n${C.green}✨ SELESAI! Cek folder: ${outputDir}${C.reset}`);
265
+ // Auto-open folder
266
+ try {
198
267
  execSync(
199
- isMac ? `open "${finalOutputDir}"` : `explorer "${finalOutputDir}"`
268
+ isWindows
269
+ ? `explorer "${outputDir}"`
270
+ : isMac
271
+ ? `open "${outputDir}"`
272
+ : `xdg-open "${outputDir}"`
200
273
  );
201
- } else {
202
- console.log('\n❌ Gagal.');
203
- }
204
- mainMenu();
205
- });
274
+ } catch (e) {}
275
+ } else {
276
+ console.log(`\n${C.red}❌ Terjadi kesalahan saat mengunduh.${C.reset}`);
277
+ }
206
278
  }
207
279
 
208
280
  async function showSupport() {
209
- console.log('\n====================================');
210
- console.log(' ❤️ SUPPORT DEVELOPER ');
211
- console.log('====================================');
212
- console.log('Terima kasih telah menggunakan MEDIA-DL!');
213
- console.log('Dukungan Anda sangat berarti bagi pengembangan skrip ini.');
214
- console.log(
215
- '\n* Beli Kopi: https://app.midtrans.com/payment-links/coffee-developer'
216
- );
217
- console.log(
218
- '* 🍕 Beli Pizza: https://app.midtrans.com/payment-links/pizza-developer'
219
- );
220
- console.log('------------------------------------');
221
- await askQuestion('\nTekan Enter untuk kembali ke Menu Utama...');
281
+ // Menggunakan 2 parameter: Judul dan Summary
282
+ printHeader('TENTANG APLIKASI', 'Media-DL Manager Pro v2.0.0 - 2026');
283
+
284
+ // --- SEKSI FITUR ---
285
+ console.log(` ${C.bright}${C.cyan}OVERVIEW${C.reset}`);
286
+ console.log(` Terima kasih telah memilih MEDIA-DL. Skrip ini dirancang`);
287
+ console.log(` untuk memudahkan manajemen unduhan media secara lokal.\n`);
288
+
289
+ console.log(` ${C.bright}${C.cyan}FITUR UNGGULAN${C.reset}`);
290
+ const features = [
291
+ {
292
+ icon: '',
293
+ title: 'High Quality',
294
+ desc: 'Mendukung hingga 4K & Audio 320kbps',
295
+ },
296
+ {
297
+ icon: '✦',
298
+ title: 'Multi-Source',
299
+ desc: 'YouTube, TikTok, IG Reels, & Shorts',
300
+ },
301
+ {
302
+ icon: '✦',
303
+ title: 'Batch Mode',
304
+ desc: 'Mendukung unduhan Playlist secara massal',
305
+ },
306
+ {
307
+ icon: '✦',
308
+ title: 'Safe Guard',
309
+ desc: 'Mode proteksi agar akun/IP tidak terblokir',
310
+ },
311
+ ];
312
+
313
+ features.forEach((f) => {
314
+ console.log(
315
+ ` ${C.green}${f.icon}${C.reset} ${C.white}${f.title.padEnd(15)}${
316
+ C.reset
317
+ } ${C.dim}• ${f.desc}${C.reset}`
318
+ );
319
+ });
320
+
321
+ console.log('\n' + '─'.repeat(52));
322
+
323
+ // --- SEKSI DUKUNGAN ---
324
+ console.log(`\n ${C.bright}${C.magenta}DUKUNGAN & DONASI${C.reset}`);
325
+ console.log(` Dukungan Anda sangat membantu pengembang untuk terus`);
326
+ console.log(` memperbarui engine dan fitur aplikasi ini.\n`);
327
+
328
+ // Menampilkan Link dengan label background agar menonjol
329
+ const links = [
330
+ { label: ' ☕ BELI KOPI ', url: 'https://app.midtrans.com/coffee' },
331
+ { label: ' 🍕 BELI PIZZA', url: 'https://app.midtrans.com/pizza' },
332
+ ];
333
+
334
+ links.forEach((l) => {
335
+ console.log(
336
+ ` ${C.bgBlue}${C.white}${l.label}${C.reset} ${C.blue}➜${C.reset} ${C.dim}${l.url}${C.reset}`
337
+ );
338
+ });
339
+
340
+ console.log(`\n${C.cyan}${'━'.repeat(52)}${C.reset}`);
341
+
342
+ await askQuestion('Tekan Enter untuk kembali ke Menu Utama...');
222
343
  mainMenu();
223
344
  }
224
345
 
225
346
  async function mainMenu() {
226
347
  const { ytExists, ffExists } = checkTools();
227
- console.log('\n====================================');
228
- console.log(' MEDIA-DL MANAGER 2026 ');
229
- console.log('====================================');
348
+
349
+ // Menggunakan 2 parameter: Judul dan Summary status singkat
350
+ printHeader('MEDIA-DL PRO 2026', 'Pusat Kendali Unduhan Media Lokal');
351
+
352
+ // --- SEKSI DASHBOARD (INFO SISTEM) ---
353
+ const statusYt = ytExists
354
+ ? `${C.green}Ready${C.reset}`
355
+ : `${C.red}Not Found${C.reset}`;
356
+ const statusFf = ffExists
357
+ ? `${C.green}Ready${C.reset}`
358
+ : `${C.yellow}Missing${C.reset}`;
359
+ const safeBadge = safeMode
360
+ ? `${C.bgBlue}${C.white} ON ${C.reset}`
361
+ : `${C.bgRed}${C.white} OFF ${C.reset}`;
362
+
363
+ console.log(` ${C.bright}SYSTEM STATUS${C.reset}`);
364
+ console.log(` 🤖 Engine : [ ${statusYt} ] | 🎬 FFmpeg : [ ${statusFf} ]`);
365
+ console.log(` 🛡️ Safe Mode Guard : ${safeBadge}\n`);
366
+
367
+ console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
368
+
369
+ // --- SEKSI NAVIGASI ---
370
+ console.log(` ${C.bright}MAIN SERVICES${C.reset}`);
230
371
  console.log(
231
- ` OS : ${process.platform} | yt-dlp: ${
232
- ytExists ? '✅' : '❌'
233
- } | ffmpeg: ${ffExists ? '✅' : '❌'}`
372
+ ` ${C.cyan}1.${C.reset} 📥 Download Media ${C.dim}(Video, Music, Playlist)${C.reset}`
234
373
  );
235
- console.log('------------------------------------');
236
- console.log(' 1. 📥 Download Media (Single/Playlist)');
237
- console.log(' 2. ⚙️ Update yt-dlp');
238
- console.log(' 3. 🔨 Instal FFmpeg (macOS)');
239
- console.log(' 4. ❤️ Support Developer');
240
- console.log(' 5. 🗑️ Uninstall & Hapus Data');
241
- console.log(' 6. 🚪 Keluar');
242
-
243
- const choice = await askQuestion('\nPilih menu: ');
244
- if (choice === '1') await startDownload();
245
- else if (choice === '2') {
246
- await installYtdlp();
247
- mainMenu();
248
- } else if (choice === '3') {
249
- await installFfmpeg();
250
- mainMenu();
251
- } else if (choice === '4') await showSupport();
252
- else if (choice === '5') {
253
- const confirm = await askQuestion('Hapus semua? (y/n): ');
254
- if (confirm.toLowerCase() === 'y') {
255
- fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
256
- console.log('✅ Folder tools dibersihkan.');
374
+ console.log(
375
+ ` ${C.cyan}2.${C.reset} 🛡️ Toggle Safe Mode ${C.dim}(Sekarang: ${
376
+ safeMode ? 'Aktif' : 'Nonaktif'
377
+ })${C.reset}`
378
+ );
379
+
380
+ console.log(`\n ${C.bright}SYSTEM & INFO${C.reset}`);
381
+ console.log(
382
+ ` ${C.cyan}3.${C.reset} ⚙️ Maintenance & Update ${C.dim}(Update engine / Cleanup)${C.reset}`
383
+ );
384
+ console.log(
385
+ ` ${C.cyan}4.${C.reset} ❤️ Tentang Aplikasi ${C.dim}(Dukungan & Fitur)${C.reset}`
386
+ );
387
+ console.log(` ${C.cyan}0.${C.reset} 🚪 Keluar`);
388
+
389
+ console.log(` ${C.cyan}━${'━'.repeat(48)}${C.reset}`);
390
+
391
+ const choice = await askQuestion('\nPilih menu (0-4): ');
392
+
393
+ switch (choice) {
394
+ case '1':
395
+ await startDownload();
396
+ break;
397
+ case '2':
398
+ safeMode = !safeMode;
399
+ // Berikan feedback visual singkat sebelum refresh menu
400
+ console.log(
401
+ `\n${C.yellow} 🛡️ Safe Mode telah ${
402
+ safeMode ? 'DIAKTIFKAN' : 'DINONAKTIFKAN'
403
+ }${C.reset}`
404
+ );
405
+ setTimeout(() => mainMenu(), 800);
406
+ break;
407
+ case '3':
408
+ await systemMaintenance();
409
+ break;
410
+ case '4':
411
+ await showSupport();
412
+ break;
413
+ case '0':
414
+ console.log(`\n${C.cyan}━${'━'.repeat(48)}${C.reset}`);
415
+ console.log(
416
+ ` ${C.bright}${C.white}Terima kasih telah menggunakan MEDIA-DL!${C.reset}`
417
+ );
418
+ console.log(
419
+ ` ${C.green}✨ Semoga Anda sukses, jaya, dan sehat selalu! ✨${C.reset}`
420
+ );
421
+ console.log(`${C.cyan}━${'━'.repeat(48)}${C.reset}\n`);
422
+
423
+ // Memberikan jeda sebentar sebelum benar-benar menutup terminal
424
+ setTimeout(() => {
425
+ rl.close();
426
+ process.exit(0);
427
+ }, 1000);
428
+
429
+ break;
430
+ default:
431
+ // Jika salah input, tampilkan kembali menu
432
+ mainMenu();
433
+ break;
434
+ }
435
+ }
436
+
437
+ async function cleanUp() {
438
+ const conf = await askQuestion('Hapus semua file tools? (y/n): ');
439
+ if (conf.toLowerCase() === 'y')
440
+ fs.rmSync(TOOLS_DIR, { recursive: true, force: true });
441
+ }
442
+
443
+ function checkTools() {
444
+ return {
445
+ ytExists: fs.existsSync(YTDLP_PATH),
446
+ ffExists: fs.existsSync(FFMPEG_PATH),
447
+ allReady: fs.existsSync(YTDLP_PATH) && fs.existsSync(FFMPEG_PATH),
448
+ };
449
+ }
450
+
451
+ async function firstTimeSetup() {
452
+ while (true) {
453
+ const { ytExists, ffExists } = checkTools();
454
+ printHeader(
455
+ 'FIRST-TIME SETUP',
456
+ 'Komponen diperlukan untuk menjalankan aplikasi'
457
+ );
458
+
459
+ console.log(`${C.white}Status Instalasi:${C.reset}`);
460
+ console.log(
461
+ ` [${ytExists ? C.green + '✓' : C.red + '✗'}${
462
+ C.reset
463
+ }] Engine yt-dlp (Wajib)`
464
+ );
465
+ console.log(
466
+ ` [${ffExists ? C.green + '✓' : C.red + '✗'}${
467
+ C.reset
468
+ }] FFmpeg (Direkomendasikan)`
469
+ );
470
+
471
+ console.log(
472
+ `\n${C.yellow}Aplikasi belum siap digunakan. Pilih opsi:${C.reset}`
473
+ );
474
+ console.log(` ${C.cyan}1.${C.reset} Install Semua Komponen Otomatis`);
475
+ console.log(` ${C.cyan}0.${C.reset} Keluar dari Aplikasi`);
476
+
477
+ const choice = await askQuestion('\nPilih: ');
478
+
479
+ if (choice === '1') {
480
+ if (!ytExists) await installYtdlp();
481
+ if (!ffExists) await installFfmpeg();
482
+
483
+ const status = checkTools();
484
+ if (status.ytExists) {
485
+ console.log(
486
+ `\n${C.green}✨ Setup Selesai! Membuka Menu Utama...${C.reset}`
487
+ );
488
+ await new Promise((r) => setTimeout(r, 1500));
489
+ return mainMenu(); // Berhasil, lanjut ke menu utama
490
+ }
491
+ } else if (choice === '0') {
492
+ console.log('Menutup aplikasi...');
493
+ process.exit(0);
257
494
  }
258
- mainMenu();
495
+ }
496
+ }
497
+
498
+ async function systemMaintenance() {
499
+ let inMaintenance = true;
500
+
501
+ while (inMaintenance) {
502
+ const { ytExists, ffExists } = checkTools();
503
+ printHeader(
504
+ 'SYSTEM MAINTENANCE',
505
+ 'Update engine atau bersihkan file sistem'
506
+ );
507
+
508
+ console.log(`${C.white}Versi Terinstal:${C.reset}`);
509
+ console.log(
510
+ ` • yt-dlp : ${ytExists ? C.green + 'Ready' : C.red + 'Not Found'}${
511
+ C.reset
512
+ }`
513
+ );
514
+ console.log(
515
+ ` • FFmpeg : ${ffExists ? C.green + 'Ready' : C.red + 'Not Found'}${
516
+ C.reset
517
+ }`
518
+ );
519
+
520
+ console.log(`\n${C.bright}Opsi Pemeliharaan:${C.reset}`);
521
+ console.log(` ${C.cyan}1.${C.reset} Update / Reinstall Engines`);
522
+ console.log(` ${C.cyan}2.${C.reset} 🗑️ Hapus Semua Tools (Reset System)`);
523
+ console.log(` ${C.cyan}3.${C.reset} ⬅️ Kembali ke Menu Utama`);
524
+
525
+ const choice = await askQuestion('\nPilih tindakan: ');
526
+
527
+ switch (choice) {
528
+ case '1':
529
+ await installYtdlp();
530
+ await installFfmpeg();
531
+ await askQuestion('\nUpdate selesai. Tekan Enter...');
532
+ break;
533
+
534
+ case '2':
535
+ const confirm = await askQuestion(
536
+ `${C.bgRed}${C.white} KONFIRMASI ${C.reset} Hapus semua tools? (y/n): `
537
+ );
538
+ if (confirm.toLowerCase() === 'y') {
539
+ await cleanUp(); // Panggil fungsi penghapusan folder
540
+ console.log(
541
+ `${C.yellow}Sistem dibersihkan. Anda akan diarahkan ke Setup Wizard.${C.reset}`
542
+ );
543
+ await askQuestion('Tekan Enter...');
544
+ return bootstrap(); // Kembali ke pengecekan awal
545
+ }
546
+ break;
547
+
548
+ case '3':
549
+ inMaintenance = false;
550
+ return mainMenu();
551
+
552
+ default:
553
+ break;
554
+ }
555
+ }
556
+ }
557
+
558
+ // --- ENTRY POINT ---
559
+ async function bootstrap() {
560
+ const status = checkTools();
561
+
562
+ if (!status.allReady) {
563
+ // Jika ada yang kurang, masuk ke mode instalasi
564
+ await firstTimeSetup();
259
565
  } else {
260
- rl.close();
261
- process.exit(0);
566
+ // Jika semua siap, langsung ke menu download
567
+ mainMenu();
262
568
  }
263
569
  }
264
570
 
265
- mainMenu();
571
+ bootstrap();
572
+
package/bin/ui.js ADDED
@@ -0,0 +1,74 @@
1
+ const readline = require('readline');
2
+
3
+ const C = {
4
+ reset: '\x1b[0m',
5
+ bright: '\x1b[1m',
6
+ dim: '\x1b[2m',
7
+ cyan: '\x1b[36m',
8
+ green: '\x1b[32m',
9
+ yellow: '\x1b[33m',
10
+ red: '\x1b[31m',
11
+ magenta: '\x1b[35m',
12
+ blue: '\x1b[34m',
13
+ white: '\x1b[37m',
14
+ bgBlue: '\x1b[44m',
15
+ bgRed: '\x1b[41m',
16
+ };
17
+
18
+ function printHeader(title, summary = '') {
19
+ console.clear();
20
+ const width = 50;
21
+ const line = '━'.repeat(width);
22
+
23
+ console.log(`${C.cyan}┏${line}┓`);
24
+
25
+ // Baris Judul (Bright White)
26
+ console.log(
27
+ `${C.cyan}┃ ${C.bright}${C.white}${title.padEnd(width - 2)} ${C.cyan}┃`
28
+ );
29
+
30
+ // Baris Summary (Hanya muncul jika summary diisi)
31
+ if (summary) {
32
+ console.log(
33
+ `${C.cyan}┃ ${C.dim}${C.white}${summary.padEnd(width - 2)} ${C.cyan}┃`
34
+ );
35
+ }
36
+
37
+ console.log(`${C.cyan}┗${line}┛${C.reset}\n`);
38
+ }
39
+
40
+ const rl = readline.createInterface({
41
+ input: process.stdin,
42
+ output: process.stdout,
43
+ });
44
+
45
+ function askQuestion(query) {
46
+ return new Promise((resolve) =>
47
+ rl.question(`${C.bright}${C.yellow}❯ ${C.reset}${query}`, resolve)
48
+ );
49
+ }
50
+
51
+ function renderProgressBar(percent, speed, eta) {
52
+ const width = 30;
53
+ const complete = Math.round((percent / 100) * width);
54
+ const incomplete = width - complete;
55
+ const bar = `${C.green}${'█'.repeat(complete)}${C.dim}${'░'.repeat(
56
+ incomplete
57
+ )}${C.reset}`;
58
+
59
+ process.stdout.clearLine(0);
60
+ process.stdout.cursorTo(0);
61
+ process.stdout.write(
62
+ ` ${C.bright}${percent.toString().padStart(5)}% ${bar} ${C.cyan}${speed}${
63
+ C.reset
64
+ } | ${C.yellow}ETA: ${eta}${C.reset}`
65
+ );
66
+ }
67
+
68
+ module.exports = {
69
+ C,
70
+ rl,
71
+ printHeader,
72
+ askQuestion,
73
+ renderProgressBar,
74
+ };
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "media-dl",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "CLI Downloader video/audio lintas platform menggunakan yt-dlp.",
5
5
  "main": "bin/cli.js",
6
6
  "scripts": {
7
+ "dev": "npm un -g media-dl && npm i -g . && media-dl",
8
+ "start": "npm un -g media-dl && npm i -g media-dl@latest && media-dl",
7
9
  "release": "npm version patch && npm publish",
8
10
  "release:minor": "npm version minor && npm publish",
9
11
  "release:major": "npm version major && npm publish"
package/readme.md CHANGED
@@ -1,64 +1,118 @@
1
- # Media-DL CLI 2026 🚀
1
+ # 🚀 Media-DL Pro 2026
2
2
 
3
- **Media-DL CLI** adalah pengunduh media berbasis terminal generasi terbaru yang dirancang untuk kecepatan, kemudahan, dan kompatibilitas penuh. Ditenagai oleh `yt-dlp`, alat ini memastikan video yang Anda unduh selalu siap diputar di perangkat apa pun tanpa kendala codec.
3
+ **The Ultimate Cross-Platform Media Engine Manager**
4
4
 
5
- ## Fitur Unggulan
5
+ Media-DL Pro adalah *cli-wrapper* canggih berbasis `yt-dlp` yang dirancang untuk pengguna profesional yang membutuhkan kecepatan, keteraturan, dan keamanan akun. Bukan sekadar pengunduh, ini adalah manajer media lokal yang cerdas.
6
6
 
7
- * 📥 **YouTube Playlist Power**: Deteksi otomatis playlist. Pilih video tertentu (misal: `1,3,5-10`) atau unduh semua sekaligus.
8
- * 🍏 **Apple Ecosystem Ready**: Secara otomatis mengonversi dan mengoptimalkan video ke format **H.264 (AVC1)** & **AAC** agar lancar diputar di **QuickTime Player**, iPhone, dan iPad.
9
- * 🛠️ **Smart Auto-Installer**: Tidak perlu pusing mengunduh `yt-dlp` atau `ffmpeg` secara manual. Skrip akan menyiapkannya untuk Anda (Optimal untuk macOS).
10
- * 📂 **Organized Storage**: Hasil unduhan tersimpan rapi di folder standar sistem `~/Downloads/media-dl/` yang dipisahkan berdasarkan kategori `video` dan `audio`.
11
- * 🛡️ **Fail-Safe Filename**: Fitur pemotongan judul otomatis untuk menghindari error *filename too long* pada sistem macOS/Windows.
7
+ ---
8
+
9
+ ## 🛡️ Mengapa Media-DL Pro?
10
+
11
+ ### 1. Safe Mode Guard™ (Exclusive)
12
+
13
+ Jangan biarkan IP atau akun Anda terblokir. Media-DL dilengkapi dengan mode proteksi yang mensimulasikan perilaku manusia melalui:
14
+
15
+ *
16
+
17
+ **Smart Rate Limiting**: Membatasi kecepatan unduh hingga 5MB/s.
18
+
19
+ *
20
+
21
+ **Randomized Sleep**: Interval jeda otomatis 3-10 detik antar unduhan.
22
+
23
+ *
24
+
25
+ **Custom User-Agents**: Menggunakan identitas browser modern untuk menghindari deteksi bot.
12
26
 
13
- ## 📦 Instalasi
27
+ ### 2. Dashboard Status Real-Time
28
+
29
+ Pantau kesehatan sistem Anda secara langsung. Aplikasi mendeteksi secara otomatis ketersediaan `yt-dlp` dan `FFmpeg` untuk memastikan hasil unduhan maksimal.
30
+
31
+ ### 3. Pengunduhan Massal & Selektif
32
+
33
+ Mendukung unduhan playlist secara penuh dengan fitur seleksi item yang presisi (Contoh: `1,3,5-10`).
34
+
35
+ ### 4. Optimalisasi Format Otomatis
36
+
37
+ *
38
+
39
+ **Video**: Konversi cerdas ke kontainer MP4 dengan codec `AVC1` agar kompatibel dengan perangkat seluler dan editor profesional.
40
+
41
+ *
42
+
43
+ **Audio**: Ekstraksi MP3 kualitas tinggi (320kbps) langsung ke folder musik Anda.
44
+
45
+ ---
14
46
 
15
- Cukup pastikan Anda memiliki [Node.js](https://nodejs.org/) terinstal, lalu jalankan:
47
+ ## 📦 Instalasi Cepat
16
48
 
17
49
  ```bash
50
+ # Pastikan Node.js sudah terinstal
18
51
  npm install -g media-dl
19
52
 
20
53
  ```
21
54
 
22
- ## 🚀 Cara Penggunaan
55
+ Cukup ketik `media-dl` di terminal Anda untuk memulai.
23
56
 
24
- Jalankan perintah berikut di terminal Anda:
57
+ ---
25
58
 
26
- ```bash
27
- media-dl
59
+ ## 🛠️ Navigasi Sistem
28
60
 
29
- ```
61
+ Aplikasi ini memiliki pusat kendali yang terorganisir:
62
+
63
+ 1.
64
+
65
+ **📥 Main Service**: Layanan unduh media tunggal atau playlist.
30
66
 
31
- ### Panduan Menu:
67
+ 1.
32
68
 
33
- 1. **Download Media**: Masukkan link video atau playlist.
34
- 2. **Pilih Format**: Video MP4 (QuickTime Compatible) atau Audio MP3.
35
- 3. **Seleksi Playlist**: Masukkan angka video yang diinginkan (Contoh: `1,3-5`) atau tekan `Enter` untuk semua.
36
- 4. **Auto-Open**: Folder tujuan akan otomatis terbuka setelah proses selesai.
69
+ **🛡️ Toggle Safe Mode**: Aktifkan/nonaktifkan perlindungan keamanan secara instan.
37
70
 
38
- ## 📁 Lokasi Penyimpanan
71
+ 1.
72
+
73
+ **⚙️ Maintenance**: Perbarui engine otomatis atau bersihkan sistem dari file sampah.
74
+
75
+ 1.
76
+
77
+ **❤️ About**: Informasi pengembang dan daftar fitur lengkap.
78
+
79
+ ---
39
80
 
40
- Skrip ini menggunakan struktur folder yang rapi:
81
+ ## 📂 Struktur Penyimpanan
41
82
 
42
- * **Tools**: `~/.media-dl/` (Tersembunyi agar sistem tetap bersih).
43
- * **Video**: `~/Downloads/media-dl/video/`
44
- * **Audio**: `~/Downloads/media-dl/audio/`
83
+ Media Anda akan tersimpan secara otomatis dengan struktur profesional di direktori pengguna:
45
84
 
46
- ## 📋 Persyaratan Sistem
85
+ *
47
86
 
48
- * **Node.js**: v14.0.0 atau lebih tinggi.
49
- * **FFmpeg**: Diperlukan untuk konversi tingkat lanjut. Gunakan **Menu 3** di dalam aplikasi untuk instalasi otomatis (macOS).
87
+ **Video**: `~/Downloads/media-dl/video/`
50
88
 
51
- ## ❤️ Dukungan & Donasi
89
+ *
52
90
 
53
- Skrip ini dikembangkan secara terbuka. Jika alat ini membantu produktivitas Anda, pertimbangkan untuk traktir pengembang:
91
+ **Audio**: `~/Downloads/media-dl/audio/`
54
92
 
55
- * **Beli Kopi ☕**: [Donasi via Midtrans](https://app.midtrans.com/payment-links/coffee-developer)
56
- * **Beli Pizza 🍕**: [Donasi via Midtrans](https://app.midtrans.com/payment-links/pizza-developer)
93
+ *
94
+
95
+ **Playlist**: Akan dibuatkan sub-folder otomatis sesuai judul playlist agar tetap rapi.
96
+
97
+ ---
98
+
99
+ ## ❤️ Dukung Pengembangan
100
+
101
+ Media-DL Pro dikembangkan oleh **Ariska Hidayat** dan tersedia secara gratis. Dukungan Anda membantu kami terus memperbarui engine untuk mengatasi perubahan algoritma platform media.
102
+
103
+ | Layanan | Link Donasi |
104
+ | --- | --- |
105
+ | **☕ Traktir Kopi** | <br>[Donasi via Midtrans](https://app.midtrans.com/coffee)
106
+
107
+ |
108
+ | **🍕 Beli Pizza** | <br>[Donasi via Midtrans](https://app.midtrans.com/pizza)
109
+
110
+ |
57
111
 
58
112
  ---
59
113
 
60
114
  ## 📄 Lisensi
61
115
 
62
- Distribusi di bawah Lisensi MIT. Bebas digunakan dan dikembangkan kembali.
116
+ Didistribusikan di bawah **Lisensi MIT**. Dibuat dengan ❤️ untuk komunitas open source.
63
117
 
64
- ---
118
+ ---
package/project-info.txt DELETED
@@ -1,158 +0,0 @@
1
- === Project Structure ===
2
- 📁 bin/
3
- 📄 cli.js
4
- 📄 package.json
5
-
6
- === File Contents ===
7
-
8
- === File: package.json ===
9
- {
10
- "name": "media-dl",
11
- "version": "1.0.0",
12
- "description": "CLI Downloader video/audio lintas platform menggunakan yt-dlp.",
13
- "main": "bin/cli.js",
14
- "bin": {
15
- "media-dl": "bin/cli.js"
16
- },
17
- "publishConfig": {
18
- "access": "public"
19
- },
20
- "keywords": [
21
- "downloader",
22
- "youtube",
23
- "cross-platform",
24
- "media-dl"
25
- ],
26
- "author": "Ariska Hidayat",
27
- "license": "MIT",
28
- "dependencies": {},
29
- "engines": {
30
- "node": ">=14.0.0"
31
- }
32
- }
33
-
34
-
35
- === File: bin/cli.js ===
36
- 201~200~#!/usr/bin/env node
37
-
38
- const { spawn, execSync } = require('child_process');
39
- const readline = require('readline');
40
- const path = require('path');
41
- const fs = require('fs');
42
- const os = require('os');
43
-
44
- const rl = readline.createInterface({
45
- input: process.stdin,
46
- output: process.stdout
47
- });
48
-
49
- // Penentuan folder Tools berdasarkan OS (menyimpan di folder Home agar aman)
50
- const TOOLS_DIR = path.join(os.homedir(), '.media-dl');
51
- const isWindows = process.platform === 'win32';
52
-
53
- const YTDLP_PATH = path.join(TOOLS_DIR, isWindows ? 'yt-dlp.exe' : 'yt-dlp');
54
- const FFMPEG_PATH = path.join(TOOLS_DIR, isWindows ? 'ffmpeg.exe' : 'ffmpeg');
55
-
56
- if (!fs.existsSync(TOOLS_DIR)) fs.mkdirSync(TOOLS_DIR, { recursive: true });
57
-
58
- function askQuestion(query) {
59
- return new Promise(resolve => rl.question(query, resolve));
60
- }
61
-
62
- function checkTools() {
63
- return {
64
- ytExists: fs.existsSync(YTDLP_PATH),
65
- ffExists: fs.existsSync(FFMPEG_PATH)
66
- };
67
- }
68
-
69
- async function installTools() {
70
- console.log(`\n--- 🛠️ INSTALASI TOOLS (${process.platform.toUpperCase()}) ---`);
71
- const { ytExists } = checkTools();
72
-
73
- if (!ytExists) {
74
- console.log('⏳ Mengunduh yt-dlp...');
75
- try {
76
- const url = isWindows
77
- ? 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp.exe'
78
- : 'https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp';
79
-
80
- if (isWindows) {
81
- execSync(`powershell -Command "Invoke-WebRequest -Uri ${url} -OutFile '${YTDLP_PATH}'"`);
82
- } else {
83
- execSync(`curl -L ${url} -o "${YTDLP_PATH}"`);
84
- execSync(`chmod a+rx "${YTDLP_PATH}"`);
85
- }
86
- console.log('✅ yt-dlp berhasil diinstal.');
87
- } catch (e) {
88
- console.error('❌ Gagal menginstal yt-dlp. Pastikan koneksi internet stabil.');
89
- }
90
- } else {
91
- console.log('✅ yt-dlp sudah terpasang.');
92
- }
93
-
94
- console.log('\n📝 Catatan untuk FFmpeg:');
95
- console.log(`Silakan unduh ffmpeg manual dan letakkan di: ${FFMPEG_PATH}`);
96
- }
97
-
98
- async function startDownload() {
99
- const { ytExists, ffExists } = checkTools();
100
- if (!ytExists) {
101
- console.log('\n⚠️ Error: Jalankan menu Instalasi terlebih dahulu.');
102
- return mainMenu();
103
- }
104
-
105
- const videoURL = await askQuestion('\n🔗 Masukkan Link: ');
106
- console.log('\n[Format]\n1. Audio (MP3)\n2. Video (MP4)');
107
- const choice = await askQuestion('Pilihan: ');
108
-
109
- const outputDir = path.join(process.cwd(), 'downloads');
110
- if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir, { recursive: true });
111
-
112
- const args = choice === '1'
113
- ? ['-x', '--audio-format', 'mp3', '--ffmpeg-location', FFMPEG_PATH, '-o', `${outputDir}/%(title)s.%(ext)s`, videoURL]
114
- : ['-f', 'mp4', '-o', `${outputDir}/%(title)s.%(ext)s`, videoURL];
115
-
116
- const download = spawn(YTDLP_PATH, args);
117
-
118
- download.stdout.on('data', (data) => process.stdout.write(data));
119
- download.on('close', (code) => {
120
- console.log(code === 0 ? `\n✅ SELESAI! Cek folder: ${outputDir}` : '\n❌ Gagal.');
121
- mainMenu();
122
- });
123
- }
124
-
125
- async function mainMenu() {
126
- const { ytExists, ffExists } = checkTools();
127
- console.log('\n==============================');
128
- console.log(' MEDIA-DL MANAGER ');
129
- console.log('==============================');
130
- console.log(`OS: ${process.platform} | yt-dlp: ${ytExists ? '✅' : '❌'} | ffmpeg: ${ffExists ? '✅' : '❌'}`);
131
- console.log('------------------------------');
132
- console.log('1. Download Media');
133
- console.log('2. Instal/Update yt-dlp');
134
- console.log('3. Keluar');
135
-
136
- const menuChoice = await askQuestion('\nPilih: ');
137
- if (menuChoice === '1') await startDownload();
138
- else if (menuChoice === '2') { await installTools(); mainMenu(); }
139
- else { rl.close(); process.exit(0); }
140
- }
141
-
142
- mainMenu();
143
- }
144
- })
145
- }
146
- }
147
- }
148
- }
149
- }
150
- }
151
- }
152
- }
153
- }
154
- }
155
- }
156
- }
157
- })
158
-