Fourstore commited on
Commit
0ecc716
·
1 Parent(s): 03fe6a7
Files changed (2) hide show
  1. index.js +189 -85
  2. scrape/ytdown.js +52 -95
index.js CHANGED
@@ -3,7 +3,8 @@ import fs from 'fs';
3
  import path from 'path';
4
  import cors from 'cors';
5
  import { fileURLToPath } from 'url';
6
- import ytdown from './scrape/ytdown.js';
 
7
  import extractAndDownloadTikTok from './scrape/tiktokdl.js';
8
 
9
  const __filename = fileURLToPath(import.meta.url);
@@ -12,16 +13,11 @@ const __dirname = path.dirname(__filename);
12
  const app = express();
13
  const PORT = process.env.PORT || 7860;
14
  const domain = process.env.DOMAIN || `https://fourstore-ytdl-api.hf.space`;
15
- const LIB_FOLDER = path.join(__dirname, 'downloads');
16
 
17
  let visitCount = 0;
18
  let successDownloads = 0;
19
  let failedDownloads = 0;
20
 
21
- if (!fs.existsSync(LIB_FOLDER)) {
22
- fs.mkdirSync(LIB_FOLDER, { recursive: true });
23
- }
24
-
25
  app.use(cors({
26
  origin: '*',
27
  methods: ['GET', 'POST', 'OPTIONS'],
@@ -30,76 +26,182 @@ app.use(cors({
30
 
31
  app.use(express.json());
32
  app.use('/image', express.static(path.join(__dirname, 'image')));
33
- app.use('/download', express.static(LIB_FOLDER));
34
 
35
- async function downloadYouTube(url, type, quality = "720") {
 
 
 
 
 
 
36
  try {
37
- const qualityParam = type === 'audio' ? "mp3" : quality;
38
- const result = await ytdown(url, qualityParam);
39
 
40
- if (!result.status) {
41
- throw new Error(result.error || 'Gagal mendownload dari YouTube');
42
  }
43
 
44
- const fileId = Math.random().toString(36).substring(2, 8);
45
- let downloadUrl = null;
46
- let fileName = '';
47
- let fileExt = '';
48
 
49
- if (type === 'audio' && result.audio) {
50
- downloadUrl = result.audio.url;
51
- fileExt = result.audio.ext || 'mp3';
52
- fileName = `audio_${fileId}.${fileExt}`;
53
- } else if (type === 'video' && result.video) {
54
- downloadUrl = result.video.url;
55
- fileExt = result.video.ext || 'mp4';
56
- fileName = `video_${fileId}.${fileExt}`;
57
- } else {
58
- throw new Error('Tidak ada media yang tersedia');
 
 
59
  }
60
 
61
- const filePath = path.join(LIB_FOLDER, fileName);
62
-
63
- const response = await fetch(downloadUrl);
64
- const buffer = await response.arrayBuffer();
65
- fs.writeFileSync(filePath, Buffer.from(buffer));
66
 
67
  successDownloads++;
68
 
69
- return {
70
- success: true,
71
- type: type.toUpperCase(),
72
- title: result.title,
73
- channel: result.channel,
74
- duration: result.duration,
75
- thumbnail: result.thumbnail,
76
- quality: type === 'audio' ? result.audio?.quality : result.video?.quality,
77
- size: type === 'audio' ? result.audio?.size : result.video?.size,
78
- download_url: `${domain}/download/${fileName}`
79
- };
 
 
 
80
  } catch (error) {
81
  failedDownloads++;
82
- return {
83
- success: false,
84
- error: `Failed to download ${type} from YouTube: ${error.message}`
85
- };
86
  }
87
- }
88
 
89
- app.get('/api/download/audio', async (req, res) => {
90
- const { url, quality } = req.query;
 
91
  if (!url) return res.status(400).json({ error: "YouTube URL required" });
92
- const result = await downloadYouTube(url, 'audio', quality);
93
- res.json(result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  });
95
 
96
- app.get('/api/download/video', async (req, res) => {
97
- const { url, quality } = req.query;
 
98
  if (!url) return res.status(400).json({ error: "YouTube URL required" });
99
- const result = await downloadYouTube(url, 'video', quality || '720');
100
- res.json(result);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  });
102
 
 
103
  app.get('/api/download/tiktok', async (req, res) => {
104
  try {
105
  const { url } = req.query;
@@ -113,6 +215,7 @@ app.get('/api/download/tiktok', async (req, res) => {
113
  }
114
  });
115
 
 
116
  app.get('/', (req, res) => {
117
  visitCount++;
118
  res.send(`<!DOCTYPE html>
@@ -353,12 +456,12 @@ app.get('/', (req, res) => {
353
  else endpoint = 'tiktok';
354
 
355
  const response = await fetch(domain + '/api/download/' + endpoint + '?' + params);
356
- const hasil = await response.json();
357
 
358
  document.getElementById('loading').style.display = 'none';
359
 
360
- if (hasil.success) {
361
- if (jenis === 'tiktok') {
 
362
  let html = '<div class="result-card">';
363
  if (hasil.type === 'video') {
364
  html += '<h3>🎬 Video TikTok</h3>';
@@ -377,17 +480,28 @@ app.get('/', (req, res) => {
377
  }
378
  html += '</div>';
379
  document.getElementById('hasil').innerHTML = html;
 
 
380
  } else {
381
- let thumbHtml = hasil.thumbnail ? '<img src="' + hasil.thumbnail + '" alt="Thumbnail">' : '';
382
- document.getElementById('hasil').innerHTML = '<div class="result-card"><h3>✨ ' + hasil.title + '</h3>' + thumbHtml + '<p>📺 Channel: ' + hasil.channel + '</p><p>⏱️ Durasi: ' + hasil.duration + '</p><p>🎚️ Kualitas: ' + (hasil.quality || 'Auto') + '</p><p>📦 Ukuran: ' + (hasil.size || 'Unknown') + '</p><a href="' + hasil.download_url + '" class="download-btn" download>⬇️ Download ' + hasil.type + '</a></div>';
 
 
 
 
 
 
 
 
 
 
383
  }
 
 
384
  let berhasilVal = parseInt(document.getElementById('berhasilDiunduh').textContent) + 1;
385
  document.getElementById('berhasilDiunduh').textContent = berhasilVal;
386
- } else {
387
- document.getElementById('hasil').innerHTML = '<div class="result-card" style="color:#ffcccc;">❌ ' + (hasil.error || 'Gagal download') + '</div>';
388
- let gagalVal = parseInt(document.getElementById('gagalDiunduh').textContent) + 1;
389
- document.getElementById('gagalDiunduh').textContent = gagalVal;
390
  }
 
391
  let kunjungan = parseInt(document.getElementById('jumlahKunjungan').textContent) + 1;
392
  document.getElementById('jumlahKunjungan').textContent = kunjungan;
393
  } catch (error) {
@@ -514,42 +628,32 @@ app.get('/docs', (req, res) => {
514
 
515
  <div class="endpoint">
516
  <h2><span class="method">GET</span> /api/download/audio</h2>
517
- <p>Download audio dari YouTube (MP3).</p>
518
  <h3>Parameter:</h3>
519
- <ul><li><code>url</code> (required) - URL YouTube</li></ul>
520
  <h3>Contoh:</h3>
521
  <pre>GET ${domain}/api/download/audio?url=https://youtube.com/watch?v=VIDEO_ID</pre>
522
  </div>
523
 
524
  <div class="endpoint">
525
  <h2><span class="method">GET</span> /api/download/video</h2>
526
- <p>Download video dari YouTube.</p>
527
  <h3>Parameter:</h3>
528
- <ul><li><code>url</code> (required) - URL YouTube</li><li><code>quality</code> (optional) - 144/240/360/480/720/1080 (default: 720)</li></ul>
529
- <pre>GET ${domain}/api/download/video?url=https://youtube.com/watch?v=VIDEO_ID&quality=1080</pre>
 
 
 
 
 
 
530
  </div>
531
 
532
  <div class="endpoint">
533
  <h2><span class="method">GET</span> /api/download/tiktok</h2>
534
  <p>Download video/slide TikTok.</p>
535
- <h3>Parameter:</h3>
536
- <ul><li><code>url</code> (required) - URL TikTok</li></ul>
537
  <pre>GET ${domain}/api/download/tiktok?url=https://tiktok.com/@user/video/123456789</pre>
538
  </div>
539
-
540
- <h2>📦 Response Format</h2>
541
- <h3>YouTube Audio/Video:</h3>
542
- <pre>{
543
- "success": true,
544
- "type": "AUDIO/VIDEO",
545
- "title": "Judul Video",
546
- "channel": "Nama Channel",
547
- "duration": "3:45",
548
- "thumbnail": "https://...",
549
- "quality": "720",
550
- "size": "4.56 MB",
551
- "download_url": "https://..."
552
- }</pre>
553
  </div>
554
  <footer>Made with ❤️ by Fourstore | Owner: RezaHaris | <a href="https://wa.me/6283163784116">Contact</a></footer>
555
  <script>
 
3
  import path from 'path';
4
  import cors from 'cors';
5
  import { fileURLToPath } from 'url';
6
+ import axios from 'axios';
7
+ import YTDown from './scrape/ytdown.js';
8
  import extractAndDownloadTikTok from './scrape/tiktokdl.js';
9
 
10
  const __filename = fileURLToPath(import.meta.url);
 
13
  const app = express();
14
  const PORT = process.env.PORT || 7860;
15
  const domain = process.env.DOMAIN || `https://fourstore-ytdl-api.hf.space`;
 
16
 
17
  let visitCount = 0;
18
  let successDownloads = 0;
19
  let failedDownloads = 0;
20
 
 
 
 
 
21
  app.use(cors({
22
  origin: '*',
23
  methods: ['GET', 'POST', 'OPTIONS'],
 
26
 
27
  app.use(express.json());
28
  app.use('/image', express.static(path.join(__dirname, 'image')));
 
29
 
30
+ const ytdown = new YTDown();
31
+
32
+ // Endpoint untuk stream audio langsung
33
+ app.get('/api/download/audio', async (req, res) => {
34
+ const { url, quality = '128K' } = req.query;
35
+ if (!url) return res.status(400).json({ error: "YouTube URL required" });
36
+
37
  try {
38
+ const info = await ytdown.getVideoInfo(url);
 
39
 
40
+ if (info?.api?.status !== 'ok') {
41
+ throw new Error(info?.api?.message || 'Gagal mendapatkan info video');
42
  }
43
 
44
+ let audioUrl = null;
45
+ let audioInfo = null;
 
 
46
 
47
+ for (const item of info.api.mediaItems) {
48
+ if (item.type === 'Audio') {
49
+ if (item.mediaQuality === quality) {
50
+ audioUrl = item.mediaPreviewUrl;
51
+ audioInfo = item;
52
+ break;
53
+ }
54
+ if (!audioUrl) {
55
+ audioUrl = item.mediaPreviewUrl;
56
+ audioInfo = item;
57
+ }
58
+ }
59
  }
60
 
61
+ if (!audioUrl) {
62
+ throw new Error('Audio tidak ditemukan');
63
+ }
 
 
64
 
65
  successDownloads++;
66
 
67
+ const response = await axios({
68
+ method: 'GET',
69
+ url: audioUrl,
70
+ responseType: 'stream',
71
+ headers: {
72
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
73
+ }
74
+ });
75
+
76
+ const filename = `${info.api.title}.mp3`.replace(/[^a-z0-9.-]/gi, '_');
77
+ res.setHeader('Content-Type', 'audio/mpeg');
78
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
79
+ response.data.pipe(res);
80
+
81
  } catch (error) {
82
  failedDownloads++;
83
+ res.status(500).json({ success: false, error: error.message });
 
 
 
84
  }
85
+ });
86
 
87
+ // Endpoint untuk stream video langsung
88
+ app.get('/api/download/video', async (req, res) => {
89
+ const { url, quality = 'HD' } = req.query;
90
  if (!url) return res.status(400).json({ error: "YouTube URL required" });
91
+
92
+ try {
93
+ const info = await ytdown.getVideoInfo(url);
94
+
95
+ if (info?.api?.status !== 'ok') {
96
+ throw new Error(info?.api?.message || 'Gagal mendapatkan info video');
97
+ }
98
+
99
+ let videoUrl = null;
100
+ let videoInfo = null;
101
+
102
+ const qualityMap = {
103
+ '1080': 'FHD',
104
+ 'fhd': 'FHD',
105
+ '720': 'HD',
106
+ 'hd': 'HD',
107
+ '480': 'SD',
108
+ '360': 'SD',
109
+ '240': 'SD',
110
+ '144': 'SD'
111
+ };
112
+
113
+ const targetQuality = qualityMap[String(quality).toLowerCase()] || quality;
114
+
115
+ for (const item of info.api.mediaItems) {
116
+ if (item.type === 'Video') {
117
+ if (item.mediaQuality === targetQuality) {
118
+ videoUrl = item.mediaPreviewUrl;
119
+ videoInfo = item;
120
+ break;
121
+ }
122
+ if (!videoUrl) {
123
+ videoUrl = item.mediaPreviewUrl;
124
+ videoInfo = item;
125
+ }
126
+ }
127
+ }
128
+
129
+ if (!videoUrl) {
130
+ throw new Error('Video tidak ditemukan');
131
+ }
132
+
133
+ successDownloads++;
134
+
135
+ const response = await axios({
136
+ method: 'GET',
137
+ url: videoUrl,
138
+ responseType: 'stream',
139
+ headers: {
140
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36'
141
+ }
142
+ });
143
+
144
+ const filename = `${info.api.title}.mp4`.replace(/[^a-z0-9.-]/gi, '_');
145
+ res.setHeader('Content-Type', 'video/mp4');
146
+ res.setHeader('Content-Disposition', `attachment; filename="${filename}"`);
147
+ response.data.pipe(res);
148
+
149
+ } catch (error) {
150
+ failedDownloads++;
151
+ res.status(500).json({ success: false, error: error.message });
152
+ }
153
  });
154
 
155
+ // Endpoint untuk get info video
156
+ app.get('/api/info', async (req, res) => {
157
+ const { url } = req.query;
158
  if (!url) return res.status(400).json({ error: "YouTube URL required" });
159
+
160
+ try {
161
+ const info = await ytdown.getVideoInfo(url);
162
+
163
+ if (info?.api?.status !== 'ok') {
164
+ throw new Error(info?.api?.message || 'Gagal mendapatkan info video');
165
+ }
166
+
167
+ const videos = [];
168
+ const audios = [];
169
+
170
+ for (const item of info.api.mediaItems) {
171
+ if (item.type === 'Video') {
172
+ videos.push({
173
+ quality: item.mediaQuality,
174
+ size: item.mediaFileSize,
175
+ resolution: item.mediaRes,
176
+ url: item.mediaPreviewUrl
177
+ });
178
+ } else if (item.type === 'Audio') {
179
+ audios.push({
180
+ quality: item.mediaQuality,
181
+ size: item.mediaFileSize,
182
+ extension: item.mediaExtension,
183
+ url: item.mediaPreviewUrl
184
+ });
185
+ }
186
+ }
187
+
188
+ res.json({
189
+ success: true,
190
+ title: info.api.title,
191
+ channel: info.api.userInfo?.name,
192
+ duration: info.api.mediaItems?.[0]?.mediaDuration,
193
+ thumbnail: info.api.imagePreviewUrl,
194
+ description: info.api.description,
195
+ videos: videos,
196
+ audios: audios
197
+ });
198
+
199
+ } catch (error) {
200
+ res.status(500).json({ success: false, error: error.message });
201
+ }
202
  });
203
 
204
+ // TikTok endpoint
205
  app.get('/api/download/tiktok', async (req, res) => {
206
  try {
207
  const { url } = req.query;
 
215
  }
216
  });
217
 
218
+ // Halaman utama
219
  app.get('/', (req, res) => {
220
  visitCount++;
221
  res.send(`<!DOCTYPE html>
 
456
  else endpoint = 'tiktok';
457
 
458
  const response = await fetch(domain + '/api/download/' + endpoint + '?' + params);
 
459
 
460
  document.getElementById('loading').style.display = 'none';
461
 
462
+ if (jenis === 'tiktok') {
463
+ const hasil = await response.json();
464
+ if (hasil.success) {
465
  let html = '<div class="result-card">';
466
  if (hasil.type === 'video') {
467
  html += '<h3>🎬 Video TikTok</h3>';
 
480
  }
481
  html += '</div>';
482
  document.getElementById('hasil').innerHTML = html;
483
+ let berhasilVal = parseInt(document.getElementById('berhasilDiunduh').textContent) + 1;
484
+ document.getElementById('berhasilDiunduh').textContent = berhasilVal;
485
  } else {
486
+ document.getElementById('hasil').innerHTML = '<div class="result-card" style="color:#ffcccc;">❌ ' + (hasil.error || 'Gagal download') + '</div>';
487
+ let gagalVal = parseInt(document.getElementById('gagalDiunduh').textContent) + 1;
488
+ document.getElementById('gagalDiunduh').textContent = gagalVal;
489
+ }
490
+ } else {
491
+ const blob = await response.blob();
492
+ const downloadUrl = URL.createObjectURL(blob);
493
+ const contentDisposition = response.headers.get('Content-Disposition');
494
+ let filename = jenis === 'audio' ? 'audio.mp3' : 'video.mp4';
495
+ if (contentDisposition) {
496
+ const match = contentDisposition.match(/filename="(.+)"/);
497
+ if (match) filename = match[1];
498
  }
499
+
500
+ document.getElementById('hasil').innerHTML = '<div class="result-card"><a href="' + downloadUrl + '" class="download-btn" download="' + filename + '">⬇️ Download ' + (jenis === 'audio' ? 'Audio' : 'Video') + '</a></div>';
501
  let berhasilVal = parseInt(document.getElementById('berhasilDiunduh').textContent) + 1;
502
  document.getElementById('berhasilDiunduh').textContent = berhasilVal;
 
 
 
 
503
  }
504
+
505
  let kunjungan = parseInt(document.getElementById('jumlahKunjungan').textContent) + 1;
506
  document.getElementById('jumlahKunjungan').textContent = kunjungan;
507
  } catch (error) {
 
628
 
629
  <div class="endpoint">
630
  <h2><span class="method">GET</span> /api/download/audio</h2>
631
+ <p>Download audio dari YouTube (MP3) - LANGSUNG STREAM.</p>
632
  <h3>Parameter:</h3>
633
+ <ul><li><code>url</code> (required) - URL YouTube</li><li><code>quality</code> (optional) - 48K / 128K (default: 128K)</li></ul>
634
  <h3>Contoh:</h3>
635
  <pre>GET ${domain}/api/download/audio?url=https://youtube.com/watch?v=VIDEO_ID</pre>
636
  </div>
637
 
638
  <div class="endpoint">
639
  <h2><span class="method">GET</span> /api/download/video</h2>
640
+ <p>Download video dari YouTube - LANGSUNG STREAM.</p>
641
  <h3>Parameter:</h3>
642
+ <ul><li><code>url</code> (required) - URL YouTube</li><li><code>quality</code> (optional) - 144/240/360/480/720/1080 atau SD/HD/FHD (default: HD)</li></ul>
643
+ <pre>GET ${domain}/api/download/video?url=https://youtube.com/watch?v=VIDEO_ID&quality=720</pre>
644
+ </div>
645
+
646
+ <div class="endpoint">
647
+ <h2><span class="method">GET</span> /api/info</h2>
648
+ <p>Get video info tanpa download.</p>
649
+ <pre>GET ${domain}/api/info?url=https://youtube.com/watch?v=VIDEO_ID</pre>
650
  </div>
651
 
652
  <div class="endpoint">
653
  <h2><span class="method">GET</span> /api/download/tiktok</h2>
654
  <p>Download video/slide TikTok.</p>
 
 
655
  <pre>GET ${domain}/api/download/tiktok?url=https://tiktok.com/@user/video/123456789</pre>
656
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
657
  </div>
658
  <footer>Made with ❤️ by Fourstore | Owner: RezaHaris | <a href="https://wa.me/6283163784116">Contact</a></footer>
659
  <script>
scrape/ytdown.js CHANGED
@@ -1,105 +1,62 @@
1
-
2
- import axios from "axios"
3
- import qs from "qs"
4
-
5
- const headers = {
6
- "accept": "*/*",
7
- "content-type": "application/x-www-form-urlencoded; charset=UTF-8",
8
- "x-requested-with": "XMLHttpRequest",
9
- "referer": "https://app.ytdown.to/id21/",
10
- "origin": "https://app.ytdown.to"
11
- }
12
-
13
- function normalizeQ(q = "") {
14
- q = q.toLowerCase()
15
- if (q.includes("1080") || q.includes("fhd")) return "1080"
16
- if (q.includes("720") || q.includes("hd")) return "720"
17
- if (q.includes("480")) return "480"
18
- if (q.includes("360") || q.includes("sd")) return "360"
19
- if (q.includes("240")) return "240"
20
- if (q.includes("144")) return "144"
21
- return ""
22
- }
23
-
24
- async function convert(url) {
25
- try {
26
- const { data } = await axios.post(
27
- "https://app.ytdown.to/proxy.php",
28
- qs.stringify({ url }),
29
- { headers, timeout: 20000 }
30
- )
31
- return data?.api?.status === "completed" ? data.api : null
32
- } catch {
33
- return null
34
  }
35
- }
36
-
37
- export default async function ytdown(url, quality = "720") {
38
- try {
39
- const { data } = await axios.post(
40
- "https://app.ytdown.to/proxy.php",
41
- qs.stringify({ url }),
42
- { headers, timeout: 20000 }
43
- )
44
-
45
- if (data?.api?.status !== "ok") return { status: false }
46
-
47
- const targetQ = normalizeQ(quality)
48
- const isAudioOnly = /mp3|audio/i.test(quality)
49
-
50
- let selectedVideo = null
51
- let fallbackVideo = null
52
- let bestAudio = null
53
- let fallbackAudio = null
54
 
55
- for (let item of data.api.mediaItems) {
56
- const res = await convert(item.mediaUrl)
57
- if (!res) continue
58
-
59
- const ext = res.fileName?.split(".").pop()?.toLowerCase()
60
-
61
- const obj = {
62
- quality: item.mediaQuality,
63
- url: res.fileUrl,
64
- size: res.fileSize,
65
- ext,
66
- mime: item.type === "Video" ? "video/" + ext : "audio/" + ext
67
- }
68
-
69
- if (item.type === "Video") {
70
- const qNum = normalizeQ(item.mediaQuality)
71
-
72
- if (qNum == targetQ && !selectedVideo) {
73
- selectedVideo = obj
74
- }
75
-
76
- if (!fallbackVideo || Number(qNum) > Number(normalizeQ(fallbackVideo.quality))) {
77
- fallbackVideo = obj
78
- }
79
- }
80
 
81
- if (item.type === "Audio") {
82
- if (ext === "mp3" && !bestAudio) {
83
- bestAudio = obj
84
- }
85
 
86
- if (!fallbackAudio) {
87
- fallbackAudio = obj
88
- }
 
 
 
 
 
 
 
 
89
  }
90
- }
91
 
92
- return {
93
- status: true,
94
- title: data.api.title,
95
- channel: data.api.userInfo?.name,
96
- thumbnail: data.api.imagePreviewUrl,
97
- duration: data.api.mediaItems?.[0]?.mediaDuration,
98
- video: isAudioOnly ? null : (selectedVideo || fallbackVideo),
99
- audio: bestAudio || fallbackAudio
100
  }
101
 
102
- } catch (e) {
103
- return { status: false, error: String(e) }
104
  }
105
  }
 
 
 
1
+ import axios from 'axios';
2
+ import FormData from 'form-data';
3
+ import { randomUUID } from 'crypto';
4
+
5
+ class YTDown {
6
+ constructor() {
7
+ this.baseUrl = 'https://app.ytdown.to';
8
+ this.headers = {
9
+ 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/145.0.0.0 Safari/537.36',
10
+ 'Accept': '*/*',
11
+ 'Accept-Language': 'id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7',
12
+ 'Origin': this.baseUrl,
13
+ 'Referer': this.baseUrl + '/id15/',
14
+ 'sec-ch-ua': '"Not:A-Brand";v="99", "Google Chrome";v="145", "Chromium";v="145"',
15
+ 'sec-ch-ua-mobile': '?0',
16
+ 'sec-ch-ua-platform': '"Linux"',
17
+ 'sec-fetch-dest': 'empty',
18
+ 'sec-fetch-mode': 'cors',
19
+ 'sec-fetch-site': 'same-origin',
20
+ 'x-requested-with': 'XMLHttpRequest'
21
+ };
22
+ this.cookies = {
23
+ 'PHPSESSID': randomUUID().replace(/-/g, ''),
24
+ '_ga': 'GA1.1.' + Math.floor(Math.random() * 1000000000) + '.' + Math.floor(Date.now() / 1000)
25
+ };
 
 
 
 
 
 
 
 
26
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
+ generateSession() {
29
+ this.cookies.PHPSESSID = randomUUID().replace(/-/g, '');
30
+ this.cookies._ga = 'GA1.1.' + Math.floor(Math.random() * 1000000000) + '.' + Math.floor(Date.now() / 1000);
31
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
+ getCookieString() {
34
+ return Object.entries(this.cookies).map(([k, v]) => `${k}=${v}`).join('; ');
35
+ }
 
36
 
37
+ async getVideoInfo(url) {
38
+ this.generateSession();
39
+
40
+ const form = new FormData();
41
+ form.append('url', url);
42
+
43
+ const response = await axios.post(`${this.baseUrl}/proxy.php`, form, {
44
+ headers: {
45
+ ...this.headers,
46
+ ...form.getHeaders(),
47
+ 'Cookie': this.getCookieString()
48
  }
49
+ });
50
 
51
+ if (response.headers['set-cookie']) {
52
+ response.headers['set-cookie'].forEach(cookie => {
53
+ const [key, value] = cookie.split(';')[0].split('=');
54
+ this.cookies[key] = value;
55
+ });
 
 
 
56
  }
57
 
58
+ return response.data;
 
59
  }
60
  }
61
+
62
+ export default YTDown;