#!/usr/bin/env runts -- --allow-all // 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 { clearLastLine, exit, formatHumanTime, formatPercent, formatSize2, getEnv, getKeyRingPassword, log, printLastLine, } from "https://script.hatter.ink/@70/deno-commons-mod.ts"; function getOutputFilename(filename: string): string { const original = filename; for (let i = 1; i < Number.MAX_SAFE_INTEGER; i++) { try { Deno.statSync(filename); // deno-lint-ignore no-explicit-any } catch (e: any) { if (e && e.name === "NotFound") { break; } throw e; } filename = `${original}.${i}`; } return filename; } function getFilenameFromUrl(url: string): string { const array = new URL(url).pathname.split("/"); let filename = array[array.length - 1].trim(); if (!filename) { filename = "index.html"; } try { return decodeURIComponent(filename); } catch (e) { return filename; } } function getOutputFilenameFromUrl(url: string): string { return getOutputFilename(getFilenameFromUrl(url)); } function getEnvironmentProxy(autoProxy: boolean): string | undefined { const proxy = Deno.env.get("ALL_PROXY") || Deno.env.get("HTTPS_PROXY") || Deno.env.get("HTTP_PROXY"); if (proxy) { return proxy; } if (autoProxy) { return getEnv("AUTO_PROXY"); } return undefined; } const args = Deno.args; const flags = parseArgs(Deno.args, { boolean: ["help", "no-proxy", "auto-proxy", "hatter-proxy"], string: ["proxy", "output"], }); if (args.length === 0) { log.info(`wget.ts --help for help`); Deno.exit(1); } if (flags.help) { console.log(`wget.ts - download file Environments: - ALL_PROXY,HTTPS_PROXY,HTTP_PROXY ~ For proxy - AUTO_PROXY ~ For auto proxy wget.ts [--proxy socks5h://ip:port] [--no-proxy] [--auto-proxy] [--hatter-proxy] [--output filename] `); Deno.exit(0); } if (flags._.length === 0) { log.error("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; log.info(`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 noProxy = flags["no-proxy"]; const autoProxy = flags["auto-proxy"]; const hatterProxy = flags["hatter-proxy"]; const proxy = flags.proxy || getEnvironmentProxy(autoProxy); if (proxy && !noProxy && !hatterProxy) { log.info(`Using proxy: ${proxy}`); init = { client: Deno.createHttpClient({ proxy: { url: proxy, }, }), }; } if (hatterProxy) { log.info(`Using hatter proxy.`); } let realUrl = url; if (hatterProxy) { try { const token = await getKeyRingPassword( "play.hatter.me", "xgetfile-token", ); if (!token) { throw new Error(`Token not found`); } realUrl = `https://play.hatter.me/xgetfile?__token=${ encodeURIComponent(token) }&url=${encodeURIComponent(url)}`; log.info(`Proxy URL: ${realUrl}`); } catch (e) { log.error("Get xgetfile token failed", e); if (!confirm("Continue get file without hatter proxy?")) { log.warn("Wget file abort for no valid hatter proxy token"); exit(1); } } } log.debug(`Wget url: ${realUrl}`); const response = await fetch(realUrl, 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(); const costTime = new Date().getTime() - startTime; const fileInfo = await Deno.stat(outputFilename); const fileSize = fileInfo.size; log.success( `Download finished, size: ${formatSize2(fileSize)}, time: ${ formatHumanTime(costTime) }`, ); // @SCRIPT-SIGNATURE-V1: yk-r1.ES256.20260412T095641+08:00.MEYCIQC6Qwz7YNhcunDVIDzC // HjRPVTi1+Ph4FbGDxZuR6lNiUwIhAJyJTsUPZrBT5GYm+9mIlN6mYCbMHce1aU6jYbe4fnyh