music-library-tools/src/pillow.js
2025-12-20 12:02:36 +02:00

139 lines
3.8 KiB
JavaScript

import https from "https";
import { createWriteStream } from "fs";
// ======== COLOR UTILS ==========
const C = {
info: msg => console.log("\x1b[36m[i]\x1b[0m " + msg),
ok: msg => console.log("\x1b[32m[✓]\x1b[0m " + msg),
warn: msg => console.log("\x1b[33m[!]\x1b[0m " + msg),
err: msg => console.log("\x1b[31m[✗]\x1b[0m " + msg),
};
// ======== DOWNLOAD WITH PROGRESS ==========
function download(url, filename, referer = url) {
return new Promise((resolve, reject) => {
let file = null; // will open later
function doReq(link, ref) {
C.info(`Requesting: ${link}`);
https.get(link, {
headers: {
"User-Agent": "Mozilla/5.0",
"Referer": ref
}
}, res => {
// --- Redirect handler ---
if ([301, 302, 303, 307, 308].includes(res.statusCode)) {
const loc = res.headers.location;
if (!loc) return reject(new Error("Redirect without Location header"));
const nextURL = loc.startsWith("http") ? loc : new URL(loc, link).href;
C.warn(`↪ Redirect (${res.statusCode}): ${link}${nextURL}`);
return doReq(nextURL, link);
}
// --- Error handler ---
if (res.statusCode !== 200) {
return reject(new Error(`HTTP ${res.statusCode}`));
}
// ======= OPEN FILE NOW (final target reached) =======
if (!file) file = createWriteStream(filename);
// ======= Progress =======
const total = parseInt(res.headers["content-length"] || "0", 10);
let downloaded = 0;
const start = Date.now();
res.on("data", chunk => {
downloaded += chunk.length;
if (total) {
const percent = ((downloaded / total) * 100).toFixed(1);
const speed = (downloaded / 1024 / ((Date.now() - start) / 1000)).toFixed(1);
const barSize = 30;
const filled = Math.round(percent / 100 * barSize);
const bar = "[" + "#".repeat(filled) + "-".repeat(barSize - filled) + "]";
process.stdout.write(`\r${bar} ${percent}% ${speed}KB/s`);
} else {
process.stdout.write(`\rDownloaded ${Math.round(downloaded / 1024)}KB`);
}
});
res.pipe(file);
file.on("finish", () => {
process.stdout.write("\n");
C.ok("Download complete!");
resolve();
});
}).on("error", err => reject(err));
}
doReq(url, referer);
});
}
// ======== MAIN FUNCTION ==========
(async () => {
const target = process.argv[2];
if (!target) {
C.err("Usage: node pillows-dl.js https://pillows.su/f/ID");
process.exit(1);
}
C.info(`Fetching webpage: ${target}`);
let html;
try {
html = await fetch(target, { headers: { "User-Agent": "Mozilla/5.0" } }).then(r => r.text());
} catch (e) {
C.err("Failed to fetch the page");
console.error(e);
process.exit(1);
}
const match = html.match(/data:\s*\[(.*)\],/);
if (!match) {
C.err("❌ Failed to extract data[] from webpage!");
process.exit(1);
}
C.ok("Found data[] block");
// Safer eval (only our captured data)
let arr;
try {
arr = eval("["+match[1]+"]");
} catch (e) {
C.err("❌ Failed to parse data[] as JavaScript");
console.error(e);
process.exit(1);
}
const item = arr.find(x => x?.type === "data" && x?.data?.filename);
if (!item) {
C.err("❌ Could not find downloadable file info in data[]");
process.exit(1);
}
const data = item.data;
C.ok(`File: ${data.filename}`);
C.info(`ID: ${data.id}`);
C.info(`Views: ${data.views}`);
C.info(`Bitrate: ${data.bitrate || "??"} kbps`);
const url = `https://api.pillows.su/api/download/${data.id}.mp3`;
try {
await download(url, data.filename);
C.ok("Done!");
} catch (e) {
C.err("❌ Download failed");
console.error(e);
process.exit(1);
}
})();