fourmovie commited on
Commit
3828040
·
1 Parent(s): 4c9cf08
Dockerfile CHANGED
@@ -8,18 +8,17 @@ RUN apt update && apt install -y \
8
  && apt install -y ./google-chrome-stable_current_amd64.deb \
9
  && rm google-chrome-stable_current_amd64.deb
10
 
11
- RUN npx playwright install-deps
12
-
13
  WORKDIR /app
14
 
 
 
 
15
  COPY package*.json ./
16
  RUN npm install
17
 
18
- RUN npx playwright install --with-deps
19
-
20
  COPY . .
21
 
22
- EXPOSE 7869
23
 
24
  CMD rm -f /tmp/.X99-lock && \
25
  Xvfb :99 -screen 0 1024x768x24 & \
 
8
  && apt install -y ./google-chrome-stable_current_amd64.deb \
9
  && rm google-chrome-stable_current_amd64.deb
10
 
 
 
11
  WORKDIR /app
12
 
13
+ RUN mkdir -p /app/endpoints && \
14
+ mkdir -p /app/cache
15
+
16
  COPY package*.json ./
17
  RUN npm install
18
 
 
 
19
  COPY . .
20
 
21
+ EXPOSE 8080
22
 
23
  CMD rm -f /tmp/.X99-lock && \
24
  Xvfb :99 -screen 0 1024x768x24 & \
api_test.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
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 DELETED
@@ -1,1098 +0,0 @@
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 ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ async function turnstile({ domain, proxy, siteKey }, page) {
2
+ if (!domain) throw new Error("Missing domain parameter");
3
+ if (!siteKey) throw new Error("Missing siteKey parameter");
4
+
5
+ const timeout = global.timeOut || 60000;
6
+ let isResolved = false;
7
+
8
+ const cl = setTimeout(async () => {
9
+ if (!isResolved) {
10
+ throw new Error("Timeout Error");
11
+ }
12
+ }, timeout);
13
+
14
+ try {
15
+ if (proxy?.username && proxy?.password) {
16
+ await page.authenticate({
17
+ username: proxy.username,
18
+ password: proxy.password,
19
+ });
20
+ }
21
+
22
+ const htmlContent = `
23
+ <!DOCTYPE html>
24
+ <html lang="en">
25
+ <body>
26
+ <div class="turnstile"></div>
27
+ <script src="https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback" defer></script>
28
+ <script>
29
+ window.onloadTurnstileCallback = function () {
30
+ turnstile.render('.turnstile', {
31
+ sitekey: '${siteKey}',
32
+ callback: function (token) {
33
+ var c = document.createElement('input');
34
+ c.type = 'hidden';
35
+ c.name = 'cf-response';
36
+ c.value = token;
37
+ document.body.appendChild(c);
38
+ },
39
+ });
40
+ };
41
+ </script>
42
+ </body>
43
+ </html>
44
+ `;
45
+
46
+ await page.setRequestInterception(true);
47
+ page.removeAllListeners("request");
48
+ page.on("request", async (request) => {
49
+ if ([domain, domain + "/"].includes(request.url()) && request.resourceType() === "document") {
50
+ await request.respond({
51
+ status: 200,
52
+ contentType: "text/html",
53
+ body: htmlContent,
54
+ });
55
+ } else {
56
+ await request.continue();
57
+ }
58
+ });
59
+
60
+ await page.goto(domain, { waitUntil: "domcontentloaded" });
61
+
62
+ await page.waitForSelector('[name="cf-response"]', { timeout });
63
+
64
+ const token = await page.evaluate(() => {
65
+ try {
66
+ return document.querySelector('[name="cf-response"]').value;
67
+ } catch {
68
+ return null;
69
+ }
70
+ });
71
+
72
+ isResolved = true;
73
+ clearTimeout(cl);
74
+
75
+ if (!token || token.length < 10) throw new Error("Failed to get token");
76
+ return token;
77
+
78
+ } catch (e) {
79
+ clearTimeout(cl);
80
+ throw e;
81
+ }
82
+ }
83
+
84
+ module.exports = turnstile;
image/Background.jpg DELETED
Binary file (37.3 kB)
 
index.js CHANGED
@@ -1,1284 +1,170 @@
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
- });
 
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 || 8080
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
+ if (!fs.existsSync(CACHE_DIR)) {
28
+ fs.mkdirSync(CACHE_DIR, { recursive: true })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
+ fs.writeFileSync(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8")
31
  }
32
 
33
+ function readCache(key) {
34
+ const cache = loadCache()
35
+ const entry = cache[key]
36
+ if (entry && Date.now() - entry.timestamp < CACHE_TTL) {
37
+ return entry.value
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
38
  }
39
+ return null
40
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
 
42
+ function writeCache(key, value) {
43
+ const cache = loadCache()
44
+ cache[key] = { timestamp: Date.now(), value }
45
+ saveCache(cache)
46
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
+ app.use(express.json())
49
+ app.use(express.urlencoded({ extended: true }))
 
 
 
 
 
 
50
 
51
+ if (process.env.NODE_ENV !== 'development') {
52
+ let server = app.listen(port, () => {
53
+ console.log(`Server running on port ${port}`)
54
+ })
55
  try {
56
+ server.timeout = global.timeOut
57
+ } catch {}
58
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
+ async function createBrowser(proxyServer = null) {
61
+ const connectOptions = {
62
+ headless: false,
63
+ turnstile: true,
64
+ connectOption: { defaultViewport: null },
65
+ disableXvfb: false,
66
  }
67
+
68
+ if (proxyServer) {
69
+ connectOptions.args = [`--proxy-server=${proxyServer}`]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
71
+
72
+ const { browser } = await connect(connectOptions)
73
 
74
+ const pages = await browser.pages()
75
+ const page = pages[0]
 
 
 
 
 
 
 
 
 
 
76
 
77
+ await page.goto('about:blank')
 
 
 
 
 
 
 
78
 
79
+ await page.setRequestInterception(true)
80
+ page.on('request', (req) => {
81
+ const type = req.resourceType()
82
+ if (["image", "stylesheet", "font", "media"].includes(type)) {
83
+ req.abort()
84
+ } else {
85
+ req.continue()
 
 
 
 
 
 
 
 
 
 
 
 
86
  }
87
+ })
88
 
89
+ return { browser, page }
90
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
+ const turnstile = require('./endpoints/turnstile')
93
+ const cloudflare = require('./endpoints/cloudflare')
 
 
 
 
 
 
 
 
 
 
 
 
94
 
95
+ app.post('/cloudflare', async (req, res) => {
96
+ const data = req.body
97
+ if (!data || typeof data.mode !== 'string') {
98
+ return res.status(400).json({ message: 'Bad Request: missing or invalid mode' })
 
 
99
  }
100
+ if (authToken && data.authToken !== authToken) {
101
+ return res.status(401).json({ message: 'Unauthorized' })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
103
 
104
+ if (global.browserLimit <= 0) {
105
+ return res.status(429).json({ message: 'Too Many Requests' })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
 
108
+ let cacheKey, cached
109
+ if (data.mode === "iuam") {
 
110
 
111
+ cacheKey = JSON.stringify(data)
112
+ cached = readCache(cacheKey)
113
+ if (cached) {
114
+ return res.status(200).json({ ...cached, cached: true })
 
115
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  }
 
117
 
118
+ global.browserLimit--
119
+ let result
120
+ let browser, page
121
 
 
122
  try {
123
+ const proxyServer = data.proxy ? `${data.proxy.hostname}:${data.proxy.port}` : null
124
+ const ctx = await createBrowser(proxyServer)
125
+ browser = ctx.browser
126
+ page = ctx.page
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ await page.goto('about:blank')
 
 
 
 
 
 
 
 
 
 
129
 
130
+ switch (data.mode) {
131
+ case "turnstile":
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ result = await turnstile(data, page)
134
+ .then(token => ({ token }))
135
+ .catch(err => ({ code: 500, message: err.message }))
136
+ break
 
 
 
 
 
137
 
138
+ case "iuam":
 
 
 
 
 
 
 
 
 
 
 
 
 
139
 
140
+ result = await cloudflare(data, page)
141
+ .then(r => ({ ...r }))
142
+ .catch(err => ({ code: 500, message: err.message }))
 
 
 
 
 
 
143
 
144
+ if (!result.code || result.code === 200) {
145
+ writeCache(cacheKey, result)
 
 
 
 
 
146
  }
147
+ break
148
 
149
+ default:
150
+ result = { code: 400, message: 'Invalid mode' }
 
 
 
 
 
 
 
151
  }
152
+ } catch (err) {
153
+ result = { code: 500, message: err.message }
154
+ } finally {
155
+ if (browser) {
156
+ try { await browser.close() } catch {}
 
 
 
 
 
 
 
 
 
 
 
157
  }
158
+ global.browserLimit++
 
 
 
 
 
 
 
159
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
+ res.status(result.code ?? 200).json(result)
162
+ })
 
163
 
164
  app.use((req, res) => {
165
+ res.status(404).json({ message: 'Not Found' })
166
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
 
168
+ if (process.env.NODE_ENV === 'development') {
169
+ module.exports = app
170
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
package.json CHANGED
@@ -1,26 +1,16 @@
1
  {
2
- "name": "eza",
3
- "version": "1.0.0",
4
- "description": "",
5
- "main": "index.js",
6
  "scripts": {
7
  "start": "node index.js",
8
- "test": "echo \"Error: no test specified\" && exit 1"
9
  },
10
- "keywords": [],
11
- "author": "",
12
- "license": "ISC",
13
- "type": "commonjs",
14
  "dependencies": {
15
- "axios": "^1.11.0",
16
- "cheerio": "^1.1.2",
17
- "bycf": "^1.0.3",
18
- "cors": "^2.8.5",
19
  "express": "^5.1.0",
20
- "file-type": "^21.0.0",
21
- "multer": "^2.0.2",
22
- "node-fetch": "^3.3.2",
23
- "swagger-ui-express": "^5.0.1",
24
- "yamljs": "^0.3.0"
25
  }
26
- }
 
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
+ }
public/index.html DELETED
@@ -1,1040 +0,0 @@
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 DELETED
@@ -1,197 +0,0 @@
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 DELETED
@@ -1,197 +0,0 @@
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 DELETED
@@ -1,191 +0,0 @@
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;