Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v0.3.1 from dev #3

Merged
merged 7 commits into from
May 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/[email protected]/<country>.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/<country>.json
const rules = await (await fetch("https://cdn.jsdelivr.net/gh/PRO-2684/[email protected]/<ruleset>.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/<ruleset>.json
purifier.importRules(rules); // Import rules
const additionalRules = {}; // You can also add your own rules
purifier.importRules(additionalRules);
Expand Down Expand Up @@ -74,7 +74,7 @@ new Purlfy({
})
```

#### Methods
#### Instance Methods

- `importRules(rules: object): void`: Import rules.
- `purify(url: string): Promise<object>`: Purify a URL.
Expand All @@ -90,14 +90,18 @@ 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`.

- `fetchEnabled: Boolean`: Whether the redirect mode is enabled.
- `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:
Expand Down Expand Up @@ -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)

Expand Down
11 changes: 8 additions & 3 deletions README_zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ new Purlfy({
})
```

#### 方法
#### 实例方法

- `importRules(rules: object): void`: 导入规则
- `purify(url: string): Promise<object>`: 净化一个 URL
Expand All @@ -90,14 +90,19 @@ new Purlfy({
- 若支持 `CustomEvent`,则其 `detail` 属性为统计数据的增量
- `removeEventListener("statisticschange", callback: function): void`: 移除统计数据变化的事件监听器

#### 属性
#### 实例属性

你可以在初始化后更改下面的属性,它们将在下次调用 `purify` 时生效。

- `fetchEnabled: Boolean`: 是否启用需要网络的模式 `redirect` 和 `visit`
- `lambdaEnabled: Boolean`: 是否启用匿名函数模式
- `maxIterations: Number`: 最大迭代次数

#### 静态属性

- `Purlfy.version: string`: pURLfy 的版本号


## 📖 规则

社区贡献的规则文件托管在 GitHub 上,您可以在 [pURLfy-rules](https://github.com/PRO-2684/pURLfy-rules) 中找到。规则文件的格式如下:
Expand Down Expand Up @@ -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)

Expand Down
145 changes: 76 additions & 69 deletions purlfy.js
Original file line number Diff line number Diff line change
@@ -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),
Expand All @@ -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();
Expand All @@ -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)) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
}
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -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;
Expand Down