diff --git a/README.md b/README.md index 2018be4..46be02c 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ const purifier = new Purlfy({ // Instantiate a Purlfy object fetchEnabled: true, lambdaEnabled: true, }); -const rules = await (await fetch("https://cdn.jsdelivr.net/gh/PRO-2684/pURLfy-rules@core-0.3.x/.json")).json(); // Rules -// You may also use GitHub raw link for really latest rules: https://raw.githubusercontent.com/PRO-2684/pURLfy-rules/core-0.3.x/.json +const rules = await (await fetch("https://cdn.jsdelivr.net/gh/PRO-2684/pURLfy-rules@core-0.3.x/.json")).json(); // Rules +// You may also use GitHub raw link for really latest rules: https://raw.githubusercontent.com/PRO-2684/pURLfy-rules/core-0.3.x/.json purifier.importRules(rules); // Import rules const additionalRules = {}; // You can also add your own rules purifier.importRules(additionalRules); @@ -74,7 +74,7 @@ new Purlfy({ }) ``` -#### Methods +#### Instance Methods - `importRules(rules: object): void`: Import rules. - `purify(url: string): Promise`: Purify a URL. @@ -90,7 +90,7 @@ new Purlfy({ - If platform supports `CustomEvent`, the `detail` property of the event object will contain the incremental statistics. - `removeEventListener("statisticschange", callback: function): void`: Remove an event listener for statistics change. -#### Properties +#### Instance Properties You can change these properties after instantiation, and they will take effect for the next call to `purify`. @@ -98,6 +98,10 @@ You can change these properties after instantiation, and they will take effect f - `lambdaEnabled: Boolean`: Whether the lambda mode is enabled. - `maxIterations: Number`: Maximum number of iterations. +#### Static Properties + +- `Purlfy.version: string`: The version of pURLfy. + ## 📖 Rules Community-contributed rules files are hosted on GitHub, and you can find them at [pURLfy-rules](https://github.com/PRO-2684/pURLfy-rules). The format of the rules files is as follows: @@ -341,7 +345,7 @@ Some processors support parameters, simply append them to the function name sepa > If you are using pURLfy in your project, feel free to submit a PR to add your project here! - Our [Demo Page](https://pro-2684.github.io/?page=purlfy) -- Our Telegram Bot [@purlfy_bot](https://t.me/purlfy_bot) +- Our Telegram Bot [@purlfy_bot](https://t.me/purlfy_bot) ([Source code](https://github.com/PRO-2684/Telegram-pURLfy)) - [pURLfy for Tampermonkey](https://greasyfork.org/scripts/492480) - [LiteLoaderQQNT-pURLfy](https://github.com/PRO-2684/LiteLoaderQQNT-pURLfy) diff --git a/README_zh.md b/README_zh.md index 672142b..4739dd6 100644 --- a/README_zh.md +++ b/README_zh.md @@ -74,7 +74,7 @@ new Purlfy({ }) ``` -#### 方法 +#### 实例方法 - `importRules(rules: object): void`: 导入规则 - `purify(url: string): Promise`: 净化一个 URL @@ -90,7 +90,7 @@ new Purlfy({ - 若支持 `CustomEvent`,则其 `detail` 属性为统计数据的增量 - `removeEventListener("statisticschange", callback: function): void`: 移除统计数据变化的事件监听器 -#### 属性 +#### 实例属性 你可以在初始化后更改下面的属性,它们将在下次调用 `purify` 时生效。 @@ -98,6 +98,11 @@ new Purlfy({ - `lambdaEnabled: Boolean`: 是否启用匿名函数模式 - `maxIterations: Number`: 最大迭代次数 +#### 静态属性 + +- `Purlfy.version: string`: pURLfy 的版本号 + + ## 📖 规则 社区贡献的规则文件托管在 GitHub 上,您可以在 [pURLfy-rules](https://github.com/PRO-2684/pURLfy-rules) 中找到。规则文件的格式如下: @@ -341,7 +346,7 @@ new Purlfy({ > 若您的项目使用了 pURLfy,欢迎提交 PR 将您的项目添加到这里! - 我们的 [示例页面](https://pro-2684.github.io/?page=purlfy) -- 我们的 Telegram 机器人 [@purlfy_bot](https://t.me/purlfy_bot) +- 我们的 Telegram 机器人 [@purlfy_bot](https://t.me/purlfy_bot) ([Source code](https://github.com/PRO-2684/Telegram-pURLfy)) - [pURLfy for Tampermonkey](https://greasyfork.org/scripts/492480) - [LiteLoaderQQNT-pURLfy](https://github.com/PRO-2684/LiteLoaderQQNT-pURLfy) diff --git a/purlfy.js b/purlfy.js index 9ccc7f4..0dcbc3a 100644 --- a/purlfy.js +++ b/purlfy.js @@ -1,10 +1,18 @@ class Purlfy extends EventTarget { - fetchEnabled = false; - lambdaEnabled = false; - maxIterations = 5; - #log = console.log.bind(console, "\x1b[38;2;220;20;60m[pURLfy]\x1b[0m"); - #fetch = fetch; - #acts = { + // Static properties + static get version() { + return "0.3.1"; + }; + static #AsyncFunction = async function () { }.constructor; + static #zeroStatistics = { + url: 0, + param: 0, + decoded: 0, + redirected: 0, + visited: 0, + char: 0 + }; + static #acts = { "url": decodeURIComponent, "base64": s => decodeURIComponent(escape(atob(s.replaceAll('_', '/').replaceAll('-', '+')))), "slice": (s, start, end) => s.slice(parseInt(start), end ? parseInt(end) : undefined), @@ -18,17 +26,14 @@ class Purlfy extends EventTarget { "attr": (e, attr) => e.getAttribute(attr), "text": (e) => e.textContent, }; - #zeroStatistics = { - url: 0, - param: 0, - decoded: 0, - redirected: 0, - visited: 0, - char: 0 - }; - #statistics = { ...this.#zeroStatistics }; + // Instance properties + fetchEnabled = false; + lambdaEnabled = false; + maxIterations = 5; + #log = console.log.bind(console, "\x1b[38;2;220;20;60m[pURLfy]\x1b[0m"); + #fetch = fetch; + #statistics = { ...Purlfy.#zeroStatistics }; #rules = {}; - #AsyncFunction = async function () { }.constructor; constructor(options) { super(); @@ -40,6 +45,38 @@ class Purlfy extends EventTarget { this.#fetch = options?.fetch ?? this.#fetch; } + // Static methods + static #udfOrType(value, type) { // If the given value is of the given type or undefined + return value === undefined || typeof value === type; + } + + static #isStandard(urlObj) { // Check if the given urlObj's search string follows the standard format + return urlObj.searchParams.toString() === urlObj.search.slice(1); + } + + static #applyActs(input, acts, logFunc) { // Apply the given acts to the given input + let dest = input; + for (const cmd of (acts)) { + const args = cmd.split(":"); + const name = args[0]; + const act = Purlfy.#acts[name]; + if (!act) { + logFunc("Invalid act:", cmd); + dest = null; + break; + } + try { + dest = act(dest, ...args.slice(1)); + } catch (e) { + logFunc(`Error processing input with act "${name}":`, e); + dest = null; + break; + } + } + return dest; + } + + // Instance methods clearStatistics() { const increment = {}; for (const [key, value] of Object.entries(this.#statistics)) { @@ -60,26 +97,22 @@ class Purlfy extends EventTarget { 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) && this.#udfOrType(rule.std, "boolean"); + return Array.isArray(rule.params) && Purlfy.#udfOrType(rule.std, "boolean"); case "param": - return Array.isArray(rule.params) && (rule.acts === undefined || Array.isArray(rule.acts)) && this.#udfOrType(rule.continue, "boolean"); + return Array.isArray(rule.params) && (rule.acts === undefined || Array.isArray(rule.acts)) && Purlfy.#udfOrType(rule.continue, "boolean"); case "regex": - return Array.isArray(rule.regex) && Array.isArray(rule.replace) && this.#udfOrType(rule.continue, "boolean") && rule.regex.length === rule.replace.length; + return Array.isArray(rule.regex) && Array.isArray(rule.replace) && Purlfy.#udfOrType(rule.continue, "boolean") && rule.regex.length === rule.replace.length; case "redirect": - return this.fetchEnabled && this.#udfOrType(rule.ua, "string") && this.#udfOrType(rule.continue, "boolean"); + return this.fetchEnabled && Purlfy.#udfOrType(rule.ua, "string") && Purlfy.#udfOrType(rule.continue, "boolean"); case "visit": - return this.fetchEnabled && this.#udfOrType(rule.ua, "string") && (rule.acts === undefined || Array.isArray(rule.acts)) && this.#udfOrType(rule.continue, "boolean"); + return this.fetchEnabled && Purlfy.#udfOrType(rule.ua, "string") && (rule.acts === undefined || Array.isArray(rule.acts)) && Purlfy.#udfOrType(rule.continue, "boolean"); case "lambda": - return this.lambdaEnabled && (typeof rule.lambda === "string" || rule.lambda instanceof this.#AsyncFunction) && this.#udfOrType(rule.continue, "boolean"); + return this.lambdaEnabled && (typeof rule.lambda === "string" || rule.lambda instanceof Purlfy.#AsyncFunction) && Purlfy.#udfOrType(rule.continue, "boolean"); default: return false; } @@ -132,41 +165,28 @@ class Purlfy extends EventTarget { return null; } - #isStandard(urlObj) { // Check if the given urlObj's search string follows the standard format - return urlObj.searchParams.toString() === urlObj.search.slice(1); - } - - #applyActs(input, acts, logFunc) { // Apply the given acts to the given input - let dest = input; - for (const cmd of (acts)) { - const args = cmd.split(":"); - const name = args[0]; - const act = this.#acts[name]; - if (!act) { - logFunc("Invalid act:", cmd); - dest = null; - break; - } - try { - dest = act(dest, ...args.slice(1)); - } catch (e) { - logFunc(`Error processing input with act "${name}":`, e); - dest = null; - break; - } + #incrementStatistics(increment) { + for (const [key, value] of Object.entries(increment)) { + this.#statistics[key] += value; + } + if (typeof CustomEvent === "function") { + this.dispatchEvent(new CustomEvent("statisticschange", { + detail: increment + })); + } else { + this.dispatchEvent(new Event("statisticschange")); } - return dest; } async #applyRule(urlObj, rule, logFunc) { // Apply the given rule to the given URL object, returning the new URL object, whether to continue and the mode-specific incremental statistics const mode = rule.mode; - const increment = { ...this.#zeroStatistics }; // Incremental statistics + const increment = { ...Purlfy.#zeroStatistics }; // Incremental statistics const lengthBefore = urlObj.href.length; const paramsCntBefore = urlObj.searchParams.size; let shallContinue = false; switch (mode) { // Purifies `urlObj` based on the rule case "white": { // Whitelist mode - if (!rule.std && !this.#isStandard(urlObj)) { + if (!rule.std && !Purlfy.#isStandard(urlObj)) { logFunc("Non-standard URL search string:", urlObj.search); break; } @@ -180,7 +200,7 @@ class Purlfy extends EventTarget { break; } case "black": { // Blacklist mode - if (!rule.std && !this.#isStandard(urlObj)) { + if (!rule.std && !Purlfy.#isStandard(urlObj)) { logFunc("Non-standard URL search string:", urlObj.search); break; } @@ -203,7 +223,7 @@ class Purlfy extends EventTarget { logFunc("Parameter(s) not found:", rule.params.join(", ")); break; } - const dest = this.#applyActs(paramValue, rule.acts ?? ["url"], logFunc); + const dest = Purlfy.#applyActs(paramValue, rule.acts ?? ["url"], logFunc); if (dest && URL.canParse(dest, urlObj.href)) { // Valid URL urlObj = new URL(dest, urlObj.href); } else { // Invalid URL @@ -296,7 +316,7 @@ class Purlfy extends EventTarget { logFunc("Visit mode, but got redirected to:", r.url); urlObj = new URL(r.url, urlObj.href); } else { - const dest = this.#applyActs(html, rule.acts ?? ["regex:https?:\/\/.(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?!&\/\/=]*)"], logFunc); + const dest = Purlfy.#applyActs(html, rule.acts ?? ["regex:https?:\/\/.(?:www\.)?[-a-zA-Z0-9@%._\+~#=]{2,256}\.[a-z]{2,6}\b(?:[-a-zA-Z0-9@:%_\+.~#?!&\/\/=]*)"], logFunc); if (dest && URL.canParse(dest, urlObj.href)) { // Valid URL urlObj = new URL(dest, urlObj.href); } else { // Invalid URL @@ -314,7 +334,7 @@ class Purlfy extends EventTarget { break; } try { - const lambda = typeof rule.lambda === "string" ? new this.#AsyncFunction("url", rule.lambda) : rule.lambda; + const lambda = typeof rule.lambda === "string" ? new Purlfy.#AsyncFunction("url", rule.lambda) : rule.lambda; rule.lambda = lambda; // "Cache" the compiled lambda function urlObj = await lambda(urlObj); shallContinue = rule.continue ?? true; @@ -334,21 +354,8 @@ class Purlfy extends EventTarget { return [urlObj, shallContinue, increment]; } - #incrementStatistics(increment) { - for (const [key, value] of Object.entries(increment)) { - this.#statistics[key] += value; - } - if (typeof CustomEvent === "function") { - this.dispatchEvent(new CustomEvent("statisticschange", { - detail: increment - })); - } else { - this.dispatchEvent(new Event("statisticschange")); - } - } - async purify(originalUrl) { // Purify the given URL based on `rules` - let increment = { ...this.#zeroStatistics }; // Incremental statistics of a single purification + let increment = { ...Purlfy.#zeroStatistics }; // Incremental statistics of a single purification let shallContinue = true; let firstRule = null; let iteration = 0;