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