diff --git a/script-meta-v2.json b/script-meta-v2.json index d02ea35..750a4dc 100644 --- a/script-meta-v2.json +++ b/script-meta-v2.json @@ -37,5 +37,12 @@ "script_length": 3515, "script_sha256": "0371be7620d5aa2607a5ed1a9b45e2664b5c27ee163966a681e630d568c4a445", "script_full_url": "https://git.hatter.ink/hatter/ts-scripts/raw/branch/main/sigstore-verify-ts/main.ts" + }, + "wget.ts": { + "script_name": "wget.ts", + "script_length": 3438, + "script_sha256": "d8cba907b1e1d669a4680c8b13328f284a2b10c47e47288389aab8ccbde71993", + "script_full_url": "https://git.hatter.ink/hatter/ts-scripts/raw/branch/main/single-scripts/wget.ts", + "single_script_file": true } } \ No newline at end of file diff --git a/single-scripts/wget.ts b/single-scripts/wget.ts new file mode 100755 index 0000000..88375bd --- /dev/null +++ b/single-scripts/wget.ts @@ -0,0 +1,124 @@ +#!/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 `); + 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.");