first commit
This commit is contained in:
commit
010598a8be
15 changed files with 1128 additions and 0 deletions
139
src/pillow.js
Normal file
139
src/pillow.js
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
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);
|
||||
}
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue