fourmovie commited on
Commit
3484365
·
1 Parent(s): ff96182
Files changed (5) hide show
  1. Dockerfile +4 -2
  2. docs/swagger.js +669 -0
  3. index.js +277 -311
  4. package.json +1 -0
  5. public/index.html +203 -32
Dockerfile CHANGED
@@ -13,10 +13,12 @@ RUN python3 -m pip install --upgrade pip && \
13
  spotdl \
14
  yt-dlp
15
 
16
- RUN mkdir -p /app/downloads && \
17
  mkdir -p /app/public && \
18
  chmod 777 -R /app/downloads && \
19
- chmod 777 -R /app/public
 
 
20
 
21
  WORKDIR /app
22
 
 
13
  spotdl \
14
  yt-dlp
15
 
16
+ RUN mkdir -p /app/downloads && \ mkdir -p /app/docs && \ mkdir -p /app/scrape && \
17
  mkdir -p /app/public && \
18
  chmod 777 -R /app/downloads && \
19
+ chmod 777 -R /app/public && \
20
+ chmod 777 -R /app/docs && \
21
+ chmod 777 -R /app/scrape
22
 
23
  WORKDIR /app
24
 
docs/swagger.js ADDED
@@ -0,0 +1,669 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const DOMAIN = process.env.DOMAIN || `https://rezaharis-ytdl.hf.space`;
2
+
3
+ const swaggerDocument = {
4
+ openapi: "3.0.0",
5
+ info: {
6
+ title: "YouTube & Facebook Downloader & File Upload API",
7
+ version: "1.0.0",
8
+ description: "API untuk mengunduh video YouTube dan Facebook sebagai MP3 atau MP4, mengupload file, dan bypass Cloudflare protection"
9
+ },
10
+ servers: [
11
+ {
12
+ url: DOMAIN,
13
+ description: "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
+ "/api/cf/waf-session": {
391
+ get: {
392
+ summary: "Get WAF Session",
393
+ description: "Mendapatkan WAF session untuk bypass Cloudflare protection",
394
+ parameters: [
395
+ {
396
+ name: "url",
397
+ in: "query",
398
+ required: true,
399
+ schema: {
400
+ type: "string"
401
+ },
402
+ description: "URL target yang diproteksi Cloudflare"
403
+ },
404
+ {
405
+ name: "proxy",
406
+ in: "query",
407
+ required: false,
408
+ schema: {
409
+ type: "string"
410
+ },
411
+ description: "Proxy format: host:port"
412
+ }
413
+ ],
414
+ responses: {
415
+ "200": {
416
+ description: "Berhasil",
417
+ content: {
418
+ "application/json": {
419
+ schema: {
420
+ type: "object",
421
+ properties: {
422
+ success: {
423
+ type: "boolean",
424
+ example: true
425
+ },
426
+ url: {
427
+ type: "string",
428
+ example: "https://example.com"
429
+ },
430
+ cookies: {
431
+ type: "string",
432
+ example: "cf_clearance=abc123; __cflb=xyz456"
433
+ },
434
+ userAgent: {
435
+ type: "string",
436
+ example: "Mozilla/5.0..."
437
+ },
438
+ headers: {
439
+ type: "object"
440
+ },
441
+ timestamp: {
442
+ type: "string",
443
+ example: "2024-01-01T00:00:00.000Z"
444
+ }
445
+ }
446
+ }
447
+ }
448
+ }
449
+ }
450
+ }
451
+ }
452
+ },
453
+ "/api/cf/turnstile": {
454
+ get: {
455
+ summary: "Solve Turnstile CAPTCHA",
456
+ description: "Menyelesaikan Cloudflare Turnstile CAPTCHA",
457
+ parameters: [
458
+ {
459
+ name: "url",
460
+ in: "query",
461
+ required: true,
462
+ schema: {
463
+ type: "string"
464
+ },
465
+ description: "URL target yang memiliki Turnstile"
466
+ },
467
+ {
468
+ name: "sitekey",
469
+ in: "query",
470
+ required: true,
471
+ schema: {
472
+ type: "string"
473
+ },
474
+ description: "Sitekey Turnstile"
475
+ },
476
+ {
477
+ name: "proxy",
478
+ in: "query",
479
+ required: false,
480
+ schema: {
481
+ type: "string"
482
+ },
483
+ description: "Proxy format: host:port"
484
+ }
485
+ ],
486
+ responses: {
487
+ "200": {
488
+ description: "Berhasil",
489
+ content: {
490
+ "application/json": {
491
+ schema: {
492
+ type: "object",
493
+ properties: {
494
+ success: {
495
+ type: "boolean",
496
+ example: true
497
+ },
498
+ url: {
499
+ type: "string",
500
+ example: "https://example.com"
501
+ },
502
+ sitekey: {
503
+ type: "string",
504
+ example: "0x4AAAAAAABBBBB"
505
+ },
506
+ turnstileToken: {
507
+ type: "string",
508
+ example: "0.xabc123def456"
509
+ },
510
+ timestamp: {
511
+ type: "string",
512
+ example: "2024-01-01T00:00:00.000Z"
513
+ }
514
+ }
515
+ }
516
+ }
517
+ }
518
+ }
519
+ }
520
+ }
521
+ },
522
+ "/api/cf/autofaucet": {
523
+ get: {
524
+ summary: "Autofaucet Bypass",
525
+ description: "Bypass Cloudflare WAF dan Turnstile untuk autofaucet.org",
526
+ parameters: [
527
+ {
528
+ name: "proxy",
529
+ in: "query",
530
+ required: false,
531
+ schema: {
532
+ type: "string"
533
+ },
534
+ description: "Proxy format: host:port"
535
+ }
536
+ ],
537
+ responses: {
538
+ "200": {
539
+ description: "Berhasil",
540
+ content: {
541
+ "application/json": {
542
+ schema: {
543
+ type: "object",
544
+ properties: {
545
+ success: {
546
+ type: "boolean",
547
+ example: true
548
+ },
549
+ url: {
550
+ type: "string",
551
+ example: "https://autofaucet.org/earn/faucet"
552
+ },
553
+ sitekey: {
554
+ type: "string",
555
+ example: "0x4AAAAAAAeegevyhnJu7zGA"
556
+ },
557
+ wafSession: {
558
+ type: "object"
559
+ },
560
+ turnstileToken: {
561
+ type: "string",
562
+ example: "0.xabc123def456"
563
+ },
564
+ timestamp: {
565
+ type: "string",
566
+ example: "2024-01-01T00:00:00.000Z"
567
+ }
568
+ }
569
+ }
570
+ }
571
+ }
572
+ }
573
+ }
574
+ }
575
+ },
576
+ "/api/cf/stats": {
577
+ get: {
578
+ summary: "Get BYCF Stats",
579
+ description: "Mendapatkan statistik penggunaan bycf",
580
+ responses: {
581
+ "200": {
582
+ description: "Berhasil",
583
+ content: {
584
+ "application/json": {
585
+ schema: {
586
+ type: "object",
587
+ properties: {
588
+ success: {
589
+ type: "boolean",
590
+ example: true
591
+ },
592
+ stats: {
593
+ type: "object"
594
+ },
595
+ timestamp: {
596
+ type: "string",
597
+ example: "2024-01-01T00:00:00.000Z"
598
+ }
599
+ }
600
+ }
601
+ }
602
+ }
603
+ }
604
+ }
605
+ }
606
+ },
607
+ "/api/upload": {
608
+ post: {
609
+ summary: "Upload file (Form Data)",
610
+ description: "Mengupload file ke server eksternal menggunakan form-data",
611
+ requestBody: {
612
+ required: true,
613
+ content: {
614
+ "multipart/form-data": {
615
+ schema: {
616
+ type: "object",
617
+ properties: {
618
+ file: {
619
+ type: "string",
620
+ format: "binary",
621
+ description: "File yang akan diupload"
622
+ }
623
+ }
624
+ }
625
+ }
626
+ }
627
+ },
628
+ responses: {
629
+ "200": {
630
+ description: "File berhasil diupload",
631
+ content: {
632
+ "application/json": {
633
+ schema: {
634
+ type: "object",
635
+ properties: {
636
+ status: {
637
+ type: "boolean",
638
+ example: true
639
+ },
640
+ path: {
641
+ type: "string",
642
+ example: "https://c.termai.cc/jpeg/T0鳍p"
643
+ },
644
+ mimetype: {
645
+ type: "string",
646
+ example: "image/jpeg"
647
+ },
648
+ size: {
649
+ type: "number",
650
+ example: 171062
651
+ }
652
+ }
653
+ }
654
+ }
655
+ }
656
+ },
657
+ "400": {
658
+ description: "Bad Request - Tidak ada file yang diunggah"
659
+ },
660
+ "500": {
661
+ description: "Internal Server Error - Terjadi kesalahan server"
662
+ }
663
+ }
664
+ }
665
+ }
666
+ }
667
+ };
668
+
669
+ module.exports = swaggerDocument;
index.js CHANGED
@@ -12,6 +12,7 @@ const fileType = require('file-type');
12
  const streamPipeline = promisify(pipeline);
13
  const multer = require('multer');
14
  const cheerio = require('cheerio');
 
15
 
16
  const app = express();
17
  const PORT = 7860;
@@ -36,13 +37,11 @@ app.use('/downloads', express.static(downloadsDir));
36
  const UPLOAD_KEY = process.env.UPLOAD_KEY || "AIzaBj7z2z3xBjsk";
37
  const UPLOAD_DOMAIN = process.env.UPLOAD_DOMAIN || 'https://c.termai.cc';
38
 
39
- // Function untuk delay dengan logging
40
  const delay = (ms, reason = '') => {
41
  console.log(`⏳ Delay ${ms}ms ${reason ? `- ${reason}` : ''}`);
42
  return new Promise(resolve => setTimeout(resolve, ms));
43
  };
44
 
45
- // Random User-Agent
46
  const getRandomUserAgent = () => {
47
  const agents = [
48
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
@@ -213,7 +212,6 @@ async function downloadAndSaveFile(url, filename, quality) {
213
  }
214
  }
215
 
216
- // YouTube Downloader dengan delay saja
217
  const yt = {
218
  get baseUrl() {
219
  return { origin: 'https://ssvid.app' };
@@ -246,7 +244,6 @@ const yt = {
246
  result = searchJson.links?.mp3?.mp3128?.k;
247
  if (!result) {
248
  console.log('❌ MP3 tidak ditemukan, mencoba alternatif...');
249
- // Coba format MP3 lainnya
250
  const mp3Formats = searchJson.links?.mp3;
251
  if (mp3Formats) {
252
  const availableMp3 = Object.keys(mp3Formats);
@@ -291,7 +288,6 @@ const yt = {
291
  try {
292
  console.log(`🌐 Request ke: ${path}`);
293
 
294
- // Delay random antara request
295
  await delay(3000 + Math.random() * 4000, `Request ke ${path}`);
296
 
297
  const body = new URLSearchParams(payload);
@@ -323,7 +319,6 @@ const yt = {
323
 
324
  this.validateFormat(userFormat);
325
 
326
- // Delay awal untuk menghindari rate limiting
327
  await delay(4000, 'Delay awal sebelum search');
328
 
329
  let search = await this.hit('/api/ajax/search?hl=en', {
@@ -345,7 +340,6 @@ const yt = {
345
  const videoUrl = 'https://www.youtube.com/watch?v=' + v;
346
  console.log(`📹 Video ditemukan:\n Judul: ${t}\n URL: ${videoUrl}`);
347
 
348
- // Delay sebelum request detail video
349
  await delay(3000, 'Sebelum request detail video');
350
 
351
  search = await this.hit('/api/ajax/search?hl=en', {
@@ -372,7 +366,6 @@ const yt = {
372
  console.log('⏳ File sedang diproses, menunggu...');
373
  let convert2;
374
 
375
- // Tanpa limit, hanya delay saja
376
  while (true) {
377
  console.log(`🔄 Checking status...`);
378
 
@@ -388,7 +381,6 @@ const yt = {
388
  return convert2;
389
  }
390
 
391
- // Delay antara pengecekan status
392
  await delay(5000, 'Menunggu proses convert');
393
  }
394
  } else {
@@ -398,7 +390,247 @@ const yt = {
398
  }
399
  };
400
 
401
- // API Routes
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  app.get('/api/fb-download', async (req, res) => {
403
  try {
404
  let { url, quality = '720p(HD)', upload = 'false' } = req.query;
@@ -543,8 +775,6 @@ app.get('/api/download', async (req, res) => {
543
  }
544
  });
545
 
546
- // ... (upload endpoints dan swagger documentation tetap sama seperti sebelumnya)
547
-
548
  app.post('/api/upload', upload.single('file'), async (req, res) => {
549
  try {
550
  if (!req.file) {
@@ -622,307 +852,37 @@ app.post('/api/upload-local', async (req, res) => {
622
  }
623
  });
624
 
625
- // ... (swagger documentation dan bagian akhir kode tetap sama)
 
 
626
 
627
- const swaggerDocument = {
628
- openapi: "3.0.0",
629
- info: {
630
- title: "YouTube & Facebook Downloader & File Upload API",
631
- version: "1.0.0",
632
- description: "API untuk mengunduh video YouTube dan Facebook sebagai MP3 atau MP4 dan mengupload file"
633
- },
634
- servers: [
635
- {
636
- url: DOMAIN,
637
- description: "Server"
638
- }
639
- ],
640
- paths: {
641
- "/api/download": {
642
- get: {
643
- summary: "Unduh video YouTube",
644
- description: "Mengunduh video YouTube dan menyimpannya ke server, dengan opsi upload otomatis",
645
- parameters: [
646
- {
647
- name: "url",
648
- in: "query",
649
- required: true,
650
- schema: {
651
- type: "string"
652
- },
653
- description: "URL video YouTube atau kata kunci"
654
- },
655
- {
656
- name: "format",
657
- in: "query",
658
- required: false,
659
- schema: {
660
- type: "string",
661
- enum: ["mp3", "360p", "720p", "1080p"],
662
- default: "360p"
663
- },
664
- description: "Format (mp3, 360p, 720p, 1080p)"
665
- },
666
- {
667
- name: "upload",
668
- in: "query",
669
- required: false,
670
- schema: {
671
- type: "string",
672
- enum: ["true", "false"],
673
- default: "false"
674
- },
675
- description: "Jika 'true', file akan diupload otomatis ke server eksternal setelah didownload"
676
- }
677
- ],
678
- responses: {
679
- "200": {
680
- description: "Berhasil",
681
- content: {
682
- "application/json": {
683
- schema: {
684
- type: "object",
685
- properties: {
686
- title: {
687
- type: "string",
688
- example: "Judul Video YouTube"
689
- },
690
- format: {
691
- type: "string",
692
- example: "mp3"
693
- },
694
- downloadURL: {
695
- type: "string",
696
- example: "https://example.com/downloads/video.mp3"
697
- },
698
- filename: {
699
- type: "string",
700
- example: "video.mp3"
701
- },
702
- upload: {
703
- type: "object",
704
- properties: {
705
- status: {
706
- type: "boolean",
707
- example: true
708
- },
709
- path: {
710
- type: "string",
711
- example: "https://c.termai.cc/mp3/abc123"
712
- },
713
- mimetype: {
714
- type: "string",
715
- example: "audio/mpeg"
716
- },
717
- size: {
718
- type: "number",
719
- example: 5123456
720
- }
721
- }
722
- }
723
- }
724
- }
725
- }
726
- }
727
- },
728
- "400": {
729
- description: "Bad Request - Parameter tidak valid"
730
- },
731
- "500": {
732
- description: "Internal Server Error - Terjadi kesalahan server"
733
- }
734
- }
735
- }
736
- },
737
- "/api/fb-download": {
738
- get: {
739
- summary: "Unduh video Facebook",
740
- description: "Mengunduh video Facebook dalam kualitas tertentu dan menyimpannya ke server, dengan opsi upload otomatis",
741
- parameters: [
742
- {
743
- name: "url",
744
- in: "query",
745
- required: true,
746
- schema: {
747
- type: "string"
748
- },
749
- description: "URL video Facebook"
750
- },
751
- {
752
- name: "quality",
753
- in: "query",
754
- required: false,
755
- schema: {
756
- type: "string",
757
- enum: ["720p(HD)", "360p(SD)", "Mp3"],
758
- default: "720p(HD)"
759
- },
760
- description: "Kualitas video (720p(HD), 360p(SD), Mp3)"
761
- },
762
- {
763
- name: "upload",
764
- in: "query",
765
- required: false,
766
- schema: {
767
- type: "string",
768
- enum: ["true", "false"],
769
- default: "false"
770
- },
771
- description: "Jika 'true', file akan diupload otomatis ke server eksternal setelah didownload"
772
- }
773
- ],
774
- responses: {
775
- "200": {
776
- description: "Berhasil",
777
- content: {
778
- "application/json": {
779
- schema: {
780
- type: "object",
781
- properties: {
782
- title: {
783
- type: "string",
784
- example: "Judul Video Facebook"
785
- },
786
- thumbnail: {
787
- type: "string",
788
- example: "https://example.com/thumbnail.jpg"
789
- },
790
- selectedQuality: {
791
- type: "string",
792
- example: "720p(HD)"
793
- },
794
- type: {
795
- type: "string",
796
- example: "video"
797
- },
798
- downloadURL: {
799
- type: "string",
800
- example: "https://example.com/downloads/video_720p(HD).mp4"
801
- },
802
- filename: {
803
- type: "string",
804
- example: "video_720p(HD).mp4"
805
- },
806
- size: {
807
- type: "number",
808
- example: 15123456
809
- },
810
- upload: {
811
- type: "object",
812
- properties: {
813
- status: {
814
- type: "boolean",
815
- example: true
816
- },
817
- path: {
818
- type: "string",
819
- example: "https://c.termai.cc/mp4/abc123"
820
- },
821
- mimetype: {
822
- type: "string",
823
- example: "video/mp4"
824
- },
825
- size: {
826
- type: "number",
827
- example: 15123456
828
- }
829
- }
830
- },
831
- availableQualities: {
832
- type: "array",
833
- items: {
834
- type: "object",
835
- properties: {
836
- quality: {
837
- type: "string",
838
- example: "720p(HD)"
839
- },
840
- type: {
841
- type: "string",
842
- example: "video"
843
- },
844
- url: {
845
- type: "string",
846
- example: "https://example.com/api/fb-download?url=...&quality=720p(HD)"
847
- }
848
- }
849
- }
850
- }
851
- }
852
- }
853
- }
854
- }
855
- },
856
- "400": {
857
- description: "Bad Request - Parameter tidak valid"
858
- },
859
- "500": {
860
- description: "Internal Server Error - Terjadi kesalahan server"
861
- }
862
- }
863
- }
864
- },
865
- "/api/upload": {
866
- post: {
867
- summary: "Upload file (Form Data)",
868
- description: "Mengupload file ke server eksternal menggunakan form-data",
869
- requestBody: {
870
- required: true,
871
- content: {
872
- "multipart/form-data": {
873
- schema: {
874
- type: "object",
875
- properties: {
876
- file: {
877
- type: "string",
878
- format: "binary",
879
- description: "File yang akan diupload"
880
- }
881
- }
882
- }
883
- }
884
- }
885
- },
886
- responses: {
887
- "200": {
888
- description: "File berhasil diupload",
889
- content: {
890
- "application/json": {
891
- schema: {
892
- type: "object",
893
- properties: {
894
- status: {
895
- type: "boolean",
896
- example: true
897
- },
898
- path: {
899
- type: "string",
900
- example: "https://c.termai.cc/jpeg/T0鳍p"
901
- },
902
- mimetype: {
903
- type: "string",
904
- example: "image/jpeg"
905
- },
906
- size: {
907
- type: "number",
908
- example: 171062
909
- }
910
- }
911
- }
912
- }
913
- }
914
- },
915
- "400": {
916
- description: "Bad Request - Tidak ada file yang diunggah"
917
- },
918
- "500": {
919
- description: "Internal Server Error - Terjadi kesalahan server"
920
- }
921
- }
922
- }
923
- }
924
  }
925
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
 
927
  app.get('/api-docs/json', (req, res) => {
928
  res.setHeader('Content-Type', 'application/json');
@@ -950,6 +910,12 @@ app.listen(PORT, () => {
950
  console.log(`📚 Swagger UI: ${DOMAIN}/api-docs`);
951
  console.log(`⬇️ API Download YouTube: ${DOMAIN}/api/download`);
952
  console.log(`⬇️ API Download Facebook: ${DOMAIN}/api/fb-download`);
 
 
 
 
 
 
953
  console.log(`📤 API Upload: ${DOMAIN}/api/upload`);
954
  console.log(`📁 Folder Unduhan: ${DOMAIN}/downloads`);
955
  console.log("=================================");
 
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;
 
37
  const UPLOAD_KEY = process.env.UPLOAD_KEY || "AIzaBj7z2z3xBjsk";
38
  const UPLOAD_DOMAIN = process.env.UPLOAD_DOMAIN || 'https://c.termai.cc';
39
 
 
40
  const delay = (ms, reason = '') => {
41
  console.log(`⏳ Delay ${ms}ms ${reason ? `- ${reason}` : ''}`);
42
  return new Promise(resolve => setTimeout(resolve, ms));
43
  };
44
 
 
45
  const getRandomUserAgent = () => {
46
  const agents = [
47
  'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
 
212
  }
213
  }
214
 
 
215
  const yt = {
216
  get baseUrl() {
217
  return { origin: 'https://ssvid.app' };
 
244
  result = searchJson.links?.mp3?.mp3128?.k;
245
  if (!result) {
246
  console.log('❌ MP3 tidak ditemukan, mencoba alternatif...');
 
247
  const mp3Formats = searchJson.links?.mp3;
248
  if (mp3Formats) {
249
  const availableMp3 = Object.keys(mp3Formats);
 
288
  try {
289
  console.log(`🌐 Request ke: ${path}`);
290
 
 
291
  await delay(3000 + Math.random() * 4000, `Request ke ${path}`);
292
 
293
  const body = new URLSearchParams(payload);
 
319
 
320
  this.validateFormat(userFormat);
321
 
 
322
  await delay(4000, 'Delay awal sebelum search');
323
 
324
  let search = await this.hit('/api/ajax/search?hl=en', {
 
340
  const videoUrl = 'https://www.youtube.com/watch?v=' + v;
341
  console.log(`📹 Video ditemukan:\n Judul: ${t}\n URL: ${videoUrl}`);
342
 
 
343
  await delay(3000, 'Sebelum request detail video');
344
 
345
  search = await this.hit('/api/ajax/search?hl=en', {
 
366
  console.log('⏳ File sedang diproses, menunggu...');
367
  let convert2;
368
 
 
369
  while (true) {
370
  console.log(`🔄 Checking status...`);
371
 
 
381
  return convert2;
382
  }
383
 
 
384
  await delay(5000, 'Menunggu proses convert');
385
  }
386
  } else {
 
390
  }
391
  };
392
 
393
+ // ============================
394
+ // BYCF CLOUDFLARE BYPASS APIs
395
+ // ============================
396
+
397
+ app.get('/api/cf/waf-session', async (req, res) => {
398
+ try {
399
+ const { url, proxy } = req.query;
400
+
401
+ if (!url) {
402
+ return res.status(400).json({
403
+ success: false,
404
+ error: "Parameter 'url' diperlukan"
405
+ });
406
+ }
407
+
408
+ console.log(`🛡️ Request WAF Session untuk: ${url}`);
409
+
410
+ const session = await bycf.shannz.wafSession(
411
+ url,
412
+ proxy || "host:port"
413
+ );
414
+
415
+ console.log('✅ WAF Session berhasil didapatkan');
416
+
417
+ res.json({
418
+ success: true,
419
+ url: url,
420
+ cookies: session.cookies,
421
+ userAgent: session.headers['User-Agent'],
422
+ headers: session.headers,
423
+ timestamp: new Date().toISOString()
424
+ });
425
+
426
+ } catch (error) {
427
+ console.error('❌ Error getting WAF session:', error.message);
428
+ res.status(500).json({
429
+ success: false,
430
+ error: error.message
431
+ });
432
+ }
433
+ });
434
+
435
+ app.get('/api/cf/turnstile', async (req, res) => {
436
+ try {
437
+ const { url, sitekey, proxy } = req.query;
438
+
439
+ if (!url || !sitekey) {
440
+ return res.status(400).json({
441
+ success: false,
442
+ error: "Parameter 'url' dan 'sitekey' diperlukan"
443
+ });
444
+ }
445
+
446
+ console.log(`🔄 Request Turnstile Token untuk: ${url}`);
447
+
448
+ const token = await bycf.shannz.turnstileMin(
449
+ url,
450
+ sitekey,
451
+ proxy || "host:port"
452
+ );
453
+
454
+ console.log('✅ Turnstile Token berhasil didapatkan');
455
+
456
+ res.json({
457
+ success: true,
458
+ url: url,
459
+ sitekey: sitekey,
460
+ turnstileToken: token,
461
+ timestamp: new Date().toISOString()
462
+ });
463
+
464
+ } catch (error) {
465
+ console.error('❌ Error getting Turnstile token:', error.message);
466
+ res.status(500).json({
467
+ success: false,
468
+ error: error.message
469
+ });
470
+ }
471
+ });
472
+
473
+ app.get('/api/cf/autofaucet', async (req, res) => {
474
+ try {
475
+ const { proxy } = req.query;
476
+ const url = "https://autofaucet.org/earn/faucet";
477
+ const sitekey = "0x4AAAAAAAeegevyhnJu7zGA";
478
+
479
+ console.log(`🎯 Request Autofaucet Bypass`);
480
+
481
+ const session = await bycf.shannz.wafSession(
482
+ url,
483
+ proxy || "host:port"
484
+ );
485
+
486
+ const token = await bycf.shannz.turnstileMin(
487
+ url,
488
+ sitekey,
489
+ proxy || "host:port"
490
+ );
491
+
492
+ console.log('✅ Autofaucet bypass berhasil');
493
+
494
+ res.json({
495
+ success: true,
496
+ url: url,
497
+ sitekey: sitekey,
498
+ wafSession: {
499
+ cookies: session.cookies,
500
+ userAgent: session.headers['User-Agent'],
501
+ headers: session.headers
502
+ },
503
+ turnstileToken: token,
504
+ timestamp: new Date().toISOString()
505
+ });
506
+
507
+ } catch (error) {
508
+ console.error('❌ Error autofaucet bypass:', error.message);
509
+ res.status(500).json({
510
+ success: false,
511
+ error: error.message
512
+ });
513
+ }
514
+ });
515
+
516
+ app.get('/api/cf/stats', async (req, res) => {
517
+ try {
518
+ console.log('📊 Request bycf stats');
519
+
520
+ const stats = await bycf.shannz.stats();
521
+
522
+ res.json({
523
+ success: true,
524
+ stats: stats,
525
+ timestamp: new Date().toISOString()
526
+ });
527
+
528
+ } catch (error) {
529
+ console.error('❌ Error getting stats:', error.message);
530
+ res.status(500).json({
531
+ success: false,
532
+ error: error.message
533
+ });
534
+ }
535
+ });
536
+
537
+ // ============================
538
+ // TIKTOK & INSTAGRAM APIs
539
+ // ============================
540
+
541
+ app.get('/api/tiktok', async (req, res) => {
542
+ try {
543
+ const { url } = req.query;
544
+
545
+ if (!url) {
546
+ return res.status(400).json({
547
+ success: false,
548
+ error: "Parameter 'url' diperlukan"
549
+ });
550
+ }
551
+
552
+ if (!url.includes('tiktok.com')) {
553
+ return res.status(400).json({
554
+ success: false,
555
+ error: "URL harus dari TikTok"
556
+ });
557
+ }
558
+
559
+ console.log(`🎵 Request TikTok Download: ${url}`);
560
+
561
+ // Import dinamis untuk ESM module
562
+ const { default: tiktokDl } = await import('./scrape/tiktokdl.js');
563
+ const result = await tiktokDl(url, DOMAIN);
564
+
565
+ if (!result.success) {
566
+ return res.status(500).json({
567
+ success: false,
568
+ error: result.error
569
+ });
570
+ }
571
+
572
+ console.log('✅ TikTok download berhasil');
573
+ res.json(result);
574
+
575
+ } catch (error) {
576
+ console.error('❌ Error in TikTok download:', error.message);
577
+ res.status(500).json({
578
+ success: false,
579
+ error: error.message
580
+ });
581
+ }
582
+ });
583
+
584
+ app.get('/api/instagram', async (req, res) => {
585
+ try {
586
+ const { url } = req.query;
587
+
588
+ if (!url) {
589
+ return res.status(400).json({
590
+ success: false,
591
+ error: "Parameter 'url' diperlukan"
592
+ });
593
+ }
594
+
595
+ if (!url.includes('instagram.com')) {
596
+ return res.status(400).json({
597
+ success: false,
598
+ error: "URL harus dari Instagram"
599
+ });
600
+ }
601
+
602
+ console.log(`📷 Request Instagram Download: ${url}`);
603
+
604
+ // Import dinamis untuk ESM module
605
+ const { default: instagramDl } = await import('./scrape/instagram.js');
606
+ const result = await instagramDl(url);
607
+
608
+ if (!result) {
609
+ return res.status(500).json({
610
+ success: false,
611
+ error: "Gagal mengambil data Instagram"
612
+ });
613
+ }
614
+
615
+ console.log('✅ Instagram download berhasil');
616
+ res.json({
617
+ success: true,
618
+ ...result
619
+ });
620
+
621
+ } catch (error) {
622
+ console.error('❌ Error in Instagram download:', error.message);
623
+ res.status(500).json({
624
+ success: false,
625
+ error: error.message
626
+ });
627
+ }
628
+ });
629
+
630
+ // ============================
631
+ // EXISTING APIs
632
+ // ============================
633
+
634
  app.get('/api/fb-download', async (req, res) => {
635
  try {
636
  let { url, quality = '720p(HD)', upload = 'false' } = req.query;
 
775
  }
776
  });
777
 
 
 
778
  app.post('/api/upload', upload.single('file'), async (req, res) => {
779
  try {
780
  if (!req.file) {
 
852
  }
853
  });
854
 
855
+ // ============================
856
+ // DOWNLOAD FOLDER ACCESS
857
+ // ============================
858
 
859
+ app.get('/download/:folder/:filename', (req, res) => {
860
+ try {
861
+ const { folder, filename } = req.params;
862
+ const filePath = path.join(downloadsDir, folder, filename);
863
+
864
+ if (!fs.existsSync(filePath)) {
865
+ return res.status(404).json({
866
+ success: false,
867
+ error: "File tidak ditemukan"
868
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
  }
870
+
871
+ res.download(filePath, filename);
872
+ } catch (error) {
873
+ console.error('❌ Error serving file:', error.message);
874
+ res.status(500).json({
875
+ success: false,
876
+ error: error.message
877
+ });
878
+ }
879
+ });
880
+
881
+ // ============================
882
+ // SWAGGER & ROUTES
883
+ // ============================
884
+
885
+ const swaggerDocument = require('./docs/swagger');
886
 
887
  app.get('/api-docs/json', (req, res) => {
888
  res.setHeader('Content-Type', 'application/json');
 
910
  console.log(`📚 Swagger UI: ${DOMAIN}/api-docs`);
911
  console.log(`⬇️ API Download YouTube: ${DOMAIN}/api/download`);
912
  console.log(`⬇️ API Download Facebook: ${DOMAIN}/api/fb-download`);
913
+ console.log(`🎵 API Download TikTok: ${DOMAIN}/api/tiktok`);
914
+ console.log(`📷 API Download Instagram: ${DOMAIN}/api/instagram`);
915
+ console.log(`🛡️ API Cloudflare Bypass: ${DOMAIN}/api/cf/waf-session`);
916
+ console.log(`🔄 API Turnstile: ${DOMAIN}/api/cf/turnstile`);
917
+ console.log(`🎯 API Autofaucet: ${DOMAIN}/api/cf/autofaucet`);
918
+ console.log(`📊 API Stats: ${DOMAIN}/api/cf/stats`);
919
  console.log(`📤 API Upload: ${DOMAIN}/api/upload`);
920
  console.log(`📁 Folder Unduhan: ${DOMAIN}/downloads`);
921
  console.log("=================================");
package.json CHANGED
@@ -13,6 +13,7 @@
13
  "dependencies": {
14
  "axios": "^1.11.0",
15
  "cheerio": "^1.1.2",
 
16
  "cors": "^2.8.5",
17
  "express": "^5.1.0",
18
  "file-type": "^21.0.0",
 
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",
public/index.html CHANGED
@@ -176,6 +176,7 @@
176
  display: flex;
177
  margin-bottom: 1rem;
178
  border-bottom: 1px solid #4990e2;
 
179
  }
180
  .tab {
181
  padding: 0.8rem 1.5rem;
@@ -186,6 +187,7 @@
186
  border-top-left-radius: 6px;
187
  border-top-right-radius: 6px;
188
  margin-right: 0.5rem;
 
189
  }
190
  .tab.active {
191
  background: rgba(73, 144, 226, 0.2);
@@ -197,6 +199,12 @@
197
  .tab-content.active {
198
  display: block;
199
  }
 
 
 
 
 
 
200
  @media (max-width: 768px) {
201
  .container {
202
  padding: 1rem;
@@ -204,13 +212,20 @@
204
  .header h1 {
205
  font-size: 2rem;
206
  }
 
 
 
 
 
 
 
207
  }
208
  </style>
209
  </head>
210
  <body>
211
  <div class="header">
212
- <h1>YouTube Downloader & Upload API</h1>
213
- <p>API untuk mengunduh video YouTube dan mengupload file (WebP, JPEG, PNG, dll)</p>
214
  </div>
215
 
216
  <div class="container">
@@ -226,8 +241,8 @@
226
  <p id="port-info">7860</p>
227
  </div>
228
  <div class="info-item">
229
- <h4>Format Download</h4>
230
- <p>MP3, MP4 (360p, 720p, 1080p)</p>
231
  </div>
232
  <div class="info-item">
233
  <h4>Format Upload</h4>
@@ -237,8 +252,12 @@
237
  </div>
238
 
239
  <div class="tabs">
240
- <div class="tab active" onclick="switchTab('download-tab')">Download YouTube</div>
 
 
 
241
  <div class="tab" onclick="switchTab('upload-tab')">Upload File</div>
 
242
  </div>
243
 
244
  <div id="download-tab" class="tab-content active">
@@ -256,7 +275,7 @@
256
  <option value="false">Hanya Download</option>
257
  <option value="true">Download + Upload</option>
258
  </select>
259
- <button onclick="testDownloadApi()">Download</button>
260
  </div>
261
  <div class="result-box" id="download-result">
262
  <h4>Hasil Download</h4>
@@ -267,6 +286,59 @@
267
  </div>
268
  </div>
269
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
270
  <div id="upload-tab" class="tab-content">
271
  <div class="try-box">
272
  <h3>Upload File (WebP, JPEG, PNG, dll)</h3>
@@ -284,49 +356,91 @@
284
  </div>
285
  </div>
286
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  <div id="swagger-ui"></div>
288
  </div>
289
 
290
  <div class="footer">
291
- <p>YouTube Downloader & Upload API &copy; 2023</p>
292
  </div>
293
 
294
  <script src="https://unpkg.com/swagger-ui-dist@3.25.0/swagger-ui-bundle.js"></script>
295
  <script>
296
- // Ambil domain secara otomatis dari URL saat ini
297
  const currentDomain = window.location.origin;
298
  const apiBaseUrl = currentDomain;
299
 
300
- // Update info domain di halaman
301
  document.getElementById('domain-info').textContent = currentDomain;
302
 
303
- // Fungsi untuk beralih tab
304
  function switchTab(tabId) {
305
- // Sembunyikan semua tab
306
  document.querySelectorAll('.tab-content').forEach(tab => {
307
  tab.classList.remove('active');
308
  });
309
-
310
- // Tampilkan tab yang dipilih
311
  document.getElementById(tabId).classList.add('active');
312
 
313
- // Update status tab
314
  document.querySelectorAll('.tab').forEach(tab => {
315
  tab.classList.remove('active');
316
  });
317
  event.currentTarget.classList.add('active');
318
  }
319
 
320
- // Fungsi untuk menguji API download
321
- function testDownloadApi() {
322
- const url = document.getElementById('yt-url').value;
323
- const format = document.getElementById('format').value;
324
- const upload = document.getElementById('upload-option').value;
325
- const resultDiv = document.getElementById('download-result');
326
- const resultContent = document.getElementById('download-result-content');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
 
328
  if (!url) {
329
- resultContent.textContent = 'Error: URL YouTube harus diisi';
330
  resultDiv.style.display = 'block';
331
  return;
332
  }
@@ -334,15 +448,26 @@
334
  resultContent.textContent = 'Memproses download...';
335
  resultDiv.style.display = 'block';
336
 
337
- fetch(`${apiBaseUrl}/api/download?url=${encodeURIComponent(url)}&format=${format}&upload=${upload}`)
 
 
 
 
 
 
 
 
 
 
 
338
  .then(response => response.json())
339
  .then(data => {
340
  resultContent.textContent = JSON.stringify(data, null, 2);
341
 
342
- // Jika ada link download, buat tombol download
343
- if (data.downloadURL) {
344
  const downloadBtn = document.createElement('a');
345
- downloadBtn.href = data.downloadURL;
346
  downloadBtn.target = '_blank';
347
  downloadBtn.textContent = 'Download File';
348
  downloadBtn.style.display = 'block';
@@ -361,7 +486,58 @@
361
  });
362
  }
363
 
364
- // Fungsi untuk upload file
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
  function uploadFile() {
366
  const fileInput = document.getElementById('file-input');
367
  const resultDiv = document.getElementById('upload-result');
@@ -379,7 +555,6 @@
379
  resultDiv.style.display = 'block';
380
  previewImage.style.display = 'none';
381
 
382
- // Tampilkan preview untuk gambar
383
  if (file.type.startsWith('image/')) {
384
  const reader = new FileReader();
385
  reader.onload = function(e) {
@@ -389,11 +564,9 @@
389
  reader.readAsDataURL(file);
390
  }
391
 
392
- // Buat FormData untuk upload
393
  const formData = new FormData();
394
  formData.append('file', file);
395
 
396
- // Kirim request upload
397
  fetch(`${apiBaseUrl}/api/upload`, {
398
  method: 'POST',
399
  body: formData
@@ -402,7 +575,6 @@
402
  .then(data => {
403
  resultContent.textContent = JSON.stringify(data, null, 2);
404
 
405
- // Jika upload berhasil dan ada URL, buat link
406
  if (data.path) {
407
  const link = document.createElement('a');
408
  link.href = data.path;
@@ -424,7 +596,6 @@
424
  });
425
  }
426
 
427
- // Inisialisasi Swagger UI dengan timeout untuk memastikan DOM sudah siap
428
  setTimeout(() => {
429
  try {
430
  const ui = SwaggerUIBundle({
 
176
  display: flex;
177
  margin-bottom: 1rem;
178
  border-bottom: 1px solid #4990e2;
179
+ flex-wrap: wrap;
180
  }
181
  .tab {
182
  padding: 0.8rem 1.5rem;
 
187
  border-top-left-radius: 6px;
188
  border-top-right-radius: 6px;
189
  margin-right: 0.5rem;
190
+ margin-bottom: 0.5rem;
191
  }
192
  .tab.active {
193
  background: rgba(73, 144, 226, 0.2);
 
199
  .tab-content.active {
200
  display: block;
201
  }
202
+ .social-tab {
203
+ background: linear-gradient(90deg, #ff6b6b, #ff9e7d);
204
+ }
205
+ .cloudflare-tab {
206
+ background: linear-gradient(90deg, #4990e2, #00c4cc);
207
+ }
208
  @media (max-width: 768px) {
209
  .container {
210
  padding: 1rem;
 
212
  .header h1 {
213
  font-size: 2rem;
214
  }
215
+ .tabs {
216
+ flex-direction: column;
217
+ }
218
+ .tab {
219
+ margin-right: 0;
220
+ margin-bottom: 0.5rem;
221
+ }
222
  }
223
  </style>
224
  </head>
225
  <body>
226
  <div class="header">
227
+ <h1>Social Media Downloader API</h1>
228
+ <p>API untuk mengunduh video dari YouTube, Facebook, TikTok, Instagram dan bypass Cloudflare</p>
229
  </div>
230
 
231
  <div class="container">
 
241
  <p id="port-info">7860</p>
242
  </div>
243
  <div class="info-item">
244
+ <h4>Platform Support</h4>
245
+ <p>YouTube, Facebook, TikTok, Instagram</p>
246
  </div>
247
  <div class="info-item">
248
  <h4>Format Upload</h4>
 
252
  </div>
253
 
254
  <div class="tabs">
255
+ <div class="tab active" onclick="switchTab('download-tab')">YouTube</div>
256
+ <div class="tab" onclick="switchTab('facebook-tab')">Facebook</div>
257
+ <div class="tab social-tab" onclick="switchTab('tiktok-tab')">TikTok</div>
258
+ <div class="tab social-tab" onclick="switchTab('instagram-tab')">Instagram</div>
259
  <div class="tab" onclick="switchTab('upload-tab')">Upload File</div>
260
+ <div class="tab cloudflare-tab" onclick="switchTab('cloudflare-tab')">Cloudflare</div>
261
  </div>
262
 
263
  <div id="download-tab" class="tab-content active">
 
275
  <option value="false">Hanya Download</option>
276
  <option value="true">Download + Upload</option>
277
  </select>
278
+ <button onclick="testDownloadApi('youtube')">Download</button>
279
  </div>
280
  <div class="result-box" id="download-result">
281
  <h4>Hasil Download</h4>
 
286
  </div>
287
  </div>
288
 
289
+ <div id="facebook-tab" class="tab-content">
290
+ <div class="try-box">
291
+ <h3>Download Video Facebook</h3>
292
+ <div class="try-form">
293
+ <input type="text" id="fb-url" placeholder="Masukkan URL Facebook">
294
+ <select id="fb-quality">
295
+ <option value="720p(HD)">720p (HD)</option>
296
+ <option value="360p(SD)">360p (SD)</option>
297
+ <option value="Mp3">MP3 Audio</option>
298
+ </select>
299
+ <button onclick="testDownloadApi('facebook')">Download</button>
300
+ </div>
301
+ <div class="result-box" id="fb-result">
302
+ <h4>Hasil Download</h4>
303
+ <div class="result-content">
304
+ <pre id="fb-result-content"></pre>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </div>
309
+
310
+ <div id="tiktok-tab" class="tab-content">
311
+ <div class="try-box">
312
+ <h3>Download Video TikTok</h3>
313
+ <div class="try-form">
314
+ <input type="text" id="tiktok-url" placeholder="Masukkan URL TikTok">
315
+ <button onclick="testDownloadApi('tiktok')">Download</button>
316
+ </div>
317
+ <div class="result-box" id="tiktok-result">
318
+ <h4>Hasil Download</h4>
319
+ <div class="result-content">
320
+ <pre id="tiktok-result-content"></pre>
321
+ </div>
322
+ </div>
323
+ </div>
324
+ </div>
325
+
326
+ <div id="instagram-tab" class="tab-content">
327
+ <div class="try-box">
328
+ <h3>Download Konten Instagram</h3>
329
+ <div class="try-form">
330
+ <input type="text" id="instagram-url" placeholder="Masukkan URL Instagram">
331
+ <button onclick="testDownloadApi('instagram')">Download</button>
332
+ </div>
333
+ <div class="result-box" id="instagram-result">
334
+ <h4>Hasil Download</h4>
335
+ <div class="result-content">
336
+ <pre id="instagram-result-content"></pre>
337
+ </div>
338
+ </div>
339
+ </div>
340
+ </div>
341
+
342
  <div id="upload-tab" class="tab-content">
343
  <div class="try-box">
344
  <h3>Upload File (WebP, JPEG, PNG, dll)</h3>
 
356
  </div>
357
  </div>
358
 
359
+ <div id="cloudflare-tab" class="tab-content">
360
+ <div class="try-box">
361
+ <h3>Cloudflare Bypass Tools</h3>
362
+ <div class="try-form">
363
+ <input type="text" id="cf-url" placeholder="Masukkan URL yang diproteksi Cloudflare">
364
+ <input type="text" id="cf-proxy" placeholder="Proxy (opsional) host:port">
365
+ <button onclick="testCFApi('waf')">Get WAF Session</button>
366
+ </div>
367
+ <div class="try-form">
368
+ <input type="text" id="cf-turnstile-url" placeholder="URL dengan Turnstile">
369
+ <input type="text" id="cf-sitekey" placeholder="Sitekey Turnstile">
370
+ <input type="text" id="cf-turnstile-proxy" placeholder="Proxy (opsional) host:port">
371
+ <button onclick="testCFApi('turnstile')">Solve Turnstile</button>
372
+ </div>
373
+ <div class="try-form">
374
+ <input type="text" id="cf-autofaucet-proxy" placeholder="Proxy (opsional) host:port">
375
+ <button onclick="testCFApi('autofaucet')">Bypass Autofaucet</button>
376
+ </div>
377
+ <div class="result-box" id="cf-result">
378
+ <h4>Hasil Cloudflare Bypass</h4>
379
+ <div class="result-content">
380
+ <pre id="cf-result-content"></pre>
381
+ </div>
382
+ </div>
383
+ </div>
384
+ </div>
385
+
386
  <div id="swagger-ui"></div>
387
  </div>
388
 
389
  <div class="footer">
390
+ <p>Social Media Downloader API &copy; 2024</p>
391
  </div>
392
 
393
  <script src="https://unpkg.com/swagger-ui-dist@3.25.0/swagger-ui-bundle.js"></script>
394
  <script>
 
395
  const currentDomain = window.location.origin;
396
  const apiBaseUrl = currentDomain;
397
 
 
398
  document.getElementById('domain-info').textContent = currentDomain;
399
 
 
400
  function switchTab(tabId) {
 
401
  document.querySelectorAll('.tab-content').forEach(tab => {
402
  tab.classList.remove('active');
403
  });
 
 
404
  document.getElementById(tabId).classList.add('active');
405
 
 
406
  document.querySelectorAll('.tab').forEach(tab => {
407
  tab.classList.remove('active');
408
  });
409
  event.currentTarget.classList.add('active');
410
  }
411
 
412
+ function testDownloadApi(platform) {
413
+ let url, endpoint, resultDiv, resultContent;
414
+
415
+ switch(platform) {
416
+ case 'youtube':
417
+ url = document.getElementById('yt-url').value;
418
+ endpoint = '/api/download';
419
+ resultDiv = document.getElementById('download-result');
420
+ resultContent = document.getElementById('download-result-content');
421
+ break;
422
+ case 'facebook':
423
+ url = document.getElementById('fb-url').value;
424
+ endpoint = '/api/fb-download';
425
+ resultDiv = document.getElementById('fb-result');
426
+ resultContent = document.getElementById('fb-result-content');
427
+ break;
428
+ case 'tiktok':
429
+ url = document.getElementById('tiktok-url').value;
430
+ endpoint = '/api/tiktok';
431
+ resultDiv = document.getElementById('tiktok-result');
432
+ resultContent = document.getElementById('tiktok-result-content');
433
+ break;
434
+ case 'instagram':
435
+ url = document.getElementById('instagram-url').value;
436
+ endpoint = '/api/instagram';
437
+ resultDiv = document.getElementById('instagram-result');
438
+ resultContent = document.getElementById('instagram-result-content');
439
+ break;
440
+ }
441
 
442
  if (!url) {
443
+ resultContent.textContent = 'Error: URL harus diisi';
444
  resultDiv.style.display = 'block';
445
  return;
446
  }
 
448
  resultContent.textContent = 'Memproses download...';
449
  resultDiv.style.display = 'block';
450
 
451
+ let apiUrl = `${apiBaseUrl}${endpoint}?url=${encodeURIComponent(url)}`;
452
+
453
+ if (platform === 'youtube') {
454
+ const format = document.getElementById('format').value;
455
+ const upload = document.getElementById('upload-option').value;
456
+ apiUrl += `&format=${format}&upload=${upload}`;
457
+ } else if (platform === 'facebook') {
458
+ const quality = document.getElementById('fb-quality').value;
459
+ apiUrl += `&quality=${quality}`;
460
+ }
461
+
462
+ fetch(apiUrl)
463
  .then(response => response.json())
464
  .then(data => {
465
  resultContent.textContent = JSON.stringify(data, null, 2);
466
 
467
+ if (data.downloadURL || (data.downloadInfo && data.downloadInfo.download_url)) {
468
+ const downloadUrl = data.downloadURL || data.downloadInfo.download_url;
469
  const downloadBtn = document.createElement('a');
470
+ downloadBtn.href = downloadUrl;
471
  downloadBtn.target = '_blank';
472
  downloadBtn.textContent = 'Download File';
473
  downloadBtn.style.display = 'block';
 
486
  });
487
  }
488
 
489
+ function testCFApi(type) {
490
+ const resultDiv = document.getElementById('cf-result');
491
+ const resultContent = document.getElementById('cf-result-content');
492
+
493
+ let apiUrl, params = '';
494
+
495
+ switch(type) {
496
+ case 'waf':
497
+ const url = document.getElementById('cf-url').value;
498
+ const proxy = document.getElementById('cf-proxy').value;
499
+ if (!url) {
500
+ resultContent.textContent = 'Error: URL harus diisi';
501
+ resultDiv.style.display = 'block';
502
+ return;
503
+ }
504
+ apiUrl = `/api/cf/waf-session?url=${encodeURIComponent(url)}`;
505
+ if (proxy) apiUrl += `&proxy=${encodeURIComponent(proxy)}`;
506
+ break;
507
+
508
+ case 'turnstile':
509
+ const turnstileUrl = document.getElementById('cf-turnstile-url').value;
510
+ const sitekey = document.getElementById('cf-sitekey').value;
511
+ const turnstileProxy = document.getElementById('cf-turnstile-proxy').value;
512
+ if (!turnstileUrl || !sitekey) {
513
+ resultContent.textContent = 'Error: URL dan Sitekey harus diisi';
514
+ resultDiv.style.display = 'block';
515
+ return;
516
+ }
517
+ apiUrl = `/api/cf/turnstile?url=${encodeURIComponent(turnstileUrl)}&sitekey=${encodeURIComponent(sitekey)}`;
518
+ if (turnstileProxy) apiUrl += `&proxy=${encodeURIComponent(turnstileProxy)}`;
519
+ break;
520
+
521
+ case 'autofaucet':
522
+ const autofaucetProxy = document.getElementById('cf-autofaucet-proxy').value;
523
+ apiUrl = '/api/cf/autofaucet';
524
+ if (autofaucetProxy) apiUrl += `?proxy=${encodeURIComponent(autofaucetProxy)}`;
525
+ break;
526
+ }
527
+
528
+ resultContent.textContent = 'Memproses...';
529
+ resultDiv.style.display = 'block';
530
+
531
+ fetch(apiBaseUrl + apiUrl)
532
+ .then(response => response.json())
533
+ .then(data => {
534
+ resultContent.textContent = JSON.stringify(data, null, 2);
535
+ })
536
+ .catch(error => {
537
+ resultContent.textContent = 'Error: ' + error.message;
538
+ });
539
+ }
540
+
541
  function uploadFile() {
542
  const fileInput = document.getElementById('file-input');
543
  const resultDiv = document.getElementById('upload-result');
 
555
  resultDiv.style.display = 'block';
556
  previewImage.style.display = 'none';
557
 
 
558
  if (file.type.startsWith('image/')) {
559
  const reader = new FileReader();
560
  reader.onload = function(e) {
 
564
  reader.readAsDataURL(file);
565
  }
566
 
 
567
  const formData = new FormData();
568
  formData.append('file', file);
569
 
 
570
  fetch(`${apiBaseUrl}/api/upload`, {
571
  method: 'POST',
572
  body: formData
 
575
  .then(data => {
576
  resultContent.textContent = JSON.stringify(data, null, 2);
577
 
 
578
  if (data.path) {
579
  const link = document.createElement('a');
580
  link.href = data.path;
 
596
  });
597
  }
598
 
 
599
  setTimeout(() => {
600
  try {
601
  const ui = SwaggerUIBundle({