update
Browse files- Dockerfile +4 -5
- api_test.py +26 -0
- cache/cache.json +10 -0
- docs/swagger.js +0 -1098
- endpoints/cloudflare.js +79 -0
- endpoints/turnstile.js +84 -0
- image/Background.jpg +0 -0
- index.js +118 -1232
- package.json +9 -19
- public/index.html +0 -1040
- scrape/instagramdl.js +0 -197
- scrape/tiktok.js +0 -197
- scrape/tiktokdl.js +0 -191
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
|
| 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
|
| 3 |
-
const
|
| 4 |
-
const
|
| 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
|
| 19 |
-
const
|
| 20 |
|
| 21 |
-
|
| 22 |
-
|
| 23 |
|
| 24 |
-
const
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
fileSize: 50 * 1024 * 1024,
|
| 28 |
-
},
|
| 29 |
-
});
|
| 30 |
|
| 31 |
-
|
| 32 |
-
|
| 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 |
-
|
| 62 |
-
|
| 63 |
-
|
| 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 |
-
|
| 168 |
-
|
| 169 |
-
|
| 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 |
-
|
| 219 |
-
|
| 220 |
-
|
| 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 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
|
| 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 |
-
|
| 667 |
-
|
| 668 |
-
res.status(500).json({
|
| 669 |
-
success: false,
|
| 670 |
-
error: error.message
|
| 671 |
-
});
|
| 672 |
-
}
|
| 673 |
-
});
|
| 674 |
|
| 675 |
-
|
| 676 |
-
app.
|
|
|
|
|
|
|
| 677 |
try {
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 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 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
}
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 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 |
-
|
| 748 |
-
|
| 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 |
-
|
| 761 |
-
|
| 762 |
-
res.json({
|
| 763 |
-
success: true,
|
| 764 |
-
sessions: sessions,
|
| 765 |
-
total: sessions.length,
|
| 766 |
-
timestamp: new Date().toISOString()
|
| 767 |
-
});
|
| 768 |
|
| 769 |
-
|
| 770 |
-
|
| 771 |
-
|
| 772 |
-
|
| 773 |
-
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 852 |
-
|
| 853 |
-
|
| 854 |
-
|
| 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 |
-
|
| 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 |
-
|
| 949 |
-
|
| 950 |
-
const result = await instagramDl(url);
|
| 951 |
|
| 952 |
-
|
| 953 |
-
|
| 954 |
-
|
| 955 |
-
|
| 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 |
-
|
| 976 |
-
|
| 977 |
|
| 978 |
-
app.get('/api/fb-download', async (req, res) => {
|
| 979 |
try {
|
| 980 |
-
|
| 981 |
-
|
| 982 |
-
|
| 983 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1123 |
-
|
| 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 |
-
|
| 1144 |
-
|
| 1145 |
-
|
| 1146 |
-
|
| 1147 |
-
return res.status(400).json({
|
| 1148 |
-
status: false,
|
| 1149 |
-
error: "Parameter 'file' diperlukan dalam format base64"
|
| 1150 |
-
});
|
| 1151 |
-
}
|
| 1152 |
|
| 1153 |
-
|
| 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 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 1171 |
-
if (!filename) {
|
| 1172 |
-
return res.status(400).json({
|
| 1173 |
-
status: false,
|
| 1174 |
-
error: "Parameter 'filename' diperlukan"
|
| 1175 |
-
});
|
| 1176 |
-
}
|
| 1177 |
|
| 1178 |
-
|
| 1179 |
-
|
| 1180 |
-
if (!fs.existsSync(filePath)) {
|
| 1181 |
-
return res.status(404).json({
|
| 1182 |
-
status: false,
|
| 1183 |
-
error: "File tidak ditemukan"
|
| 1184 |
-
});
|
| 1185 |
}
|
|
|
|
| 1186 |
|
| 1187 |
-
|
| 1188 |
-
|
| 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 |
-
|
| 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 |
-
|
| 1239 |
-
|
| 1240 |
-
});
|
| 1241 |
|
| 1242 |
app.use((req, res) => {
|
| 1243 |
-
|
| 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 |
-
|
| 1269 |
-
|
| 1270 |
-
|
| 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": "
|
| 3 |
-
"version": "1.0
|
| 4 |
-
"description": "",
|
| 5 |
-
"main": "index.js",
|
| 6 |
"scripts": {
|
| 7 |
"start": "node index.js",
|
| 8 |
-
"
|
| 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 |
-
"
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
"
|
| 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 © 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;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|