LOADING
const fs = require("fs");
const { pipeline } = require("stream/promises");
const { Readable } = require("stream");
const BASE = "https://snapdouyin.app";
const UA = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36";
function calculateHash(url, salt) {
return Buffer.from(url).toString("base64") + (url.length + 1000) + Buffer.from(salt).toString("base64");
}
function parseCookies(setCookieHeaders) {
if (!setCookieHeaders) return "";
return setCookieHeaders.map((c) => c.split(";")[0]).join("; ");
}
async function getSessionAndToken() {
const res = await fetch(BASE + "/", { headers: { "User-Agent": UA } });
const html = await res.text();
const setCookie = res.headers.getSetCookie ? res.headers.getSetCookie() : [];
const cookie = parseCookies(setCookie);
const match = html.match(/id="token"[^>]*value="([^"]+)"/);
if (!match) throw new Error("Token tidak ditemukan di halaman");
return { cookie, token: match[1] };
}
async function getVideoData(videoUrl, retries = 2) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
const { cookie: initialCookie, token } = await getSessionAndToken();
const hash = calculateHash(videoUrl, "aio-dl");
const body = new URLSearchParams();
body.append("url", videoUrl);
body.append("token", token);
body.append("hash", hash);
const res = await fetch(BASE + "/wp-json/mx-downloader/video-data/", {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Origin: BASE,
Referer: BASE + "/",
Cookie: initialCookie,
"User-Agent": UA,
},
body,
});
if (!res.ok) throw new Error(`video-data gagal dengan status ${res.status}`);
const newSetCookie = res.headers.getSetCookie ? res.headers.getSetCookie() : [];
const newCookie = parseCookies(newSetCookie);
const cookie = newCookie || initialCookie;
const data = await res.json();
if (data.error) throw new Error(data.error);
data._cookie = cookie;
return data;
} catch (err) {
if (attempt === retries) throw err;
}
}
}
async function downloadMedia(mediaUrl, cookie, outputPath) {
const res = await fetch(mediaUrl + "&bandwidth_saving=1", {
headers: {
accept: "*/*",
"accept-language": "id-ID,id;q=0.9,en-US;q=0.8,en;q=0.7",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
Cookie: cookie,
"User-Agent": UA,
},
signal: AbortSignal.timeout(30000),
});
if (!res.ok) throw new Error(`download gagal dengan status ${res.status}`);
if (!res.body) throw new Error("Response tidak punya body");
await pipeline(Readable.fromWeb(res.body), fs.createWriteStream(outputPath));
return outputPath;
}
async function main() {
const url = process.argv[2];
const qualityArg = process.argv[3];
if (!url) {
console.error("Gunakan: node snapdouyin.js <url_douyin> [index_kualitas]");
process.exit(1);
}
const data = await getVideoData(url);
if (qualityArg === undefined) {
console.log(JSON.stringify(data, null, 2));
return;
}
const media = data.medias[Number(qualityArg)];
if (!media) throw new Error("Index kualitas tidak valid");
const filename = `douyin-${media.quality.replace(/\s/g, "")}-${Date.now()}.${media.extension}`;
await downloadMedia(media.url, data._cookie, filename);
console.log(JSON.stringify({
url: data.url,
title: data.title,
thumbnail: data.thumbnail,
duration: data.duration,
source: data.source,
downloaded: {
saved: filename,
quality: media.quality,
extension: media.extension,
size: media.formattedSize,
},
}, null, 2));
}
main().catch((err) => {
console.error("Error:", err.message);
if (err.cause) console.error("Cause:", err.cause.message || err.cause);
process.exit(1);
});| Index | Kualitas |
|---|---|
| 0 | hd ⭐ |
| 1 | hd |
| 2 | sd |
| 3 | watermark |
| 4 | 128kbps (audio mp3) |
bashnode douyin.js https://v.douyin.com/CCzMawkS6vU/
bashnode douyin.js https://v.douyin.com/CCzMawkS6vU/ 0