85 lines
3.1 KiB
JavaScript
Executable File
85 lines
3.1 KiB
JavaScript
Executable File
const fs = require('fs');
|
|
const http = require('http');
|
|
const https = require('https');
|
|
|
|
function getProtocol(url) {
|
|
return url.startsWith('https') ? https : http;
|
|
}
|
|
|
|
function fetchJSON(url) {
|
|
return new Promise((resolve, reject) => {
|
|
getProtocol(url).get(url, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => {
|
|
try { resolve(JSON.parse(data)); }
|
|
catch { reject(new Error(`JSON 解析失败: ${data.slice(0, 200)}`)); }
|
|
});
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
function downloadFile(url, dest, showProgress = true) {
|
|
return new Promise((resolve, reject) => {
|
|
getProtocol(url).get(url, (res) => {
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
return downloadFile(res.headers.location, dest, showProgress).then(resolve).catch(reject);
|
|
}
|
|
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
|
|
|
const total = parseInt(res.headers['content-length'], 10);
|
|
let downloaded = 0;
|
|
let lastTime = Date.now();
|
|
let lastBytes = 0;
|
|
const file = fs.createWriteStream(dest);
|
|
|
|
res.on('data', (chunk) => {
|
|
downloaded += chunk.length;
|
|
if (!showProgress) return;
|
|
const now = Date.now();
|
|
const elapsed = (now - lastTime) / 1000;
|
|
if (elapsed < 0.3) return;
|
|
|
|
const speed = (downloaded - lastBytes) / elapsed;
|
|
lastTime = now;
|
|
lastBytes = downloaded;
|
|
const speedStr = speed >= 1048576
|
|
? `${(speed / 1048576).toFixed(1)} MB/s`
|
|
: `${(speed / 1024).toFixed(0)} KB/s`;
|
|
|
|
if (total) {
|
|
const pct = downloaded / total;
|
|
const barWidth = 30;
|
|
const filled = Math.round(pct * barWidth);
|
|
const bar = '█'.repeat(filled) + '░'.repeat(barWidth - filled);
|
|
const eta = speed > 0 ? Math.ceil((total - downloaded) / speed) : 0;
|
|
const etaStr = eta > 60 ? `${Math.floor(eta / 60)}m${eta % 60}s` : `${eta}s`;
|
|
process.stdout.write(`\r ⬇️ [${bar}] ${(pct * 100).toFixed(1)}% ${(downloaded / 1048576).toFixed(1)}/${(total / 1048576).toFixed(1)} MB ${speedStr} ETA ${etaStr} `);
|
|
} else {
|
|
process.stdout.write(`\r ⬇️ ${(downloaded / 1048576).toFixed(1)} MB ${speedStr} `);
|
|
}
|
|
});
|
|
|
|
res.pipe(file);
|
|
file.on('finish', () => { file.close(); if (showProgress) console.log(); resolve(); });
|
|
file.on('error', (err) => { fs.unlink(dest, () => {}); reject(err); });
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
function downloadBuffer(url) {
|
|
return new Promise((resolve, reject) => {
|
|
getProtocol(url).get(url, (res) => {
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
return downloadBuffer(res.headers.location).then(resolve).catch(reject);
|
|
}
|
|
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
|
const chunks = [];
|
|
res.on('data', chunk => chunks.push(chunk));
|
|
res.on('end', () => resolve(Buffer.concat(chunks)));
|
|
}).on('error', reject);
|
|
});
|
|
}
|
|
|
|
module.exports = { fetchJSON, downloadFile, downloadBuffer };
|