fourmovie commited on
Commit
9cc4df0
·
1 Parent(s): f6ffef2
Dockerfile CHANGED
@@ -1,25 +1,37 @@
1
- FROM node:20-slim
2
 
3
- RUN apt update && apt install -y \
4
- wget gnupg xvfb \
5
- fonts-liberation libnss3 libxss1 libxcomposite1 libxrandr2 libgbm1 \
6
- && wget -q https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
7
- && apt install -y ./google-chrome-stable_current_amd64.deb \
8
- && rm google-chrome-stable_current_amd64.deb \
9
- && apt clean && rm -rf /var/lib/apt/lists/*
10
 
11
- RUN mkdir -p /app/cache && chmod 777 -R /app
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
  WORKDIR /app
14
 
15
  COPY package*.json ./
16
- RUN npm install --production
17
 
18
  COPY . .
19
 
20
- EXPOSE 7860
 
 
 
21
 
22
- CMD rm -f /tmp/.X99-lock && \
23
- Xvfb :99 -screen 0 1024x768x24 & \
24
- export DISPLAY=:99 && \
25
- npm start
 
1
+ FROM node:20-bullseye
2
 
3
+ RUN apt-get update && \
4
+ apt-get install -y --no-install-recommends \
5
+ python3 \
6
+ python3-pip \
7
+ python3-dev \
8
+ ffmpeg \
9
+ && rm -rf /var/lib/apt/lists/*
10
 
11
+ RUN python3 -m pip install --upgrade pip && \
12
+ python3 -m pip install --no-cache-dir --upgrade \
13
+ spotdl \
14
+ yt-dlp
15
+
16
+ RUN mkdir -p /app/downloads && \
17
+ mkdir -p /app/docs && \
18
+ mkdir -p /app/scrape && \
19
+ mkdir -p /app/public && \
20
+ chmod 777 -R /app/downloads && \
21
+ chmod 777 -R /app/public && \
22
+ chmod 777 -R /app/docs && \
23
+ chmod 777 -R /app/scrape
24
 
25
  WORKDIR /app
26
 
27
  COPY package*.json ./
28
+ RUN npm install
29
 
30
  COPY . .
31
 
32
+ RUN touch /app/public/index.html && \
33
+ chmod 666 /app/public/index.html
34
+
35
+ CMD ["node", "index.js"]
36
 
37
+ EXPOSE 7860
 
 
 
api_test.py DELETED
@@ -1,26 +0,0 @@
1
- import asyncio
2
- import httpx
3
-
4
- async def main():
5
- async with httpx.AsyncClient(timeout=30.0) as client:
6
- resp1 = await client.post(
7
- "http://localhost:8080/cloudflare",
8
- json={
9
- "domain": "https://olamovies.watch/generate",
10
- "mode": "iuam",
11
- },
12
- )
13
- print(resp1.json())
14
-
15
- resp2 = await client.post(
16
- "http://localhost:8080/cloudflare",
17
- json={
18
- "domain": "https://lksfy.com/",
19
- "siteKey": "0x4AAAAAAA49NnPZwQijgRoi",
20
- "mode": "turnstile",
21
- },
22
- )
23
- print(resp2.json())
24
-
25
- if __name__ == "__main__":
26
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cache/cache.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "{\"domain\":\"https://v2links.org\",\"mode\":\"iuam\"}": {
3
- "timestamp": 1758616160392,
4
- "value": {
5
- "cf_clearance": "qqs5f4MpFMgA0v78Qmh_HYDWoKhbwqlQ57bTW5KeIr8-1758616161-1.2.1.1-D5PdpmheHl.26ssIRKQBsXzPtPPkSXntEZ_H9FUJVt7MMTS_BEE8iH.E48MDzKtFBLwZqRYxE_1GLo1gj3ChXrwatOGEJcmGRUwavy2qvGgUPizn7qufd.sW0ULfhaRO7Gz_H_eO1TU3iEqCzltEUDNk0SviKRFkF8ozbvJ91MW_qmO.qrjQorbu_jxcgJv5BHs6rTNOitWtVDAmYMDukASq0viHXIUAIvTM.LIfQ88",
6
- "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36",
7
- "elapsed_time": 5.028
8
- }
9
- }
10
- }
 
 
 
 
 
 
 
 
 
 
 
docs/swagger.js ADDED
@@ -0,0 +1,1098 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const DOMAIN = process.env.DOMAIN;
2
+
3
+ const swaggerDocument = {
4
+ openapi: "3.0.0",
5
+ info: {
6
+ title: "YouTube & Facebook Downloader & File Upload API",
7
+ version: "2.0.0",
8
+ description: "API untuk mengunduh video YouTube, Facebook, TikTok, Instagram sebagai MP3 atau MP4, mengupload file, dan bypass Cloudflare protection dengan session management"
9
+ },
10
+ servers: [
11
+ {
12
+ url: DOMAIN,
13
+ description: "Main Server"
14
+ }
15
+ ],
16
+ paths: {
17
+ "/api/download": {
18
+ get: {
19
+ summary: "Unduh video YouTube",
20
+ description: "Mengunduh video YouTube dan menyimpannya ke server, dengan opsi upload otomatis",
21
+ parameters: [
22
+ {
23
+ name: "url",
24
+ in: "query",
25
+ required: true,
26
+ schema: {
27
+ type: "string"
28
+ },
29
+ description: "URL video YouTube atau kata kunci"
30
+ },
31
+ {
32
+ name: "format",
33
+ in: "query",
34
+ required: false,
35
+ schema: {
36
+ type: "string",
37
+ enum: ["mp3", "360p", "720p", "1080p"],
38
+ default: "360p"
39
+ },
40
+ description: "Format (mp3, 360p, 720p, 1080p)"
41
+ },
42
+ {
43
+ name: "upload",
44
+ in: "query",
45
+ required: false,
46
+ schema: {
47
+ type: "string",
48
+ enum: ["true", "false"],
49
+ default: "false"
50
+ },
51
+ description: "Jika 'true', file akan diupload otomatis ke server eksternal setelah didownload"
52
+ }
53
+ ],
54
+ responses: {
55
+ "200": {
56
+ description: "Berhasil",
57
+ content: {
58
+ "application/json": {
59
+ schema: {
60
+ type: "object",
61
+ properties: {
62
+ title: {
63
+ type: "string",
64
+ example: "Judul Video YouTube"
65
+ },
66
+ format: {
67
+ type: "string",
68
+ example: "mp3"
69
+ },
70
+ downloadURL: {
71
+ type: "string",
72
+ example: "https://example.com/downloads/video.mp3"
73
+ },
74
+ filename: {
75
+ type: "string",
76
+ example: "video.mp3"
77
+ },
78
+ upload: {
79
+ type: "object",
80
+ properties: {
81
+ status: {
82
+ type: "boolean",
83
+ example: true
84
+ },
85
+ path: {
86
+ type: "string",
87
+ example: "https://c.termai.cc/mp3/abc123"
88
+ },
89
+ mimetype: {
90
+ type: "string",
91
+ example: "audio/mpeg"
92
+ },
93
+ size: {
94
+ type: "number",
95
+ example: 5123456
96
+ }
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ }
103
+ },
104
+ "400": {
105
+ description: "Bad Request - Parameter tidak valid"
106
+ },
107
+ "500": {
108
+ description: "Internal Server Error - Terjadi kesalahan server"
109
+ }
110
+ }
111
+ }
112
+ },
113
+ "/api/fb-download": {
114
+ get: {
115
+ summary: "Unduh video Facebook",
116
+ description: "Mengunduh video Facebook dalam kualitas tertentu dan menyimpannya ke server, dengan opsi upload otomatis",
117
+ parameters: [
118
+ {
119
+ name: "url",
120
+ in: "query",
121
+ required: true,
122
+ schema: {
123
+ type: "string"
124
+ },
125
+ description: "URL video Facebook"
126
+ },
127
+ {
128
+ name: "quality",
129
+ in: "query",
130
+ required: false,
131
+ schema: {
132
+ type: "string",
133
+ enum: ["720p(HD)", "360p(SD)", "Mp3"],
134
+ default: "720p(HD)"
135
+ },
136
+ description: "Kualitas video (720p(HD), 360p(SD), Mp3)"
137
+ },
138
+ {
139
+ name: "upload",
140
+ in: "query",
141
+ required: false,
142
+ schema: {
143
+ type: "string",
144
+ enum: ["true", "false"],
145
+ default: "false"
146
+ },
147
+ description: "Jika 'true', file akan diupload otomatis ke server eksternal setelah didownload"
148
+ }
149
+ ],
150
+ responses: {
151
+ "200": {
152
+ description: "Berhasil",
153
+ content: {
154
+ "application/json": {
155
+ schema: {
156
+ type: "object",
157
+ properties: {
158
+ title: {
159
+ type: "string",
160
+ example: "Judul Video Facebook"
161
+ },
162
+ thumbnail: {
163
+ type: "string",
164
+ example: "https://example.com/thumbnail.jpg"
165
+ },
166
+ selectedQuality: {
167
+ type: "string",
168
+ example: "720p(HD)"
169
+ },
170
+ type: {
171
+ type: "string",
172
+ example: "video"
173
+ },
174
+ downloadURL: {
175
+ type: "string",
176
+ example: "https://example.com/downloads/video_720p(HD).mp4"
177
+ },
178
+ filename: {
179
+ type: "string",
180
+ example: "video_720p(HD).mp4"
181
+ },
182
+ size: {
183
+ type: "number",
184
+ example: 15123456
185
+ },
186
+ upload: {
187
+ type: "object",
188
+ properties: {
189
+ status: {
190
+ type: "boolean",
191
+ example: true
192
+ },
193
+ path: {
194
+ type: "string",
195
+ example: "https://c.termai.cc/mp4/abc123"
196
+ },
197
+ mimetype: {
198
+ type: "string",
199
+ example: "video/mp4"
200
+ },
201
+ size: {
202
+ type: "number",
203
+ example: 15123456
204
+ }
205
+ }
206
+ },
207
+ availableQualities: {
208
+ type: "array",
209
+ items: {
210
+ type: "object",
211
+ properties: {
212
+ quality: {
213
+ type: "string",
214
+ example: "720p(HD)"
215
+ },
216
+ type: {
217
+ type: "string",
218
+ example: "video"
219
+ },
220
+ url: {
221
+ type: "string",
222
+ example: "https://example.com/api/fb-download?url=...&quality=720p(HD)"
223
+ }
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+ },
232
+ "400": {
233
+ description: "Bad Request - Parameter tidak valid"
234
+ },
235
+ "500": {
236
+ description: "Internal Server Error - Terjadi kesalahan server"
237
+ }
238
+ }
239
+ }
240
+ },
241
+ "/api/tiktok": {
242
+ get: {
243
+ summary: "Unduh video TikTok",
244
+ description: "Mengunduh video atau slide TikTok dan menyimpannya ke server",
245
+ parameters: [
246
+ {
247
+ name: "url",
248
+ in: "query",
249
+ required: true,
250
+ schema: {
251
+ type: "string"
252
+ },
253
+ description: "URL video TikTok"
254
+ }
255
+ ],
256
+ responses: {
257
+ "200": {
258
+ description: "Berhasil",
259
+ content: {
260
+ "application/json": {
261
+ schema: {
262
+ type: "object",
263
+ properties: {
264
+ success: {
265
+ type: "boolean",
266
+ example: true
267
+ },
268
+ type: {
269
+ type: "string",
270
+ example: "video"
271
+ },
272
+ description: {
273
+ type: "string",
274
+ example: "Deskripsi video TikTok"
275
+ },
276
+ author: {
277
+ type: "object"
278
+ },
279
+ stats: {
280
+ type: "object"
281
+ },
282
+ downloadInfo: {
283
+ type: "object",
284
+ properties: {
285
+ download_url: {
286
+ type: "string",
287
+ example: "https://example.com/download/tiktok_folder/video.mp4"
288
+ },
289
+ filename: {
290
+ type: "string",
291
+ example: "video.mp4"
292
+ }
293
+ }
294
+ }
295
+ }
296
+ }
297
+ }
298
+ }
299
+ },
300
+ "400": {
301
+ description: "Bad Request - Parameter tidak valid"
302
+ },
303
+ "500": {
304
+ description: "Internal Server Error - Terjadi kesalahan server"
305
+ }
306
+ }
307
+ }
308
+ },
309
+ "/api/instagram": {
310
+ get: {
311
+ summary: "Unduh konten Instagram",
312
+ description: "Mengunduh foto, video, atau reel dari Instagram",
313
+ parameters: [
314
+ {
315
+ name: "url",
316
+ in: "query",
317
+ required: true,
318
+ schema: {
319
+ type: "string"
320
+ },
321
+ description: "URL post/reel Instagram"
322
+ }
323
+ ],
324
+ responses: {
325
+ "200": {
326
+ description: "Berhasil",
327
+ content: {
328
+ "application/json": {
329
+ schema: {
330
+ type: "object",
331
+ properties: {
332
+ success: {
333
+ type: "boolean",
334
+ example: true
335
+ },
336
+ type: {
337
+ type: "string",
338
+ example: "post"
339
+ },
340
+ title: {
341
+ type: "string",
342
+ example: "Judul post Instagram"
343
+ },
344
+ likes: {
345
+ type: "string",
346
+ example: "1500"
347
+ },
348
+ comments: {
349
+ type: "string",
350
+ example: "250"
351
+ },
352
+ accountName: {
353
+ type: "string",
354
+ example: "username"
355
+ },
356
+ konten: {
357
+ type: "array",
358
+ items: {
359
+ type: "object",
360
+ properties: {
361
+ type: {
362
+ type: "string",
363
+ example: "video"
364
+ },
365
+ url: {
366
+ type: "string",
367
+ example: "https://example.com/video.mp4"
368
+ },
369
+ thumbnail: {
370
+ type: "string",
371
+ example: "https://example.com/thumbnail.jpg"
372
+ }
373
+ }
374
+ }
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+ },
381
+ "400": {
382
+ description: "Bad Request - Parameter tidak valid"
383
+ },
384
+ "500": {
385
+ description: "Internal Server Error - Terjadi kesalahan server"
386
+ }
387
+ }
388
+ }
389
+ },
390
+
391
+ // ============================
392
+ // BYCF CLOUDFLARE BYPASS APIs
393
+ // ============================
394
+
395
+ "/api/cf/waf-session": {
396
+ get: {
397
+ summary: "Get WAF Session (GET)",
398
+ description: "Mendapatkan WAF session untuk bypass Cloudflare protection - GET method",
399
+ parameters: [
400
+ {
401
+ name: "url",
402
+ in: "query",
403
+ required: true,
404
+ schema: {
405
+ type: "string"
406
+ },
407
+ description: "URL target yang diproteksi Cloudflare"
408
+ },
409
+ {
410
+ name: "proxy",
411
+ in: "query",
412
+ required: false,
413
+ schema: {
414
+ type: "string"
415
+ },
416
+ description: "Proxy format: host:port"
417
+ }
418
+ ],
419
+ responses: {
420
+ "200": {
421
+ description: "Berhasil",
422
+ content: {
423
+ "application/json": {
424
+ schema: {
425
+ type: "object",
426
+ properties: {
427
+ success: {
428
+ type: "boolean",
429
+ example: true
430
+ },
431
+ url: {
432
+ type: "string",
433
+ example: "https://example.com"
434
+ },
435
+ cookies: {
436
+ type: "object",
437
+ example: {"cf_clearance": "abc123", "__cflb": "xyz456"}
438
+ },
439
+ userAgent: {
440
+ type: "string",
441
+ example: "Mozilla/5.0..."
442
+ },
443
+ headers: {
444
+ type: "object"
445
+ },
446
+ timestamp: {
447
+ type: "string",
448
+ example: "2024-01-01T00:00:00.000Z"
449
+ }
450
+ }
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+ },
457
+ post: {
458
+ summary: "Create WAF Session dengan Cookie Lama (POST)",
459
+ description: "Membuat WAF session baru dengan input cookie lama untuk menghasilkan cookie baru",
460
+ requestBody: {
461
+ required: true,
462
+ content: {
463
+ "application/json": {
464
+ schema: {
465
+ type: "object",
466
+ required: ["url"],
467
+ properties: {
468
+ url: {
469
+ type: "string",
470
+ example: "https://target.com",
471
+ description: "URL target yang diproteksi Cloudflare"
472
+ },
473
+ cookies: {
474
+ type: "object",
475
+ example: {"old_cookie": "value123", "session_id": "abc456"},
476
+ description: "Cookie lama yang akan digunakan untuk membuat session baru"
477
+ },
478
+ proxy: {
479
+ type: "string",
480
+ example: "127.0.0.1:8080",
481
+ description: "Proxy format: host:port"
482
+ },
483
+ headers: {
484
+ type: "object",
485
+ description: "Custom headers tambahan"
486
+ },
487
+ userAgent: {
488
+ type: "string",
489
+ description: "Custom User-Agent"
490
+ },
491
+ sessionId: {
492
+ type: "string",
493
+ description: "Session ID khusus (opsional)"
494
+ }
495
+ }
496
+ }
497
+ }
498
+ }
499
+ },
500
+ responses: {
501
+ "200": {
502
+ description: "Berhasil membuat session dengan cookie baru",
503
+ content: {
504
+ "application/json": {
505
+ schema: {
506
+ type: "object",
507
+ properties: {
508
+ success: {
509
+ type: "boolean",
510
+ example: true
511
+ },
512
+ url: {
513
+ type: "string",
514
+ example: "https://target.com"
515
+ },
516
+ sessionId: {
517
+ type: "string",
518
+ example: "session_123456789"
519
+ },
520
+ oldCookies: {
521
+ type: "object",
522
+ example: {"old_cookie": "value123"}
523
+ },
524
+ newCookies: {
525
+ type: "object",
526
+ example: {"cf_clearance": "new_value", "__cflb": "new_value2"}
527
+ },
528
+ userAgent: {
529
+ type: "string",
530
+ example: "Mozilla/5.0..."
531
+ },
532
+ headers: {
533
+ type: "object"
534
+ },
535
+ sessionData: {
536
+ type: "object",
537
+ properties: {
538
+ id: { type: "string" },
539
+ createdAt: { type: "string" },
540
+ expiresAt: { type: "string" },
541
+ usageCount: { type: "number" }
542
+ }
543
+ },
544
+ timestamp: {
545
+ type: "string",
546
+ example: "2024-01-01T00:00:00.000Z"
547
+ }
548
+ }
549
+ }
550
+ }
551
+ }
552
+ }
553
+ }
554
+ }
555
+ },
556
+
557
+ "/api/cf/waf-session-with-host": {
558
+ post: {
559
+ summary: "Create WAF Session dengan Host Khusus",
560
+ description: "Membuat WAF session dengan host header khusus dan cookie lama",
561
+ requestBody: {
562
+ required: true,
563
+ content: {
564
+ "application/json": {
565
+ schema: {
566
+ type: "object",
567
+ required: ["url", "host"],
568
+ properties: {
569
+ url: {
570
+ type: "string",
571
+ example: "https://target.com",
572
+ description: "URL target"
573
+ },
574
+ host: {
575
+ type: "string",
576
+ example: "api.target.com",
577
+ description: "Host header khusus"
578
+ },
579
+ cookies: {
580
+ type: "object",
581
+ example: {"old_cookie": "value123"},
582
+ description: "Cookie lama"
583
+ },
584
+ proxy: {
585
+ type: "string",
586
+ example: "127.0.0.1:8080",
587
+ description: "Proxy format: host:port"
588
+ },
589
+ userAgent: {
590
+ type: "string",
591
+ description: "Custom User-Agent"
592
+ },
593
+ sessionId: {
594
+ type: "string",
595
+ description: "Session ID khusus"
596
+ }
597
+ }
598
+ }
599
+ }
600
+ }
601
+ },
602
+ responses: {
603
+ "200": {
604
+ description: "Berhasil",
605
+ content: {
606
+ "application/json": {
607
+ schema: {
608
+ type: "object",
609
+ properties: {
610
+ success: { type: "boolean", example: true },
611
+ url: { type: "string" },
612
+ host: { type: "string" },
613
+ sessionId: { type: "string" },
614
+ oldCookies: { type: "object" },
615
+ newCookies: { type: "object" },
616
+ userAgent: { type: "string" },
617
+ headers: { type: "object" },
618
+ sessionData: { type: "object" },
619
+ timestamp: { type: "string" }
620
+ }
621
+ }
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+ },
628
+
629
+ "/api/cf/renew-session": {
630
+ post: {
631
+ summary: "Renew Session",
632
+ description: "Memperbarui session yang sudah ada dengan cookie lama untuk menghasilkan cookie baru",
633
+ requestBody: {
634
+ required: true,
635
+ content: {
636
+ "application/json": {
637
+ schema: {
638
+ type: "object",
639
+ required: ["sessionId"],
640
+ properties: {
641
+ sessionId: {
642
+ type: "string",
643
+ example: "session_123456789",
644
+ description: "Session ID yang akan diperbarui"
645
+ },
646
+ url: {
647
+ type: "string",
648
+ description: "URL target (opsional, default: https://www.example.com)"
649
+ },
650
+ proxy: {
651
+ type: "string",
652
+ description: "Proxy format: host:port"
653
+ }
654
+ }
655
+ }
656
+ }
657
+ }
658
+ },
659
+ responses: {
660
+ "200": {
661
+ description: "Berhasil memperbarui session",
662
+ content: {
663
+ "application/json": {
664
+ schema: {
665
+ type: "object",
666
+ properties: {
667
+ success: { type: "boolean", example: true },
668
+ sessionId: { type: "string" },
669
+ oldCookies: { type: "object" },
670
+ newCookies: { type: "object" },
671
+ userAgent: { type: "string" },
672
+ headers: { type: "object" },
673
+ sessionData: { type: "object" },
674
+ timestamp: { type: "string" }
675
+ }
676
+ }
677
+ }
678
+ }
679
+ }
680
+ }
681
+ }
682
+ },
683
+
684
+ "/api/cf/session/{sessionId}": {
685
+ get: {
686
+ summary: "Get Session by ID",
687
+ description: "Mendapatkan informasi session berdasarkan ID",
688
+ parameters: [
689
+ {
690
+ name: "sessionId",
691
+ in: "path",
692
+ required: true,
693
+ schema: { type: "string" },
694
+ description: "Session ID"
695
+ }
696
+ ],
697
+ responses: {
698
+ "200": {
699
+ description: "Berhasil",
700
+ content: {
701
+ "application/json": {
702
+ schema: {
703
+ type: "object",
704
+ properties: {
705
+ success: { type: "boolean", example: true },
706
+ sessionId: { type: "string" },
707
+ cookies: { type: "object" },
708
+ userAgent: { type: "string" },
709
+ headers: { type: "object" },
710
+ sessionData: { type: "object" },
711
+ timestamp: { type: "string" }
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
717
+ }
718
+ },
719
+ delete: {
720
+ summary: "Delete Session",
721
+ description: "Menghapus session berdasarkan ID",
722
+ parameters: [
723
+ {
724
+ name: "sessionId",
725
+ in: "path",
726
+ required: true,
727
+ schema: { type: "string" },
728
+ description: "Session ID yang akan dihapus"
729
+ }
730
+ ],
731
+ responses: {
732
+ "200": {
733
+ description: "Berhasil menghapus session",
734
+ content: {
735
+ "application/json": {
736
+ schema: {
737
+ type: "object",
738
+ properties: {
739
+ success: { type: "boolean", example: true },
740
+ message: { type: "string" },
741
+ sessionId: { type: "string" },
742
+ timestamp: { type: "string" }
743
+ }
744
+ }
745
+ }
746
+ }
747
+ }
748
+ }
749
+ }
750
+ },
751
+
752
+ "/api/cf/sessions": {
753
+ get: {
754
+ summary: "Get All Sessions",
755
+ description: "Mendapatkan daftar semua session yang aktif",
756
+ responses: {
757
+ "200": {
758
+ description: "Berhasil",
759
+ content: {
760
+ "application/json": {
761
+ schema: {
762
+ type: "object",
763
+ properties: {
764
+ success: { type: "boolean", example: true },
765
+ sessions: {
766
+ type: "array",
767
+ items: {
768
+ type: "object",
769
+ properties: {
770
+ id: { type: "string" },
771
+ cookies: { type: "object" },
772
+ userAgent: { type: "string" },
773
+ createdAt: { type: "string" },
774
+ lastUsed: { type: "string" },
775
+ expiresAt: { type: "string" },
776
+ usageCount: { type: "number" }
777
+ }
778
+ }
779
+ },
780
+ total: { type: "number" },
781
+ timestamp: { type: "string" }
782
+ }
783
+ }
784
+ }
785
+ }
786
+ }
787
+ }
788
+ }
789
+ },
790
+
791
+ "/api/cf/turnstile": {
792
+ get: {
793
+ summary: "Solve Turnstile CAPTCHA",
794
+ description: "Menyelesaikan Cloudflare Turnstile CAPTCHA",
795
+ parameters: [
796
+ {
797
+ name: "url",
798
+ in: "query",
799
+ required: true,
800
+ schema: {
801
+ type: "string"
802
+ },
803
+ description: "URL target yang memiliki Turnstile"
804
+ },
805
+ {
806
+ name: "sitekey",
807
+ in: "query",
808
+ required: true,
809
+ schema: {
810
+ type: "string"
811
+ },
812
+ description: "Sitekey Turnstile"
813
+ },
814
+ {
815
+ name: "proxy",
816
+ in: "query",
817
+ required: false,
818
+ schema: {
819
+ type: "string"
820
+ },
821
+ description: "Proxy format: host:port"
822
+ }
823
+ ],
824
+ responses: {
825
+ "200": {
826
+ description: "Berhasil",
827
+ content: {
828
+ "application/json": {
829
+ schema: {
830
+ type: "object",
831
+ properties: {
832
+ success: {
833
+ type: "boolean",
834
+ example: true
835
+ },
836
+ url: {
837
+ type: "string",
838
+ example: "https://example.com"
839
+ },
840
+ sitekey: {
841
+ type: "string",
842
+ example: "0x4AAAAAAABBBBB"
843
+ },
844
+ turnstileToken: {
845
+ type: "string",
846
+ example: "0.xabc123def456"
847
+ },
848
+ timestamp: {
849
+ type: "string",
850
+ example: "2024-01-01T00:00:00.000Z"
851
+ }
852
+ }
853
+ }
854
+ }
855
+ }
856
+ }
857
+ }
858
+ }
859
+ },
860
+
861
+ "/api/cf/autofaucet": {
862
+ get: {
863
+ summary: "Autofaucet Bypass",
864
+ description: "Bypass Cloudflare WAF dan Turnstile untuk autofaucet.org",
865
+ parameters: [
866
+ {
867
+ name: "proxy",
868
+ in: "query",
869
+ required: false,
870
+ schema: {
871
+ type: "string"
872
+ },
873
+ description: "Proxy format: host:port"
874
+ }
875
+ ],
876
+ responses: {
877
+ "200": {
878
+ description: "Berhasil",
879
+ content: {
880
+ "application/json": {
881
+ schema: {
882
+ type: "object",
883
+ properties: {
884
+ success: {
885
+ type: "boolean",
886
+ example: true
887
+ },
888
+ url: {
889
+ type: "string",
890
+ example: "https://autofaucet.org/earn/faucet"
891
+ },
892
+ sitekey: {
893
+ type: "string",
894
+ example: "0x4AAAAAAAeegevyhnJu7zGA"
895
+ },
896
+ wafSession: {
897
+ type: "object"
898
+ },
899
+ turnstileToken: {
900
+ type: "string",
901
+ example: "0.xabc123def456"
902
+ },
903
+ timestamp: {
904
+ type: "string",
905
+ example: "2024-01-01T00:00:00.000Z"
906
+ }
907
+ }
908
+ }
909
+ }
910
+ }
911
+ }
912
+ }
913
+ }
914
+ },
915
+
916
+ "/api/cf/stats": {
917
+ get: {
918
+ summary: "Get BYCF Stats",
919
+ description: "Mendapatkan statistik penggunaan bycf",
920
+ responses: {
921
+ "200": {
922
+ description: "Berhasil",
923
+ content: {
924
+ "application/json": {
925
+ schema: {
926
+ type: "object",
927
+ properties: {
928
+ success: {
929
+ type: "boolean",
930
+ example: true
931
+ },
932
+ stats: {
933
+ type: "object"
934
+ },
935
+ timestamp: {
936
+ type: "string",
937
+ example: "2024-01-01T00:00:00.000Z"
938
+ }
939
+ }
940
+ }
941
+ }
942
+ }
943
+ }
944
+ }
945
+ }
946
+ },
947
+
948
+ // ============================
949
+ // UPLOAD APIs
950
+ // ============================
951
+
952
+ "/api/upload": {
953
+ post: {
954
+ summary: "Upload file (Form Data)",
955
+ description: "Mengupload file ke server eksternal menggunakan form-data",
956
+ requestBody: {
957
+ required: true,
958
+ content: {
959
+ "multipart/form-data": {
960
+ schema: {
961
+ type: "object",
962
+ properties: {
963
+ file: {
964
+ type: "string",
965
+ format: "binary",
966
+ description: "File yang akan diupload"
967
+ }
968
+ }
969
+ }
970
+ }
971
+ }
972
+ },
973
+ responses: {
974
+ "200": {
975
+ description: "File berhasil diupload",
976
+ content: {
977
+ "application/json": {
978
+ schema: {
979
+ type: "object",
980
+ properties: {
981
+ status: {
982
+ type: "boolean",
983
+ example: true
984
+ },
985
+ path: {
986
+ type: "string",
987
+ example: "https://c.termai.cc/jpeg/T0鳍p"
988
+ },
989
+ mimetype: {
990
+ type: "string",
991
+ example: "image/jpeg"
992
+ },
993
+ size: {
994
+ type: "number",
995
+ example: 171062
996
+ }
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ },
1002
+ "400": {
1003
+ description: "Bad Request - Tidak ada file yang diunggah"
1004
+ },
1005
+ "500": {
1006
+ description: "Internal Server Error - Terjadi kesalahan server"
1007
+ }
1008
+ }
1009
+ }
1010
+ },
1011
+
1012
+ "/api/upload-base64": {
1013
+ post: {
1014
+ summary: "Upload file (Base64)",
1015
+ description: "Mengupload file ke server eksternal menggunakan base64",
1016
+ requestBody: {
1017
+ required: true,
1018
+ content: {
1019
+ "application/json": {
1020
+ schema: {
1021
+ type: "object",
1022
+ required: ["file"],
1023
+ properties: {
1024
+ file: {
1025
+ type: "string",
1026
+ description: "File dalam format base64"
1027
+ }
1028
+ }
1029
+ }
1030
+ }
1031
+ }
1032
+ },
1033
+ responses: {
1034
+ "200": {
1035
+ description: "File berhasil diupload",
1036
+ content: {
1037
+ "application/json": {
1038
+ schema: {
1039
+ type: "object",
1040
+ properties: {
1041
+ status: { type: "boolean", example: true },
1042
+ path: { type: "string" },
1043
+ mimetype: { type: "string" },
1044
+ size: { type: "number" }
1045
+ }
1046
+ }
1047
+ }
1048
+ }
1049
+ }
1050
+ }
1051
+ }
1052
+ },
1053
+
1054
+ "/api/upload-local": {
1055
+ post: {
1056
+ summary: "Upload file dari local",
1057
+ description: "Mengupload file yang sudah ada di server local ke server eksternal",
1058
+ requestBody: {
1059
+ required: true,
1060
+ content: {
1061
+ "application/json": {
1062
+ schema: {
1063
+ type: "object",
1064
+ required: ["filename"],
1065
+ properties: {
1066
+ filename: {
1067
+ type: "string",
1068
+ description: "Nama file yang ada di folder downloads"
1069
+ }
1070
+ }
1071
+ }
1072
+ }
1073
+ }
1074
+ },
1075
+ responses: {
1076
+ "200": {
1077
+ description: "File berhasil diupload",
1078
+ content: {
1079
+ "application/json": {
1080
+ schema: {
1081
+ type: "object",
1082
+ properties: {
1083
+ status: { type: "boolean", example: true },
1084
+ path: { type: "string" },
1085
+ mimetype: { type: "string" },
1086
+ size: { type: "number" }
1087
+ }
1088
+ }
1089
+ }
1090
+ }
1091
+ }
1092
+ }
1093
+ }
1094
+ }
1095
+ }
1096
+ };
1097
+
1098
+ module.exports = swaggerDocument;
endpoints/cloudflare.js DELETED
@@ -1,79 +0,0 @@
1
- async function cloudflare(data, page) {
2
- return new Promise(async (resolve, reject) => {
3
- if (!data.domain) return reject(new Error("Missing domain parameter"))
4
-
5
- const startTime = Date.now()
6
- let isResolved = false
7
- let userAgent = null
8
-
9
- const cl = setTimeout(() => {
10
- if (!isResolved) {
11
- isResolved = true
12
- const elapsedTime = (Date.now() - startTime) / 1000
13
- resolve({
14
- cf_clearance: null,
15
- user_agent: userAgent,
16
- elapsed_time: elapsedTime,
17
- })
18
- }
19
- }, 20000)
20
-
21
- try {
22
- if (data.proxy?.username && data.proxy?.password) {
23
- await page.authenticate({
24
- username: data.proxy.username,
25
- password: data.proxy.password,
26
- })
27
- }
28
-
29
- page.removeAllListeners("request")
30
- page.removeAllListeners("response")
31
- await page.setRequestInterception(true)
32
-
33
- page.on("request", async (req) => {
34
- try {
35
- await req.continue()
36
- } catch (_) {}
37
- })
38
-
39
- page.on("response", async (res) => {
40
- try {
41
- const url = res.url()
42
- if (url.includes("/cdn-cgi/challenge-platform/")) {
43
- const headers = res.headers()
44
- if (headers["set-cookie"]) {
45
- const match = headers["set-cookie"].match(/cf_clearance=([^;]+)/)
46
- if (match) {
47
- const cf_clearance = match[1]
48
- const userAgent = (await res.request().headers())["user-agent"]
49
- const elapsedTime = (Date.now() - startTime) / 1000
50
-
51
- if (!isResolved) {
52
- isResolved = true
53
- clearTimeout(cl)
54
-
55
- resolve({
56
- cf_clearance,
57
- user_agent: userAgent,
58
- elapsed_time: elapsedTime,
59
- })
60
- }
61
- }
62
- }
63
- }
64
- } catch (_) {}
65
- })
66
-
67
- await page.goto(data.domain, { waitUntil: "domcontentloaded" })
68
- userAgent = await page.evaluate(() => navigator.userAgent)
69
- } catch (err) {
70
- if (!isResolved) {
71
- isResolved = true
72
- clearTimeout(cl)
73
- reject(err)
74
- }
75
- }
76
- })
77
- }
78
-
79
- module.exports = cloudflare
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
endpoints/turnstile.js DELETED
@@ -1,72 +0,0 @@
1
- async function turnstile({ domain, proxy, siteKey }, page) {
2
- if (!domain) throw new Error("Missing domain")
3
- if (!siteKey) throw new Error("Missing siteKey")
4
-
5
- const timeout = global.timeOut || 60000
6
- let resolved = false
7
- let timeoutId
8
-
9
- try {
10
- if (proxy?.username && proxy?.password) {
11
- await page.authenticate({
12
- username: proxy.username,
13
- password: proxy.password,
14
- }).catch(() => {})
15
- }
16
-
17
- const htmlContent = `
18
- <!DOCTYPE html>
19
- <html><body>
20
- <div class="turnstile"></div>
21
- <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
22
- <script>
23
- window.onloadTurnstileCallback = function() {
24
- turnstile.render('.turnstile', {
25
- sitekey: '${siteKey}',
26
- callback: function(token) {
27
- window.turnstileToken = token
28
- }
29
- })
30
- }
31
- </script>
32
- </body></html>
33
- `
34
-
35
- await page.setRequestInterception(true)
36
- page.removeAllListeners("request")
37
-
38
- page.on("request", async (request) => {
39
- const url = request.url().split('?')[0]
40
- if ([domain, domain + "/"].includes(url) && request.resourceType() === "document") {
41
- await request.respond({
42
- status: 200,
43
- contentType: "text/html",
44
- body: htmlContent,
45
- })
46
- } else {
47
- await request.continue()
48
- }
49
- })
50
-
51
- timeoutId = setTimeout(() => {
52
- if (!resolved) throw new Error("Timeout")
53
- }, timeout)
54
-
55
- await page.goto(domain, { waitUntil: "domcontentloaded", timeout: 30000 })
56
-
57
- await page.waitForFunction(() => window.turnstileToken, { timeout })
58
- const token = await page.evaluate(() => window.turnstileToken)
59
-
60
- resolved = true
61
- clearTimeout(timeoutId)
62
-
63
- if (!token) throw new Error("No token received")
64
- return token
65
-
66
- } catch (e) {
67
- clearTimeout(timeoutId)
68
- throw e
69
- }
70
- }
71
-
72
- module.exports = turnstile
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
image/Background.jpg ADDED
index.js CHANGED
@@ -1,154 +1,1284 @@
1
- const express = require('express')
2
- const { connect } = require("puppeteer-real-browser")
3
- const fs = require("fs")
4
- const path = require("path")
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- const app = express()
7
- const port = process.env.PORT || 7860
8
- const authToken = process.env.authToken || null
9
 
10
- global.browserLimit = Number(process.env.browserLimit) || 20
11
- global.timeOut = Number(process.env.timeOut) || 60000
12
 
13
- const CACHE_DIR = path.join(__dirname, "cache")
14
- const CACHE_FILE = path.join(CACHE_DIR, "cache.json")
15
- const CACHE_TTL = 5 * 60 * 1000
 
 
 
16
 
17
- function loadCache() {
18
- if (!fs.existsSync(CACHE_FILE)) return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  try {
20
- return JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8"))
21
- } catch {
22
- return {}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  }
24
- }
25
 
26
- function saveCache(cache) {
27
  try {
28
- if (!fs.existsSync(CACHE_DIR)) fs.mkdirSync(CACHE_DIR, { recursive: true })
29
- fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2))
30
- } catch {}
31
- }
 
 
 
 
 
 
 
 
 
 
 
 
32
 
33
- function readCache(key) {
34
- const cache = loadCache()
35
- const entry = cache[key]
36
- if (entry && Date.now() - entry.timestamp < CACHE_TTL) return entry.value
37
- return null
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
 
40
- function writeCache(key, value) {
41
- const cache = loadCache()
42
- cache[key] = { timestamp: Date.now(), value }
43
- saveCache(cache)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  }
45
 
46
- app.use(express.json())
47
- app.use(express.urlencoded({ extended: true }))
 
 
48
 
49
- // Root endpoint sederhana
50
- app.get('/', (req, res) => {
51
- res.status(200).json({
52
- message: 'Cloudflare Solver API',
53
- port: port,
54
- status: 'active'
55
- })
56
- })
57
-
58
- async function createBrowser(proxyServer = null) {
59
- const connectOptions = {
60
- headless: true,
61
- turnstile: true,
62
- connectOption: {
63
- defaultViewport: null,
64
- args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage']
65
- }
66
- }
67
-
68
- if (proxyServer) {
69
- connectOptions.args = [...(connectOptions.connectOption.args || []), `--proxy-server=${proxyServer}`]
70
- }
71
-
72
- const { browser } = await connect(connectOptions)
73
- const page = (await browser.pages())[0] || await browser.newPage()
74
-
75
- await page.setRequestInterception(true)
76
- page.on('request', (req) => {
77
- if (["image", "stylesheet", "font", "media"].includes(req.resourceType())) {
78
- req.abort()
79
- } else {
80
- req.continue()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  }
82
- })
83
 
84
- return { browser, page }
85
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
- const turnstile = require('./endpoints/turnstile')
88
- const cloudflare = require('./endpoints/cloudflare')
 
 
 
 
 
 
 
89
 
90
- app.post('/cloudflare', async (req, res) => {
91
- const data = req.body
92
-
93
- if (!data?.mode) return res.status(400).json({ message: 'Bad Request' })
94
- if (authToken && data.authToken !== authToken) return res.status(401).json({ message: 'Unauthorized' })
95
- if (global.browserLimit <= 0) return res.status(429).json({ message: 'Too Many Requests' })
 
 
96
 
97
- let cacheKey, cached
98
- if (data.mode === "iuam") {
99
- cacheKey = JSON.stringify(data)
100
- cached = readCache(cacheKey)
101
- if (cached) return res.status(200).json({ ...cached, cached: true })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- global.browserLimit--
105
- let browser = null
 
 
 
 
 
 
 
 
106
 
 
 
 
 
 
 
 
 
 
 
 
107
  try {
108
- const proxyServer = data.proxy ? `${data.proxy.hostname}:${data.proxy.port}` : null
109
- const { browser: br, page } = await createBrowser(proxyServer)
110
- browser = br
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
 
112
- let result
113
- switch (data.mode) {
114
- case "turnstile":
115
- result = await turnstile(data, page)
116
- .then(token => ({ token }))
117
- .catch(err => ({ code: 500, message: err.message }))
118
- break
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
 
120
- case "iuam":
121
- result = await cloudflare(data, page)
122
- .then(r => ({ ...r }))
123
- .catch(err => ({ code: 500, message: err.message }))
 
 
 
 
124
 
125
- if (!result.code) writeCache(cacheKey, result)
126
- break
 
 
 
 
 
 
 
 
 
127
 
128
- default:
129
- result = { code: 400, message: 'Invalid mode' }
 
 
 
 
130
  }
131
 
132
- res.status(result.code ?? 200).json(result)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  } catch (err) {
134
- res.status(500).json({ code: 500, message: err.message })
135
- } finally {
136
- if (browser) await browser.close().catch(() => {})
137
- global.browserLimit++
138
  }
139
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
  app.use((req, res) => {
142
- res.status(404).json({ message: 'Not Found' })
143
- })
144
-
145
- if (process.env.NODE_ENV !== 'development') {
146
- const server = app.listen(port, () => {
147
- console.log(`Server running on port ${port}`)
148
- })
149
- server.timeout = global.timeOut + 10000
150
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- if (process.env.NODE_ENV === 'development') {
153
- module.exports = app
154
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require('express');
2
+ const cors = require('cors');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+ const swaggerUi = require('swagger-ui-express');
6
+ const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
7
+ const { pipeline } = require('stream');
8
+ const { promisify } = require('util');
9
+ const axios = require("axios");
10
+ const FormData = require("form-data");
11
+ const fileType = require('file-type');
12
+ const streamPipeline = promisify(pipeline);
13
+ const multer = require('multer');
14
+ const cheerio = require('cheerio');
15
+ const bycf = require('bycf');
16
 
17
+ const app = express();
18
+ const PORT = 7860;
19
+ const DOMAIN = process.env.DOMAIN || `https://rezaharis-cf.hf.space`;
20
 
21
+ const downloadsDir = path.join(__dirname, 'downloads');
22
+ if (!fs.existsSync(downloadsDir)) fs.mkdirSync(downloadsDir, { recursive: true });
23
 
24
+ const upload = multer({
25
+ storage: multer.memoryStorage(),
26
+ limits: {
27
+ fileSize: 50 * 1024 * 1024,
28
+ },
29
+ });
30
 
31
+ app.use(cors());
32
+ app.use(express.json({ limit: '50mb' }));
33
+ app.use(express.urlencoded({ extended: true, limit: '50mb' }));
34
+ app.use(express.static('public'));
35
+ app.use('/downloads', express.static(downloadsDir));
36
+
37
+ const UPLOAD_KEY = process.env.UPLOAD_KEY || "AIzaBj7z2z3xBjsk";
38
+ const UPLOAD_DOMAIN = process.env.UPLOAD_DOMAIN || 'https://c.termai.cc';
39
+
40
+ // Session storage untuk menyimpan cookie sessions
41
+ const sessionStorage = new Map();
42
+
43
+ const delay = (ms, reason = '') => {
44
+ console.log(`⏳ Delay ${ms}ms ${reason ? `- ${reason}` : ''}`);
45
+ return new Promise(resolve => setTimeout(resolve, ms));
46
+ };
47
+
48
+ const getRandomUserAgent = () => {
49
+ const agents = [
50
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
51
+ 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
52
+ 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
53
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/121.0',
54
+ 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.0 Mobile/15E148 Safari/604.1'
55
+ ];
56
+ return agents[Math.floor(Math.random() * agents.length)];
57
+ };
58
+
59
+ const uploadFile = async (file) => {
60
  try {
61
+ console.log('📤 Memulai proses upload file...');
62
+
63
+ let fileTypeResult;
64
+ if (typeof fileType.fromBuffer === 'function') {
65
+ fileTypeResult = await fileType.fromBuffer(file);
66
+ } else if (typeof fileType.fileTypeFromBuffer === 'function') {
67
+ fileTypeResult = await fileType.fileTypeFromBuffer(file);
68
+ } else {
69
+ throw new Error('Tidak dapat mendeteksi tipe file');
70
+ }
71
+
72
+ if (!fileTypeResult) {
73
+ throw new Error('Tipe file tidak dikenali');
74
+ }
75
+
76
+ console.log(`📄 Tipe file terdeteksi: ${fileTypeResult.ext}`);
77
+
78
+ const formData = new FormData();
79
+ formData.append('file', file, {
80
+ filename: `file.${fileTypeResult.ext}`
81
+ });
82
+
83
+ console.log('🔄 Mengirim file ke server upload...');
84
+ const response = await axios.post(
85
+ `${UPLOAD_DOMAIN}/api/upload?key=${UPLOAD_KEY}`,
86
+ formData,
87
+ {
88
+ headers: {
89
+ ...formData.getHeaders(),
90
+ },
91
+ maxContentLength: Infinity,
92
+ maxBodyLength: Infinity
93
+ }
94
+ );
95
+
96
+ console.log('✅ File berhasil diupload');
97
+ return response.data;
98
+ } catch (error) {
99
+ console.error('❌ Error uploading file:', error.response?.data || error.message);
100
+ throw new Error(error.response?.data?.message || 'Gagal mengupload file');
101
  }
102
+ };
103
 
104
+ async function getFacebookVideoData(fbUrl) {
105
  try {
106
+ console.log(`🔍 Mencari data video Facebook: ${fbUrl}`);
107
+
108
+ const encodedUrl = encodeURIComponent(fbUrl);
109
+ const postData = `id=${encodedUrl}&locale=id`;
110
+
111
+ const response = await axios.post('https://getmyfb.com/process', postData, {
112
+ headers: {
113
+ 'HX-Request': 'true',
114
+ 'HX-Trigger': 'form',
115
+ 'HX-Target': 'target',
116
+ 'HX-Current-URL': 'https://getmyfb.com/id',
117
+ 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
118
+ 'User-Agent': getRandomUserAgent(),
119
+ 'Referer': 'https://getmyfb.com/id'
120
+ }
121
+ });
122
 
123
+ console.log('✅ Berhasil mendapatkan response dari getmyfb.com');
124
+ const $ = cheerio.load(response.data);
125
+
126
+ const result = {
127
+ title: $('.results-item-text').text().trim() || 'Facebook Video',
128
+ thumbnail: $('.results-item-image').attr('src'),
129
+ downloads: []
130
+ };
131
+
132
+ console.log(`📝 Judul video: ${result.title}`);
133
+
134
+ $('.results-list-item').each((index, element) => {
135
+ const item = $(element);
136
+ const qualityText = item.contents().first().text().trim();
137
+ const link = item.find('a');
138
+
139
+ let quality = qualityText;
140
+ if (qualityText.includes('HD') || qualityText.includes('720')) {
141
+ quality = '720p(HD)';
142
+ } else if (qualityText.includes('SD') || qualityText.includes('360')) {
143
+ quality = '360p(SD)';
144
+ } else if (qualityText.toLowerCase().includes('audio') || qualityText.toLowerCase().includes('mp3')) {
145
+ quality = 'Mp3';
146
+ }
147
+
148
+ if (link.attr('href')) {
149
+ result.downloads.push({
150
+ quality: quality,
151
+ url: link.attr('href'),
152
+ type: link.hasClass('mp3') ? 'audio' : 'video'
153
+ });
154
+ console.log(`🎯 Kualitas ditemukan: ${quality} - ${link.attr('href')}`);
155
+ }
156
+ });
157
+
158
+ console.log(`📊 Total kualitas tersedia: ${result.downloads.length}`);
159
+ return result;
160
+
161
+ } catch (error) {
162
+ console.error('❌ Error getting Facebook video data:', error.message);
163
+ throw new Error('Gagal mendapatkan data video Facebook');
164
+ }
165
  }
166
 
167
+ async function downloadAndSaveFile(url, filename, quality) {
168
+ try {
169
+ const filePath = path.join(downloadsDir, filename);
170
+
171
+ console.log(`⬇️ Memulai download dari: ${url}`);
172
+ console.log(`📁 Menyimpan sebagai: ${filename}`);
173
+
174
+ const response = await fetch(url);
175
+ if (!response.ok) {
176
+ throw new Error(`HTTP error! status: ${response.status}`);
177
+ }
178
+
179
+ if (!response.body) {
180
+ throw new Error('Tidak ada body response');
181
+ }
182
+
183
+ const fileStream = fs.createWriteStream(filePath);
184
+ await streamPipeline(response.body, fileStream);
185
+
186
+ console.log('⏳ Menunggu file tersimpan...');
187
+ await delay(500, 'Memastikan file tersimpan sempurna');
188
+
189
+ if (!fs.existsSync(filePath)) {
190
+ throw new Error('File tidak berhasil dibuat');
191
+ }
192
+
193
+ const stats = fs.statSync(filePath);
194
+ if (stats.size === 0) {
195
+ fs.unlinkSync(filePath);
196
+ throw new Error('File berukuran 0 byte - download gagal');
197
+ }
198
+
199
+ console.log(`✅ Download selesai: ${filename} (${(stats.size / 1024 / 1024).toFixed(2)} MB)`);
200
+
201
+ return {
202
+ quality: quality,
203
+ filename: filename,
204
+ path: filePath,
205
+ downloadURL: `${DOMAIN}/downloads/${encodeURIComponent(filename)}`,
206
+ size: stats.size
207
+ };
208
+ } catch (error) {
209
+ const filePath = path.join(downloadsDir, filename);
210
+ if (fs.existsSync(filePath)) {
211
+ fs.unlinkSync(filePath);
212
+ }
213
+ console.error(`❌ Gagal mendownload file ${quality}: ${error.message}`);
214
+ throw new Error(`Gagal mendownload file ${quality}: ${error.message}`);
215
+ }
216
  }
217
 
218
+ const yt = {
219
+ get baseUrl() {
220
+ return { origin: 'https://ssvid.app' };
221
+ },
222
 
223
+ get baseHeaders() {
224
+ return {
225
+ 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
226
+ 'origin': this.baseUrl.origin,
227
+ 'referer': this.baseUrl.origin + '/youtube-to-mp3',
228
+ 'User-Agent': getRandomUserAgent()
229
+ };
230
+ },
231
+
232
+ validateFormat(userFormat) {
233
+ const validFormat = ['mp3', '360p', '720p', '1080p'];
234
+ if (!validFormat.includes(userFormat)) {
235
+ console.error(`❌ Format tidak valid: ${userFormat}. Format tersedia: ${validFormat.join(', ')}`);
236
+ throw Error(`invalid format!. available formats: ${validFormat.join(', ')}`);
237
+ }
238
+ console.log(`✅ Format valid: ${userFormat}`);
239
+ },
240
+
241
+ handleFormat(userFormat, searchJson) {
242
+ this.validateFormat(userFormat);
243
+ let result;
244
+
245
+ if (userFormat === 'mp3') {
246
+ console.log('🎵 Mencari format MP3...');
247
+ result = searchJson.links?.mp3?.mp3128?.k;
248
+ if (!result) {
249
+ console.log('❌ MP3 tidak ditemukan, mencoba alternatif...');
250
+ const mp3Formats = searchJson.links?.mp3;
251
+ if (mp3Formats) {
252
+ const availableMp3 = Object.keys(mp3Formats);
253
+ console.log(`🎵 Format MP3 tersedia: ${availableMp3.join(', ')}`);
254
+ result = mp3Formats[availableMp3[0]]?.k;
255
+ }
256
+ }
257
+ } else {
258
+ console.log(`🎥 Mencari format video: ${userFormat}`);
259
+ let selectedFormat;
260
+ const allFormats = Object.entries(searchJson.links.mp4);
261
+ const quality = allFormats
262
+ .map(v => v[1].q)
263
+ .filter(v => /\d+p/.test(v))
264
+ .map(v => parseInt(v))
265
+ .sort((a, b) => b - a)
266
+ .map(v => v + 'p');
267
+
268
+ console.log(`📊 Kualitas video tersedia: ${quality.join(', ')}`);
269
+
270
+ if (!quality.includes(userFormat)) {
271
+ selectedFormat = quality.includes('360p') ? '360p' : quality[0];
272
+ console.log(`⚠️ Format ${userFormat} tidak ada. Fallback ke ${selectedFormat}`);
273
+ } else {
274
+ selectedFormat = userFormat;
275
+ }
276
+
277
+ const find = allFormats.find(v => v[1].q == selectedFormat);
278
+ result = find?.[1]?.k;
279
+ }
280
+
281
+ if (!result) {
282
+ console.error('❌ Tidak ada hasil untuk format yang diminta');
283
+ throw Error(`${userFormat} gak ada cuy. aneh`);
284
+ }
285
+
286
+ console.log(`✅ Format ${userFormat} ditemukan`);
287
+ return result;
288
+ },
289
+
290
+ async hit(path, payload) {
291
+ try {
292
+ console.log(`🌐 Request ke: ${path}`);
293
+
294
+ await delay(3000 + Math.random() * 4000, `Request ke ${path}`);
295
+
296
+ const body = new URLSearchParams(payload);
297
+ const opts = {
298
+ headers: this.baseHeaders,
299
+ body,
300
+ method: 'POST'
301
+ };
302
+
303
+ const r = await fetch(`${this.baseUrl.origin}${path}`, opts);
304
+
305
+ if (!r.ok) {
306
+ console.error(`❌ HTTP Error: ${r.status} ${r.statusText}`);
307
+ throw Error(`${r.status} ${r.statusText}`);
308
+ }
309
+
310
+ const j = await r.json();
311
+ console.log(`✅ Response berhasil dari: ${path}`);
312
+ return j;
313
+ } catch (e) {
314
+ console.error(`❌ Error pada request ${path}:`, e.message);
315
+ throw Error(`${path}\n${e.message}`);
316
+ }
317
+ },
318
+
319
+ async download(queryOrYtUrl, userFormat = 'mp3') {
320
+ console.log(`🚀 Memulai proses download YouTube: ${queryOrYtUrl}`);
321
+ console.log(`🎯 Format yang diminta: ${userFormat}`);
322
+
323
+ this.validateFormat(userFormat);
324
+
325
+ await delay(4000, 'Delay awal sebelum search');
326
+
327
+ let search = await this.hit('/api/ajax/search?hl=en', {
328
+ query: queryOrYtUrl,
329
+ cf_token: "",
330
+ vt: "youtube"
331
+ });
332
+
333
+ console.log('🔍 Search result:', search.p);
334
+
335
+ if (search.p === 'search') {
336
+ console.log('🔎 Mode: Pencarian video');
337
+ if (!search?.items?.length) {
338
+ console.error('❌ Hasil pencarian kosong');
339
+ throw Error(`hasil pencarian ${queryOrYtUrl} tidak ada`);
340
+ }
341
+
342
+ const { v, t } = search.items[0];
343
+ const videoUrl = 'https://www.youtube.com/watch?v=' + v;
344
+ console.log(`📹 Video ditemukan:\n Judul: ${t}\n URL: ${videoUrl}`);
345
+
346
+ await delay(3000, 'Sebelum request detail video');
347
+
348
+ search = await this.hit('/api/ajax/search?hl=en', {
349
+ query: videoUrl,
350
+ cf_token: "",
351
+ vt: "youtube"
352
+ });
353
+ } else {
354
+ console.log('📹 Mode: Direct video URL');
355
+ }
356
+
357
+ const vid = search.vid;
358
+ console.log(`🆔 Video ID: ${vid}`);
359
+
360
+ const k = this.handleFormat(userFormat, search);
361
+ console.log(`🔑 Download key: ${k}`);
362
+
363
+ console.log('🔄 Memulai proses convert...');
364
+ const convert = await this.hit('/api/ajax/convert', { k, vid });
365
+
366
+ console.log(`📊 Convert status: ${convert.c_status}`);
367
+
368
+ if (convert.c_status === 'CONVERTING') {
369
+ console.log('⏳ File sedang diproses, menunggu...');
370
+ let convert2;
371
+
372
+ while (true) {
373
+ console.log(`🔄 Checking status...`);
374
+
375
+ convert2 = await this.hit('/api/convert/check?hl=en', {
376
+ vid,
377
+ b_id: convert.b_id
378
+ });
379
+
380
+ console.log(`📊 Status: ${convert2.c_status}`);
381
+
382
+ if (convert2.c_status === 'CONVERTED') {
383
+ console.log('✅ Convert selesai!');
384
+ return convert2;
385
+ }
386
+
387
+ await delay(5000, 'Menunggu proses convert');
388
+ }
389
+ } else {
390
+ console.log('✅ Convert langsung selesai');
391
+ return convert;
392
+ }
393
  }
394
+ };
395
 
396
+ // ============================
397
+ // BYCF CLOUDFLARE BYPASS APIs - ENHANCED
398
+ // ============================
399
+
400
+ // Session management functions
401
+ const createSession = (sessionId, data) => {
402
+ const sessionData = {
403
+ id: sessionId,
404
+ cookies: data.cookies || {},
405
+ headers: data.headers || {},
406
+ userAgent: data.userAgent || getRandomUserAgent(),
407
+ createdAt: new Date().toISOString(),
408
+ lastUsed: new Date().toISOString(),
409
+ expiresAt: new Date(Date.now() + 30 * 60 * 1000).toISOString(), // 30 menit
410
+ usageCount: 0
411
+ };
412
+ sessionStorage.set(sessionId, sessionData);
413
+ return sessionData;
414
+ };
415
 
416
+ const getSession = (sessionId) => {
417
+ const session = sessionStorage.get(sessionId);
418
+ if (session) {
419
+ session.lastUsed = new Date().toISOString();
420
+ session.usageCount++;
421
+ sessionStorage.set(sessionId, session);
422
+ }
423
+ return session;
424
+ };
425
 
426
+ const updateSessionCookies = (sessionId, newCookies) => {
427
+ const session = getSession(sessionId);
428
+ if (session) {
429
+ session.cookies = { ...session.cookies, ...newCookies };
430
+ sessionStorage.set(sessionId, session);
431
+ }
432
+ return session;
433
+ };
434
 
435
+ // POST endpoint untuk WAF Session dengan input cookie lama
436
+ app.post('/api/cf/waf-session', async (req, res) => {
437
+ try {
438
+ const { url, proxy, cookies: oldCookies, headers: customHeaders, userAgent, sessionId } = req.body;
439
+
440
+ if (!url) {
441
+ return res.status(400).json({
442
+ success: false,
443
+ error: "Parameter 'url' diperlukan"
444
+ });
445
+ }
446
+
447
+ console.log(`🛡️ Request WAF Session dengan cookie lama untuk: ${url}`);
448
+ console.log(`🍪 Cookie lama:`, oldCookies);
449
+
450
+ // Konfigurasi session dengan cookie lama
451
+ const sessionConfig = {
452
+ proxy: proxy || "host:port",
453
+ headers: customHeaders || {},
454
+ userAgent: userAgent || getRandomUserAgent()
455
+ };
456
+
457
+ // Jika ada cookie lama, tambahkan ke konfigurasi
458
+ if (oldCookies) {
459
+ sessionConfig.cookies = oldCookies;
460
+ }
461
+
462
+ const session = await bycf.shannz.wafSession(url, sessionConfig);
463
+
464
+ console.log('✅ WAF Session berhasil dibuat dengan cookie baru');
465
+ console.log(`🍪 Cookie baru:`, session.cookies);
466
+
467
+ // Buat atau update session
468
+ const finalSessionId = sessionId || `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
469
+ const sessionData = createSession(finalSessionId, {
470
+ cookies: session.cookies,
471
+ headers: session.headers,
472
+ userAgent: session.headers['User-Agent']
473
+ });
474
+
475
+ res.json({
476
+ success: true,
477
+ url: url,
478
+ sessionId: finalSessionId,
479
+ oldCookies: oldCookies,
480
+ newCookies: session.cookies,
481
+ userAgent: session.headers['User-Agent'],
482
+ headers: session.headers,
483
+ sessionData: {
484
+ id: finalSessionId,
485
+ createdAt: sessionData.createdAt,
486
+ expiresAt: sessionData.expiresAt,
487
+ usageCount: sessionData.usageCount
488
+ },
489
+ timestamp: new Date().toISOString()
490
+ });
491
+
492
+ } catch (error) {
493
+ console.error('❌ Error creating WAF session with old cookies:', error.message);
494
+ res.status(500).json({
495
+ success: false,
496
+ error: error.message
497
+ });
498
  }
499
+ });
500
+
501
+ // GET endpoint untuk WAF Session (backward compatibility)
502
+ app.get('/api/cf/waf-session', async (req, res) => {
503
+ try {
504
+ const { url, proxy } = req.query;
505
+
506
+ if (!url) {
507
+ return res.status(400).json({
508
+ success: false,
509
+ error: "Parameter 'url' diperlukan"
510
+ });
511
+ }
512
+
513
+ console.log(`🛡️ Request WAF Session untuk: ${url}`);
514
+
515
+ const session = await bycf.shannz.wafSession(
516
+ url,
517
+ proxy || "host:port"
518
+ );
519
 
520
+ console.log('✅ WAF Session berhasil didapatkan');
521
+
522
+ res.json({
523
+ success: true,
524
+ url: url,
525
+ cookies: session.cookies,
526
+ userAgent: session.headers['User-Agent'],
527
+ headers: session.headers,
528
+ timestamp: new Date().toISOString()
529
+ });
530
 
531
+ } catch (error) {
532
+ console.error('❌ Error getting WAF session:', error.message);
533
+ res.status(500).json({
534
+ success: false,
535
+ error: error.message
536
+ });
537
+ }
538
+ });
539
+
540
+ // Endpoint untuk WAF Session dengan host khusus
541
+ app.post('/api/cf/waf-session-with-host', async (req, res) => {
542
  try {
543
+ const { url, host, cookies: oldCookies, proxy, userAgent, sessionId } = req.body;
544
+
545
+ if (!url || !host) {
546
+ return res.status(400).json({
547
+ success: false,
548
+ error: "Parameter 'url' dan 'host' diperlukan"
549
+ });
550
+ }
551
+
552
+ console.log(`🛡️ Request WAF Session dengan host khusus: ${url}`);
553
+ console.log(`🌐 Host: ${host}`);
554
+ console.log(`🍪 Cookie lama:`, oldCookies);
555
+
556
+ // Konfigurasi session dengan host khusus
557
+ const sessionConfig = {
558
+ proxy: proxy || "host:port",
559
+ userAgent: userAgent || getRandomUserAgent(),
560
+ headers: {
561
+ 'Host': host,
562
+ ...(oldCookies && { 'Cookie': Object.entries(oldCookies).map(([k, v]) => `${k}=${v}`).join('; ') })
563
+ }
564
+ };
565
+
566
+ // Jika ada cookie lama, tambahkan ke konfigurasi
567
+ if (oldCookies) {
568
+ sessionConfig.cookies = oldCookies;
569
+ }
570
+
571
+ const session = await bycf.shannz.wafSession(url, sessionConfig);
572
 
573
+ console.log('✅ WAF Session dengan host khusus berhasil dibuat');
574
+ console.log(`🍪 Cookie baru:`, session.cookies);
575
+
576
+ // Buat atau update session
577
+ const finalSessionId = sessionId || `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
578
+ const sessionData = createSession(finalSessionId, {
579
+ cookies: session.cookies,
580
+ headers: session.headers,
581
+ userAgent: session.headers['User-Agent']
582
+ });
583
+
584
+ res.json({
585
+ success: true,
586
+ url: url,
587
+ host: host,
588
+ sessionId: finalSessionId,
589
+ oldCookies: oldCookies,
590
+ newCookies: session.cookies,
591
+ userAgent: session.headers['User-Agent'],
592
+ headers: session.headers,
593
+ sessionData: {
594
+ id: finalSessionId,
595
+ createdAt: sessionData.createdAt,
596
+ expiresAt: sessionData.expiresAt,
597
+ usageCount: sessionData.usageCount
598
+ },
599
+ timestamp: new Date().toISOString()
600
+ });
601
 
602
+ } catch (error) {
603
+ console.error('❌ Error creating WAF session with custom host:', error.message);
604
+ res.status(500).json({
605
+ success: false,
606
+ error: error.message
607
+ });
608
+ }
609
+ });
610
 
611
+ // Endpoint untuk renew session dengan cookie lama
612
+ app.post('/api/cf/renew-session', async (req, res) => {
613
+ try {
614
+ const { sessionId, url, proxy } = req.body;
615
+
616
+ if (!sessionId) {
617
+ return res.status(400).json({
618
+ success: false,
619
+ error: "Parameter 'sessionId' diperlukan"
620
+ });
621
+ }
622
 
623
+ const existingSession = getSession(sessionId);
624
+ if (!existingSession) {
625
+ return res.status(404).json({
626
+ success: false,
627
+ error: "Session tidak ditemukan"
628
+ });
629
  }
630
 
631
+ console.log(`🔄 Renew session: ${sessionId}`);
632
+ console.log(`🍪 Cookie lama dari session:`, existingSession.cookies);
633
+
634
+ const sessionConfig = {
635
+ proxy: proxy || "host:port",
636
+ cookies: existingSession.cookies,
637
+ headers: existingSession.headers,
638
+ userAgent: existingSession.userAgent
639
+ };
640
+
641
+ const targetUrl = url || 'https://www.example.com';
642
+ const newSession = await bycf.shannz.wafSession(targetUrl, sessionConfig);
643
+
644
+ // Update session dengan cookie baru
645
+ const updatedSession = updateSessionCookies(sessionId, newSession.cookies);
646
+
647
+ console.log('✅ Session berhasil di-renew dengan cookie baru');
648
+
649
+ res.json({
650
+ success: true,
651
+ sessionId: sessionId,
652
+ oldCookies: existingSession.cookies,
653
+ newCookies: newSession.cookies,
654
+ userAgent: newSession.headers['User-Agent'],
655
+ headers: newSession.headers,
656
+ sessionData: {
657
+ id: sessionId,
658
+ createdAt: updatedSession.createdAt,
659
+ lastUsed: updatedSession.lastUsed,
660
+ expiresAt: updatedSession.expiresAt,
661
+ usageCount: updatedSession.usageCount
662
+ },
663
+ timestamp: new Date().toISOString()
664
+ });
665
+
666
+ } catch (error) {
667
+ console.error('❌ Error renewing session:', error.message);
668
+ res.status(500).json({
669
+ success: false,
670
+ error: error.message
671
+ });
672
+ }
673
+ });
674
+
675
+ // Endpoint untuk mendapatkan session by ID
676
+ app.get('/api/cf/session/:sessionId', async (req, res) => {
677
+ try {
678
+ const { sessionId } = req.params;
679
+
680
+ const session = getSession(sessionId);
681
+ if (!session) {
682
+ return res.status(404).json({
683
+ success: false,
684
+ error: "Session tidak ditemukan"
685
+ });
686
+ }
687
+
688
+ console.log(`📋 Get session: ${sessionId}`);
689
+
690
+ res.json({
691
+ success: true,
692
+ sessionId: sessionId,
693
+ cookies: session.cookies,
694
+ userAgent: session.userAgent,
695
+ headers: session.headers,
696
+ sessionData: {
697
+ id: session.id,
698
+ createdAt: session.createdAt,
699
+ lastUsed: session.lastUsed,
700
+ expiresAt: session.expiresAt,
701
+ usageCount: session.usageCount
702
+ },
703
+ timestamp: new Date().toISOString()
704
+ });
705
+
706
+ } catch (error) {
707
+ console.error('❌ Error getting session:', error.message);
708
+ res.status(500).json({
709
+ success: false,
710
+ error: error.message
711
+ });
712
+ }
713
+ });
714
+
715
+ // Endpoint untuk menghapus session
716
+ app.delete('/api/cf/session/:sessionId', async (req, res) => {
717
+ try {
718
+ const { sessionId } = req.params;
719
+
720
+ const deleted = sessionStorage.delete(sessionId);
721
+
722
+ if (!deleted) {
723
+ return res.status(404).json({
724
+ success: false,
725
+ error: "Session tidak ditemukan"
726
+ });
727
+ }
728
+
729
+ console.log(`🗑️ Delete session: ${sessionId}`);
730
+
731
+ res.json({
732
+ success: true,
733
+ message: "Session berhasil dihapus",
734
+ sessionId: sessionId,
735
+ timestamp: new Date().toISOString()
736
+ });
737
+
738
+ } catch (error) {
739
+ console.error('❌ Error deleting session:', error.message);
740
+ res.status(500).json({
741
+ success: false,
742
+ error: error.message
743
+ });
744
+ }
745
+ });
746
+
747
+ // Endpoint untuk mendapatkan semua sessions
748
+ app.get('/api/cf/sessions', async (req, res) => {
749
+ try {
750
+ const sessions = Array.from(sessionStorage.entries()).map(([id, data]) => ({
751
+ id,
752
+ cookies: data.cookies,
753
+ userAgent: data.userAgent,
754
+ createdAt: data.createdAt,
755
+ lastUsed: data.lastUsed,
756
+ expiresAt: data.expiresAt,
757
+ usageCount: data.usageCount
758
+ }));
759
+
760
+ console.log(`📊 Get all sessions: ${sessions.length} sessions found`);
761
+
762
+ res.json({
763
+ success: true,
764
+ sessions: sessions,
765
+ total: sessions.length,
766
+ timestamp: new Date().toISOString()
767
+ });
768
+
769
+ } catch (error) {
770
+ console.error('❌ Error getting sessions:', error.message);
771
+ res.status(500).json({
772
+ success: false,
773
+ error: error.message
774
+ });
775
+ }
776
+ });
777
+
778
+ // Existing BYCF endpoints (tetap dipertahankan untuk backward compatibility)
779
+ app.get('/api/cf/turnstile', async (req, res) => {
780
+ try {
781
+ const { url, sitekey, proxy } = req.query;
782
+
783
+ if (!url || !sitekey) {
784
+ return res.status(400).json({
785
+ success: false,
786
+ error: "Parameter 'url' dan 'sitekey' diperlukan"
787
+ });
788
+ }
789
+
790
+ console.log(`🔄 Request Turnstile Token untuk: ${url}`);
791
+
792
+ const token = await bycf.shannz.turnstileMin(
793
+ url,
794
+ sitekey,
795
+ proxy || "host:port"
796
+ );
797
+
798
+ console.log('✅ Turnstile Token berhasil didapatkan');
799
+
800
+ res.json({
801
+ success: true,
802
+ url: url,
803
+ sitekey: sitekey,
804
+ turnstileToken: token,
805
+ timestamp: new Date().toISOString()
806
+ });
807
+
808
+ } catch (error) {
809
+ console.error('❌ Error getting Turnstile token:', error.message);
810
+ res.status(500).json({
811
+ success: false,
812
+ error: error.message
813
+ });
814
+ }
815
+ });
816
+
817
+ app.get('/api/cf/autofaucet', async (req, res) => {
818
+ try {
819
+ const { proxy } = req.query;
820
+ const url = "https://autofaucet.org/earn/faucet";
821
+ const sitekey = "0x4AAAAAAAeegevyhnJu7zGA";
822
+
823
+ console.log(`🎯 Request Autofaucet Bypass`);
824
+
825
+ const session = await bycf.shannz.wafSession(
826
+ url,
827
+ proxy || "host:port"
828
+ );
829
+
830
+ const token = await bycf.shannz.turnstileMin(
831
+ url,
832
+ sitekey,
833
+ proxy || "host:port"
834
+ );
835
+
836
+ console.log('✅ Autofaucet bypass berhasil');
837
+
838
+ res.json({
839
+ success: true,
840
+ url: url,
841
+ sitekey: sitekey,
842
+ wafSession: {
843
+ cookies: session.cookies,
844
+ userAgent: session.headers['User-Agent'],
845
+ headers: session.headers
846
+ },
847
+ turnstileToken: token,
848
+ timestamp: new Date().toISOString()
849
+ });
850
+
851
+ } catch (error) {
852
+ console.error('❌ Error autofaucet bypass:', error.message);
853
+ res.status(500).json({
854
+ success: false,
855
+ error: error.message
856
+ });
857
+ }
858
+ });
859
+
860
+ app.get('/api/cf/stats', async (req, res) => {
861
+ try {
862
+ console.log('📊 Request bycf stats');
863
+
864
+ const stats = await bycf.shannz.stats();
865
+
866
+ res.json({
867
+ success: true,
868
+ stats: stats,
869
+ timestamp: new Date().toISOString()
870
+ });
871
+
872
+ } catch (error) {
873
+ console.error('❌ Error getting stats:', error.message);
874
+ res.status(500).json({
875
+ success: false,
876
+ error: error.message
877
+ });
878
+ }
879
+ });
880
+
881
+ // ============================
882
+ // TIKTOK & INSTAGRAM APIs
883
+ // ============================
884
+
885
+ app.get('/api/tiktok', async (req, res) => {
886
+ try {
887
+ const { url } = req.query;
888
+
889
+ if (!url) {
890
+ return res.status(400).json({
891
+ success: false,
892
+ error: "Parameter 'url' diperlukan"
893
+ });
894
+ }
895
+
896
+ if (!url.includes('tiktok.com')) {
897
+ return res.status(400).json({
898
+ success: false,
899
+ error: "URL harus dari TikTok"
900
+ });
901
+ }
902
+
903
+ console.log(`🎵 Request TikTok Download: ${url}`);
904
+
905
+ // Import dinamis untuk ESM module
906
+ const { default: tiktokDl } = await import('./scrape/tiktokdl.js');
907
+ const result = await tiktokDl(url, DOMAIN);
908
+
909
+ if (!result.success) {
910
+ return res.status(500).json({
911
+ success: false,
912
+ error: result.error
913
+ });
914
+ }
915
+
916
+ console.log('✅ TikTok download berhasil');
917
+ res.json(result);
918
+
919
+ } catch (error) {
920
+ console.error('❌ Error in TikTok download:', error.message);
921
+ res.status(500).json({
922
+ success: false,
923
+ error: error.message
924
+ });
925
+ }
926
+ });
927
+
928
+ app.get('/api/instagram', async (req, res) => {
929
+ try {
930
+ const { url } = req.query;
931
+
932
+ if (!url) {
933
+ return res.status(400).json({
934
+ success: false,
935
+ error: "Parameter 'url' diperlukan"
936
+ });
937
+ }
938
+
939
+ if (!url.includes('instagram.com')) {
940
+ return res.status(400).json({
941
+ success: false,
942
+ error: "URL harus dari Instagram"
943
+ });
944
+ }
945
+
946
+ console.log(`📷 Request Instagram Download: ${url}`);
947
+
948
+ // Import dinamis untuk ESM module
949
+ const { default: instagramDl } = await import('./scrape/instagram.js');
950
+ const result = await instagramDl(url);
951
+
952
+ if (!result) {
953
+ return res.status(500).json({
954
+ success: false,
955
+ error: "Gagal mengambil data Instagram"
956
+ });
957
+ }
958
+
959
+ console.log('✅ Instagram download berhasil');
960
+ res.json({
961
+ success: true,
962
+ ...result
963
+ });
964
+
965
+ } catch (error) {
966
+ console.error('❌ Error in Instagram download:', error.message);
967
+ res.status(500).json({
968
+ success: false,
969
+ error: error.message
970
+ });
971
+ }
972
+ });
973
+
974
+ // ============================
975
+ // EXISTING APIs
976
+ // ============================
977
+
978
+ app.get('/api/fb-download', async (req, res) => {
979
+ try {
980
+ let { url, quality = '720p(HD)', upload = 'false' } = req.query;
981
+ if (!url) return res.status(400).json({ success: false, error: "Parameter 'url' diperlukan" });
982
+
983
+ if (!url.includes('facebook.com') && !url.includes('fb.com') && !url.includes('fb.watch')) {
984
+ return res.status(400).json({ success: false, error: "URL harus dari Facebook" });
985
+ }
986
+
987
+ console.log(`\n📥 Request Facebook Download: ${url}`);
988
+ console.log(`🎯 Kualitas: ${quality}, Upload: ${upload}`);
989
+
990
+ const result = await getFacebookVideoData(url);
991
+
992
+ if (!result.downloads || result.downloads.length === 0) {
993
+ return res.status(404).json({ success: false, error: "Tidak ada hasil download yang ditemukan" });
994
+ }
995
+
996
+ let selectedDownload = null;
997
+
998
+ if (quality === '720p(HD)') {
999
+ selectedDownload = result.downloads.find(d => d.quality.includes('720p(HD)') || d.quality.includes('HD'));
1000
+ } else if (quality === '360p(SD)') {
1001
+ selectedDownload = result.downloads.find(d => d.quality.includes('360p(SD)') || d.quality.includes('SD'));
1002
+ } else if (quality === 'Mp3') {
1003
+ selectedDownload = result.downloads.find(d => d.quality.includes('Mp3') || d.type === 'audio');
1004
+ }
1005
+
1006
+ if (!selectedDownload) {
1007
+ selectedDownload = result.downloads[0];
1008
+ console.log(`⚠️ Kualitas ${quality} tidak ditemukan, menggunakan ${selectedDownload.quality} sebagai fallback`);
1009
+ }
1010
+
1011
+ console.log(`✅ Kualitas dipilih: ${selectedDownload.quality}`);
1012
+
1013
+ const safeTitle = (result.title || 'facebook_video').replace(/[^\w\s.-]/g, '').replace(/\s+/g, '_').substring(0, 100);
1014
+ const fileExt = selectedDownload.type === 'audio' ? 'mp3' : 'mp4';
1015
+ const timestamp = Date.now();
1016
+ const safeFilename = `${safeTitle}_${selectedDownload.quality}_${timestamp}.${fileExt}`;
1017
+
1018
+ console.log(`📁 Filename: ${safeFilename}`);
1019
+
1020
+ const downloadResult = await downloadAndSaveFile(selectedDownload.url, safeFilename, selectedDownload.quality);
1021
+
1022
+ let uploadResult = null;
1023
+ if (upload.toLowerCase() === 'true') {
1024
+ try {
1025
+ console.log('📤 Mengupload file Facebook...');
1026
+ const fileBuffer = fs.readFileSync(downloadResult.path);
1027
+ uploadResult = await uploadFile(fileBuffer);
1028
+ console.log('✅ Upload Facebook berhasil');
1029
+ } catch (uploadError) {
1030
+ console.error('❌ Gagal mengupload file Facebook:', uploadError.message);
1031
+ }
1032
+ }
1033
+
1034
+ const responseData = {
1035
+ success: true,
1036
+ title: result.title,
1037
+ thumbnail: result.thumbnail,
1038
+ selectedQuality: selectedDownload.quality,
1039
+ type: selectedDownload.type,
1040
+ downloadURL: downloadResult.downloadURL,
1041
+ filename: downloadResult.filename,
1042
+ size: downloadResult.size
1043
+ };
1044
+
1045
+ if (uploadResult) {
1046
+ responseData.upload = uploadResult;
1047
+ }
1048
+
1049
+ responseData.availableQualities = result.downloads.map(d => ({
1050
+ quality: d.quality,
1051
+ type: d.type,
1052
+ url: `${DOMAIN}/api/fb-download?url=${encodeURIComponent(url)}&quality=${encodeURIComponent(d.quality)}`
1053
+ }));
1054
+
1055
+ console.log('✅ Facebook download selesai');
1056
+ return res.json(responseData);
1057
+
1058
  } catch (err) {
1059
+ console.error('❌ Error in fb-download:', err.message);
1060
+ return res.status(500).json({ success: false, error: err.message });
 
 
1061
  }
1062
+ });
1063
+
1064
+ app.get('/api/download', async (req, res) => {
1065
+ try {
1066
+ let { url, format = '360p', upload = 'false' } = req.query;
1067
+ if (!url) return res.status(400).json({ success: false, error: "Parameter 'url' diperlukan" });
1068
+
1069
+ console.log(`\n📥 Request YouTube Download: ${url}`);
1070
+ console.log(`🎯 Format: ${format}, Upload: ${upload}`);
1071
+
1072
+ if (format.toLowerCase() === 'mp4') {
1073
+ format = '360p';
1074
+ }
1075
+
1076
+ const result = await yt.download(url, format);
1077
+ console.log(`✅ YouTube download berhasil: ${result.title}`);
1078
+
1079
+ const fileExt = result.ftype === 'mp3' ? 'mp3' : 'mp4';
1080
+ const safeFilename = result.title.replace(/[^\w\s.-]/g, '').replace(/\s+/g, '_') + '.' + fileExt;
1081
+ const filePath = path.join(downloadsDir, safeFilename);
1082
+
1083
+ console.log(`⬇️ Mendownload file dari: ${result.dlink}`);
1084
+ const response = await fetch(result.dlink);
1085
+ if (!response.ok) throw new Error("Gagal unduh file dari dlink");
1086
+
1087
+ const fileStream = fs.createWriteStream(filePath);
1088
+ await streamPipeline(response.body, fileStream);
1089
+ console.log(`💾 File tersimpan: ${safeFilename}`);
1090
+
1091
+ let uploadResult = null;
1092
+ if (upload.toLowerCase() === 'true') {
1093
+ try {
1094
+ console.log('📤 Mengupload file YouTube...');
1095
+ const fileBuffer = fs.readFileSync(filePath);
1096
+ uploadResult = await uploadFile(fileBuffer);
1097
+ console.log('✅ Upload YouTube berhasil');
1098
+ } catch (uploadError) {
1099
+ console.error('❌ Gagal mengupload file YouTube:', uploadError.message);
1100
+ }
1101
+ }
1102
+
1103
+ const responseData = {
1104
+ title: result.title,
1105
+ format: result.ftype,
1106
+ downloadURL: `${DOMAIN}/downloads/${encodeURIComponent(safeFilename)}`,
1107
+ filename: safeFilename
1108
+ };
1109
+
1110
+ if (uploadResult) {
1111
+ responseData.upload = uploadResult;
1112
+ }
1113
+
1114
+ console.log('✅ YouTube download selesai');
1115
+ return res.json(responseData);
1116
+ } catch (err) {
1117
+ console.error('❌ Error in YouTube download:', err.message);
1118
+ return res.status(500).json({ success: false, error: err.message });
1119
+ }
1120
+ });
1121
+
1122
+ app.post('/api/upload', upload.single('file'), async (req, res) => {
1123
+ try {
1124
+ if (!req.file) {
1125
+ return res.status(400).json({
1126
+ status: false,
1127
+ error: "Tidak ada file yang diunggah"
1128
+ });
1129
+ }
1130
+
1131
+ console.log('📤 Request upload file');
1132
+ const uploadResult = await uploadFile(req.file.buffer);
1133
+ return res.json(uploadResult);
1134
+ } catch (err) {
1135
+ console.error('❌ Upload error:', err.message);
1136
+ return res.status(500).json({
1137
+ status: false,
1138
+ error: err.message
1139
+ });
1140
+ }
1141
+ });
1142
+
1143
+ app.post('/api/upload-base64', async (req, res) => {
1144
+ try {
1145
+ const { file } = req.body;
1146
+ if (!file) {
1147
+ return res.status(400).json({
1148
+ status: false,
1149
+ error: "Parameter 'file' diperlukan dalam format base64"
1150
+ });
1151
+ }
1152
+
1153
+ console.log('📤 Request upload base64 file');
1154
+ const base64Data = file.replace(/^data:.+;base64,/, '');
1155
+ const fileBuffer = Buffer.from(base64Data, 'base64');
1156
+
1157
+ const uploadResult = await uploadFile(fileBuffer);
1158
+ return res.json(uploadResult);
1159
+ } catch (err) {
1160
+ console.error('❌ Upload base64 error:', err.message);
1161
+ return res.status(500).json({
1162
+ status: false,
1163
+ error: err.message
1164
+ });
1165
+ }
1166
+ });
1167
+
1168
+ app.post('/api/upload-local', async (req, res) => {
1169
+ try {
1170
+ const { filename } = req.body;
1171
+ if (!filename) {
1172
+ return res.status(400).json({
1173
+ status: false,
1174
+ error: "Parameter 'filename' diperlukan"
1175
+ });
1176
+ }
1177
+
1178
+ console.log(`📤 Request upload local file: ${filename}`);
1179
+ const filePath = path.join(downloadsDir, filename);
1180
+ if (!fs.existsSync(filePath)) {
1181
+ return res.status(404).json({
1182
+ status: false,
1183
+ error: "File tidak ditemukan"
1184
+ });
1185
+ }
1186
+
1187
+ const fileBuffer = fs.readFileSync(filePath);
1188
+ const uploadResult = await uploadFile(fileBuffer);
1189
+ return res.json(uploadResult);
1190
+ } catch (err) {
1191
+ console.error('❌ Upload local error:', err.message);
1192
+ return res.status(500).json({
1193
+ status: false,
1194
+ error: err.message
1195
+ });
1196
+ }
1197
+ });
1198
+
1199
+ // ============================
1200
+ // DOWNLOAD FOLDER ACCESS
1201
+ // ============================
1202
+
1203
+ app.get('/download/:folder/:filename', (req, res) => {
1204
+ try {
1205
+ const { folder, filename } = req.params;
1206
+ const filePath = path.join(downloadsDir, folder, filename);
1207
+
1208
+ if (!fs.existsSync(filePath)) {
1209
+ return res.status(404).json({
1210
+ success: false,
1211
+ error: "File tidak ditemukan"
1212
+ });
1213
+ }
1214
+
1215
+ res.download(filePath, filename);
1216
+ } catch (error) {
1217
+ console.error('❌ Error serving file:', error.message);
1218
+ res.status(500).json({
1219
+ success: false,
1220
+ error: error.message
1221
+ });
1222
+ }
1223
+ });
1224
+
1225
+ // ============================
1226
+ // SWAGGER & ROUTES
1227
+ // ============================
1228
+
1229
+ const swaggerDocument = require('./docs/swagger');
1230
+
1231
+ app.get('/api-docs/json', (req, res) => {
1232
+ res.setHeader('Content-Type', 'application/json');
1233
+ res.json(swaggerDocument);
1234
+ });
1235
+
1236
+ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
1237
+
1238
+ app.get('/', (req, res) => {
1239
+ res.sendFile(path.join(__dirname, 'public', 'index.html'));
1240
+ });
1241
 
1242
  app.use((req, res) => {
1243
+ res.status(404).json({ status: false, error: 'Endpoint tidak ditemukan' });
1244
+ });
1245
+
1246
+ // Cleanup expired sessions setiap 5 menit
1247
+ setInterval(() => {
1248
+ const now = new Date();
1249
+ let cleanedCount = 0;
1250
+
1251
+ for (const [sessionId, session] of sessionStorage.entries()) {
1252
+ if (new Date(session.expiresAt) < now) {
1253
+ sessionStorage.delete(sessionId);
1254
+ cleanedCount++;
1255
+ }
1256
+ }
1257
+
1258
+ if (cleanedCount > 0) {
1259
+ console.log(`🧹 Cleaned ${cleanedCount} expired sessions`);
1260
+ }
1261
+ }, 5 * 60 * 1000);
1262
+
1263
+ process.on('SIGINT', () => {
1264
+ console.log('\nServer dimatikan');
1265
+ process.exit(0);
1266
+ });
1267
 
1268
+ app.listen(PORT, () => {
1269
+ console.log("=================================");
1270
+ console.log(`🚀 Server berjalan di ${DOMAIN}`);
1271
+ console.log(`📚 Swagger UI: ${DOMAIN}/api-docs`);
1272
+ console.log(`⬇️ API Download YouTube: ${DOMAIN}/api/download`);
1273
+ console.log(`📊 API Stats: ${DOMAIN}/api/cf/stats`);
1274
+ console.log(`📤 API Upload: ${DOMAIN}/api/upload`);
1275
+ console.log(`📁 Folder Unduhan: ${DOMAIN}/downloads`);
1276
+ console.log("\n🆕 Enhanced BYCF Session Endpoints:");
1277
+ console.log(` POST ${DOMAIN}/api/cf/waf-session (dengan cookie lama)`);
1278
+ console.log(` POST ${DOMAIN}/api/cf/waf-session-with-host (dengan host khusus)`);
1279
+ console.log(` POST ${DOMAIN}/api/cf/renew-session (renew session)`);
1280
+ console.log(` GET ${DOMAIN}/api/cf/session/:id (get session by ID)`);
1281
+ console.log(` GET ${DOMAIN}/api/cf/sessions (get semua sessions)`);
1282
+ console.log(` DELETE ${DOMAIN}/api/cf/session/:id (hapus session)`);
1283
+ console.log("=================================");
1284
+ });
package.json CHANGED
@@ -1,16 +1,25 @@
1
  {
2
- "name": "cf-bypass",
3
- "version": "1.0",
4
- "description": "get the cf_clearance cookie from any website",
 
5
  "scripts": {
6
- "start": "node index.js",
7
- "dev": "nodemon index.js"
8
  },
 
 
 
 
9
  "dependencies": {
 
 
 
 
10
  "express": "^5.1.0",
11
- "puppeteer-real-browser": "^1.4.0"
12
- },
13
- "devDependencies": {
14
- "nodemon": "^3.1.10"
 
15
  }
16
  }
 
1
  {
2
+ "name": "eza",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "index.js",
6
  "scripts": {
7
+ "test": "echo \"Error: no test specified\" && exit 1"
 
8
  },
9
+ "keywords": [],
10
+ "author": "",
11
+ "license": "ISC",
12
+ "type": "commonjs",
13
  "dependencies": {
14
+ "axios": "^1.11.0",
15
+ "cheerio": "^1.1.2",
16
+ "bycf": "^1.0.3",
17
+ "cors": "^2.8.5",
18
  "express": "^5.1.0",
19
+ "file-type": "^21.0.0",
20
+ "multer": "^2.0.2",
21
+ "node-fetch": "^3.3.2",
22
+ "swagger-ui-express": "^5.0.1",
23
+ "yamljs": "^0.3.0"
24
  }
25
  }
public/index.html ADDED
@@ -0,0 +1,1040 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Social Media Downloader & Cloudflare Bypass API</title>
7
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@3.25.0/swagger-ui.css">
8
+ <style>
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
13
+ background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
14
+ color: #fff;
15
+ min-height: 100vh;
16
+ }
17
+ .header {
18
+ background-color: rgba(0, 0, 0, 0.7);
19
+ color: white;
20
+ padding: 1.5rem;
21
+ text-align: center;
22
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
23
+ border-bottom: 2px solid #4990e2;
24
+ }
25
+ .header h1 {
26
+ margin: 0;
27
+ font-size: 2.5rem;
28
+ background: linear-gradient(90deg, #4990e2, #00c4cc);
29
+ -webkit-background-clip: text;
30
+ -webkit-text-fill-color: transparent;
31
+ }
32
+ .header p {
33
+ margin: 0.5rem 0 0;
34
+ font-size: 1.2rem;
35
+ opacity: 0.9;
36
+ }
37
+ .container {
38
+ max-width: 1200px;
39
+ margin: 0 auto;
40
+ padding: 2rem;
41
+ }
42
+ .info-box {
43
+ background: rgba(255, 255, 255, 0.1);
44
+ border-radius: 8px;
45
+ padding: 1.5rem;
46
+ margin-bottom: 2rem;
47
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
48
+ border-left: 4px solid #4990e2;
49
+ backdrop-filter: blur(10px);
50
+ }
51
+ .info-box h3 {
52
+ margin-top: 0;
53
+ color: #4990e2;
54
+ }
55
+ .info-content {
56
+ display: flex;
57
+ flex-wrap: wrap;
58
+ gap: 1.5rem;
59
+ }
60
+ .info-item {
61
+ flex: 1;
62
+ min-width: 250px;
63
+ background: rgba(0, 0, 0, 0.3);
64
+ padding: 1rem;
65
+ border-radius: 6px;
66
+ }
67
+ .info-item h4 {
68
+ margin: 0 0 0.5rem;
69
+ color: #00c4cc;
70
+ }
71
+ .info-item p {
72
+ margin: 0;
73
+ font-size: 0.95rem;
74
+ }
75
+ .try-box {
76
+ background: rgba(0, 0, 0, 0.3);
77
+ border-radius: 8px;
78
+ padding: 1.5rem;
79
+ margin-bottom: 2rem;
80
+ }
81
+ .try-box h3 {
82
+ margin-top: 0;
83
+ color: #4990e2;
84
+ }
85
+ .try-form {
86
+ display: flex;
87
+ flex-wrap: wrap;
88
+ gap: 1rem;
89
+ margin-bottom: 1rem;
90
+ }
91
+ .try-form input, .try-form select, .try-form textarea {
92
+ flex: 1;
93
+ min-width: 200px;
94
+ padding: 0.8rem;
95
+ border: 1px solid #4990e2;
96
+ border-radius: 4px;
97
+ background: rgba(0, 0, 0, 0.5);
98
+ color: white;
99
+ font-family: monospace;
100
+ font-size: 0.9rem;
101
+ }
102
+ .try-form textarea {
103
+ min-height: 80px;
104
+ resize: vertical;
105
+ }
106
+ .try-form button {
107
+ padding: 0.8rem 1.5rem;
108
+ background: linear-gradient(90deg, #4990e2, #00c4cc);
109
+ border: none;
110
+ border-radius: 4px;
111
+ color: white;
112
+ font-weight: bold;
113
+ cursor: pointer;
114
+ transition: transform 0.2s;
115
+ }
116
+ .try-form button:hover {
117
+ transform: translateY(-2px);
118
+ }
119
+ .upload-form {
120
+ display: flex;
121
+ flex-direction: column;
122
+ gap: 1rem;
123
+ margin-bottom: 1rem;
124
+ }
125
+ .upload-form input[type="file"] {
126
+ padding: 0.8rem;
127
+ border: 1px solid #4990e2;
128
+ border-radius: 4px;
129
+ background: rgba(0, 0, 0, 0.5);
130
+ color: white;
131
+ }
132
+ .upload-form button {
133
+ padding: 0.8rem 1.5rem;
134
+ background: linear-gradient(90deg, #ff6b6b, #ff9e7d);
135
+ border: none;
136
+ border-radius: 4px;
137
+ color: white;
138
+ font-weight: bold;
139
+ cursor: pointer;
140
+ transition: transform 0.2s;
141
+ }
142
+ .upload-form button:hover {
143
+ transform: translateY(-2px);
144
+ }
145
+ .result-box {
146
+ background: rgba(0, 0, 0, 0.3);
147
+ border-radius: 8px;
148
+ padding: 1.5rem;
149
+ margin-top: 1.5rem;
150
+ display: none;
151
+ }
152
+ .result-box h4 {
153
+ margin-top: 0;
154
+ color: #4990e2;
155
+ }
156
+ .result-content {
157
+ background: rgba(0, 0, 0, 0.5);
158
+ padding: 1rem;
159
+ border-radius: 6px;
160
+ overflow-x: auto;
161
+ max-height: 400px;
162
+ overflow-y: auto;
163
+ }
164
+ .preview-image {
165
+ max-width: 100%;
166
+ max-height: 200px;
167
+ margin-top: 1rem;
168
+ border-radius: 4px;
169
+ display: none;
170
+ }
171
+ .swagger-ui {
172
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
173
+ border-radius: 8px;
174
+ overflow: hidden;
175
+ }
176
+ .footer {
177
+ text-align: center;
178
+ padding: 2rem;
179
+ margin-top: 2rem;
180
+ font-size: 0.9rem;
181
+ opacity: 0.7;
182
+ }
183
+ .tabs {
184
+ display: flex;
185
+ margin-bottom: 1rem;
186
+ border-bottom: 1px solid #4990e2;
187
+ flex-wrap: wrap;
188
+ }
189
+ .tab {
190
+ padding: 0.8rem 1.5rem;
191
+ cursor: pointer;
192
+ background: rgba(0, 0, 0, 0.3);
193
+ border: 1px solid transparent;
194
+ border-bottom: none;
195
+ border-top-left-radius: 6px;
196
+ border-top-right-radius: 6px;
197
+ margin-right: 0.5rem;
198
+ margin-bottom: 0.5rem;
199
+ transition: all 0.3s ease;
200
+ }
201
+ .tab.active {
202
+ background: rgba(73, 144, 226, 0.2);
203
+ border-color: #4990e2;
204
+ }
205
+ .tab-content {
206
+ display: none;
207
+ }
208
+ .tab-content.active {
209
+ display: block;
210
+ }
211
+ .social-tab {
212
+ background: linear-gradient(90deg, #ff6b6b, #ff9e7d);
213
+ }
214
+ .cloudflare-tab {
215
+ background: linear-gradient(90deg, #4990e2, #00c4cc);
216
+ }
217
+ .session-tab {
218
+ background: linear-gradient(90deg, #9b59b6, #8e44ad);
219
+ }
220
+ .form-group {
221
+ margin-bottom: 1rem;
222
+ }
223
+ .form-group label {
224
+ display: block;
225
+ margin-bottom: 0.5rem;
226
+ color: #00c4cc;
227
+ font-weight: bold;
228
+ }
229
+ .cookie-input {
230
+ font-family: monospace;
231
+ font-size: 0.8rem;
232
+ }
233
+ .session-actions {
234
+ display: flex;
235
+ gap: 0.5rem;
236
+ margin-top: 1rem;
237
+ }
238
+ .session-actions button {
239
+ flex: 1;
240
+ padding: 0.5rem 1rem;
241
+ border: none;
242
+ border-radius: 4px;
243
+ cursor: pointer;
244
+ font-size: 0.8rem;
245
+ }
246
+ .btn-primary {
247
+ background: #4990e2;
248
+ color: white;
249
+ }
250
+ .btn-success {
251
+ background: #27ae60;
252
+ color: white;
253
+ }
254
+ .btn-danger {
255
+ background: #e74c3c;
256
+ color: white;
257
+ }
258
+ .btn-warning {
259
+ background: #f39c12;
260
+ color: white;
261
+ }
262
+ .sessions-list {
263
+ max-height: 300px;
264
+ overflow-y: auto;
265
+ margin-top: 1rem;
266
+ }
267
+ .session-item {
268
+ background: rgba(0, 0, 0, 0.3);
269
+ padding: 1rem;
270
+ margin-bottom: 0.5rem;
271
+ border-radius: 4px;
272
+ border-left: 4px solid #9b59b6;
273
+ }
274
+ .session-header {
275
+ display: flex;
276
+ justify-content: between;
277
+ align-items: center;
278
+ margin-bottom: 0.5rem;
279
+ }
280
+ .session-id {
281
+ font-weight: bold;
282
+ color: #9b59b6;
283
+ }
284
+ .session-meta {
285
+ font-size: 0.8rem;
286
+ opacity: 0.7;
287
+ }
288
+ @media (max-width: 768px) {
289
+ .container {
290
+ padding: 1rem;
291
+ }
292
+ .header h1 {
293
+ font-size: 2rem;
294
+ }
295
+ .tabs {
296
+ flex-direction: column;
297
+ }
298
+ .tab {
299
+ margin-right: 0;
300
+ margin-bottom: 0.5rem;
301
+ }
302
+ .try-form {
303
+ flex-direction: column;
304
+ }
305
+ }
306
+ </style>
307
+ </head>
308
+ <body>
309
+ <div class="header">
310
+ <h1>Social Media Downloader & Cloudflare Bypass API</h1>
311
+ <p>Download video dari YouTube, Facebook, TikTok, Instagram + BYCF Session Management</p>
312
+ </div>
313
+
314
+ <div class="container">
315
+ <div class="info-box">
316
+ <h3>Informasi API</h3>
317
+ <div class="info-content">
318
+ <div class="info-item">
319
+ <h4>Domain</h4>
320
+ <p id="domain-info">Loading...</p>
321
+ </div>
322
+ <div class="info-item">
323
+ <h4>Port</h4>
324
+ <p id="port-info">7860</p>
325
+ </div>
326
+ <div class="info-item">
327
+ <h4>Platform Support</h4>
328
+ <p>YouTube, Facebook, TikTok, Instagram</p>
329
+ </div>
330
+ <div class="info-item">
331
+ <h4>BYCF Features</h4>
332
+ <p>WAF Session, Turnstile, Session Management</p>
333
+ </div>
334
+ </div>
335
+ </div>
336
+
337
+ <div class="tabs">
338
+ <div class="tab active" onclick="switchTab('download-tab')">YouTube</div>
339
+ <div class="tab" onclick="switchTab('facebook-tab')">Facebook</div>
340
+ <div class="tab social-tab" onclick="switchTab('tiktok-tab')">TikTok</div>
341
+ <div class="tab social-tab" onclick="switchTab('instagram-tab')">Instagram</div>
342
+ <div class="tab" onclick="switchTab('upload-tab')">Upload File</div>
343
+ <div class="tab cloudflare-tab" onclick="switchTab('cloudflare-tab')">Cloudflare</div>
344
+ <div class="tab session-tab" onclick="switchTab('session-tab')">Session Management</div>
345
+ </div>
346
+
347
+ <!-- YouTube Tab -->
348
+ <div id="download-tab" class="tab-content active">
349
+ <div class="try-box">
350
+ <h3>Download Video YouTube</h3>
351
+ <div class="try-form">
352
+ <input type="text" id="yt-url" placeholder="Masukkan URL YouTube" value="https://www.youtube.com/watch?v=dQw4w9WgXcQ">
353
+ <select id="format">
354
+ <option value="mp3">MP3</option>
355
+ <option value="360p">MP4 360p</option>
356
+ <option value="720p">MP4 720p</option>
357
+ <option value="1080p">MP4 1080p</option>
358
+ </select>
359
+ <select id="upload-option">
360
+ <option value="false">Hanya Download</option>
361
+ <option value="true">Download + Upload</option>
362
+ </select>
363
+ <button onclick="testDownloadApi('youtube')">Download</button>
364
+ </div>
365
+ <div class="result-box" id="download-result">
366
+ <h4>Hasil Download</h4>
367
+ <div class="result-content">
368
+ <pre id="download-result-content"></pre>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ </div>
373
+
374
+ <!-- Facebook Tab -->
375
+ <div id="facebook-tab" class="tab-content">
376
+ <div class="try-box">
377
+ <h3>Download Video Facebook</h3>
378
+ <div class="try-form">
379
+ <input type="text" id="fb-url" placeholder="Masukkan URL Facebook">
380
+ <select id="fb-quality">
381
+ <option value="720p(HD)">720p (HD)</option>
382
+ <option value="360p(SD)">360p (SD)</option>
383
+ <option value="Mp3">MP3 Audio</option>
384
+ </select>
385
+ <button onclick="testDownloadApi('facebook')">Download</button>
386
+ </div>
387
+ <div class="result-box" id="fb-result">
388
+ <h4>Hasil Download</h4>
389
+ <div class="result-content">
390
+ <pre id="fb-result-content"></pre>
391
+ </div>
392
+ </div>
393
+ </div>
394
+ </div>
395
+
396
+ <!-- TikTok Tab -->
397
+ <div id="tiktok-tab" class="tab-content">
398
+ <div class="try-box">
399
+ <h3>Download Video TikTok</h3>
400
+ <div class="try-form">
401
+ <input type="text" id="tiktok-url" placeholder="Masukkan URL TikTok">
402
+ <button onclick="testDownloadApi('tiktok')">Download</button>
403
+ </div>
404
+ <div class="result-box" id="tiktok-result">
405
+ <h4>Hasil Download</h4>
406
+ <div class="result-content">
407
+ <pre id="tiktok-result-content"></pre>
408
+ </div>
409
+ </div>
410
+ </div>
411
+ </div>
412
+
413
+ <!-- Instagram Tab -->
414
+ <div id="instagram-tab" class="tab-content">
415
+ <div class="try-box">
416
+ <h3>Download Konten Instagram</h3>
417
+ <div class="try-form">
418
+ <input type="text" id="instagram-url" placeholder="Masukkan URL Instagram">
419
+ <button onclick="testDownloadApi('instagram')">Download</button>
420
+ </div>
421
+ <div class="result-box" id="instagram-result">
422
+ <h4>Hasil Download</h4>
423
+ <div class="result-content">
424
+ <pre id="instagram-result-content"></pre>
425
+ </div>
426
+ </div>
427
+ </div>
428
+ </div>
429
+
430
+ <!-- Upload Tab -->
431
+ <div id="upload-tab" class="tab-content">
432
+ <div class="try-box">
433
+ <h3>Upload File (WebP, JPEG, PNG, dll)</h3>
434
+ <div class="upload-form">
435
+ <input type="file" id="file-input" accept=".webp,.jpg,.jpeg,.png,.gif,.mp4,.mp3">
436
+ <button onclick="uploadFile()">Upload File</button>
437
+ </div>
438
+ <div class="result-box" id="upload-result">
439
+ <h4>Hasil Upload</h4>
440
+ <div class="result-content">
441
+ <pre id="upload-result-content"></pre>
442
+ </div>
443
+ <img id="preview-image" class="preview-image" alt="Preview gambar">
444
+ </div>
445
+ </div>
446
+ </div>
447
+
448
+ <!-- Cloudflare Tab -->
449
+ <div id="cloudflare-tab" class="tab-content">
450
+ <div class="try-box">
451
+ <h3>Cloudflare Bypass Tools</h3>
452
+
453
+ <div class="form-group">
454
+ <label>WAF Session (GET)</label>
455
+ <div class="try-form">
456
+ <input type="text" id="cf-url" placeholder="Masukkan URL yang diproteksi Cloudflare">
457
+ <input type="text" id="cf-proxy" placeholder="Proxy (opsional) host:port">
458
+ <button onclick="testCFApi('waf')">Get WAF Session</button>
459
+ </div>
460
+ </div>
461
+
462
+ <div class="form-group">
463
+ <label>Turnstile CAPTCHA</label>
464
+ <div class="try-form">
465
+ <input type="text" id="cf-turnstile-url" placeholder="URL dengan Turnstile">
466
+ <input type="text" id="cf-sitekey" placeholder="Sitekey Turnstile">
467
+ <input type="text" id="cf-turnstile-proxy" placeholder="Proxy (opsional) host:port">
468
+ <button onclick="testCFApi('turnstile')">Solve Turnstile</button>
469
+ </div>
470
+ </div>
471
+
472
+ <div class="form-group">
473
+ <label>Autofaucet Bypass</label>
474
+ <div class="try-form">
475
+ <input type="text" id="cf-autofaucet-proxy" placeholder="Proxy (opsional) host:port">
476
+ <button onclick="testCFApi('autofaucet')">Bypass Autofaucet</button>
477
+ </div>
478
+ </div>
479
+
480
+ <div class="result-box" id="cf-result">
481
+ <h4>Hasil Cloudflare Bypass</h4>
482
+ <div class="result-content">
483
+ <pre id="cf-result-content"></pre>
484
+ </div>
485
+ </div>
486
+ </div>
487
+ </div>
488
+
489
+ <!-- Session Management Tab -->
490
+ <div id="session-tab" class="tab-content">
491
+ <div class="try-box">
492
+ <h3>BYCF Session Management</h3>
493
+
494
+ <!-- Create Session with Old Cookies -->
495
+ <div class="form-group">
496
+ <label>Create WAF Session dengan Cookie Lama</label>
497
+ <div class="try-form">
498
+ <input type="text" id="session-url" placeholder="URL target" value="https://www.example.com">
499
+ <input type="text" id="session-proxy" placeholder="Proxy (opsional) host:port">
500
+ </div>
501
+ <textarea id="old-cookies" placeholder='Cookie lama (JSON format): {"old_cookie": "value123", "session_id": "abc456"}' class="cookie-input"></textarea>
502
+ <div class="session-actions">
503
+ <button class="btn-primary" onclick="createSession()">Create Session</button>
504
+ <button class="btn-success" onclick="createSessionWithHost()">Create with Host</button>
505
+ </div>
506
+ </div>
507
+
508
+ <!-- Session Operations -->
509
+ <div class="form-group">
510
+ <label>Session Operations</label>
511
+ <div class="try-form">
512
+ <input type="text" id="session-id" placeholder="Session ID">
513
+ <input type="text" id="renew-url" placeholder="URL untuk renew (opsional)">
514
+ </div>
515
+ <div class="session-actions">
516
+ <button class="btn-primary" onclick="getSession()">Get Session</button>
517
+ <button class="btn-warning" onclick="renewSession()">Renew Session</button>
518
+ <button class="btn-danger" onclick="deleteSession()">Delete Session</button>
519
+ <button class="btn-success" onclick="getAllSessions()">Get All Sessions</button>
520
+ </div>
521
+ </div>
522
+
523
+ <!-- Active Sessions List -->
524
+ <div class="sessions-list" id="sessions-list">
525
+ <!-- Sessions will be populated here -->
526
+ </div>
527
+
528
+ <div class="result-box" id="session-result">
529
+ <h4>Hasil Session Management</h4>
530
+ <div class="result-content">
531
+ <pre id="session-result-content"></pre>
532
+ </div>
533
+ </div>
534
+ </div>
535
+ </div>
536
+
537
+ <!-- Swagger UI -->
538
+ <div id="swagger-ui"></div>
539
+ </div>
540
+
541
+ <div class="footer">
542
+ <p>Social Media Downloader & Cloudflare Bypass API &copy; 2024</p>
543
+ </div>
544
+
545
+ <script src="https://unpkg.com/swagger-ui-dist@3.25.0/swagger-ui-bundle.js"></script>
546
+ <script>
547
+ const currentDomain = window.location.origin;
548
+ const apiBaseUrl = currentDomain;
549
+
550
+ document.getElementById('domain-info').textContent = currentDomain;
551
+
552
+ // Tab Management
553
+ function switchTab(tabId) {
554
+ document.querySelectorAll('.tab-content').forEach(tab => {
555
+ tab.classList.remove('active');
556
+ });
557
+ document.getElementById(tabId).classList.add('active');
558
+
559
+ document.querySelectorAll('.tab').forEach(tab => {
560
+ tab.classList.remove('active');
561
+ });
562
+ event.currentTarget.classList.add('active');
563
+
564
+ // Refresh sessions list when switching to session tab
565
+ if (tabId === 'session-tab') {
566
+ getAllSessions();
567
+ }
568
+ }
569
+
570
+ // Download APIs
571
+ function testDownloadApi(platform) {
572
+ let url, endpoint, resultDiv, resultContent;
573
+
574
+ switch(platform) {
575
+ case 'youtube':
576
+ url = document.getElementById('yt-url').value;
577
+ endpoint = '/api/download';
578
+ resultDiv = document.getElementById('download-result');
579
+ resultContent = document.getElementById('download-result-content');
580
+ break;
581
+ case 'facebook':
582
+ url = document.getElementById('fb-url').value;
583
+ endpoint = '/api/fb-download';
584
+ resultDiv = document.getElementById('fb-result');
585
+ resultContent = document.getElementById('fb-result-content');
586
+ break;
587
+ case 'tiktok':
588
+ url = document.getElementById('tiktok-url').value;
589
+ endpoint = '/api/tiktok';
590
+ resultDiv = document.getElementById('tiktok-result');
591
+ resultContent = document.getElementById('tiktok-result-content');
592
+ break;
593
+ case 'instagram':
594
+ url = document.getElementById('instagram-url').value;
595
+ endpoint = '/api/instagram';
596
+ resultDiv = document.getElementById('instagram-result');
597
+ resultContent = document.getElementById('instagram-result-content');
598
+ break;
599
+ }
600
+
601
+ if (!url) {
602
+ resultContent.textContent = 'Error: URL harus diisi';
603
+ resultDiv.style.display = 'block';
604
+ return;
605
+ }
606
+
607
+ resultContent.textContent = 'Memproses download...';
608
+ resultDiv.style.display = 'block';
609
+
610
+ let apiUrl = `${apiBaseUrl}${endpoint}?url=${encodeURIComponent(url)}`;
611
+
612
+ if (platform === 'youtube') {
613
+ const format = document.getElementById('format').value;
614
+ const upload = document.getElementById('upload-option').value;
615
+ apiUrl += `&format=${format}&upload=${upload}`;
616
+ } else if (platform === 'facebook') {
617
+ const quality = document.getElementById('fb-quality').value;
618
+ apiUrl += `&quality=${quality}`;
619
+ }
620
+
621
+ fetch(apiUrl)
622
+ .then(response => response.json())
623
+ .then(data => {
624
+ resultContent.textContent = JSON.stringify(data, null, 2);
625
+
626
+ if (data.downloadURL || (data.downloadInfo && data.downloadInfo.download_url)) {
627
+ const downloadUrl = data.downloadURL || data.downloadInfo.download_url;
628
+ const downloadBtn = document.createElement('a');
629
+ downloadBtn.href = downloadUrl;
630
+ downloadBtn.target = '_blank';
631
+ downloadBtn.textContent = 'Download File';
632
+ downloadBtn.style.display = 'block';
633
+ downloadBtn.style.marginTop = '1rem';
634
+ downloadBtn.style.padding = '0.5rem 1rem';
635
+ downloadBtn.style.background = '#4990e2';
636
+ downloadBtn.style.color = 'white';
637
+ downloadBtn.style.borderRadius = '4px';
638
+ downloadBtn.style.textDecoration = 'none';
639
+
640
+ resultContent.appendChild(downloadBtn);
641
+ }
642
+ })
643
+ .catch(error => {
644
+ resultContent.textContent = 'Error: ' + error.message;
645
+ });
646
+ }
647
+
648
+ // Cloudflare APIs
649
+ function testCFApi(type) {
650
+ const resultDiv = document.getElementById('cf-result');
651
+ const resultContent = document.getElementById('cf-result-content');
652
+
653
+ let apiUrl, params = '';
654
+
655
+ switch(type) {
656
+ case 'waf':
657
+ const url = document.getElementById('cf-url').value;
658
+ const proxy = document.getElementById('cf-proxy').value;
659
+ if (!url) {
660
+ resultContent.textContent = 'Error: URL harus diisi';
661
+ resultDiv.style.display = 'block';
662
+ return;
663
+ }
664
+ apiUrl = `/api/cf/waf-session?url=${encodeURIComponent(url)}`;
665
+ if (proxy) apiUrl += `&proxy=${encodeURIComponent(proxy)}`;
666
+ break;
667
+
668
+ case 'turnstile':
669
+ const turnstileUrl = document.getElementById('cf-turnstile-url').value;
670
+ const sitekey = document.getElementById('cf-sitekey').value;
671
+ const turnstileProxy = document.getElementById('cf-turnstile-proxy').value;
672
+ if (!turnstileUrl || !sitekey) {
673
+ resultContent.textContent = 'Error: URL dan Sitekey harus diisi';
674
+ resultDiv.style.display = 'block';
675
+ return;
676
+ }
677
+ apiUrl = `/api/cf/turnstile?url=${encodeURIComponent(turnstileUrl)}&sitekey=${encodeURIComponent(sitekey)}`;
678
+ if (turnstileProxy) apiUrl += `&proxy=${encodeURIComponent(turnstileProxy)}`;
679
+ break;
680
+
681
+ case 'autofaucet':
682
+ const autofaucetProxy = document.getElementById('cf-autofaucet-proxy').value;
683
+ apiUrl = '/api/cf/autofaucet';
684
+ if (autofaucetProxy) apiUrl += `?proxy=${encodeURIComponent(autofaucetProxy)}`;
685
+ break;
686
+ }
687
+
688
+ resultContent.textContent = 'Memproses...';
689
+ resultDiv.style.display = 'block';
690
+
691
+ fetch(apiBaseUrl + apiUrl)
692
+ .then(response => response.json())
693
+ .then(data => {
694
+ resultContent.textContent = JSON.stringify(data, null, 2);
695
+ })
696
+ .catch(error => {
697
+ resultContent.textContent = 'Error: ' + error.message;
698
+ });
699
+ }
700
+
701
+ // Session Management Functions
702
+ function createSession() {
703
+ const url = document.getElementById('session-url').value;
704
+ const proxy = document.getElementById('session-proxy').value;
705
+ const cookiesInput = document.getElementById('old-cookies').value;
706
+ const resultDiv = document.getElementById('session-result');
707
+ const resultContent = document.getElementById('session-result-content');
708
+
709
+ if (!url) {
710
+ resultContent.textContent = 'Error: URL harus diisi';
711
+ resultDiv.style.display = 'block';
712
+ return;
713
+ }
714
+
715
+ let cookies = {};
716
+ if (cookiesInput) {
717
+ try {
718
+ cookies = JSON.parse(cookiesInput);
719
+ } catch (e) {
720
+ resultContent.textContent = 'Error: Format cookie tidak valid (harus JSON)';
721
+ resultDiv.style.display = 'block';
722
+ return;
723
+ }
724
+ }
725
+
726
+ resultContent.textContent = 'Membuat session...';
727
+ resultDiv.style.display = 'block';
728
+
729
+ fetch(`${apiBaseUrl}/api/cf/waf-session`, {
730
+ method: 'POST',
731
+ headers: {
732
+ 'Content-Type': 'application/json',
733
+ },
734
+ body: JSON.stringify({
735
+ url: url,
736
+ proxy: proxy,
737
+ cookies: cookies
738
+ })
739
+ })
740
+ .then(response => response.json())
741
+ .then(data => {
742
+ resultContent.textContent = JSON.stringify(data, null, 2);
743
+ if (data.success && data.sessionId) {
744
+ document.getElementById('session-id').value = data.sessionId;
745
+ getAllSessions(); // Refresh sessions list
746
+ }
747
+ })
748
+ .catch(error => {
749
+ resultContent.textContent = 'Error: ' + error.message;
750
+ });
751
+ }
752
+
753
+ function createSessionWithHost() {
754
+ const url = document.getElementById('session-url').value;
755
+ const proxy = document.getElementById('session-proxy').value;
756
+ const cookiesInput = document.getElementById('old-cookies').value;
757
+ const resultDiv = document.getElementById('session-result');
758
+ const resultContent = document.getElementById('session-result-content');
759
+
760
+ if (!url) {
761
+ resultContent.textContent = 'Error: URL harus diisi';
762
+ resultDiv.style.display = 'block';
763
+ return;
764
+ }
765
+
766
+ // Extract host from URL
767
+ const host = new URL(url).host;
768
+
769
+ let cookies = {};
770
+ if (cookiesInput) {
771
+ try {
772
+ cookies = JSON.parse(cookiesInput);
773
+ } catch (e) {
774
+ resultContent.textContent = 'Error: Format cookie tidak valid (harus JSON)';
775
+ resultDiv.style.display = 'block';
776
+ return;
777
+ }
778
+ }
779
+
780
+ resultContent.textContent = 'Membuat session dengan host...';
781
+ resultDiv.style.display = 'block';
782
+
783
+ fetch(`${apiBaseUrl}/api/cf/waf-session-with-host`, {
784
+ method: 'POST',
785
+ headers: {
786
+ 'Content-Type': 'application/json',
787
+ },
788
+ body: JSON.stringify({
789
+ url: url,
790
+ host: host,
791
+ proxy: proxy,
792
+ cookies: cookies
793
+ })
794
+ })
795
+ .then(response => response.json())
796
+ .then(data => {
797
+ resultContent.textContent = JSON.stringify(data, null, 2);
798
+ if (data.success && data.sessionId) {
799
+ document.getElementById('session-id').value = data.sessionId;
800
+ getAllSessions(); // Refresh sessions list
801
+ }
802
+ })
803
+ .catch(error => {
804
+ resultContent.textContent = 'Error: ' + error.message;
805
+ });
806
+ }
807
+
808
+ function getSession() {
809
+ const sessionId = document.getElementById('session-id').value;
810
+ const resultDiv = document.getElementById('session-result');
811
+ const resultContent = document.getElementById('session-result-content');
812
+
813
+ if (!sessionId) {
814
+ resultContent.textContent = 'Error: Session ID harus diisi';
815
+ resultDiv.style.display = 'block';
816
+ return;
817
+ }
818
+
819
+ resultContent.textContent = 'Mengambil session...';
820
+ resultDiv.style.display = 'block';
821
+
822
+ fetch(`${apiBaseUrl}/api/cf/session/${sessionId}`)
823
+ .then(response => response.json())
824
+ .then(data => {
825
+ resultContent.textContent = JSON.stringify(data, null, 2);
826
+ })
827
+ .catch(error => {
828
+ resultContent.textContent = 'Error: ' + error.message;
829
+ });
830
+ }
831
+
832
+ function renewSession() {
833
+ const sessionId = document.getElementById('session-id').value;
834
+ const renewUrl = document.getElementById('renew-url').value;
835
+ const resultDiv = document.getElementById('session-result');
836
+ const resultContent = document.getElementById('session-result-content');
837
+
838
+ if (!sessionId) {
839
+ resultContent.textContent = 'Error: Session ID harus diisi';
840
+ resultDiv.style.display = 'block';
841
+ return;
842
+ }
843
+
844
+ resultContent.textContent = 'Memperbarui session...';
845
+ resultDiv.style.display = 'block';
846
+
847
+ fetch(`${apiBaseUrl}/api/cf/renew-session`, {
848
+ method: 'POST',
849
+ headers: {
850
+ 'Content-Type': 'application/json',
851
+ },
852
+ body: JSON.stringify({
853
+ sessionId: sessionId,
854
+ url: renewUrl || 'https://www.example.com'
855
+ })
856
+ })
857
+ .then(response => response.json())
858
+ .then(data => {
859
+ resultContent.textContent = JSON.stringify(data, null, 2);
860
+ getAllSessions(); // Refresh sessions list
861
+ })
862
+ .catch(error => {
863
+ resultContent.textContent = 'Error: ' + error.message;
864
+ });
865
+ }
866
+
867
+ function deleteSession() {
868
+ const sessionId = document.getElementById('session-id').value;
869
+ const resultDiv = document.getElementById('session-result');
870
+ const resultContent = document.getElementById('session-result-content');
871
+
872
+ if (!sessionId) {
873
+ resultContent.textContent = 'Error: Session ID harus diisi';
874
+ resultDiv.style.display = 'block';
875
+ return;
876
+ }
877
+
878
+ resultContent.textContent = 'Menghapus session...';
879
+ resultDiv.style.display = 'block';
880
+
881
+ fetch(`${apiBaseUrl}/api/cf/session/${sessionId}`, {
882
+ method: 'DELETE'
883
+ })
884
+ .then(response => response.json())
885
+ .then(data => {
886
+ resultContent.textContent = JSON.stringify(data, null, 2);
887
+ getAllSessions(); // Refresh sessions list
888
+ })
889
+ .catch(error => {
890
+ resultContent.textContent = 'Error: ' + error.message;
891
+ });
892
+ }
893
+
894
+ function getAllSessions() {
895
+ const sessionsList = document.getElementById('sessions-list');
896
+ const resultDiv = document.getElementById('session-result');
897
+ const resultContent = document.getElementById('session-result-content');
898
+
899
+ sessionsList.innerHTML = '<p>Memuat sessions...</p>';
900
+
901
+ fetch(`${apiBaseUrl}/api/cf/sessions`)
902
+ .then(response => response.json())
903
+ .then(data => {
904
+ if (data.success && data.sessions && data.sessions.length > 0) {
905
+ sessionsList.innerHTML = '';
906
+ data.sessions.forEach(session => {
907
+ const sessionItem = document.createElement('div');
908
+ sessionItem.className = 'session-item';
909
+ sessionItem.innerHTML = `
910
+ <div class="session-header">
911
+ <span class="session-id">${session.id}</span>
912
+ <span class="session-meta">Usage: ${session.usageCount}</span>
913
+ </div>
914
+ <div class="session-meta">
915
+ Created: ${new Date(session.createdAt).toLocaleString()} |
916
+ Last Used: ${new Date(session.lastUsed).toLocaleString()}
917
+ </div>
918
+ <div class="session-actions">
919
+ <button class="btn-primary" onclick="setSessionId('${session.id}')">Select</button>
920
+ <button class="btn-warning" onclick="renewSpecificSession('${session.id}')">Renew</button>
921
+ <button class="btn-danger" onclick="deleteSpecificSession('${session.id}')">Delete</button>
922
+ </div>
923
+ `;
924
+ sessionsList.appendChild(sessionItem);
925
+ });
926
+ } else {
927
+ sessionsList.innerHTML = '<p>Tidak ada session aktif</p>';
928
+ }
929
+ })
930
+ .catch(error => {
931
+ sessionsList.innerHTML = '<p>Error loading sessions</p>';
932
+ });
933
+ }
934
+
935
+ function setSessionId(sessionId) {
936
+ document.getElementById('session-id').value = sessionId;
937
+ }
938
+
939
+ function renewSpecificSession(sessionId) {
940
+ document.getElementById('session-id').value = sessionId;
941
+ renewSession();
942
+ }
943
+
944
+ function deleteSpecificSession(sessionId) {
945
+ document.getElementById('session-id').value = sessionId;
946
+ deleteSession();
947
+ }
948
+
949
+ // Upload Function
950
+ function uploadFile() {
951
+ const fileInput = document.getElementById('file-input');
952
+ const resultDiv = document.getElementById('upload-result');
953
+ const resultContent = document.getElementById('upload-result-content');
954
+ const previewImage = document.getElementById('preview-image');
955
+
956
+ if (!fileInput.files || fileInput.files.length === 0) {
957
+ resultContent.textContent = 'Error: Pilih file terlebih dahulu';
958
+ resultDiv.style.display = 'block';
959
+ return;
960
+ }
961
+
962
+ const file = fileInput.files[0];
963
+ resultContent.textContent = 'Mengupload file...';
964
+ resultDiv.style.display = 'block';
965
+ previewImage.style.display = 'none';
966
+
967
+ if (file.type.startsWith('image/')) {
968
+ const reader = new FileReader();
969
+ reader.onload = function(e) {
970
+ previewImage.src = e.target.result;
971
+ previewImage.style.display = 'block';
972
+ };
973
+ reader.readAsDataURL(file);
974
+ }
975
+
976
+ const formData = new FormData();
977
+ formData.append('file', file);
978
+
979
+ fetch(`${apiBaseUrl}/api/upload`, {
980
+ method: 'POST',
981
+ body: formData
982
+ })
983
+ .then(response => response.json())
984
+ .then(data => {
985
+ resultContent.textContent = JSON.stringify(data, null, 2);
986
+
987
+ if (data.path) {
988
+ const link = document.createElement('a');
989
+ link.href = data.path;
990
+ link.target = '_blank';
991
+ link.textContent = 'Buka File';
992
+ link.style.display = 'block';
993
+ link.style.marginTop = '1rem';
994
+ link.style.padding = '0.5rem 1rem';
995
+ link.style.background = '#ff6b6b';
996
+ link.style.color = 'white';
997
+ link.style.borderRadius = '4px';
998
+ link.style.textDecoration = 'none';
999
+
1000
+ resultContent.appendChild(link);
1001
+ }
1002
+ })
1003
+ .catch(error => {
1004
+ resultContent.textContent = 'Error: ' + error.message;
1005
+ });
1006
+ }
1007
+
1008
+ // Initialize Swagger UI
1009
+ setTimeout(() => {
1010
+ try {
1011
+ const ui = SwaggerUIBundle({
1012
+ url: `${apiBaseUrl}/api-docs/json`,
1013
+ dom_id: '#swagger-ui',
1014
+ presets: [
1015
+ SwaggerUIBundle.presets.apis,
1016
+ SwaggerUIBundle.presets.standalone
1017
+ ],
1018
+ layout: "BaseLayout"
1019
+ });
1020
+ } catch (error) {
1021
+ console.error('Error loading Swagger UI:', error);
1022
+ document.getElementById('swagger-ui').innerHTML = `
1023
+ <div style="padding: 20px; color: white; text-align: center;">
1024
+ <h3>Swagger UI tidak dapat dimuat</h3>
1025
+ <p>Pastikan server berjalan dan endpoint /api-docs/json dapat diakses</p>
1026
+ <a href="${apiBaseUrl}/api-docs/json" target="_blank" style="color: #4990e2;">Cek endpoint spec</a>
1027
+ </div>
1028
+ `;
1029
+ }
1030
+ }, 1000);
1031
+
1032
+ // Load sessions on page load if on session tab
1033
+ window.addEventListener('load', function() {
1034
+ if (window.location.hash === '#session-tab') {
1035
+ switchTab('session-tab');
1036
+ }
1037
+ });
1038
+ </script>
1039
+ </body>
1040
+ </html>
scrape/instagramdl.js ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const cheerio = require("cheerio");
2
+ const Form = require("form-data");
3
+ const axios = require("axios");
4
+
5
+ function extractInnerHtml(data) {
6
+ try {
7
+ return data
8
+ .split('getElementById("download-section").innerHTML = "')[1]
9
+ .split('"; document.getElementById("inputData").remove(); ')[0]
10
+ .replace(/\\(\\)?/g, "");
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ function decode(encoded) {
17
+ try {
18
+ const karakter = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/".split("");
19
+ const [h, u, n, t, e, r] = encoded;
20
+ function decodeInner(d, e, f) {
21
+ const h = karakter.slice(0, e);
22
+ const i = karakter.slice(0, f);
23
+ let j = d
24
+ .split("")
25
+ .reverse()
26
+ .reduce((a, b, c) => (h.indexOf(b) !== -1 ? a + h.indexOf(b) * Math.pow(e, c) : a), 0);
27
+ let k = "";
28
+ while (j > 0) {
29
+ k = i[j % f] + k;
30
+ j = Math.floor(j / f);
31
+ }
32
+ return k || "0";
33
+ }
34
+ let hasil = "";
35
+ for (let i = 0, len = h.length; i < len; i++) {
36
+ let s = "";
37
+ while (h[i] !== n[e]) {
38
+ s += h[i];
39
+ i++;
40
+ }
41
+ for (let j = 0; j < n.length; j++) {
42
+ s = s.replace(new RegExp(n[j], "g"), j.toString());
43
+ }
44
+ hasil += String.fromCharCode(decodeInner(s, e, 10) - t);
45
+ }
46
+ return decodeURIComponent(encodeURIComponent(hasil));
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+
52
+ function getType(url) {
53
+ return ["/p/", "/reel"].some((a) => url.includes(a)) ? "post" : "profile";
54
+ }
55
+
56
+ async function getUserInfo(username) {
57
+ try {
58
+ const res = await axios.get(`https://api-ig.storiesig.info/api/userInfoByUsername/${username}`, {
59
+ headers: { Referer: "https://storiesig.info/" },
60
+ });
61
+ return res.data?.result?.user || {};
62
+ } catch {
63
+ return {};
64
+ }
65
+ }
66
+
67
+ function getUsername(url) {
68
+ try {
69
+ const potongan = url.split("://")[1].split("/")[1];
70
+ return potongan || null;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ async function instagram(q) {
77
+ try {
78
+ const kontenInstagram = await (
79
+ await fetch(q, {
80
+ headers: {
81
+ accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
82
+ "accept-language": "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7",
83
+ "cache-control": "no-cache",
84
+ dpr: "3",
85
+ pragma: "no-cache",
86
+ "sec-ch-prefers-color-scheme": "dark",
87
+ "sec-ch-ua": '"Not-A.Brand";v="99", "Chromium";v="124"',
88
+ "sec-ch-ua-full-version-list": '"Not-A.Brand";v="99.0.0.0", "Chromium";v="124.0.6327.4"',
89
+ "sec-ch-ua-mobile": "?1",
90
+ "sec-ch-ua-model": '"Infinix X6882"',
91
+ "sec-ch-ua-platform": '"Android"',
92
+ "sec-ch-ua-platform-version": '"14.0.0"',
93
+ "sec-fetch-dest": "document",
94
+ "sec-fetch-mode": "navigate",
95
+ "sec-fetch-site": "same-origin",
96
+ "sec-fetch-user": "?1",
97
+ "upgrade-insecure-requests": "1",
98
+ "viewport-width": "980",
99
+ cookie: "csrftoken=Ugye7fapneCzmN74O5UL0t; dpr=2.5506443977355957; mid=Z3PyVwABAAHxEqZRd-JfL7cYG2Ch; ig_did=A2C375D0-66A4-461E-8947-0AEB57237C37; ig_nrcb=1; datr=V_JzZ0dn4ZO1qbuoVydc7Gcp; wd=423x804",
100
+ },
101
+ referrerPolicy: "strict-origin-when-cross-origin",
102
+ body: null,
103
+ method: "GET",
104
+ })
105
+ ).text();
106
+
107
+ const form = new Form();
108
+ form.append("url", q);
109
+
110
+ const response = await axios({
111
+ url: "https://snapsave.app/id/action.php?lang=id",
112
+ method: "POST",
113
+ data: form,
114
+ });
115
+
116
+ const data = response.data;
117
+ const isEncodedHtml = data.includes("decodeURIComponent(escape(r))}(");
118
+ const encodedHtml = isEncodedHtml
119
+ ? data
120
+ .split("decodeURIComponent(escape(r))}(")[1]
121
+ .split("))")[0]
122
+ .split(",")
123
+ .map((v) => v.replace(/"/g, "").trim())
124
+ : null;
125
+
126
+ const decoded = isEncodedHtml ? extractInnerHtml(decode(encodedHtml)) : data.data;
127
+ const html = isEncodedHtml ? decoded : data;
128
+ if (!html) throw new Error("Tidak ada konten HTML yang ditemukan");
129
+
130
+ const $ = cheerio.load(html);
131
+ const $$ = cheerio.load(kontenInstagram);
132
+ const konten = [];
133
+
134
+ const metaDescription = $$('meta[name="description"]').attr("content");
135
+ const likesMatch = metaDescription?.match(/(\d+)\s+likes/);
136
+ const commentsMatch = metaDescription?.match(/(\d+)\s+comments/);
137
+
138
+ const likes = likesMatch ? likesMatch[1] : "N/A";
139
+ const comments = commentsMatch ? commentsMatch[1] : "N/A";
140
+
141
+ const imageUrl = $$('meta[name="twitter:image"]').attr("content");
142
+ const postUrl = $$('link[rel="alternate"][hreflang="x-default"]').attr("href");
143
+ const description = $$('meta[property="og:description"]').attr("content");
144
+ const postTimePattern = /on\s([^:]+):/;
145
+ const titlePattern = /:\s"([^"]+)"/;
146
+ const type = getType(q);
147
+ const postTimeMatch = description ? description.match(postTimePattern) : null;
148
+ const titleMatch = description ? description.match(titlePattern) : null;
149
+ const postingTime = postTimeMatch ? postTimeMatch[1] : null;
150
+ const title = titleMatch ? titleMatch[1] : null;
151
+ const accountName = getUsername(q) || description?.split("comments - ")[1]?.split(" on")[0];
152
+ const userInfo = accountName ? await getUserInfo(accountName) : {};
153
+ let avatarUrl = "";
154
+
155
+ $(".download-items").each((i, element) => {
156
+ const thumbElement = $(element).find(".download-items__thumb img");
157
+ const type = thumbElement
158
+ .siblings(".format-icon")
159
+ .find("i")
160
+ .hasClass("icon-dlvideo")
161
+ ? "video"
162
+ : "image";
163
+ const thumbnail =
164
+ $(element).find(".download-items__thumb img").attr("data-src") ||
165
+ $(element).find(".download-items__thumb img").attr("src");
166
+ const url = $(element).find(".download-items__btn a").attr("href");
167
+ let av =
168
+ $(element).find('.download-items__btn a[title="Download Avatar"]').attr("href") ||
169
+ $(element).find('.download-items__btn a[title="Unduh Avatar"]').attr("href");
170
+ if (av) {
171
+ avatarUrl = av;
172
+ return;
173
+ }
174
+ if (url) {
175
+ konten.push({ type, thumbnail, url });
176
+ }
177
+ });
178
+
179
+ return {
180
+ type,
181
+ title,
182
+ likes,
183
+ comments,
184
+ accountName,
185
+ avatarUrl,
186
+ imageUrl,
187
+ postUrl,
188
+ postingTime,
189
+ userInfo,
190
+ konten,
191
+ };
192
+ } catch {
193
+ return null;
194
+ }
195
+ }
196
+
197
+ module.exports = instagram;
scrape/tiktok.js ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const axios = require("axios");
2
+
3
+ function extractJSONObject(str, startIndex) {
4
+ let openBraces = 0;
5
+ let inString = false;
6
+ let escapeNext = false;
7
+ let endIndex = startIndex;
8
+ for (; endIndex < str.length; endIndex++) {
9
+ const char = str[endIndex];
10
+ if (escapeNext) {
11
+ escapeNext = false;
12
+ continue;
13
+ }
14
+ if (char === "\\") {
15
+ escapeNext = true;
16
+ continue;
17
+ }
18
+ if (char === "\"") {
19
+ inString = !inString;
20
+ }
21
+ if (!inString) {
22
+ if (char === "{") openBraces++;
23
+ else if (char === "}") {
24
+ openBraces--;
25
+ if (openBraces === 0) {
26
+ return str.substring(startIndex, endIndex + 1);
27
+ }
28
+ }
29
+ }
30
+ }
31
+ throw new Error("Gagal menemukan akhir JSON.");
32
+ }
33
+
34
+ async function resolveTikTokUrl(url) {
35
+ try {
36
+ const response = await axios.get(url, {
37
+ maxRedirects: 0,
38
+ validateStatus: status => status >= 300 && status < 400,
39
+ });
40
+ if (response.headers.location) {
41
+ return new URL(response.headers.location, url).href;
42
+ }
43
+ } catch (err) {
44
+ if (err.response && err.response.status === 302 && err.response.headers.location) {
45
+ return err.response.headers.location;
46
+ }
47
+ }
48
+ const response = await axios.get(url, {
49
+ maxRedirects: 20,
50
+ validateStatus: () => true
51
+ });
52
+ return response.request.res.responseUrl || url;
53
+ }
54
+
55
+ function formatOutput(title, data) {
56
+ return `\n=== ${title} ===\n${JSON.stringify(data, null, 2)}`;
57
+ }
58
+
59
+ const COMMON_HEADERS = {
60
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
61
+ "Accept-Language": "en-US,en;q=0.9",
62
+ "Accept-Encoding": "gzip, deflate, br",
63
+ "Connection": "keep-alive",
64
+ "Sec-Fetch-Dest": "document",
65
+ "Sec-Fetch-Mode": "navigate",
66
+ "Sec-Fetch-Site": "none",
67
+ "Sec-Fetch-User": "?1",
68
+ "Upgrade-Insecure-Requests": "1",
69
+ "Cache-Control": "max-age=0",
70
+ };
71
+
72
+ async function getTikTokHtml(url) {
73
+ const resolvedUrl = await resolveTikTokUrl(url);
74
+ const isVideo = resolvedUrl.includes('/video/');
75
+ const isSlide = resolvedUrl.includes('/photo/') || resolvedUrl.includes('/image/');
76
+ const headers = {
77
+ ...COMMON_HEADERS,
78
+ "User-Agent": isVideo
79
+ ? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"
80
+ : "Mozilla/5.0 (Linux; Android 14; CPH2641) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.7049.111 Mobile Safari/537.36",
81
+ "Referer": "https://www.tiktok.com/",
82
+ };
83
+ const response = await axios.get(resolvedUrl, {
84
+ headers,
85
+ validateStatus: () => true
86
+ });
87
+ return {
88
+ html: response.data,
89
+ isVideo,
90
+ isSlide,
91
+ resolvedUrl
92
+ };
93
+ }
94
+
95
+ async function extractTikTokData(url) {
96
+ try {
97
+ const { html, isVideo, isSlide, resolvedUrl } = await getTikTokHtml(url);
98
+ let jsonKey, result;
99
+ if (isVideo) {
100
+ jsonKey = "\"webapp.video-detail\":";
101
+ const index = html.indexOf(jsonKey);
102
+ if (index === -1) throw new Error("Data video tidak ditemukan.");
103
+ const start = html.indexOf("{", index + jsonKey.length);
104
+ const jsonStr = extractJSONObject(html, start);
105
+ const jsonData = JSON.parse(jsonStr);
106
+ const item = jsonData?.itemInfo?.itemStruct;
107
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
108
+ result = {
109
+ type: "video",
110
+ url: resolvedUrl,
111
+ description: item.desc,
112
+ createTime: item.createTime,
113
+ video: item.video,
114
+ author: item.author,
115
+ music: item.music,
116
+ stats: item.stats,
117
+ };
118
+ console.log(formatOutput("Video TikTok", result));
119
+ } else if (isSlide) {
120
+ jsonKey = "\"webapp.reflow.video.detail\":";
121
+ const index = html.indexOf(jsonKey);
122
+ if (index === -1) throw new Error("Data slide tidak ditemukan.");
123
+ const start = html.indexOf("{", index + jsonKey.length);
124
+ const jsonStr = extractJSONObject(html, start);
125
+ const jsonData = JSON.parse(jsonStr);
126
+ const item = jsonData?.itemInfo?.itemStruct;
127
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
128
+ result = {
129
+ type: "slide",
130
+ url: resolvedUrl,
131
+ description: item.desc,
132
+ createTime: item.createTime,
133
+ images: item.imagePost?.images?.map(img => ({
134
+ url: img.imageURL?.urlList?.[0] || null,
135
+ width: img.imageWidth,
136
+ height: img.imageHeight,
137
+ })),
138
+ author: item.author,
139
+ music: item.music,
140
+ stats: item.stats,
141
+ };
142
+ console.log(formatOutput("Slide TikTok", result));
143
+ } else {
144
+ const videoIndex = html.indexOf("\"webapp.video-detail\":");
145
+ const slideIndex = html.indexOf("\"webapp.reflow.video.detail\":");
146
+ if (videoIndex !== -1) {
147
+ jsonKey = "\"webapp.video-detail\":";
148
+ const start = html.indexOf("{", videoIndex + jsonKey.length);
149
+ const jsonStr = extractJSONObject(html, start);
150
+ const jsonData = JSON.parse(jsonStr);
151
+ const item = jsonData?.itemInfo?.itemStruct;
152
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
153
+ result = {
154
+ type: "video",
155
+ url: resolvedUrl,
156
+ description: item.desc,
157
+ createTime: item.createTime,
158
+ video: item.video,
159
+ author: item.author,
160
+ music: item.music,
161
+ stats: item.stats,
162
+ };
163
+ console.log(formatOutput("Video TikTok", result));
164
+ } else if (slideIndex !== -1) {
165
+ jsonKey = "\"webapp.reflow.video.detail\":";
166
+ const start = html.indexOf("{", slideIndex + jsonKey.length);
167
+ const jsonStr = extractJSONObject(html, start);
168
+ const jsonData = JSON.parse(jsonStr);
169
+ const item = jsonData?.itemInfo?.itemStruct;
170
+ if (!item) throw new Error("ItemStruct tidak ditemukan.");
171
+ result = {
172
+ type: "slide",
173
+ url: resolvedUrl,
174
+ description: item.desc,
175
+ createTime: item.createTime,
176
+ images: item.imagePost?.images?.map(img => ({
177
+ url: img.imageURL?.urlList?.[0] || null,
178
+ width: img.imageWidth,
179
+ height: img.imageHeight,
180
+ })),
181
+ author: item.author,
182
+ music: item.music,
183
+ stats: item.stats,
184
+ };
185
+ console.log(formatOutput("Slide TikTok", result));
186
+ } else {
187
+ throw new Error("URL tidak dikenali sebagai video atau slide TikTok dan tidak ditemukan data di HTML.");
188
+ }
189
+ }
190
+ return result;
191
+ } catch (err) {
192
+ console.error("❌ Gagal mengambil data:", err.message);
193
+ throw err;
194
+ }
195
+ }
196
+
197
+ module.exports = extractTikTokData;
scrape/tiktokdl.js ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // extractAndDownloadTikTok.js
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const axios = require('axios');
5
+ const { pipeline } = require('stream/promises');
6
+ const extractTikTokData = require('../scrape/tiktok'); // ✅ tanpa .cjs, otomatis kebaca
7
+
8
+ const DOWNLOAD_FOLDER = path.join(__dirname, '../downloads');
9
+
10
+ if (!fs.existsSync(DOWNLOAD_FOLDER)) {
11
+ fs.mkdirSync(DOWNLOAD_FOLDER, { recursive: true });
12
+ }
13
+
14
+ async function downloadMedia(url, outputPath, type = 'file') {
15
+ console.log(`Downloading ${type} from: ${url}`);
16
+
17
+ const response = await axios({
18
+ method: "GET",
19
+ url,
20
+ responseType: "stream",
21
+ headers: {
22
+ "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",
23
+ "Referer": "https://www.tiktok.com/",
24
+ "Origin": "https://www.tiktok.com",
25
+ "Accept": "*/*",
26
+ "Accept-Encoding": "identity"
27
+ },
28
+ timeout: 60000
29
+ });
30
+
31
+ if (response.status !== 200) {
32
+ throw new Error(`Failed to download ${type}: Status ${response.status}`);
33
+ }
34
+
35
+ const writer = fs.createWriteStream(outputPath);
36
+ await pipeline(response.data, writer);
37
+
38
+ if (!fs.existsSync(outputPath)) {
39
+ throw new Error(`File ${type} not created`);
40
+ }
41
+
42
+ const stats = fs.statSync(outputPath);
43
+ if (stats.size === 0) {
44
+ fs.unlinkSync(outputPath);
45
+ throw new Error(`File ${type} is empty (0 bytes)`);
46
+ }
47
+
48
+ return stats;
49
+ }
50
+
51
+ function getVideoUrl(videoData) {
52
+ const bitrate = videoData?.bitrateInfo?.[0];
53
+ const uri = bitrate?.PlayAddr?.Uri || videoData?.uri;
54
+ const urlList = bitrate?.PlayAddr?.UrlList || videoData?.playAddr?.UrlList || [];
55
+ const finalUrl = urlList.find(url => url.includes(uri)) || urlList[0];
56
+
57
+ if (!finalUrl) throw new Error("Video URL not found");
58
+ return finalUrl;
59
+ }
60
+
61
+ async function extractAndDownloadTikTok(url, domain) {
62
+ try {
63
+ const data = await extractTikTokData(url);
64
+ const { author, stats, video, music, type, description, images, resolvedUrl } = data;
65
+
66
+ const folderName = `tiktok_${Date.now()}_${Math.random().toString(36).substring(2, 8)}`;
67
+ const downloadFolder = path.join(DOWNLOAD_FOLDER, folderName);
68
+ fs.mkdirSync(downloadFolder, { recursive: true });
69
+
70
+ const caption = `🔹 Username: ${author.uniqueId}\n` +
71
+ `🔹 Nickname: ${author.nickname}\n` +
72
+ `🔹 Description: ${description}\n` +
73
+ `❤️ Likes: ${stats.diggCount} | ` +
74
+ `💬 Comments: ${stats.commentCount} | ` +
75
+ `🔄 Shares: ${stats.shareCount}\n` +
76
+ `🎵 Music: ${music?.title || "-"}`;
77
+
78
+ if (type === "video") {
79
+ const videoUrl = getVideoUrl(video);
80
+ const videoFilename = `video.mp4`;
81
+ const videoPath = path.join(downloadFolder, videoFilename);
82
+
83
+ await downloadMedia(videoUrl, videoPath, 'video');
84
+
85
+ let musicInfo = null;
86
+ if (music?.playUrl) {
87
+ try {
88
+ const musicFilename = `music.mp3`;
89
+ const musicPath = path.join(downloadFolder, musicFilename);
90
+ await downloadMedia(music.playUrl, musicPath, 'music');
91
+ musicInfo = {
92
+ filename: musicFilename,
93
+ filePath: musicPath,
94
+ download_url: `${domain}/download/${folderName}/${musicFilename}`
95
+ };
96
+ } catch (err) {
97
+ console.error('Failed to download music:', err);
98
+ }
99
+ }
100
+
101
+ return {
102
+ success: true,
103
+ type: "video",
104
+ originalUrl: url,
105
+ resolvedUrl,
106
+ description,
107
+ createTime: data.createTime,
108
+ author,
109
+ music,
110
+ stats,
111
+ downloadInfo: {
112
+ success: true,
113
+ filename: videoFilename,
114
+ filePath: videoPath,
115
+ download_url: `${domain}/download/${folderName}/${videoFilename}`,
116
+ music: musicInfo,
117
+ folder_url: `${domain}/download/${folderName}`
118
+ },
119
+ caption
120
+ };
121
+ }
122
+ else if (type === "slide") {
123
+ const downloadResults = [];
124
+
125
+ for (let i = 0; i < images.length; i++) {
126
+ try {
127
+ const img = images[i];
128
+ const imgFilename = `slide_${i+1}.jpg`;
129
+ const imgPath = path.join(downloadFolder, imgFilename);
130
+
131
+ await downloadMedia(img.url, imgPath, 'image');
132
+
133
+ downloadResults.push({
134
+ success: true,
135
+ index: i+1,
136
+ filename: imgFilename,
137
+ filePath: imgPath,
138
+ download_url: `${domain}/download/${folderName}/${imgFilename}`
139
+ });
140
+ } catch (err) {
141
+ downloadResults.push({
142
+ success: false,
143
+ index: i+1,
144
+ error: err.message
145
+ });
146
+ }
147
+ }
148
+
149
+ let musicInfo = null;
150
+ if (music?.playUrl) {
151
+ try {
152
+ const musicFilename = `music.mp3`;
153
+ const musicPath = path.join(downloadFolder, musicFilename);
154
+ await downloadMedia(music.playUrl, musicPath, 'music');
155
+ musicInfo = {
156
+ filename: musicFilename,
157
+ filePath: musicPath,
158
+ download_url: `${domain}/download/${folderName}/${musicFilename}`
159
+ };
160
+ } catch (err) {
161
+ console.error('Failed to download music:', err);
162
+ }
163
+ }
164
+
165
+ return {
166
+ success: true,
167
+ type: "slide",
168
+ originalUrl: url,
169
+ resolvedUrl,
170
+ description,
171
+ createTime: data.createTime,
172
+ author,
173
+ music,
174
+ stats,
175
+ images: downloadResults,
176
+ musicInfo,
177
+ folder_url: `${domain}/download/${folderName}`,
178
+ caption
179
+ };
180
+ }
181
+ } catch (error) {
182
+ console.error('Error in TikTok download:', error);
183
+ return {
184
+ success: false,
185
+ error: error.message,
186
+ details: error.stack
187
+ };
188
+ }
189
+ }
190
+
191
+ module.exports = extractAndDownloadTikTok;