windowsonbash
Version:
Linux terminal experience reimagined in Node.js, built for Windows only.
129 lines (106 loc) • 4.83 kB
JavaScript
const fs = require('fs');
const http = require('http');
const https = require('https');
const path = require('path');
const url = require('url');
const dns = require('dns');
const util = require('util');
const dnsLookup = util.promisify(dns.lookup);
const args = process.argv.slice(2);
let targetUrl = '';
let outputFile = '';
for (let i = 0; i < args.length; i++) {
if (args[i] === '-O' && i + 1 < args.length) {
outputFile = args[i + 1];
i++;
} else if (!targetUrl && !args[i].startsWith('-')) {
targetUrl = args[i];
}
}
if (!targetUrl) {
console.error('wget: missing URL');
console.error('Usage: wget [OPTIONS] URL');
process.exit(1);
}
if (!outputFile) {
const parsedUrl = url.parse(targetUrl);
outputFile = path.basename(parsedUrl.pathname) || 'index.html';
}
function formatDate() {
const date = new Date();
return date.toISOString().replace('T', ' ').substring(0, 19);
}
function formatSize(bytes) {
if (bytes === 0) return '0B';
const units = ['B', 'K', 'M', 'G', 'T'];
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${(bytes / Math.pow(1024, i)).toFixed(2)}${units[i]}`;
}
function createProgressBar(percentage, width = 50) {
const completed = Math.round(width * percentage / 100);
const remaining = width - completed;
return '[' + '='.repeat(completed) + ' '.repeat(remaining) + ']';
}
async function download() {
const startTime = Date.now();
console.log(`--${formatDate()}-- ${targetUrl}`);
try {
const parsedUrl = new URL(targetUrl);
const hostname = parsedUrl.hostname;
console.log(`Resolving ${hostname} (${hostname})...`);
const { address } = await dnsLookup(hostname);
console.log(`Connecting to ${hostname} (${hostname})|${address}|:${parsedUrl.port || (parsedUrl.protocol === 'https:' ? 443 : 80)}... connected.`);
const protocol = parsedUrl.protocol === 'https:' ? https : http;
const request = protocol.get(targetUrl, (response) => {
if (response.statusCode === 301 || response.statusCode === 302) {
const newUrl = response.headers.location;
console.log(`Location: ${newUrl} [following]`);
args[args.indexOf(targetUrl)] = newUrl;
process.argv = ['node', 'wget.js', ...args];
require('./wget');
return;
}
console.log(`HTTP request sent, awaiting response... ${response.statusCode} ${response.statusMessage}`);
const contentLength = parseInt(response.headers['content-length'], 10) || 0;
const contentType = response.headers['content-type'] || '';
if (contentLength) {
console.log(`Length: ${contentLength} (${formatSize(contentLength)}) [${contentType}]`);
} else {
console.log(`Length: unspecified [${contentType}]`);
}
console.log(`Saving to: '${outputFile}'`);
console.log();
const fileStream = fs.createWriteStream(outputFile);
let downloadedBytes = 0;
response.on('data', (chunk) => {
downloadedBytes += chunk.length;
fileStream.write(chunk);
if (contentLength > 0) {
const percentage = Math.floor((downloadedBytes / contentLength) * 100);
const progressBar = createProgressBar(percentage);
const formattedSize = formatSize(downloadedBytes);
process.stdout.write(`\r${outputFile.padEnd(40)} ${percentage}%${progressBar} ${formattedSize}`);
} else {
process.stdout.write(`\r${downloadedBytes} bytes received`);
}
});
response.on('end', () => {
fileStream.end();
const endTime = Date.now();
const duration = (endTime - startTime) / 1000;
const speedInBytesPerSec = downloadedBytes / duration;
console.log();
console.log();
console.log(`${formatDate()} (${formatSize(speedInBytesPerSec)}/s) - '${outputFile}' saved [${downloadedBytes}/${contentLength || downloadedBytes}]`);
});
}).on('error', (err) => {
console.error(`wget: error: ${err.message}`);
process.exit(1);
});
} catch (error) {
console.error(`wget: error: ${error.message}`);
process.exit(1);
}
}
download();