125 lines
3.4 KiB
TypeScript
Executable File
125 lines
3.4 KiB
TypeScript
Executable File
#!/usr/bin/env -S deno run --allow-env --allow-import --allow-read --allow-write --allow-net
|
|
|
|
// Reference:
|
|
// - https://docs.deno.com/examples/command_line_arguments/
|
|
// - https://jsr.io/@chiba/wget/1.0.0/mod.ts
|
|
|
|
import { parseArgs } from "jsr:@std/cli/parse-args";
|
|
import * as fs from "jsr:@std/fs@^1.0.0";
|
|
import {
|
|
clearLastLine,
|
|
formatHumanTime,
|
|
formatPercent,
|
|
formatSize2,
|
|
printLastLine,
|
|
} from "https://hatter.ink/script/fetch/library/deno-commons-mod.ts?202401191305";
|
|
|
|
function getOutputFilename(filename: string): string {
|
|
const original = filename;
|
|
for (let i = 1; i < Number.MAX_SAFE_INTEGER; i++) {
|
|
if (!fs.existsSync(filename)) {
|
|
break;
|
|
}
|
|
filename = `${original}.${i}`;
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
function getFilenameFromUrl(url: string): string {
|
|
// response.headers.get("Content-Disposition") // TODO
|
|
const array = new URL(url).pathname.split("/");
|
|
let filename = array[array.length - 1].trim();
|
|
if (!filename) {
|
|
filename = "index.html";
|
|
}
|
|
return filename;
|
|
}
|
|
|
|
function getOutputFilenameFromUrl(url: string): string {
|
|
return getOutputFilename(getFilenameFromUrl(url));
|
|
}
|
|
|
|
function getEnvironmentProxy(): string | undefined {
|
|
return Deno.env.get("ALL_PROXY") || Deno.env.get("HTTPS_PROXY") ||
|
|
Deno.env.get("HTTP_PROXY");
|
|
}
|
|
|
|
const args = Deno.args;
|
|
const flags = parseArgs(Deno.args, {
|
|
boolean: ["help"],
|
|
string: ["proxy", "output"],
|
|
});
|
|
|
|
if (args.length === 0) {
|
|
console.log(`wget.ts --help for help`);
|
|
Deno.exit(1);
|
|
}
|
|
if (flags.help) {
|
|
console.log(`wget.ts - download file
|
|
|
|
wget.ts <URL>`);
|
|
Deno.exit(0);
|
|
}
|
|
|
|
if (flags._.length === 0) {
|
|
console.log("Require URL.");
|
|
Deno.exit(1);
|
|
}
|
|
|
|
const url = flags._[0] as string;
|
|
const outputFilename = flags.output || getOutputFilenameFromUrl(url);
|
|
|
|
const startTime = new Date().getTime();
|
|
let totalLength: number = -2;
|
|
|
|
console.log("Start download file:", url, " --> ", outputFilename);
|
|
|
|
const stateFileInterVal = setInterval(async () => {
|
|
const costTime = new Date().getTime() - startTime;
|
|
if (totalLength === -2) {
|
|
await printLastLine(
|
|
`Waiting download, time: ${formatHumanTime(costTime)}`,
|
|
);
|
|
return;
|
|
}
|
|
const fileInfo = await Deno.stat(outputFilename);
|
|
const fileSize = fileInfo.size;
|
|
const speed = Math.floor(fileSize / (costTime / 1000));
|
|
await printLastLine(
|
|
`Downloading file, total: ${formatSize2(totalLength)}, size: ${
|
|
formatSize2(fileSize)
|
|
}, percent: ${formatPercent(fileSize, totalLength)}, speed: ${
|
|
formatSize2(speed)
|
|
}, time: ${formatHumanTime(costTime)}`,
|
|
);
|
|
}, 1000);
|
|
|
|
const outputFileWritable = Deno.createSync(outputFilename).writable;
|
|
|
|
let init = undefined;
|
|
const proxy = flags.proxy || getEnvironmentProxy();
|
|
if (proxy) {
|
|
console.log("Using proxy: ", proxy);
|
|
init = {
|
|
client: Deno.createHttpClient({
|
|
proxy: {
|
|
url: proxy,
|
|
},
|
|
}),
|
|
};
|
|
}
|
|
const response = await fetch(url, init);
|
|
const contentLength = response.headers.get("content-length");
|
|
totalLength = -1;
|
|
if (contentLength !== null) {
|
|
try {
|
|
totalLength = parseInt(contentLength, 10);
|
|
} catch (_e) {
|
|
// IGNORE
|
|
}
|
|
}
|
|
await response.body?.pipeTo(outputFileWritable);
|
|
clearInterval(stateFileInterVal);
|
|
await clearLastLine();
|
|
console.log("Download finished.");
|