Files
ts-scripts/single-scripts/wget.ts

172 lines
4.8 KiB
TypeScript
Executable File

#!/usr/bin/env runts -- --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 {
clearLastLine,
exit,
formatHumanTime,
formatPercent,
formatSize2,
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(): 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", "no-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
wget.ts [--proxy socks5h://ip:port] [--no-proxy] [--hatter-proxy] [--output filename] <URL>`);
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 hatterProxy = flags["hatter-proxy"];
const proxy = flags.proxy || getEnvironmentProxy();
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-hater-me",
"xgetfile-token",
);
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.20260412T093742+08:00.MEQCIG3Xz35jg15qpr2fBc8B
// hKxbjDlAXpETYYKNzkaZb6a7AiBeZOvcgvU+V7VUhs1rGZze2NyKreSXoMxWFhdblic4tA==