Skip to content

Commit

Permalink
Merge pull request #3 from PRO-2684/dev
Browse files Browse the repository at this point in the history
v0.3.1 from dev
  • Loading branch information
PRO-2684 authored May 11, 2024
2 parents bbcef32 + b298d55 commit 0bd3c0c
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 77 deletions.
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

0 comments on commit 0bd3c0c

Please sign in to comment.