Fourstore commited on
Commit
be610f3
·
1 Parent(s): 5a248c8
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ node_modules/
Dockerfile ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Pakai image base Node.js 20 + Python
2
+ FROM node:20-bullseye
3
+
4
+ # Install Python dan dependencies penting
5
+ RUN apt-get update && \
6
+ apt-get install -y --no-install-recommends \
7
+ python3 \
8
+ python3-pip \
9
+ python3-dev \
10
+ ffmpeg \
11
+ && rm -rf /var/lib/apt/lists/*
12
+
13
+ # Upgrade pip dan install tools downloader
14
+ RUN python3 -m pip install --upgrade pip && \
15
+ python3 -m pip install --no-cache-dir spotdl yt-dlp
16
+
17
+ # Buat direktori dan set permission
18
+ RUN mkdir -p /app/downloads && \
19
+ mkdir -p /app/cookies && \
20
+ chmod 777 -R /app/downloads && \
21
+ chmod 777 -R /app/cookies
22
+
23
+ # Setup workdir
24
+ WORKDIR /app
25
+
26
+ # Install node_modules dulu buat caching
27
+ COPY package*.json ./
28
+ RUN npm install
29
+
30
+ # Copy semua file
31
+ COPY . .
32
+
33
+ # Buat file cookies default jika tidak ada
34
+ RUN touch /app/cookies/youtube_cookies.txt && \
35
+ chmod 666 /app/cookies/youtube_cookies.txt
36
+
37
+ # Verifikasi environment
38
+ RUN echo "Versi tools:" && \
39
+ node --version && \
40
+ npm --version && \
41
+ python3 --version && \
42
+ pip --version && \
43
+ spotdl --version && \
44
+ yt-dlp --version
45
+
46
+ CMD ["node", "index.js"]
47
+
48
+ EXPOSE 7860
README.md CHANGED
@@ -3,10 +3,8 @@ title: Ytdl Api
3
  emoji: 🐢
4
  colorFrom: pink
5
  colorTo: yellow
6
- sdk: gradio
7
  sdk_version: 6.11.0
8
- app_file: app.py
9
- pinned: false
10
  license: apache-2.0
11
  short_description: ytdl
12
  ---
 
3
  emoji: 🐢
4
  colorFrom: pink
5
  colorTo: yellow
6
+ sdk: docker
7
  sdk_version: 6.11.0
 
 
8
  license: apache-2.0
9
  short_description: ytdl
10
  ---
cookies/youtube_cookies.txt ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Netscape HTTP Cookie File
2
+ # This file is generated by yt-dlp. Do not edit.
3
+
4
+ .youtube.com TRUE / FALSE 1782131576 APISID vQ3bQBYAxpTycTv7/AGoP_5VvEjdPkD3hg
5
+ .youtube.com TRUE / TRUE 1763577434 DEVICE_INFO ChxOelV3TnpJM01qUTFNVGMyTVRVMk1qRTVPUT09ENqAw8EGGIXhvMEG
6
+ .youtube.com TRUE / FALSE 1782131576 HSID Az9cetIcURk0XKGU6
7
+ .youtube.com TRUE / TRUE 1782131576 LOGIN_INFO AFmmF2swRQIhAOmORcGZJ5gjkiice4un3_GGpFDIJod4uYPo7k0MHsbYAiAA_jBPNYC31A1p9VCyQXJPtNQYVzc1DZ7bpNLNE07cZg:QUQ3MjNmem5tVzBhaUVUYy1HaVF6MDFjQUVDb3N4V3I5ZGRqMUwtRjZURUM3RlRlUFMzUXR4ZTNQbU1xRUZhb1RJbkRoRER2dUg3UnF6aVdFaEM4LVVIYUwxVkR1RWVHWkRidTBzZk5lanEwZGlnSWRFUlNzVnA1Zy1Za19JNS1vWWVlZHB6N2R2S1B2ZXAycmFnTnVkRGtLXzQ3Q0JKVHdR
8
+ .youtube.com TRUE / FALSE 0 PREF f6=40000000&f7=100&tz=UTC&f4=4000000&hl=en
9
+ .youtube.com TRUE / TRUE 1782131576 SAPISID LQcC15xXrpnJTi7Q/AJ-2HwPN82P46JA9w
10
+ .youtube.com TRUE / FALSE 1782131576 SID g.a000xAi31c6NS6rhuZ6moWOGE8mFb-RNY56eb3AO0QI45DL855jF0nt0X4Jzho5KCDEllSLUNgACgYKARkSARcSFQHGX2MighRO7TvO-htkX-S2jV3UUxoVAUF8yKoM8r_ABtcdfmboTqnomhX30076
11
+ .youtube.com TRUE / FALSE 1779561435 SIDCC AKEyXzXPb0M3-zE8pP2o_8qMeiBcBmsNJ2sdg5Gos_YYZyNVmaVw9yY5sPO03O2X6UHgWLqpaw
12
+ .youtube.com TRUE / TRUE 1782131576 SSID AlFmxvMg2GJy-t016
13
+ .youtube.com TRUE / TRUE 1763577434 VISITOR_INFO1_LIVE SYSStDYQ_LU
14
+ .youtube.com TRUE / TRUE 1763577434 VISITOR_PRIVACY_METADATA CgJJRBIEGgAgZQ%3D%3D
15
+ .youtube.com TRUE / TRUE 0 YSC kAyjY8f68rw
16
+ .youtube.com TRUE / TRUE 1763577434 YT_DEVICE_MEASUREMENT_ID MIUb_wk=
17
+ .youtube.com TRUE / TRUE 1782131576 __Secure-1PAPISID LQcC15xXrpnJTi7Q/AJ-2HwPN82P46JA9w
18
+ .youtube.com TRUE / TRUE 1782131576 __Secure-1PSID g.a000xAi31c6NS6rhuZ6moWOGE8mFb-RNY56eb3AO0QI45DL855jFNUTQ0jpn71ggUpjGmzVaUAACgYKAYsSARcSFQHGX2Miqli39DBci7MKI5T6Wsc12BoVAUF8yKrVC7sKClSNjuy-L-9KvdV-0076
19
+ .youtube.com TRUE / TRUE 1779561435 __Secure-1PSIDCC AKEyXzVOgOg-E6lEO5sZvKEnB4CllzogsGE73AQrtM60J0gT3hNsq-g8K2ZVKmvL6nnW6pqKLgg
20
+ .youtube.com TRUE / TRUE 1779445962 __Secure-1PSIDTS sidts-CjIBjplskPcPVfxYYHKJIHHJ2iy3ZRhiCubEqr13ePQ1Lnw7dnTGQ9xUCPuqV7wRBFmJLxAA
21
+ .youtube.com TRUE / TRUE 1782131576 __Secure-3PAPISID LQcC15xXrpnJTi7Q/AJ-2HwPN82P46JA9w
22
+ .youtube.com TRUE / TRUE 1782131576 __Secure-3PSID g.a000xAi31c6NS6rhuZ6moWOGE8mFb-RNY56eb3AO0QI45DL855jF1lrtzXKTEEbc1SlazM2q1QACgYKAXISARcSFQHGX2Mi4jae2f3_ZOET1JnpJlDVZhoVAUF8yKoTplJPjYY1ne5R9aW2PBNf0076
23
+ .youtube.com TRUE / TRUE 1779561435 __Secure-3PSIDCC AKEyXzXsLi8btJTOTlRMV1JttFLbr1Kb5ix_eCbHcQ4XiAU72KIRH0BXhbjF2CVwBU_4kKmCNDM
24
+ .youtube.com TRUE / TRUE 1779445962 __Secure-3PSIDTS sidts-CjIBjplskPcPVfxYYHKJIHHJ2iy3ZRhiCubEqr13ePQ1Lnw7dnTGQ9xUCPuqV7wRBFmJLxAA
25
+ .youtube.com TRUE / TRUE 1763549411 __Secure-ROLLOUT_TOKEN CKDDrsnJtYyu2gEQptCr_ceQjQMYkPum5rW5jQM%3D
26
+ .youtube.com TRUE / TRUE 1811097434 __Secure-YT_TVFAS t=485164&s=2
image/Background.jpg ADDED
index.js ADDED
@@ -0,0 +1,627 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import express from 'express';
2
+ 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);
10
+ const __dirname = path.dirname(__filename);
11
+
12
+ const app = express();
13
+ const PORT = process.env.PORT || 7860;
14
+ const domain = `https://fourstore-ytdl-api.hf.space`;
15
+ const LIB_FOLDER = path.join(__dirname, 'downloads');
16
+
17
+ // Statistics
18
+ let visitCount = 0;
19
+ let successDownloads = 0;
20
+ let failedDownloads = 0;
21
+
22
+ if (!fs.existsSync(LIB_FOLDER)) {
23
+ fs.mkdirSync(LIB_FOLDER, { recursive: true });
24
+ }
25
+
26
+ app.use(cors({
27
+ origin: '*',
28
+ methods: ['GET', 'POST', 'OPTIONS'],
29
+ allowedHeaders: ['Content-Type', 'Authorization']
30
+ }));
31
+
32
+ app.use(express.json());
33
+ app.use('/image', express.static(path.join(__dirname, 'image')));
34
+ app.use('/download', express.static(LIB_FOLDER));
35
+
36
+ async function downloadYouTube(url, type, quality = "720") {
37
+ try {
38
+ const qualityParam = type === 'audio' ? "mp3" : quality;
39
+ const result = await ytdown(url, qualityParam);
40
+
41
+ if (!result.status) {
42
+ throw new Error(result.error || 'Gagal mendownload dari YouTube');
43
+ }
44
+
45
+ const fileId = Math.random().toString(36).substring(2, 8);
46
+ let downloadUrl = null;
47
+ let fileName = '';
48
+ let fileExt = '';
49
+
50
+ if (type === 'audio' && result.audio) {
51
+ downloadUrl = result.audio.url;
52
+ fileExt = result.audio.ext || 'mp3';
53
+ fileName = `audio_${fileId}.${fileExt}`;
54
+ } else if (type === 'video' && result.video) {
55
+ downloadUrl = result.video.url;
56
+ fileExt = result.video.ext || 'mp4';
57
+ fileName = `video_${fileId}.${fileExt}`;
58
+ } else {
59
+ throw new Error('Tidak ada media yang tersedia');
60
+ }
61
+
62
+ const filePath = path.join(LIB_FOLDER, fileName);
63
+
64
+ const response = await fetch(downloadUrl);
65
+ const buffer = await response.arrayBuffer();
66
+ fs.writeFileSync(filePath, Buffer.from(buffer));
67
+
68
+ successDownloads++;
69
+
70
+ return {
71
+ success: true,
72
+ type: type.toUpperCase(),
73
+ title: result.title,
74
+ channel: result.channel,
75
+ duration: result.duration,
76
+ thumbnail: result.thumbnail,
77
+ quality: type === 'audio' ? result.audio?.quality : result.video?.quality,
78
+ size: type === 'audio' ? result.audio?.size : result.video?.size,
79
+ download_url: `${domain}/download/${fileName}`
80
+ };
81
+ } catch (error) {
82
+ failedDownloads++;
83
+ return {
84
+ success: false,
85
+ error: `Failed to download ${type} from YouTube: ${error.message}`
86
+ };
87
+ }
88
+ }
89
+
90
+ // API Endpoints
91
+ app.get('/api/download/audio', async (req, res) => {
92
+ const { url, quality } = req.query;
93
+ if (!url) return res.status(400).json({ error: "YouTube URL required" });
94
+ const result = await downloadYouTube(url, 'audio', quality);
95
+ res.json(result);
96
+ });
97
+
98
+ app.get('/api/download/video', async (req, res) => {
99
+ const { url, quality } = req.query;
100
+ if (!url) return res.status(400).json({ error: "YouTube URL required" });
101
+ const result = await downloadYouTube(url, 'video', quality || '720');
102
+ res.json(result);
103
+ });
104
+
105
+ app.get('/api/download/tiktok', async (req, res) => {
106
+ try {
107
+ const { url } = req.query;
108
+ if (!url) return res.status(400).json({ error: "TikTok URL required" });
109
+ const result = await extractAndDownloadTikTok(url, domain);
110
+ successDownloads++;
111
+ res.json(result);
112
+ } catch (error) {
113
+ failedDownloads++;
114
+ res.status(500).json({ success: false, error: error.message });
115
+ }
116
+ });
117
+
118
+ // Homepage
119
+ app.get('/', (req, res) => {
120
+ visitCount++;
121
+ res.send(`<!DOCTYPE html>
122
+ <html lang="id">
123
+ <head>
124
+ <meta charset="UTF-8">
125
+ <meta name="viewport" content="width=device-width, initial-scale=1">
126
+ <title>Downloader YouTube & TikTok</title>
127
+ <style>
128
+ * { box-sizing: border-box; margin: 0; padding: 0; }
129
+ body {
130
+ font-family: 'Poppins', Arial, sans-serif;
131
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
132
+ color: #fff;
133
+ padding-bottom: 50px;
134
+ min-height: 100vh;
135
+ }
136
+ .navbar {
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: space-between;
140
+ background: rgba(0, 0, 0, 0.3);
141
+ backdrop-filter: blur(10px);
142
+ padding: 15px 25px;
143
+ position: fixed;
144
+ top: 0;
145
+ left: 0;
146
+ right: 0;
147
+ z-index: 1000;
148
+ }
149
+ .navbar .menu-icon { font-size: 24px; cursor: pointer; }
150
+ .navbar .title { font-size: 22px; font-weight: bold; background: linear-gradient(135deg, #fff, #ffd89b); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
151
+ .sidebar {
152
+ height: 100%;
153
+ width: 260px;
154
+ position: fixed;
155
+ top: 0;
156
+ left: -260px;
157
+ background: rgba(0, 0, 0, 0.95);
158
+ backdrop-filter: blur(10px);
159
+ overflow-x: hidden;
160
+ transition: 0.3s;
161
+ padding-top: 70px;
162
+ z-index: 1100;
163
+ }
164
+ .sidebar a {
165
+ padding: 15px 25px;
166
+ text-decoration: none;
167
+ font-size: 16px;
168
+ color: #fff;
169
+ display: block;
170
+ transition: 0.3s;
171
+ }
172
+ .sidebar a:hover { background: rgba(102, 126, 234, 0.8); padding-left: 35px; }
173
+ .sidebar .close-btn { position: absolute; top: 15px; right: 20px; font-size: 28px; cursor: pointer; }
174
+ .container {
175
+ margin-top: 80px;
176
+ padding: 25px;
177
+ max-width: 550px;
178
+ margin-left: auto;
179
+ margin-right: auto;
180
+ background: rgba(255, 255, 255, 0.1);
181
+ backdrop-filter: blur(10px);
182
+ border-radius: 20px;
183
+ box-shadow: 0 8px 32px rgba(0,0,0,0.2);
184
+ border: 1px solid rgba(255,255,255,0.2);
185
+ }
186
+ h1 {
187
+ margin-bottom: 20px;
188
+ text-align: center;
189
+ font-size: 28px;
190
+ background: linear-gradient(135deg, #fff, #ffd89b);
191
+ -webkit-background-clip: text;
192
+ -webkit-text-fill-color: transparent;
193
+ }
194
+ input[type="text"] {
195
+ width: 100%;
196
+ padding: 14px;
197
+ margin: 10px 0;
198
+ border: none;
199
+ border-radius: 12px;
200
+ background: rgba(255,255,255,0.2);
201
+ color: #fff;
202
+ font-size: 16px;
203
+ outline: none;
204
+ transition: 0.3s;
205
+ }
206
+ input[type="text"]:focus {
207
+ background: rgba(255,255,255,0.3);
208
+ box-shadow: 0 0 10px rgba(255,255,255,0.3);
209
+ }
210
+ input[type="text"]::placeholder { color: rgba(255,255,255,0.7); }
211
+ .btn-group {
212
+ display: flex;
213
+ gap: 12px;
214
+ margin: 15px 0;
215
+ }
216
+ button {
217
+ flex: 1;
218
+ padding: 12px;
219
+ background: linear-gradient(135deg, #667eea, #764ba2);
220
+ color: #fff;
221
+ border: none;
222
+ border-radius: 12px;
223
+ cursor: pointer;
224
+ transition: 0.3s;
225
+ font-size: 15px;
226
+ font-weight: bold;
227
+ }
228
+ button:hover { transform: translateY(-2px); box-shadow: 0 5px 15px rgba(0,0,0,0.3); }
229
+ .tiktok-btn {
230
+ background: linear-gradient(135deg, #25F4EE, #000);
231
+ }
232
+ .loading {
233
+ display: none;
234
+ width: 50px;
235
+ height: 50px;
236
+ border: 4px solid rgba(255,255,255,0.3);
237
+ border-top: 4px solid #fff;
238
+ border-radius: 50%;
239
+ animation: spin 1s linear infinite;
240
+ margin: 20px auto;
241
+ }
242
+ @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
243
+ .stats {
244
+ display: flex;
245
+ justify-content: space-around;
246
+ margin-top: 25px;
247
+ gap: 12px;
248
+ }
249
+ .stat-box {
250
+ flex: 1;
251
+ background: rgba(0, 0, 0, 0.3);
252
+ padding: 12px;
253
+ border-radius: 12px;
254
+ text-align: center;
255
+ backdrop-filter: blur(5px);
256
+ }
257
+ .stat-box span {
258
+ font-size: 24px;
259
+ font-weight: bold;
260
+ display: block;
261
+ margin-bottom: 5px;
262
+ }
263
+ .result {
264
+ margin-top: 25px;
265
+ text-align: center;
266
+ }
267
+ .result-card {
268
+ background: rgba(0,0,0,0.3);
269
+ border-radius: 16px;
270
+ padding: 20px;
271
+ margin-top: 15px;
272
+ }
273
+ .result-card img {
274
+ max-width: 100%;
275
+ border-radius: 12px;
276
+ margin: 10px 0;
277
+ }
278
+ .download-btn {
279
+ display: inline-block;
280
+ padding: 12px 24px;
281
+ margin-top: 15px;
282
+ background: linear-gradient(135deg, #00d4ff, #667eea);
283
+ color: #fff;
284
+ border-radius: 12px;
285
+ text-decoration: none;
286
+ font-weight: bold;
287
+ transition: 0.3s;
288
+ }
289
+ .download-btn:hover { transform: scale(1.05); box-shadow: 0 5px 20px rgba(0,0,0,0.3); }
290
+ .quality-select {
291
+ width: 100%;
292
+ padding: 10px;
293
+ margin: 10px 0;
294
+ border-radius: 10px;
295
+ background: rgba(255,255,255,0.2);
296
+ color: #fff;
297
+ border: none;
298
+ outline: none;
299
+ }
300
+ footer {
301
+ text-align: center;
302
+ padding: 20px;
303
+ font-size: 13px;
304
+ color: rgba(255,255,255,0.7);
305
+ margin-top: 30px;
306
+ }
307
+ footer a { color: #ffd89b; text-decoration: none; }
308
+ @media screen and (max-width: 600px) {
309
+ .btn-group { flex-direction: column; }
310
+ .container { margin: 80px 15px 15px; }
311
+ }
312
+ </style>
313
+ </head>
314
+ <body>
315
+ <div class="navbar">
316
+ <div class="menu-icon" onclick="toggleSidebar()">☰</div>
317
+ <div class="title">🎬 Media Downloader</div>
318
+ </div>
319
+ <div id="sidebar" class="sidebar">
320
+ <span class="close-btn" onclick="toggleSidebar()">×</span>
321
+ <a href="/">🏠 Beranda</a>
322
+ <a href="/docs">📚 Dokumentasi</a>
323
+ <a href="https://github.com/" target="_blank">💻 GitHub</a>
324
+ </div>
325
+ <div class="container">
326
+ <h1>🎥 YouTube & TikTok Downloader</h1>
327
+ <input type="text" id="url" placeholder="Masukkan URL YouTube / TikTok...">
328
+
329
+ <div class="btn-group">
330
+ <button onclick="unduh('audio')">🎵 Audio YouTube</button>
331
+ <button onclick="unduh('video')">📹 Video YouTube</button>
332
+ <button class="tiktok-btn" onclick="unduh('tiktok')">📱 TikTok</button>
333
+ </div>
334
+
335
+ <div class="loading" id="loading"></div>
336
+ <div id="hasil" class="result"></div>
337
+
338
+ <div class="stats">
339
+ <div class="stat-box"><span id="jumlahKunjungan">${visitCount}</span>Kunjungan</div>
340
+ <div class="stat-box"><span id="berhasilDiunduh">${successDownloads}</span>Berhasil</div>
341
+ <div class="stat-box"><span id="gagalDiunduh">${failedDownloads}</span>Gagal</div>
342
+ </div>
343
+ </div>
344
+ <footer>
345
+ <p>Made with ❤️ by Fourstore | Owner: RezaHaris</p>
346
+ <p>💬 <a href="https://wa.me/6283163784116" target="_blank">Hubungi Owner</a></p>
347
+ </footer>
348
+ <script>
349
+ const domain = "${domain}";
350
+
351
+ function toggleSidebar() {
352
+ const sidebar = document.getElementById('sidebar');
353
+ sidebar.style.left = (sidebar.style.left === '0px') ? '-260px' : '0px';
354
+ }
355
+
356
+ async function unduh(jenis) {
357
+ const url = document.getElementById('url').value.trim();
358
+ if (!url) return alert("⚠️ Harap masukkan URL!");
359
+
360
+ document.getElementById('loading').style.display = 'block';
361
+ document.getElementById('hasil').innerHTML = '';
362
+
363
+ try {
364
+ let endpoint = '';
365
+ let params = new URLSearchParams({ url });
366
+
367
+ if (jenis === 'audio') endpoint = 'audio';
368
+ else if (jenis === 'video') {
369
+ const quality = document.getElementById('videoQuality')?.value || '720';
370
+ params.append('quality', quality);
371
+ endpoint = 'video';
372
+ } else endpoint = 'tiktok';
373
+
374
+ const response = await fetch(`${domain}/api/download/${endpoint}?${params}`);
375
+ const hasil = await response.json();
376
+
377
+ document.getElementById('loading').style.display = 'none';
378
+
379
+ if (hasil.success) {
380
+ if (jenis === 'tiktok') {
381
+ let html = '<div class="result-card">';
382
+ if (hasil.type === 'video') {
383
+ html += \`<h3>🎬 Video TikTok</h3>
384
+ <p>${hasil.description || 'No description'}</p>
385
+ <video controls style="width:100%; border-radius:12px; margin:10px 0;">
386
+ <source src="${hasil.downloadInfo?.download_url}" type="video/mp4">
387
+ </video>
388
+ <a href="${hasil.downloadInfo?.download_url}" class="download-btn" download>⬇️ Download Video</a>\`;
389
+ } else if (hasil.images) {
390
+ html += \`<h3>🖼️ Slide TikTok</h3>
391
+ <div style="display:grid; grid-template-columns:repeat(auto-fill,minmax(140px,1fr)); gap:10px;">\`;
392
+ hasil.images.forEach(img => {
393
+ html += \`<div><img src="${img.download_url}" style="width:100%; border-radius:8px;"><a href="${img.download_url}" download style="display:block;text-align:center;margin-top:5px;">⬇️</a></div>\`;
394
+ });
395
+ html += \`</div><a href="${hasil.folder_url}" class="download-btn" download>📦 Download Semua</a>\`;
396
+ }
397
+ if (hasil.musicInfo?.download_url) {
398
+ html += \`<a href="${hasil.musicInfo.download_url}" class="download-btn" style="margin-top:10px;display:inline-block;">🎵 Download Musik</a>\`;
399
+ }
400
+ html += '</div>';
401
+ document.getElementById('hasil').innerHTML = html;
402
+ } else {
403
+ document.getElementById('hasil').innerHTML = \`
404
+ <div class="result-card">
405
+ <h3>✨ \${hasil.title}</h3>
406
+ ${hasil.thumbnail ? '<img src="' + hasil.thumbnail + '" alt="Thumbnail">' : ''}
407
+ <p>📺 Channel: \${hasil.channel}</p>
408
+ <p>⏱️ Durasi: \${hasil.duration}</p>
409
+ <p>🎚️ Kualitas: \${hasil.quality || 'Auto'}</p>
410
+ <p>📦 Ukuran: \${hasil.size || 'Unknown'}</p>
411
+ <a href="\${hasil.download_url}" class="download-btn" download>⬇️ Download \${hasil.type}</a>
412
+ </div>
413
+ \`;
414
+ }
415
+ updateStatistik(true);
416
+ } else {
417
+ document.getElementById('hasil').innerHTML = \`<div class="result-card" style="color:#ffcccc;">❌ \${hasil.error || 'Gagal download'}</div>\`;
418
+ updateStatistik(false);
419
+ }
420
+ } catch (error) {
421
+ document.getElementById('loading').style.display = 'none';
422
+ document.getElementById('hasil').innerHTML = \`<div class="result-card" style="color:#ffcccc;">❌ Error: \${error.message}</div>\`;
423
+ updateStatistik(false);
424
+ }
425
+ }
426
+
427
+ function updateStatistik(berhasil) {
428
+ const kunjungan = parseInt(document.getElementById('jumlahKunjungan').textContent) + 1;
429
+ document.getElementById('jumlahKunjungan').textContent = kunjungan;
430
+ if (berhasil) {
431
+ let berhasilVal = parseInt(document.getElementById('berhasilDiunduh').textContent) + 1;
432
+ document.getElementById('berhasilDiunduh').textContent = berhasilVal;
433
+ } else {
434
+ let gagalVal = parseInt(document.getElementById('gagalDiunduh').textContent) + 1;
435
+ document.getElementById('gagalDiunduh').textContent = gagalVal;
436
+ }
437
+ }
438
+ </script>
439
+ </body>
440
+ </html>
441
+ `);
442
+ });
443
+
444
+ // Documentation
445
+ app.get('/docs', (req, res) => {
446
+ res.send(`<!DOCTYPE html>
447
+ <html lang="id">
448
+ <head>
449
+ <meta charset="UTF-8">
450
+ <meta name="viewport" content="width=device-width, initial-scale=1">
451
+ <title>Dokumentasi API</title>
452
+ <style>
453
+ * { margin: 0; padding: 0; box-sizing: border-box; }
454
+ body {
455
+ font-family: 'Poppins', Arial, sans-serif;
456
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
457
+ color: #fff;
458
+ padding: 20px;
459
+ }
460
+ .navbar {
461
+ display: flex;
462
+ align-items: center;
463
+ justify-content: space-between;
464
+ background: rgba(0,0,0,0.3);
465
+ backdrop-filter: blur(10px);
466
+ padding: 12px 20px;
467
+ position: fixed;
468
+ top: 0;
469
+ left: 0;
470
+ right: 0;
471
+ z-index: 1000;
472
+ }
473
+ .navbar .menu-icon { font-size: 24px; cursor: pointer; }
474
+ .navbar .title { font-size: 20px; font-weight: bold; }
475
+ .sidebar {
476
+ height: 100%;
477
+ width: 260px;
478
+ position: fixed;
479
+ top: 0;
480
+ left: -260px;
481
+ background: rgba(0,0,0,0.95);
482
+ transition: 0.3s;
483
+ padding-top: 70px;
484
+ z-index: 1100;
485
+ }
486
+ .sidebar a {
487
+ padding: 15px 25px;
488
+ text-decoration: none;
489
+ font-size: 16px;
490
+ color: #fff;
491
+ display: block;
492
+ }
493
+ .sidebar a:hover { background: rgba(102,126,234,0.8); }
494
+ .sidebar .close-btn { position: absolute; top: 15px; right: 20px; font-size: 28px; cursor: pointer; }
495
+ .container {
496
+ margin-top: 80px;
497
+ max-width: 900px;
498
+ margin-left: auto;
499
+ margin-right: auto;
500
+ background: rgba(255,255,255,0.1);
501
+ backdrop-filter: blur(10px);
502
+ border-radius: 20px;
503
+ padding: 30px;
504
+ }
505
+ h1 { margin-bottom: 20px; color: #ffd89b; }
506
+ h2 { margin-top: 30px; color: #667eea; border-bottom: 2px solid rgba(255,255,255,0.3); padding-bottom: 8px; }
507
+ code {
508
+ background: rgba(0,0,0,0.5);
509
+ padding: 3px 8px;
510
+ border-radius: 6px;
511
+ font-family: monospace;
512
+ }
513
+ pre {
514
+ background: rgba(0,0,0,0.5);
515
+ padding: 15px;
516
+ border-radius: 10px;
517
+ overflow-x: auto;
518
+ margin: 15px 0;
519
+ }
520
+ .endpoint {
521
+ background: rgba(0,0,0,0.3);
522
+ padding: 20px;
523
+ border-radius: 12px;
524
+ margin-bottom: 25px;
525
+ border-left: 4px solid #667eea;
526
+ }
527
+ .method {
528
+ display: inline-block;
529
+ padding: 4px 12px;
530
+ background: #667eea;
531
+ border-radius: 20px;
532
+ font-weight: bold;
533
+ margin-right: 10px;
534
+ }
535
+ table {
536
+ width: 100%;
537
+ border-collapse: collapse;
538
+ margin: 15px 0;
539
+ }
540
+ th, td {
541
+ border: 1px solid rgba(255,255,255,0.2);
542
+ padding: 10px;
543
+ text-align: left;
544
+ }
545
+ th { background: rgba(102,126,234,0.3); }
546
+ footer { text-align: center; padding: 20px; margin-top: 20px; color: rgba(255,255,255,0.7); }
547
+ a { color: #ffd89b; text-decoration: none; }
548
+ </style>
549
+ </head>
550
+ <body>
551
+ <div class="navbar">
552
+ <div class="menu-icon" onclick="toggleSidebar()">☰</div>
553
+ <div class="title">📚 Dokumentasi API</div>
554
+ </div>
555
+ <div id="sidebar" class="sidebar">
556
+ <span class="close-btn" onclick="toggleSidebar()">×</span>
557
+ <a href="/">🏠 Home</a>
558
+ <a href="/docs">📚 Dokumentasi</a>
559
+ <a href="https://github.com/" target="_blank">💻 GitHub</a>
560
+ </div>
561
+ <div class="container">
562
+ <h1>📖 Dokumentasi API Downloader</h1>
563
+ <p>API untuk download video/audio dari YouTube dan TikTok.</p>
564
+
565
+ <div class="endpoint">
566
+ <h2><span class="method">GET</span> /api/download/audio</h2>
567
+ <p>Download audio dari YouTube (MP3).</p>
568
+ <h3>Parameter:</h3>
569
+ <ul><li><code>url</code> (required) - URL YouTube</li></ul>
570
+ <h3>Contoh:</h3>
571
+ <pre>GET ${domain}/api/download/audio?url=https://youtube.com/watch?v=VIDEO_ID</pre>
572
+ </div>
573
+
574
+ <div class="endpoint">
575
+ <h2><span class="method">GET</span> /api/download/video</h2>
576
+ <p>Download video dari YouTube.</p>
577
+ <h3>Parameter:</h3>
578
+ <ul><li><code>url</code> (required) - URL YouTube</li><li><code>quality</code> (optional) - 144/240/360/480/720/1080 (default: 720)</li></ul>
579
+ <pre>GET ${domain}/api/download/video?url=https://youtube.com/watch?v=VIDEO_ID&quality=1080</pre>
580
+ </div>
581
+
582
+ <div class="endpoint">
583
+ <h2><span class="method">GET</span> /api/download/tiktok</h2>
584
+ <p>Download video/slide TikTok.</p>
585
+ <h3>Parameter:</h3>
586
+ <ul><li><code>url</code> (required) - URL TikTok</li></ul>
587
+ <pre>GET ${domain}/api/download/tiktok?url=https://tiktok.com/@user/video/123456789</pre>
588
+ </div>
589
+
590
+ <h2>📦 Response Format</h2>
591
+ <h3>YouTube Audio/Video:</h3>
592
+ <pre>{
593
+ "success": true,
594
+ "type": "AUDIO/VIDEO",
595
+ "title": "Judul Video",
596
+ "channel": "Nama Channel",
597
+ "duration": "3:45",
598
+ "thumbnail": "https://...",
599
+ "quality": "720",
600
+ "size": "4.56 MB",
601
+ "download_url": "https://..."
602
+ }</pre>
603
+
604
+ <h3>TikTok Video:</h3>
605
+ <pre>{
606
+ "success": true,
607
+ "type": "video",
608
+ "description": "Deskripsi video",
609
+ "downloadInfo": { "download_url": "https://..." }
610
+ }</pre>
611
+ </div>
612
+ <footer>Made with ❤️ by Fourstore | Owner: RezaHaris | <a href="https://wa.me/6283163784116">Contact</a></footer>
613
+ <script>
614
+ function toggleSidebar() {
615
+ const sidebar = document.getElementById('sidebar');
616
+ sidebar.style.left = (sidebar.style.left === '0px') ? '-260px' : '0px';
617
+ }
618
+ </script>
619
+ </body>
620
+ </html>
621
+ `);
622
+ });
623
+
624
+ app.listen(PORT, () => {
625
+ console.log(`Server running at ${domain}`);
626
+ console.log(`Documentation available at ${domain}/docs`);
627
+ });
package-lock.json ADDED
@@ -0,0 +1,1621 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ytdl",
3
+ "lockfileVersion": 3,
4
+ "requires": true,
5
+ "packages": {
6
+ "": {
7
+ "dependencies": {
8
+ "@distube/ytdl-core": "^4.16.4",
9
+ "atob": "^2.1.2",
10
+ "axios": "^1.7.9",
11
+ "cheerio": "^1.0.0-rc.12",
12
+ "child_process": "^1.0.2",
13
+ "cors": "^2.8.5",
14
+ "express": "^4.21.2",
15
+ "file-type": "^20.1.0",
16
+ "fs": "^0.0.1-security",
17
+ "http": "^0.0.1-security",
18
+ "path": "^0.12.7",
19
+ "prompt-sync": "^4.2.0",
20
+ "qs": "^6.14.0"
21
+ }
22
+ },
23
+ "node_modules/@distube/ytdl-core": {
24
+ "version": "4.16.10",
25
+ "resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.16.10.tgz",
26
+ "integrity": "sha512-KFKZtNlynOO0PYxelUF5h2bKyAU1d8fe6aZmo+gxWt7H2MQbd0bUeyV4y9iWhI57nukjkSXXQGB625CfhrVdGQ==",
27
+ "license": "MIT",
28
+ "dependencies": {
29
+ "http-cookie-agent": "^7.0.1",
30
+ "https-proxy-agent": "^7.0.6",
31
+ "m3u8stream": "^0.8.6",
32
+ "miniget": "^4.2.3",
33
+ "sax": "^1.4.1",
34
+ "tough-cookie": "^5.1.2",
35
+ "undici": "^7.8.0"
36
+ },
37
+ "engines": {
38
+ "node": ">=20.18.1"
39
+ },
40
+ "funding": {
41
+ "url": "https://github.com/distubejs/ytdl-core?sponsor"
42
+ }
43
+ },
44
+ "node_modules/@tokenizer/inflate": {
45
+ "version": "0.2.7",
46
+ "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz",
47
+ "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==",
48
+ "license": "MIT",
49
+ "dependencies": {
50
+ "debug": "^4.4.0",
51
+ "fflate": "^0.8.2",
52
+ "token-types": "^6.0.0"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ },
57
+ "funding": {
58
+ "type": "github",
59
+ "url": "https://github.com/sponsors/Borewit"
60
+ }
61
+ },
62
+ "node_modules/@tokenizer/inflate/node_modules/debug": {
63
+ "version": "4.4.1",
64
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
65
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
66
+ "license": "MIT",
67
+ "dependencies": {
68
+ "ms": "^2.1.3"
69
+ },
70
+ "engines": {
71
+ "node": ">=6.0"
72
+ },
73
+ "peerDependenciesMeta": {
74
+ "supports-color": {
75
+ "optional": true
76
+ }
77
+ }
78
+ },
79
+ "node_modules/@tokenizer/inflate/node_modules/ms": {
80
+ "version": "2.1.3",
81
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
82
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
83
+ "license": "MIT"
84
+ },
85
+ "node_modules/@tokenizer/token": {
86
+ "version": "0.3.0",
87
+ "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
88
+ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
89
+ "license": "MIT"
90
+ },
91
+ "node_modules/accepts": {
92
+ "version": "1.3.8",
93
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
94
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
95
+ "license": "MIT",
96
+ "dependencies": {
97
+ "mime-types": "~2.1.34",
98
+ "negotiator": "0.6.3"
99
+ },
100
+ "engines": {
101
+ "node": ">= 0.6"
102
+ }
103
+ },
104
+ "node_modules/agent-base": {
105
+ "version": "7.1.3",
106
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz",
107
+ "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==",
108
+ "license": "MIT",
109
+ "engines": {
110
+ "node": ">= 14"
111
+ }
112
+ },
113
+ "node_modules/ansi-regex": {
114
+ "version": "4.1.1",
115
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
116
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
117
+ "license": "MIT",
118
+ "engines": {
119
+ "node": ">=6"
120
+ }
121
+ },
122
+ "node_modules/array-flatten": {
123
+ "version": "1.1.1",
124
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
125
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
126
+ "license": "MIT"
127
+ },
128
+ "node_modules/asynckit": {
129
+ "version": "0.4.0",
130
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
131
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
132
+ "license": "MIT"
133
+ },
134
+ "node_modules/atob": {
135
+ "version": "2.1.2",
136
+ "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
137
+ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
138
+ "license": "(MIT OR Apache-2.0)",
139
+ "bin": {
140
+ "atob": "bin/atob.js"
141
+ },
142
+ "engines": {
143
+ "node": ">= 4.5.0"
144
+ }
145
+ },
146
+ "node_modules/axios": {
147
+ "version": "1.9.0",
148
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
149
+ "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
150
+ "license": "MIT",
151
+ "dependencies": {
152
+ "follow-redirects": "^1.15.6",
153
+ "form-data": "^4.0.0",
154
+ "proxy-from-env": "^1.1.0"
155
+ }
156
+ },
157
+ "node_modules/body-parser": {
158
+ "version": "1.20.3",
159
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
160
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
161
+ "license": "MIT",
162
+ "dependencies": {
163
+ "bytes": "3.1.2",
164
+ "content-type": "~1.0.5",
165
+ "debug": "2.6.9",
166
+ "depd": "2.0.0",
167
+ "destroy": "1.2.0",
168
+ "http-errors": "2.0.0",
169
+ "iconv-lite": "0.4.24",
170
+ "on-finished": "2.4.1",
171
+ "qs": "6.13.0",
172
+ "raw-body": "2.5.2",
173
+ "type-is": "~1.6.18",
174
+ "unpipe": "1.0.0"
175
+ },
176
+ "engines": {
177
+ "node": ">= 0.8",
178
+ "npm": "1.2.8000 || >= 1.4.16"
179
+ }
180
+ },
181
+ "node_modules/body-parser/node_modules/iconv-lite": {
182
+ "version": "0.4.24",
183
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
184
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
185
+ "license": "MIT",
186
+ "dependencies": {
187
+ "safer-buffer": ">= 2.1.2 < 3"
188
+ },
189
+ "engines": {
190
+ "node": ">=0.10.0"
191
+ }
192
+ },
193
+ "node_modules/body-parser/node_modules/qs": {
194
+ "version": "6.13.0",
195
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
196
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
197
+ "license": "BSD-3-Clause",
198
+ "dependencies": {
199
+ "side-channel": "^1.0.6"
200
+ },
201
+ "engines": {
202
+ "node": ">=0.6"
203
+ },
204
+ "funding": {
205
+ "url": "https://github.com/sponsors/ljharb"
206
+ }
207
+ },
208
+ "node_modules/boolbase": {
209
+ "version": "1.0.0",
210
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
211
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
212
+ "license": "ISC"
213
+ },
214
+ "node_modules/bytes": {
215
+ "version": "3.1.2",
216
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
217
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
218
+ "license": "MIT",
219
+ "engines": {
220
+ "node": ">= 0.8"
221
+ }
222
+ },
223
+ "node_modules/call-bind-apply-helpers": {
224
+ "version": "1.0.2",
225
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
226
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
227
+ "license": "MIT",
228
+ "dependencies": {
229
+ "es-errors": "^1.3.0",
230
+ "function-bind": "^1.1.2"
231
+ },
232
+ "engines": {
233
+ "node": ">= 0.4"
234
+ }
235
+ },
236
+ "node_modules/call-bound": {
237
+ "version": "1.0.4",
238
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
239
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
240
+ "license": "MIT",
241
+ "dependencies": {
242
+ "call-bind-apply-helpers": "^1.0.2",
243
+ "get-intrinsic": "^1.3.0"
244
+ },
245
+ "engines": {
246
+ "node": ">= 0.4"
247
+ },
248
+ "funding": {
249
+ "url": "https://github.com/sponsors/ljharb"
250
+ }
251
+ },
252
+ "node_modules/cheerio": {
253
+ "version": "1.0.0-rc.12",
254
+ "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
255
+ "integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
256
+ "license": "MIT",
257
+ "dependencies": {
258
+ "cheerio-select": "^2.1.0",
259
+ "dom-serializer": "^2.0.0",
260
+ "domhandler": "^5.0.3",
261
+ "domutils": "^3.0.1",
262
+ "htmlparser2": "^8.0.1",
263
+ "parse5": "^7.0.0",
264
+ "parse5-htmlparser2-tree-adapter": "^7.0.0"
265
+ },
266
+ "engines": {
267
+ "node": ">= 6"
268
+ },
269
+ "funding": {
270
+ "url": "https://github.com/cheeriojs/cheerio?sponsor=1"
271
+ }
272
+ },
273
+ "node_modules/cheerio-select": {
274
+ "version": "2.1.0",
275
+ "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
276
+ "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
277
+ "license": "BSD-2-Clause",
278
+ "dependencies": {
279
+ "boolbase": "^1.0.0",
280
+ "css-select": "^5.1.0",
281
+ "css-what": "^6.1.0",
282
+ "domelementtype": "^2.3.0",
283
+ "domhandler": "^5.0.3",
284
+ "domutils": "^3.0.1"
285
+ },
286
+ "funding": {
287
+ "url": "https://github.com/sponsors/fb55"
288
+ }
289
+ },
290
+ "node_modules/child_process": {
291
+ "version": "1.0.2",
292
+ "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
293
+ "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==",
294
+ "license": "ISC"
295
+ },
296
+ "node_modules/combined-stream": {
297
+ "version": "1.0.8",
298
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
299
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
300
+ "license": "MIT",
301
+ "dependencies": {
302
+ "delayed-stream": "~1.0.0"
303
+ },
304
+ "engines": {
305
+ "node": ">= 0.8"
306
+ }
307
+ },
308
+ "node_modules/content-disposition": {
309
+ "version": "0.5.4",
310
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
311
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
312
+ "license": "MIT",
313
+ "dependencies": {
314
+ "safe-buffer": "5.2.1"
315
+ },
316
+ "engines": {
317
+ "node": ">= 0.6"
318
+ }
319
+ },
320
+ "node_modules/content-type": {
321
+ "version": "1.0.5",
322
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
323
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
324
+ "license": "MIT",
325
+ "engines": {
326
+ "node": ">= 0.6"
327
+ }
328
+ },
329
+ "node_modules/cookie": {
330
+ "version": "0.7.1",
331
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
332
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
333
+ "license": "MIT",
334
+ "engines": {
335
+ "node": ">= 0.6"
336
+ }
337
+ },
338
+ "node_modules/cookie-signature": {
339
+ "version": "1.0.6",
340
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
341
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
342
+ "license": "MIT"
343
+ },
344
+ "node_modules/cors": {
345
+ "version": "2.8.5",
346
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
347
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
348
+ "license": "MIT",
349
+ "dependencies": {
350
+ "object-assign": "^4",
351
+ "vary": "^1"
352
+ },
353
+ "engines": {
354
+ "node": ">= 0.10"
355
+ }
356
+ },
357
+ "node_modules/css-select": {
358
+ "version": "5.1.0",
359
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
360
+ "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
361
+ "license": "BSD-2-Clause",
362
+ "dependencies": {
363
+ "boolbase": "^1.0.0",
364
+ "css-what": "^6.1.0",
365
+ "domhandler": "^5.0.2",
366
+ "domutils": "^3.0.1",
367
+ "nth-check": "^2.0.1"
368
+ },
369
+ "funding": {
370
+ "url": "https://github.com/sponsors/fb55"
371
+ }
372
+ },
373
+ "node_modules/css-what": {
374
+ "version": "6.1.0",
375
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
376
+ "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
377
+ "license": "BSD-2-Clause",
378
+ "engines": {
379
+ "node": ">= 6"
380
+ },
381
+ "funding": {
382
+ "url": "https://github.com/sponsors/fb55"
383
+ }
384
+ },
385
+ "node_modules/debug": {
386
+ "version": "2.6.9",
387
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
388
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
389
+ "license": "MIT",
390
+ "dependencies": {
391
+ "ms": "2.0.0"
392
+ }
393
+ },
394
+ "node_modules/delayed-stream": {
395
+ "version": "1.0.0",
396
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
397
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
398
+ "license": "MIT",
399
+ "engines": {
400
+ "node": ">=0.4.0"
401
+ }
402
+ },
403
+ "node_modules/depd": {
404
+ "version": "2.0.0",
405
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
406
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
407
+ "license": "MIT",
408
+ "engines": {
409
+ "node": ">= 0.8"
410
+ }
411
+ },
412
+ "node_modules/destroy": {
413
+ "version": "1.2.0",
414
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
415
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
416
+ "license": "MIT",
417
+ "engines": {
418
+ "node": ">= 0.8",
419
+ "npm": "1.2.8000 || >= 1.4.16"
420
+ }
421
+ },
422
+ "node_modules/dom-serializer": {
423
+ "version": "2.0.0",
424
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
425
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
426
+ "license": "MIT",
427
+ "dependencies": {
428
+ "domelementtype": "^2.3.0",
429
+ "domhandler": "^5.0.2",
430
+ "entities": "^4.2.0"
431
+ },
432
+ "funding": {
433
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
434
+ }
435
+ },
436
+ "node_modules/domelementtype": {
437
+ "version": "2.3.0",
438
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
439
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
440
+ "funding": [
441
+ {
442
+ "type": "github",
443
+ "url": "https://github.com/sponsors/fb55"
444
+ }
445
+ ],
446
+ "license": "BSD-2-Clause"
447
+ },
448
+ "node_modules/domhandler": {
449
+ "version": "5.0.3",
450
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
451
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
452
+ "license": "BSD-2-Clause",
453
+ "dependencies": {
454
+ "domelementtype": "^2.3.0"
455
+ },
456
+ "engines": {
457
+ "node": ">= 4"
458
+ },
459
+ "funding": {
460
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
461
+ }
462
+ },
463
+ "node_modules/domutils": {
464
+ "version": "3.2.2",
465
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
466
+ "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
467
+ "license": "BSD-2-Clause",
468
+ "dependencies": {
469
+ "dom-serializer": "^2.0.0",
470
+ "domelementtype": "^2.3.0",
471
+ "domhandler": "^5.0.3"
472
+ },
473
+ "funding": {
474
+ "url": "https://github.com/fb55/domutils?sponsor=1"
475
+ }
476
+ },
477
+ "node_modules/dunder-proto": {
478
+ "version": "1.0.1",
479
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
480
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
481
+ "license": "MIT",
482
+ "dependencies": {
483
+ "call-bind-apply-helpers": "^1.0.1",
484
+ "es-errors": "^1.3.0",
485
+ "gopd": "^1.2.0"
486
+ },
487
+ "engines": {
488
+ "node": ">= 0.4"
489
+ }
490
+ },
491
+ "node_modules/ee-first": {
492
+ "version": "1.1.1",
493
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
494
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
495
+ "license": "MIT"
496
+ },
497
+ "node_modules/encodeurl": {
498
+ "version": "2.0.0",
499
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
500
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
501
+ "license": "MIT",
502
+ "engines": {
503
+ "node": ">= 0.8"
504
+ }
505
+ },
506
+ "node_modules/entities": {
507
+ "version": "4.5.0",
508
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
509
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
510
+ "license": "BSD-2-Clause",
511
+ "engines": {
512
+ "node": ">=0.12"
513
+ },
514
+ "funding": {
515
+ "url": "https://github.com/fb55/entities?sponsor=1"
516
+ }
517
+ },
518
+ "node_modules/es-define-property": {
519
+ "version": "1.0.1",
520
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
521
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
522
+ "license": "MIT",
523
+ "engines": {
524
+ "node": ">= 0.4"
525
+ }
526
+ },
527
+ "node_modules/es-errors": {
528
+ "version": "1.3.0",
529
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
530
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
531
+ "license": "MIT",
532
+ "engines": {
533
+ "node": ">= 0.4"
534
+ }
535
+ },
536
+ "node_modules/es-object-atoms": {
537
+ "version": "1.1.1",
538
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
539
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
540
+ "license": "MIT",
541
+ "dependencies": {
542
+ "es-errors": "^1.3.0"
543
+ },
544
+ "engines": {
545
+ "node": ">= 0.4"
546
+ }
547
+ },
548
+ "node_modules/es-set-tostringtag": {
549
+ "version": "2.1.0",
550
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
551
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
552
+ "license": "MIT",
553
+ "dependencies": {
554
+ "es-errors": "^1.3.0",
555
+ "get-intrinsic": "^1.2.6",
556
+ "has-tostringtag": "^1.0.2",
557
+ "hasown": "^2.0.2"
558
+ },
559
+ "engines": {
560
+ "node": ">= 0.4"
561
+ }
562
+ },
563
+ "node_modules/escape-html": {
564
+ "version": "1.0.3",
565
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
566
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
567
+ "license": "MIT"
568
+ },
569
+ "node_modules/etag": {
570
+ "version": "1.8.1",
571
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
572
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
573
+ "license": "MIT",
574
+ "engines": {
575
+ "node": ">= 0.6"
576
+ }
577
+ },
578
+ "node_modules/express": {
579
+ "version": "4.21.2",
580
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
581
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
582
+ "license": "MIT",
583
+ "dependencies": {
584
+ "accepts": "~1.3.8",
585
+ "array-flatten": "1.1.1",
586
+ "body-parser": "1.20.3",
587
+ "content-disposition": "0.5.4",
588
+ "content-type": "~1.0.4",
589
+ "cookie": "0.7.1",
590
+ "cookie-signature": "1.0.6",
591
+ "debug": "2.6.9",
592
+ "depd": "2.0.0",
593
+ "encodeurl": "~2.0.0",
594
+ "escape-html": "~1.0.3",
595
+ "etag": "~1.8.1",
596
+ "finalhandler": "1.3.1",
597
+ "fresh": "0.5.2",
598
+ "http-errors": "2.0.0",
599
+ "merge-descriptors": "1.0.3",
600
+ "methods": "~1.1.2",
601
+ "on-finished": "2.4.1",
602
+ "parseurl": "~1.3.3",
603
+ "path-to-regexp": "0.1.12",
604
+ "proxy-addr": "~2.0.7",
605
+ "qs": "6.13.0",
606
+ "range-parser": "~1.2.1",
607
+ "safe-buffer": "5.2.1",
608
+ "send": "0.19.0",
609
+ "serve-static": "1.16.2",
610
+ "setprototypeof": "1.2.0",
611
+ "statuses": "2.0.1",
612
+ "type-is": "~1.6.18",
613
+ "utils-merge": "1.0.1",
614
+ "vary": "~1.1.2"
615
+ },
616
+ "engines": {
617
+ "node": ">= 0.10.0"
618
+ },
619
+ "funding": {
620
+ "type": "opencollective",
621
+ "url": "https://opencollective.com/express"
622
+ }
623
+ },
624
+ "node_modules/express/node_modules/qs": {
625
+ "version": "6.13.0",
626
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
627
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
628
+ "license": "BSD-3-Clause",
629
+ "dependencies": {
630
+ "side-channel": "^1.0.6"
631
+ },
632
+ "engines": {
633
+ "node": ">=0.6"
634
+ },
635
+ "funding": {
636
+ "url": "https://github.com/sponsors/ljharb"
637
+ }
638
+ },
639
+ "node_modules/fflate": {
640
+ "version": "0.8.2",
641
+ "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
642
+ "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
643
+ "license": "MIT"
644
+ },
645
+ "node_modules/file-type": {
646
+ "version": "20.5.0",
647
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz",
648
+ "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==",
649
+ "license": "MIT",
650
+ "dependencies": {
651
+ "@tokenizer/inflate": "^0.2.6",
652
+ "strtok3": "^10.2.0",
653
+ "token-types": "^6.0.0",
654
+ "uint8array-extras": "^1.4.0"
655
+ },
656
+ "engines": {
657
+ "node": ">=18"
658
+ },
659
+ "funding": {
660
+ "url": "https://github.com/sindresorhus/file-type?sponsor=1"
661
+ }
662
+ },
663
+ "node_modules/finalhandler": {
664
+ "version": "1.3.1",
665
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
666
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
667
+ "license": "MIT",
668
+ "dependencies": {
669
+ "debug": "2.6.9",
670
+ "encodeurl": "~2.0.0",
671
+ "escape-html": "~1.0.3",
672
+ "on-finished": "2.4.1",
673
+ "parseurl": "~1.3.3",
674
+ "statuses": "2.0.1",
675
+ "unpipe": "~1.0.0"
676
+ },
677
+ "engines": {
678
+ "node": ">= 0.8"
679
+ }
680
+ },
681
+ "node_modules/follow-redirects": {
682
+ "version": "1.15.9",
683
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
684
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
685
+ "funding": [
686
+ {
687
+ "type": "individual",
688
+ "url": "https://github.com/sponsors/RubenVerborgh"
689
+ }
690
+ ],
691
+ "license": "MIT",
692
+ "engines": {
693
+ "node": ">=4.0"
694
+ },
695
+ "peerDependenciesMeta": {
696
+ "debug": {
697
+ "optional": true
698
+ }
699
+ }
700
+ },
701
+ "node_modules/form-data": {
702
+ "version": "4.0.2",
703
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
704
+ "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
705
+ "license": "MIT",
706
+ "dependencies": {
707
+ "asynckit": "^0.4.0",
708
+ "combined-stream": "^1.0.8",
709
+ "es-set-tostringtag": "^2.1.0",
710
+ "mime-types": "^2.1.12"
711
+ },
712
+ "engines": {
713
+ "node": ">= 6"
714
+ }
715
+ },
716
+ "node_modules/forwarded": {
717
+ "version": "0.2.0",
718
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
719
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
720
+ "license": "MIT",
721
+ "engines": {
722
+ "node": ">= 0.6"
723
+ }
724
+ },
725
+ "node_modules/fresh": {
726
+ "version": "0.5.2",
727
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
728
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
729
+ "license": "MIT",
730
+ "engines": {
731
+ "node": ">= 0.6"
732
+ }
733
+ },
734
+ "node_modules/fs": {
735
+ "version": "0.0.1-security",
736
+ "resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
737
+ "integrity": "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w==",
738
+ "license": "ISC"
739
+ },
740
+ "node_modules/function-bind": {
741
+ "version": "1.1.2",
742
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
743
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
744
+ "license": "MIT",
745
+ "funding": {
746
+ "url": "https://github.com/sponsors/ljharb"
747
+ }
748
+ },
749
+ "node_modules/get-intrinsic": {
750
+ "version": "1.3.0",
751
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
752
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
753
+ "license": "MIT",
754
+ "dependencies": {
755
+ "call-bind-apply-helpers": "^1.0.2",
756
+ "es-define-property": "^1.0.1",
757
+ "es-errors": "^1.3.0",
758
+ "es-object-atoms": "^1.1.1",
759
+ "function-bind": "^1.1.2",
760
+ "get-proto": "^1.0.1",
761
+ "gopd": "^1.2.0",
762
+ "has-symbols": "^1.1.0",
763
+ "hasown": "^2.0.2",
764
+ "math-intrinsics": "^1.1.0"
765
+ },
766
+ "engines": {
767
+ "node": ">= 0.4"
768
+ },
769
+ "funding": {
770
+ "url": "https://github.com/sponsors/ljharb"
771
+ }
772
+ },
773
+ "node_modules/get-proto": {
774
+ "version": "1.0.1",
775
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
776
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
777
+ "license": "MIT",
778
+ "dependencies": {
779
+ "dunder-proto": "^1.0.1",
780
+ "es-object-atoms": "^1.0.0"
781
+ },
782
+ "engines": {
783
+ "node": ">= 0.4"
784
+ }
785
+ },
786
+ "node_modules/gopd": {
787
+ "version": "1.2.0",
788
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
789
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
790
+ "license": "MIT",
791
+ "engines": {
792
+ "node": ">= 0.4"
793
+ },
794
+ "funding": {
795
+ "url": "https://github.com/sponsors/ljharb"
796
+ }
797
+ },
798
+ "node_modules/has-symbols": {
799
+ "version": "1.1.0",
800
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
801
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
802
+ "license": "MIT",
803
+ "engines": {
804
+ "node": ">= 0.4"
805
+ },
806
+ "funding": {
807
+ "url": "https://github.com/sponsors/ljharb"
808
+ }
809
+ },
810
+ "node_modules/has-tostringtag": {
811
+ "version": "1.0.2",
812
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
813
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
814
+ "license": "MIT",
815
+ "dependencies": {
816
+ "has-symbols": "^1.0.3"
817
+ },
818
+ "engines": {
819
+ "node": ">= 0.4"
820
+ },
821
+ "funding": {
822
+ "url": "https://github.com/sponsors/ljharb"
823
+ }
824
+ },
825
+ "node_modules/hasown": {
826
+ "version": "2.0.2",
827
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
828
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
829
+ "license": "MIT",
830
+ "dependencies": {
831
+ "function-bind": "^1.1.2"
832
+ },
833
+ "engines": {
834
+ "node": ">= 0.4"
835
+ }
836
+ },
837
+ "node_modules/htmlparser2": {
838
+ "version": "8.0.2",
839
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
840
+ "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
841
+ "funding": [
842
+ "https://github.com/fb55/htmlparser2?sponsor=1",
843
+ {
844
+ "type": "github",
845
+ "url": "https://github.com/sponsors/fb55"
846
+ }
847
+ ],
848
+ "license": "MIT",
849
+ "dependencies": {
850
+ "domelementtype": "^2.3.0",
851
+ "domhandler": "^5.0.3",
852
+ "domutils": "^3.0.1",
853
+ "entities": "^4.4.0"
854
+ }
855
+ },
856
+ "node_modules/http": {
857
+ "version": "0.0.1-security",
858
+ "resolved": "https://registry.npmjs.org/http/-/http-0.0.1-security.tgz",
859
+ "integrity": "sha512-RnDvP10Ty9FxqOtPZuxtebw1j4L/WiqNMDtuc1YMH1XQm5TgDRaR1G9u8upL6KD1bXHSp9eSXo/ED+8Q7FAr+g=="
860
+ },
861
+ "node_modules/http-cookie-agent": {
862
+ "version": "7.0.1",
863
+ "resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-7.0.1.tgz",
864
+ "integrity": "sha512-lZHFZUdPTw64PdksQac5xbUd4NWjUbyDYnvR//2sbLpcC4UqEUW0x/6O+rDntVzJzJ07QvhtL5XZSC+c5EK+IQ==",
865
+ "license": "MIT",
866
+ "dependencies": {
867
+ "agent-base": "^7.1.3"
868
+ },
869
+ "engines": {
870
+ "node": ">=20.0.0"
871
+ },
872
+ "funding": {
873
+ "url": "https://github.com/sponsors/3846masa"
874
+ },
875
+ "peerDependencies": {
876
+ "tough-cookie": "^4.0.0 || ^5.0.0",
877
+ "undici": "^7.0.0"
878
+ },
879
+ "peerDependenciesMeta": {
880
+ "undici": {
881
+ "optional": true
882
+ }
883
+ }
884
+ },
885
+ "node_modules/http-errors": {
886
+ "version": "2.0.0",
887
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
888
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
889
+ "license": "MIT",
890
+ "dependencies": {
891
+ "depd": "2.0.0",
892
+ "inherits": "2.0.4",
893
+ "setprototypeof": "1.2.0",
894
+ "statuses": "2.0.1",
895
+ "toidentifier": "1.0.1"
896
+ },
897
+ "engines": {
898
+ "node": ">= 0.8"
899
+ }
900
+ },
901
+ "node_modules/https-proxy-agent": {
902
+ "version": "7.0.6",
903
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
904
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
905
+ "license": "MIT",
906
+ "dependencies": {
907
+ "agent-base": "^7.1.2",
908
+ "debug": "4"
909
+ },
910
+ "engines": {
911
+ "node": ">= 14"
912
+ }
913
+ },
914
+ "node_modules/https-proxy-agent/node_modules/debug": {
915
+ "version": "4.4.1",
916
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
917
+ "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
918
+ "license": "MIT",
919
+ "dependencies": {
920
+ "ms": "^2.1.3"
921
+ },
922
+ "engines": {
923
+ "node": ">=6.0"
924
+ },
925
+ "peerDependenciesMeta": {
926
+ "supports-color": {
927
+ "optional": true
928
+ }
929
+ }
930
+ },
931
+ "node_modules/https-proxy-agent/node_modules/ms": {
932
+ "version": "2.1.3",
933
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
934
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
935
+ "license": "MIT"
936
+ },
937
+ "node_modules/ieee754": {
938
+ "version": "1.2.1",
939
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
940
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
941
+ "funding": [
942
+ {
943
+ "type": "github",
944
+ "url": "https://github.com/sponsors/feross"
945
+ },
946
+ {
947
+ "type": "patreon",
948
+ "url": "https://www.patreon.com/feross"
949
+ },
950
+ {
951
+ "type": "consulting",
952
+ "url": "https://feross.org/support"
953
+ }
954
+ ],
955
+ "license": "BSD-3-Clause"
956
+ },
957
+ "node_modules/inherits": {
958
+ "version": "2.0.4",
959
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
960
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
961
+ "license": "ISC"
962
+ },
963
+ "node_modules/ipaddr.js": {
964
+ "version": "1.9.1",
965
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
966
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
967
+ "license": "MIT",
968
+ "engines": {
969
+ "node": ">= 0.10"
970
+ }
971
+ },
972
+ "node_modules/m3u8stream": {
973
+ "version": "0.8.6",
974
+ "resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz",
975
+ "integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==",
976
+ "license": "MIT",
977
+ "dependencies": {
978
+ "miniget": "^4.2.2",
979
+ "sax": "^1.2.4"
980
+ },
981
+ "engines": {
982
+ "node": ">=12"
983
+ }
984
+ },
985
+ "node_modules/math-intrinsics": {
986
+ "version": "1.1.0",
987
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
988
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
989
+ "license": "MIT",
990
+ "engines": {
991
+ "node": ">= 0.4"
992
+ }
993
+ },
994
+ "node_modules/media-typer": {
995
+ "version": "0.3.0",
996
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
997
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
998
+ "license": "MIT",
999
+ "engines": {
1000
+ "node": ">= 0.6"
1001
+ }
1002
+ },
1003
+ "node_modules/merge-descriptors": {
1004
+ "version": "1.0.3",
1005
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
1006
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
1007
+ "license": "MIT",
1008
+ "funding": {
1009
+ "url": "https://github.com/sponsors/sindresorhus"
1010
+ }
1011
+ },
1012
+ "node_modules/methods": {
1013
+ "version": "1.1.2",
1014
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
1015
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
1016
+ "license": "MIT",
1017
+ "engines": {
1018
+ "node": ">= 0.6"
1019
+ }
1020
+ },
1021
+ "node_modules/mime": {
1022
+ "version": "1.6.0",
1023
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
1024
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
1025
+ "license": "MIT",
1026
+ "bin": {
1027
+ "mime": "cli.js"
1028
+ },
1029
+ "engines": {
1030
+ "node": ">=4"
1031
+ }
1032
+ },
1033
+ "node_modules/mime-db": {
1034
+ "version": "1.52.0",
1035
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
1036
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
1037
+ "license": "MIT",
1038
+ "engines": {
1039
+ "node": ">= 0.6"
1040
+ }
1041
+ },
1042
+ "node_modules/mime-types": {
1043
+ "version": "2.1.35",
1044
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
1045
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
1046
+ "license": "MIT",
1047
+ "dependencies": {
1048
+ "mime-db": "1.52.0"
1049
+ },
1050
+ "engines": {
1051
+ "node": ">= 0.6"
1052
+ }
1053
+ },
1054
+ "node_modules/miniget": {
1055
+ "version": "4.2.3",
1056
+ "resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz",
1057
+ "integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==",
1058
+ "license": "MIT",
1059
+ "engines": {
1060
+ "node": ">=12"
1061
+ }
1062
+ },
1063
+ "node_modules/ms": {
1064
+ "version": "2.0.0",
1065
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
1066
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
1067
+ "license": "MIT"
1068
+ },
1069
+ "node_modules/negotiator": {
1070
+ "version": "0.6.3",
1071
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
1072
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
1073
+ "license": "MIT",
1074
+ "engines": {
1075
+ "node": ">= 0.6"
1076
+ }
1077
+ },
1078
+ "node_modules/nth-check": {
1079
+ "version": "2.1.1",
1080
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
1081
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
1082
+ "license": "BSD-2-Clause",
1083
+ "dependencies": {
1084
+ "boolbase": "^1.0.0"
1085
+ },
1086
+ "funding": {
1087
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
1088
+ }
1089
+ },
1090
+ "node_modules/object-assign": {
1091
+ "version": "4.1.1",
1092
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
1093
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
1094
+ "license": "MIT",
1095
+ "engines": {
1096
+ "node": ">=0.10.0"
1097
+ }
1098
+ },
1099
+ "node_modules/object-inspect": {
1100
+ "version": "1.13.4",
1101
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
1102
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
1103
+ "license": "MIT",
1104
+ "engines": {
1105
+ "node": ">= 0.4"
1106
+ },
1107
+ "funding": {
1108
+ "url": "https://github.com/sponsors/ljharb"
1109
+ }
1110
+ },
1111
+ "node_modules/on-finished": {
1112
+ "version": "2.4.1",
1113
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
1114
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
1115
+ "license": "MIT",
1116
+ "dependencies": {
1117
+ "ee-first": "1.1.1"
1118
+ },
1119
+ "engines": {
1120
+ "node": ">= 0.8"
1121
+ }
1122
+ },
1123
+ "node_modules/parse5": {
1124
+ "version": "7.3.0",
1125
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
1126
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
1127
+ "license": "MIT",
1128
+ "dependencies": {
1129
+ "entities": "^6.0.0"
1130
+ },
1131
+ "funding": {
1132
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
1133
+ }
1134
+ },
1135
+ "node_modules/parse5-htmlparser2-tree-adapter": {
1136
+ "version": "7.1.0",
1137
+ "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
1138
+ "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
1139
+ "license": "MIT",
1140
+ "dependencies": {
1141
+ "domhandler": "^5.0.3",
1142
+ "parse5": "^7.0.0"
1143
+ },
1144
+ "funding": {
1145
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
1146
+ }
1147
+ },
1148
+ "node_modules/parse5/node_modules/entities": {
1149
+ "version": "6.0.0",
1150
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz",
1151
+ "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==",
1152
+ "license": "BSD-2-Clause",
1153
+ "engines": {
1154
+ "node": ">=0.12"
1155
+ },
1156
+ "funding": {
1157
+ "url": "https://github.com/fb55/entities?sponsor=1"
1158
+ }
1159
+ },
1160
+ "node_modules/parseurl": {
1161
+ "version": "1.3.3",
1162
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
1163
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
1164
+ "license": "MIT",
1165
+ "engines": {
1166
+ "node": ">= 0.8"
1167
+ }
1168
+ },
1169
+ "node_modules/path": {
1170
+ "version": "0.12.7",
1171
+ "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz",
1172
+ "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==",
1173
+ "license": "MIT",
1174
+ "dependencies": {
1175
+ "process": "^0.11.1",
1176
+ "util": "^0.10.3"
1177
+ }
1178
+ },
1179
+ "node_modules/path-to-regexp": {
1180
+ "version": "0.1.12",
1181
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
1182
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
1183
+ "license": "MIT"
1184
+ },
1185
+ "node_modules/peek-readable": {
1186
+ "version": "7.0.0",
1187
+ "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-7.0.0.tgz",
1188
+ "integrity": "sha512-nri2TO5JE3/mRryik9LlHFT53cgHfRK0Lt0BAZQXku/AW3E6XLt2GaY8siWi7dvW/m1z0ecn+J+bpDa9ZN3IsQ==",
1189
+ "license": "MIT",
1190
+ "engines": {
1191
+ "node": ">=18"
1192
+ },
1193
+ "funding": {
1194
+ "type": "github",
1195
+ "url": "https://github.com/sponsors/Borewit"
1196
+ }
1197
+ },
1198
+ "node_modules/process": {
1199
+ "version": "0.11.10",
1200
+ "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
1201
+ "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==",
1202
+ "license": "MIT",
1203
+ "engines": {
1204
+ "node": ">= 0.6.0"
1205
+ }
1206
+ },
1207
+ "node_modules/prompt-sync": {
1208
+ "version": "4.2.0",
1209
+ "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz",
1210
+ "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==",
1211
+ "license": "MIT",
1212
+ "dependencies": {
1213
+ "strip-ansi": "^5.0.0"
1214
+ }
1215
+ },
1216
+ "node_modules/proxy-addr": {
1217
+ "version": "2.0.7",
1218
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
1219
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
1220
+ "license": "MIT",
1221
+ "dependencies": {
1222
+ "forwarded": "0.2.0",
1223
+ "ipaddr.js": "1.9.1"
1224
+ },
1225
+ "engines": {
1226
+ "node": ">= 0.10"
1227
+ }
1228
+ },
1229
+ "node_modules/proxy-from-env": {
1230
+ "version": "1.1.0",
1231
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
1232
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
1233
+ "license": "MIT"
1234
+ },
1235
+ "node_modules/qs": {
1236
+ "version": "6.14.0",
1237
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
1238
+ "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
1239
+ "license": "BSD-3-Clause",
1240
+ "dependencies": {
1241
+ "side-channel": "^1.1.0"
1242
+ },
1243
+ "engines": {
1244
+ "node": ">=0.6"
1245
+ },
1246
+ "funding": {
1247
+ "url": "https://github.com/sponsors/ljharb"
1248
+ }
1249
+ },
1250
+ "node_modules/range-parser": {
1251
+ "version": "1.2.1",
1252
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
1253
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
1254
+ "license": "MIT",
1255
+ "engines": {
1256
+ "node": ">= 0.6"
1257
+ }
1258
+ },
1259
+ "node_modules/raw-body": {
1260
+ "version": "2.5.2",
1261
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
1262
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
1263
+ "license": "MIT",
1264
+ "dependencies": {
1265
+ "bytes": "3.1.2",
1266
+ "http-errors": "2.0.0",
1267
+ "iconv-lite": "0.4.24",
1268
+ "unpipe": "1.0.0"
1269
+ },
1270
+ "engines": {
1271
+ "node": ">= 0.8"
1272
+ }
1273
+ },
1274
+ "node_modules/raw-body/node_modules/iconv-lite": {
1275
+ "version": "0.4.24",
1276
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
1277
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
1278
+ "license": "MIT",
1279
+ "dependencies": {
1280
+ "safer-buffer": ">= 2.1.2 < 3"
1281
+ },
1282
+ "engines": {
1283
+ "node": ">=0.10.0"
1284
+ }
1285
+ },
1286
+ "node_modules/safe-buffer": {
1287
+ "version": "5.2.1",
1288
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
1289
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
1290
+ "funding": [
1291
+ {
1292
+ "type": "github",
1293
+ "url": "https://github.com/sponsors/feross"
1294
+ },
1295
+ {
1296
+ "type": "patreon",
1297
+ "url": "https://www.patreon.com/feross"
1298
+ },
1299
+ {
1300
+ "type": "consulting",
1301
+ "url": "https://feross.org/support"
1302
+ }
1303
+ ],
1304
+ "license": "MIT"
1305
+ },
1306
+ "node_modules/safer-buffer": {
1307
+ "version": "2.1.2",
1308
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
1309
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
1310
+ "license": "MIT"
1311
+ },
1312
+ "node_modules/sax": {
1313
+ "version": "1.4.1",
1314
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
1315
+ "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
1316
+ "license": "ISC"
1317
+ },
1318
+ "node_modules/send": {
1319
+ "version": "0.19.0",
1320
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
1321
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
1322
+ "license": "MIT",
1323
+ "dependencies": {
1324
+ "debug": "2.6.9",
1325
+ "depd": "2.0.0",
1326
+ "destroy": "1.2.0",
1327
+ "encodeurl": "~1.0.2",
1328
+ "escape-html": "~1.0.3",
1329
+ "etag": "~1.8.1",
1330
+ "fresh": "0.5.2",
1331
+ "http-errors": "2.0.0",
1332
+ "mime": "1.6.0",
1333
+ "ms": "2.1.3",
1334
+ "on-finished": "2.4.1",
1335
+ "range-parser": "~1.2.1",
1336
+ "statuses": "2.0.1"
1337
+ },
1338
+ "engines": {
1339
+ "node": ">= 0.8.0"
1340
+ }
1341
+ },
1342
+ "node_modules/send/node_modules/encodeurl": {
1343
+ "version": "1.0.2",
1344
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
1345
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
1346
+ "license": "MIT",
1347
+ "engines": {
1348
+ "node": ">= 0.8"
1349
+ }
1350
+ },
1351
+ "node_modules/send/node_modules/ms": {
1352
+ "version": "2.1.3",
1353
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
1354
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
1355
+ "license": "MIT"
1356
+ },
1357
+ "node_modules/serve-static": {
1358
+ "version": "1.16.2",
1359
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
1360
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
1361
+ "license": "MIT",
1362
+ "dependencies": {
1363
+ "encodeurl": "~2.0.0",
1364
+ "escape-html": "~1.0.3",
1365
+ "parseurl": "~1.3.3",
1366
+ "send": "0.19.0"
1367
+ },
1368
+ "engines": {
1369
+ "node": ">= 0.8.0"
1370
+ }
1371
+ },
1372
+ "node_modules/setprototypeof": {
1373
+ "version": "1.2.0",
1374
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
1375
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
1376
+ "license": "ISC"
1377
+ },
1378
+ "node_modules/side-channel": {
1379
+ "version": "1.1.0",
1380
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
1381
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
1382
+ "license": "MIT",
1383
+ "dependencies": {
1384
+ "es-errors": "^1.3.0",
1385
+ "object-inspect": "^1.13.3",
1386
+ "side-channel-list": "^1.0.0",
1387
+ "side-channel-map": "^1.0.1",
1388
+ "side-channel-weakmap": "^1.0.2"
1389
+ },
1390
+ "engines": {
1391
+ "node": ">= 0.4"
1392
+ },
1393
+ "funding": {
1394
+ "url": "https://github.com/sponsors/ljharb"
1395
+ }
1396
+ },
1397
+ "node_modules/side-channel-list": {
1398
+ "version": "1.0.0",
1399
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
1400
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
1401
+ "license": "MIT",
1402
+ "dependencies": {
1403
+ "es-errors": "^1.3.0",
1404
+ "object-inspect": "^1.13.3"
1405
+ },
1406
+ "engines": {
1407
+ "node": ">= 0.4"
1408
+ },
1409
+ "funding": {
1410
+ "url": "https://github.com/sponsors/ljharb"
1411
+ }
1412
+ },
1413
+ "node_modules/side-channel-map": {
1414
+ "version": "1.0.1",
1415
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
1416
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
1417
+ "license": "MIT",
1418
+ "dependencies": {
1419
+ "call-bound": "^1.0.2",
1420
+ "es-errors": "^1.3.0",
1421
+ "get-intrinsic": "^1.2.5",
1422
+ "object-inspect": "^1.13.3"
1423
+ },
1424
+ "engines": {
1425
+ "node": ">= 0.4"
1426
+ },
1427
+ "funding": {
1428
+ "url": "https://github.com/sponsors/ljharb"
1429
+ }
1430
+ },
1431
+ "node_modules/side-channel-weakmap": {
1432
+ "version": "1.0.2",
1433
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
1434
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
1435
+ "license": "MIT",
1436
+ "dependencies": {
1437
+ "call-bound": "^1.0.2",
1438
+ "es-errors": "^1.3.0",
1439
+ "get-intrinsic": "^1.2.5",
1440
+ "object-inspect": "^1.13.3",
1441
+ "side-channel-map": "^1.0.1"
1442
+ },
1443
+ "engines": {
1444
+ "node": ">= 0.4"
1445
+ },
1446
+ "funding": {
1447
+ "url": "https://github.com/sponsors/ljharb"
1448
+ }
1449
+ },
1450
+ "node_modules/statuses": {
1451
+ "version": "2.0.1",
1452
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
1453
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
1454
+ "license": "MIT",
1455
+ "engines": {
1456
+ "node": ">= 0.8"
1457
+ }
1458
+ },
1459
+ "node_modules/strip-ansi": {
1460
+ "version": "5.2.0",
1461
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
1462
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
1463
+ "license": "MIT",
1464
+ "dependencies": {
1465
+ "ansi-regex": "^4.1.0"
1466
+ },
1467
+ "engines": {
1468
+ "node": ">=6"
1469
+ }
1470
+ },
1471
+ "node_modules/strtok3": {
1472
+ "version": "10.2.2",
1473
+ "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.2.2.tgz",
1474
+ "integrity": "sha512-Xt18+h4s7Z8xyZ0tmBoRmzxcop97R4BAh+dXouUDCYn+Em+1P3qpkUfI5ueWLT8ynC5hZ+q4iPEmGG1urvQGBg==",
1475
+ "license": "MIT",
1476
+ "dependencies": {
1477
+ "@tokenizer/token": "^0.3.0",
1478
+ "peek-readable": "^7.0.0"
1479
+ },
1480
+ "engines": {
1481
+ "node": ">=18"
1482
+ },
1483
+ "funding": {
1484
+ "type": "github",
1485
+ "url": "https://github.com/sponsors/Borewit"
1486
+ }
1487
+ },
1488
+ "node_modules/tldts": {
1489
+ "version": "6.1.86",
1490
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
1491
+ "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
1492
+ "license": "MIT",
1493
+ "dependencies": {
1494
+ "tldts-core": "^6.1.86"
1495
+ },
1496
+ "bin": {
1497
+ "tldts": "bin/cli.js"
1498
+ }
1499
+ },
1500
+ "node_modules/tldts-core": {
1501
+ "version": "6.1.86",
1502
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
1503
+ "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
1504
+ "license": "MIT"
1505
+ },
1506
+ "node_modules/toidentifier": {
1507
+ "version": "1.0.1",
1508
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
1509
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
1510
+ "license": "MIT",
1511
+ "engines": {
1512
+ "node": ">=0.6"
1513
+ }
1514
+ },
1515
+ "node_modules/token-types": {
1516
+ "version": "6.0.0",
1517
+ "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
1518
+ "integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==",
1519
+ "license": "MIT",
1520
+ "dependencies": {
1521
+ "@tokenizer/token": "^0.3.0",
1522
+ "ieee754": "^1.2.1"
1523
+ },
1524
+ "engines": {
1525
+ "node": ">=14.16"
1526
+ },
1527
+ "funding": {
1528
+ "type": "github",
1529
+ "url": "https://github.com/sponsors/Borewit"
1530
+ }
1531
+ },
1532
+ "node_modules/tough-cookie": {
1533
+ "version": "5.1.2",
1534
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
1535
+ "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
1536
+ "license": "BSD-3-Clause",
1537
+ "dependencies": {
1538
+ "tldts": "^6.1.32"
1539
+ },
1540
+ "engines": {
1541
+ "node": ">=16"
1542
+ }
1543
+ },
1544
+ "node_modules/type-is": {
1545
+ "version": "1.6.18",
1546
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
1547
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
1548
+ "license": "MIT",
1549
+ "dependencies": {
1550
+ "media-typer": "0.3.0",
1551
+ "mime-types": "~2.1.24"
1552
+ },
1553
+ "engines": {
1554
+ "node": ">= 0.6"
1555
+ }
1556
+ },
1557
+ "node_modules/uint8array-extras": {
1558
+ "version": "1.4.0",
1559
+ "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
1560
+ "integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
1561
+ "license": "MIT",
1562
+ "engines": {
1563
+ "node": ">=18"
1564
+ },
1565
+ "funding": {
1566
+ "url": "https://github.com/sponsors/sindresorhus"
1567
+ }
1568
+ },
1569
+ "node_modules/undici": {
1570
+ "version": "7.10.0",
1571
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.10.0.tgz",
1572
+ "integrity": "sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==",
1573
+ "license": "MIT",
1574
+ "engines": {
1575
+ "node": ">=20.18.1"
1576
+ }
1577
+ },
1578
+ "node_modules/unpipe": {
1579
+ "version": "1.0.0",
1580
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
1581
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
1582
+ "license": "MIT",
1583
+ "engines": {
1584
+ "node": ">= 0.8"
1585
+ }
1586
+ },
1587
+ "node_modules/util": {
1588
+ "version": "0.10.4",
1589
+ "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz",
1590
+ "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==",
1591
+ "license": "MIT",
1592
+ "dependencies": {
1593
+ "inherits": "2.0.3"
1594
+ }
1595
+ },
1596
+ "node_modules/util/node_modules/inherits": {
1597
+ "version": "2.0.3",
1598
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
1599
+ "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==",
1600
+ "license": "ISC"
1601
+ },
1602
+ "node_modules/utils-merge": {
1603
+ "version": "1.0.1",
1604
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
1605
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
1606
+ "license": "MIT",
1607
+ "engines": {
1608
+ "node": ">= 0.4.0"
1609
+ }
1610
+ },
1611
+ "node_modules/vary": {
1612
+ "version": "1.1.2",
1613
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
1614
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
1615
+ "license": "MIT",
1616
+ "engines": {
1617
+ "node": ">= 0.8"
1618
+ }
1619
+ }
1620
+ }
1621
+ }
package.json ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "ytdl",
3
+ "version": "1.0.0",
4
+ "description": "My Node.js application",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "scripts": {
8
+ "start": "node index.js",
9
+ "dev": "nodemon index.js",
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "dependencies": {
13
+ "@distube/ytdl-core": "^4.16.4",
14
+ "atob": "^2.1.2",
15
+ "axios": "^1.7.9",
16
+ "cheerio": "^1.0.0-rc.12",
17
+ "cors": "^2.8.5",
18
+ "express": "^4.21.2",
19
+ "file-type": "^20.1.0",
20
+ "prompt-sync": "^4.2.0",
21
+ "qs": "^6.14.0"
22
+ },
23
+ "devDependencies": {
24
+ "nodemon": "^3.1.0"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "keywords": [],
30
+ "author": "reza",
31
+ "license": "MIT"
32
+ }
scrape/tiktok.js ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from "axios";
2
+
3
+ // Fungsi bantu untuk ekstrak JSON dari string HTML
4
+ function extractJSONObject(str, startIndex) {
5
+ let openBraces = 0;
6
+ let inString = false;
7
+ let escapeNext = false;
8
+ let endIndex = startIndex;
9
+
10
+ for (; endIndex < str.length; endIndex++) {
11
+ const char = str[endIndex];
12
+ if (escapeNext) {
13
+ escapeNext = false;
14
+ continue;
15
+ }
16
+ if (char === "\\") {
17
+ escapeNext = true;
18
+ continue;
19
+ }
20
+ if (char === "\"") {
21
+ inString = !inString;
22
+ }
23
+ if (!inString) {
24
+ if (char === "{") openBraces++;
25
+ else if (char === "}") {
26
+ openBraces--;
27
+ if (openBraces === 0) {
28
+ return str.substring(startIndex, endIndex + 1);
29
+ }
30
+ }
31
+ }
32
+ }
33
+ throw new Error("Gagal menemukan akhir JSON.");
34
+ }
35
+
36
+ async function resolveTikTokUrl(url) {
37
+ try {
38
+ // First try with no redirects
39
+ const response = await axios.get(url, {
40
+ maxRedirects: 0,
41
+ validateStatus: status => status >= 300 && status < 400,
42
+ });
43
+
44
+ if (response.headers.location) {
45
+ return new URL(response.headers.location, url).href;
46
+ }
47
+ } catch (err) {
48
+ if (err.response && err.response.status === 302 && err.response.headers.location) {
49
+ return err.response.headers.location;
50
+ }
51
+ // If redirect fails, try to follow through to final URL
52
+ }
53
+
54
+ // Fallback - follow all redirects and return final URL
55
+ const response = await axios.get(url, {
56
+ maxRedirects: 20,
57
+ validateStatus: () => true // Accept all status codes
58
+ });
59
+
60
+ return response.request.res.responseUrl || url;
61
+ }
62
+
63
+ function formatOutput(title, data) {
64
+ return `\n=== ${title} ===\n${JSON.stringify(data, null, 2)}`;
65
+ }
66
+
67
+ // Headers tambahan untuk menghindari 503
68
+ const COMMON_HEADERS = {
69
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
70
+ "Accept-Language": "en-US,en;q=0.9",
71
+ "Accept-Encoding": "gzip, deflate, br",
72
+ "Connection": "keep-alive",
73
+ "Sec-Fetch-Dest": "document",
74
+ "Sec-Fetch-Mode": "navigate",
75
+ "Sec-Fetch-Site": "none",
76
+ "Sec-Fetch-User": "?1",
77
+ "Upgrade-Insecure-Requests": "1",
78
+ "Cache-Control": "max-age=0",
79
+ };
80
+
81
+ async function getTikTokHtml(url) {
82
+ const resolvedUrl = await resolveTikTokUrl(url);
83
+
84
+ // Determine if it's a video or slide based on the URL
85
+ const isVideo = resolvedUrl.includes('/video/');
86
+ const isSlide = resolvedUrl.includes('/photo/') || resolvedUrl.includes('/image/');
87
+
88
+ const headers = {
89
+ ...COMMON_HEADERS,
90
+ "User-Agent": isVideo
91
+ ? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
92
+ : "Mozilla/5.0 (Linux; Android 14; CPH2641) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.111 Mobile Safari/537.36",
93
+ "Referer": "https://www.tiktok.com/",
94
+ };
95
+
96
+ const response = await axios.get(resolvedUrl, {
97
+ headers,
98
+ validateStatus: () => true // Accept all status codes
99
+ });
100
+
101
+ return {
102
+ html: response.data,
103
+ isVideo,
104
+ isSlide,
105
+ resolvedUrl
106
+ };
107
+ }
108
+
109
+ async function extractTikTokData(url) {
110
+ try {
111
+ const { html, isVideo, isSlide, resolvedUrl } = await getTikTokHtml(url);
112
+
113
+ let jsonKey, result;
114
+
115
+ if (isVideo) {
116
+ jsonKey = "\"webapp.video-detail\":";
117
+ const index = html.indexOf(jsonKey);
118
+ if (index === -1) throw new Error("Data video tidak ditemukan.");
119
+
120
+ const start = html.indexOf("{", index + jsonKey.length);
121
+ const jsonStr = extractJSONObject(html, start);
122
+ const jsonData = JSON.parse(jsonStr);
123
+
124
+ const item = jsonData?.itemInfo?.itemStruct;
125
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
126
+
127
+ result = {
128
+ type: "video",
129
+ url: resolvedUrl,
130
+ description: item.desc,
131
+ createTime: item.createTime,
132
+ video: item.video,
133
+ author: item.author,
134
+ music: item.music,
135
+ stats: item.stats,
136
+ };
137
+
138
+ console.log(formatOutput("Video TikTok", result));
139
+ }
140
+ else if (isSlide) {
141
+ jsonKey = "\"webapp.reflow.video.detail\":";
142
+ const index = html.indexOf(jsonKey);
143
+ if (index === -1) throw new Error("Data slide tidak ditemukan.");
144
+
145
+ const start = html.indexOf("{", index + jsonKey.length);
146
+ const jsonStr = extractJSONObject(html, start);
147
+ const jsonData = JSON.parse(jsonStr);
148
+
149
+ const item = jsonData?.itemInfo?.itemStruct;
150
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
151
+
152
+ result = {
153
+ type: "slide",
154
+ url: resolvedUrl,
155
+ description: item.desc,
156
+ createTime: item.createTime,
157
+ images: item.imagePost?.images?.map(img => ({
158
+ url: img.imageURL?.urlList?.[0] || null,
159
+ width: img.imageWidth,
160
+ height: img.imageHeight,
161
+ })),
162
+ author: item.author,
163
+ music: item.music,
164
+ stats: item.stats,
165
+ };
166
+
167
+ console.log(formatOutput("Slide TikTok", result));
168
+ }
169
+ else {
170
+ // Try to detect content type from HTML if URL doesn't indicate it
171
+ const videoIndex = html.indexOf("\"webapp.video-detail\":");
172
+ const slideIndex = html.indexOf("\"webapp.reflow.video.detail\":");
173
+
174
+ if (videoIndex !== -1) {
175
+ console.warn("URL tidak menunjukkan video, tapi ditemukan data video di HTML");
176
+ jsonKey = "\"webapp.video-detail\":";
177
+ const start = html.indexOf("{", videoIndex + jsonKey.length);
178
+ const jsonStr = extractJSONObject(html, start);
179
+ const jsonData = JSON.parse(jsonStr);
180
+
181
+ const item = jsonData?.itemInfo?.itemStruct;
182
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
183
+
184
+ result = {
185
+ type: "video",
186
+ url: resolvedUrl,
187
+ description: item.desc,
188
+ createTime: item.createTime,
189
+ video: item.video,
190
+ author: item.author,
191
+ music: item.music,
192
+ stats: item.stats,
193
+ };
194
+
195
+ console.log(formatOutput("Video TikTok", result));
196
+ }
197
+ else if (slideIndex !== -1) {
198
+ console.warn("URL tidak menunjukkan slide, tapi ditemukan data slide di HTML");
199
+ jsonKey = "\"webapp.reflow.video.detail\":";
200
+ const start = html.indexOf("{", slideIndex + jsonKey.length);
201
+ const jsonStr = extractJSONObject(html, start);
202
+ const jsonData = JSON.parse(jsonStr);
203
+
204
+ const item = jsonData?.itemInfo?.itemStruct;
205
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
206
+
207
+ result = {
208
+ type: "slide",
209
+ url: resolvedUrl,
210
+ description: item.desc,
211
+ createTime: item.createTime,
212
+ images: item.imagePost?.images?.map(img => ({
213
+ url: img.imageURL?.urlList?.[0] || null,
214
+ width: img.imageWidth,
215
+ height: img.imageHeight,
216
+ })),
217
+ author: item.author,
218
+ music: item.music,
219
+ stats: item.stats,
220
+ };
221
+
222
+ console.log(formatOutput("Slide TikTok", result));
223
+ }
224
+ else {
225
+ throw new Error("URL tidak dikenali sebagai video atau slide TikTok dan tidak ditemukan data di HTML.");
226
+ }
227
+ }
228
+
229
+ return result;
230
+ } catch (err) {
231
+ console.error("❌ Gagal mengambil data:", err.message);
232
+ throw err;
233
+ }
234
+ }
235
+
236
+ export default extractTikTokData;
scrape/tiktokdl.js ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import axios from 'axios';
4
+ import { pipeline } from 'stream/promises';
5
+ import { fileURLToPath } from 'url';
6
+ import extractTikTokData from "../scrape/tiktok.js";
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const DOWNLOAD_FOLDER = path.join(__dirname, '../downloads');
11
+
12
+ if (!fs.existsSync(DOWNLOAD_FOLDER)) {
13
+ fs.mkdirSync(DOWNLOAD_FOLDER, { recursive: true });
14
+ }
15
+
16
+ async function downloadMedia(url, outputPath, type = 'file') {
17
+ console.log(`Downloading ${type} from: ${url}`);
18
+
19
+ const response = await axios({
20
+ method: "GET",
21
+ url,
22
+ responseType: "stream",
23
+ headers: {
24
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
25
+ "Referer": "https://www.tiktok.com/",
26
+ "Origin": "https://www.tiktok.com",
27
+ "Accept": "*/*",
28
+ "Accept-Encoding": "identity"
29
+ },
30
+ timeout: 60000
31
+ });
32
+
33
+ if (response.status !== 200) {
34
+ throw new Error(`Failed to download ${type}: Status ${response.status}`);
35
+ }
36
+
37
+ const writer = fs.createWriteStream(outputPath);
38
+ await pipeline(response.data, writer);
39
+
40
+ if (!fs.existsSync(outputPath)) {
41
+ throw new Error(`File ${type} not created`);
42
+ }
43
+
44
+ const stats = fs.statSync(outputPath);
45
+ if (stats.size === 0) {
46
+ fs.unlinkSync(outputPath);
47
+ throw new Error(`File ${type} is empty (0 bytes)`);
48
+ }
49
+
50
+ return stats;
51
+ }
52
+
53
+ function getVideoUrl(videoData) {
54
+ const bitrate = videoData?.bitrateInfo?.[0];
55
+ const uri = bitrate?.PlayAddr?.Uri || videoData?.uri;
56
+ const urlList = bitrate?.PlayAddr?.UrlList || videoData?.playAddr?.UrlList || [];
57
+ const finalUrl = urlList.find(url => url.includes(uri)) || urlList[0];
58
+
59
+ if (!finalUrl) throw new Error("Video URL not found");
60
+ return finalUrl;
61
+ }
62
+
63
+ export default async function extractAndDownloadTikTok(url, domain) {
64
+ try {
65
+ const data = await extractTikTokData(url);
66
+ const { author, stats, video, music, type, description, images, resolvedUrl } = data;
67
+
68
+ const folderName = `tiktok_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
69
+ const downloadFolder = path.join(DOWNLOAD_FOLDER, folderName);
70
+ fs.mkdirSync(downloadFolder, { recursive: true });
71
+
72
+ const caption = `🔹 Username: ${author.uniqueId}\n` +
73
+ `🔹 Nickname: ${author.nickname}\n` +
74
+ `🔹 Description: ${description}\n` +
75
+ `❤️ Likes: ${stats.diggCount} | ` +
76
+ `💬 Comments: ${stats.commentCount} | ` +
77
+ `🔄 Shares: ${stats.shareCount}\n` +
78
+ `🎵 Music: ${music?.title || "-"}`;
79
+
80
+ if (type === "video") {
81
+ const videoUrl = getVideoUrl(video);
82
+ const videoFilename = `video.mp4`;
83
+ const videoPath = path.join(downloadFolder, videoFilename);
84
+
85
+ await downloadMedia(videoUrl, videoPath, 'video');
86
+
87
+ let musicInfo = null;
88
+ if (music?.playUrl) {
89
+ try {
90
+ const musicFilename = `music.mp3`;
91
+ const musicPath = path.join(downloadFolder, musicFilename);
92
+ await downloadMedia(music.playUrl, musicPath, 'music');
93
+ musicInfo = {
94
+ filename: musicFilename,
95
+ filePath: musicPath,
96
+ download_url: `${domain}/download/${folderName}/${musicFilename}`
97
+ };
98
+ } catch (err) {
99
+ console.error('Failed to download music:', err);
100
+ }
101
+ }
102
+
103
+ return {
104
+ success: true,
105
+ type: "video",
106
+ originalUrl: url,
107
+ resolvedUrl,
108
+ description,
109
+ createTime: data.createTime,
110
+ author,
111
+ music,
112
+ stats,
113
+ downloadInfo: {
114
+ success: true,
115
+ filename: videoFilename,
116
+ filePath: videoPath,
117
+ download_url: `${domain}/download/${folderName}/${videoFilename}`,
118
+ music: musicInfo,
119
+ folder_url: `${domain}/download/${folderName}`
120
+ },
121
+ caption
122
+ };
123
+ }
124
+ else if (type === "slide") {
125
+ const downloadResults = [];
126
+
127
+ for (let i = 0; i < images.length; i++) {
128
+ try {
129
+ const img = images[i];
130
+ const imgFilename = `slide_${i+1}.jpg`;
131
+ const imgPath = path.join(downloadFolder, imgFilename);
132
+
133
+ await downloadMedia(img.url, imgPath, 'image');
134
+
135
+ downloadResults.push({
136
+ success: true,
137
+ index: i+1,
138
+ filename: imgFilename,
139
+ filePath: imgPath,
140
+ download_url: `${domain}/download/${folderName}/${imgFilename}`
141
+ });
142
+ } catch (err) {
143
+ downloadResults.push({
144
+ success: false,
145
+ index: i+1,
146
+ error: err.message
147
+ });
148
+ }
149
+ }
150
+
151
+ let musicInfo = null;
152
+ if (music?.playUrl) {
153
+ try {
154
+ const musicFilename = `music.mp3`;
155
+ const musicPath = path.join(downloadFolder, musicFilename);
156
+ await downloadMedia(music.playUrl, musicPath, 'music');
157
+ musicInfo = {
158
+ filename: musicFilename,
159
+ filePath: musicPath,
160
+ download_url: `${domain}/download/${folderName}/${musicFilename}`
161
+ };
162
+ } catch (err) {
163
+ console.error('Failed to download music:', err);
164
+ }
165
+ }
166
+
167
+ return {
168
+ success: true,
169
+ type: "slide",
170
+ originalUrl: url,
171
+ resolvedUrl,
172
+ description,
173
+ createTime: data.createTime,
174
+ author,
175
+ music,
176
+ stats,
177
+ images: downloadResults,
178
+ musicInfo,
179
+ folder_url: `${domain}/download/${folderName}`,
180
+ caption
181
+ };
182
+ }
183
+ } catch (error) {
184
+ console.error('Error in TikTok download:', error);
185
+ return {
186
+ success: false,
187
+ error: error.message,
188
+ details: error.stack
189
+ };
190
+ }
191
+ }
scrape/ytdown.js ADDED
@@ -0,0 +1,105 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }