您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
The ultimate URL purifier
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/492078/1357326/pURLfy.js
class Purlfy extends EventTarget { redirectEnabled = false; lambdaEnabled = false; maxIterations = 5; #log = console.log.bind(console, "\x1b[38;2;220;20;60m[pURLfy]\x1b[0m"); #paramDecoders = { "url": decodeURIComponent, "base64": s => decodeURIComponent(escape(atob(s))), }; #statistics = { url: 0, param: 0, decoded: 0, redirected: 0, char: 0 }; #rules = {}; constructor(options) { super(); this.redirectEnabled = options?.redirectEnabled ?? this.redirectEnabled; this.lambdaEnabled = options?.lambdaEnabled ?? this.lambdaEnabled; this.maxIterations = options?.maxIterations ?? this.maxIterations; Object.assign(this.#statistics, options?.statistics); this.#log = options?.log ?? this.#log; } clearStatistics() { this.#statistics = { url: 0, param: 0, decoded: 0, redirected: 0, char: 0 }; this.#onStatisticsChange(); } clearRules() { this.#rules = {}; } getStatistics() { return this.#statistics; } importRules(rules) { Object.assign(this.#rules, rules); } #udfOrType(value, type) { // If the given value is of the given type or undefined return value === undefined || typeof value === type; } #validRule(rule) { // Check if the given rule is valid if (!rule || !rule.mode || !rule.description || !rule.author) return false; switch (rule.mode) { case "white": case "black": return Array.isArray(rule.params); case "param": return Array.isArray(rule.params) && (rule.decode === undefined || Array.isArray(rule.decode)) && this.#udfOrType(rule.continue, "boolean"); case "regex": return false; // Not implemented yet case "redirect": return this.redirectEnabled && this.#udfOrType(rule.continue, "boolean"); case "lambda": return this.lambdaEnabled && typeof rule.lambda === "string" && this.#udfOrType(rule.continue, "boolean"); default: return false; } } #matchRule(parts) { // Iteratively match the longest rule for the given URL parts let fallbackRule = null; // Most precise fallback rule let currentRules = this.#rules; for (const part of parts) { if (currentRules.hasOwnProperty("")) { fallbackRule = currentRules[""]; } if (currentRules.hasOwnProperty(part + "/")) { currentRules = currentRules[part + "/"]; } else if (currentRules.hasOwnProperty(part)) { const rule = currentRules[part]; if (this.#validRule(rule)) { return rule; } } } if (this.#validRule(fallbackRule)) { return fallbackRule; } return null; } #onStatisticsChange() { this.dispatchEvent(new Event("statisticschange", { detail: this.#statistics })); } async purify(originalUrl) { // Purify the given URL based on `rules` let shallContinue = true; let url = originalUrl; let firstRule = null; let iteration = 0; this.#log("Purifying URL:", url); while (shallContinue && iteration++ < this.maxIterations) { const logi = (...args) => this.#log(`[#${iteration}]`, ...args); let urlObj; if (URL.canParse(url)) { urlObj = new URL(url); } else { logi(`Cannot parse URL ${url}`); return url; } const protocol = urlObj.protocol; if (protocol !== "http:" && protocol !== "https:") { // Not a valid HTTP URL logi(`Not a HTTP URL: ${url}`); return url; } const hostAndPath = urlObj.host + urlObj.pathname; const parts = hostAndPath.split("/").filter(part => part !== ""); const rule = this.#matchRule(parts); if (!rule) { // No matching rule found logi(`No matching rule found for ${url}.`); return url; } firstRule ??= rule; logi(`Matching rule: ${rule.description} by ${rule.author}`); const mode = rule.mode; const paramsCntBefore = urlObj.searchParams.size; shallContinue = false; switch (mode) { // Purifies `urlObj` based on the rule case "white": { // Whitelist mode const newParams = new URLSearchParams(); for (const param of rule.params) { if (urlObj.searchParams.has(param)) { newParams.set(param, urlObj.searchParams.get(param)); } } urlObj.search = newParams.toString(); break; } case "black": { // Blacklist mode for (const param of rule.params) { urlObj.searchParams.delete(param); } break; } case "param": { // Specific param mode // Decode given parameter to be used as a new URL let paramValue = null; for (const param of rule.params) { // Find the first available parameter value if (urlObj.searchParams.has(param)) { paramValue = urlObj.searchParams.get(param); break; } } if (!paramValue) { logi("Parameter(s) not found:", rule.params.join(", ")); break; } let dest = paramValue; for (const name of (rule.decode ?? ["url"])) { const decoder = this.#paramDecoders[name] ?? (s => s); dest = decoder(dest); } urlObj = new URL(dest); shallContinue = rule.continue ?? true; this.#statistics.decoded++; break; } case "regex": { // Regex mode logi("Regex mode not implemented yet"); break; } case "redirect": { // Redirect mode if (!this.redirectEnabled) { logi("Redirect mode is disabled."); break; } let r = null; try { r = await fetch(url, { method: "HEAD", redirect: "manual" }); } catch (e) { logi("Error fetching URL:", e); break; } if ((r.status === 301 || r.status === 302) && r.headers.has("location")) { let dest = r.headers.get("location"); urlObj = new URL(dest); shallContinue = rule.continue ?? true; this.#statistics.redirected++; } break; } case "lambda": { if (!this.lambdaEnabled) { logi("Lambda mode is disabled."); break; } try { const lambda = new Function("url", rule.lambda); urlObj = lambda(urlObj); } catch (e) { logi("Error executing lambda:", e); } shallContinue = rule.continue ?? true; break; } default: { logi("Invalid mode:", mode); break; } } logi("Purified URL:", urlObj.href); const paramsCntAfter = urlObj.searchParams.size; this.#statistics.param += (["white", "black"].includes(mode)) ? (paramsCntBefore - paramsCntAfter) : 0; this.#statistics.char += Math.max(url.length - urlObj.href.length, 0); // Prevent negative char count url = urlObj.href; } if (originalUrl === url) { // No changes made this.#log("No changes made."); return { url: url, rule: `* ${firstRule.description} by ${firstRule.author}` }; } this.#statistics.url++; this.#onStatisticsChange(); return { url: url, rule: `${firstRule.description} by ${firstRule.author}` }; } }