2 Star 5 Fork 2

Lemon399/TamperMonkey-CLI

forked from devaper/TamperMonkey-CLI
关闭
 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
util.mts 9.93 KB
一键复制 编辑 原始数据 按行查看 历史
Lemon399 提交于 5个月前 . feat: .meta.js
import { debug, Jest } from "./buildConfig.mts";
import type { BRule, Rule } from "./types.d.mts";
export const /** 元素规则 */
CRRE =
/^(\[\$domain=)?(~?[\w-]+(?:(?:\.[\w-]+)*\.(?:[\w-]+|\*))?(?:[,|]~?[\w-]+(?:(?:\.[\w-]+)*\.(?:[\w-]+|\*))?)*)?]?(#@?\$?\??#)([^\s+@^][^@]*(?:["'([]+.*["')\]]+)*[^@]*)\s*$/,
/** 基本规则 */
BRRE =
/^@@?(?:\/(.*[^\\])\/|(\|\|?)?(https?:\/\/)?([^\s"<>`]+?[^|]?))?\$((?:(?:~?[\w-]+(?:=[^$]+)?|_+)(?:[^\\],|$))+)/,
/** 预存 CSS */
CCRE =
/^\/\*\s(\d)(\|)?(.+?)\s\*\/\s((.+?)\s*(?:{\s*[A-Za-z-]+\s*:\s*.+}|,))\s*$/,
/** 预存注释 */
CMRE = /\/\*\s*\d.+?\s*\*\//g,
/** CSS 选择器 */
CSRE = /^(.+?)\s*{\s*[A-Za-z-]+\s*:\s*.+}\s*$/,
BROptions = [
"elemhide",
"ehide",
"specifichide",
"shide",
"generichide",
"ghide",
] as const,
CRFlags = [
"##",
"#@#",
"#?#",
"#@?#",
"#$#",
"#@$#",
"#$?#",
"#@$?#",
] as const,
styleBoxes = [
"genHideCss",
"genExtraCss",
"spcHideCss",
"spcExtraCss",
] as const;
/**
* 处理 禁用元素隐藏规则
* @param rule 禁用元素隐藏规则
* @returns 失败返回 null,成功返回 { rule: BRule; bad: boolean }
*/
export function bRuleSpliter(
rule: string,
): { rule: BRule; bad: boolean } | null {
const group = BRRE.exec(rule);
if (!group) {
return null;
}
const [, regex, pipe, proto, body, option] = group,
options = option.split(","),
separatorChar = String.raw`[^\w\.%-]`,
anyChar = '(?:[^\\s"<>`]*)',
eh = hasSome(options, ["elemhide", "ehide"]),
sh = hasSome(options, ["specifichide", "shide"]),
gh = hasSome(options, ["generichide", "ghide"]);
let domains: string[] = [];
for (const opt of options) {
if (opt.startsWith("domain=")) {
domains = opt.slice(7).split("|");
}
}
let match: string | string[] = "";
if (debug && Jest) {
console.info(pipe, proto, body, regex);
}
if (regex) {
match = regex;
} else if (body) {
match += pipe
? proto
? `^${proto}`
: `^https?://(?:[\\w-]+\\.)*?`
: `^${anyChar}`;
match += body
.replaceAll(/[$()+.[\\\]{}-]/g, String.raw`\$&`)
.replaceAll("^", "$^")
.replace(/\|$/, "$")
.replaceAll("|", String.raw`\|`)
.replace(/\*$/, "")
.replaceAll("*", anyChar)
.replace(/\$\^$/, `(?:${separatorChar}.*|$)`)
.replaceAll("$^", separatorChar);
} else if (domains.length > 0) {
match = domains;
}
return {
rule: {
rule,
match,
level: eh || (gh && sh) ? 3 : sh ? 2 : gh ? 1 : 0,
},
bad: options.includes("badfilter"),
};
}
/**
* 判断是否为禁用元素隐藏规则
* @param {string} rule ABP 规则
* @returns {boolean} 判断结果
*/
export function isBasicRule(rule: string): boolean {
return BRRE.test(rule) && hasSome(rule, BROptions);
}
/**
* 检查 BRule 对象是否匹配应用地址
* @param {?BRule} rule BRule 对象
* @param {string=} url 应用地址
* @returns {number} 应用级别,不匹配返回 0
*/
export function bRuleParser(
rule?: BRule,
url: string = location.href,
): BRule["level"] {
if (debug && Jest) {
console.info(
`${
Array.isArray(rule?.match) ? rule.match.toString() : (rule?.match ?? "")
}\n${url}`,
);
}
return rule
? (Array.isArray(rule.match) && domainChecker(rule.match)[0]) ||
(!Array.isArray(rule.match) && new RegExp(rule.match).test(url))
? rule.level
: 0
: 0;
}
/**
* 裁剪提取 ETag
* @param {?string} header 请求头中的 ETag 属性字符串
* @returns {?string} ETag 属性字符串,未找到返回 null
*/
export function getEtag(header?: string): string | null {
let result: RegExpMatchArray | null = null;
if (!header) {
return null;
}
for (const re of [
/[Ee|][Tt|]ag[:=]\s?\[?(?:W\/)?"(?:gz\[)?(\w+)]?"]?/,
// 海阔世界
/^(?:W\/)?"(?:gz\[)?(\w+)]?"/,
]) {
result ??= header.match(re);
}
return result?.[1] ?? null;
}
/**
* 检查 ABP 域名范围是否匹配当前域名
* @param {string[]} domains 一组 ABP 域名
* @param {string=} currDomain 当前域名
* @returns {boolean[]} [ 是否匹配, 是否是通用规则 ]
*/
export function domainChecker(
domains: string[],
currentDomain: string = location.hostname,
): [boolean, boolean] {
type Result = [number, boolean];
const results: Result[] = [],
invResults: Result[] = [],
urlSuffix = /\.+?[\w-]+$/.exec(currentDomain)?.[0];
let totalResult: Result = [0, false],
black = false,
white = false,
match = false;
for (let domain of domains) {
const invert = domain.startsWith("~");
if (invert) {
domain = domain.slice(1);
}
if (domain.endsWith(".*") && urlSuffix) {
domain = domain.replace(".*", urlSuffix);
}
const result = currentDomain.endsWith(domain);
if (invert) {
if (result) {
white = true;
}
invResults.push([domain.length, !result]);
} else {
if (result) {
black = true;
}
results.push([domain.length, result]);
}
}
if (results.length > 0 && !black) {
match = false;
} else if (invResults.length > 0 && !white) {
match = true;
} else {
for (const r of results) {
if (r[0] >= totalResult[0] && r[1]) {
totalResult = r;
}
}
for (const r of invResults) {
if (r[0] >= totalResult[0] && !r[1]) {
totalResult = r;
}
}
match = totalResult[1];
}
return [match, results.length === 0];
}
/**
* 检查“句子”内容或“给定单词”中是否存在任一“匹配单词”
* @param {(string | string[])} str 一个“句子”或一组“给定单词”
* @param {string[]} arr 一组“匹配单词”
* @returns {boolean} 结果
*/
export function hasSome(
string_: string | string[],
array: readonly string[],
): boolean {
return array.some((word) => string_.includes(word));
}
/**
* 处理 ABP 元素隐藏规则
* @param {string} rule ABP 元素隐藏规则
* @returns {(Rule | undefined)} Rule 对象,失败返回 undefined
*/
export function ruleLoader(rule: string): Rule | undefined {
if (
hasSome(rule, [
":matches-path(",
":min-text-length(",
":watch-attr(",
":-abp-properties(",
":matches-property(",
])
) {
return;
}
rule = rule.trim();
// 如果 #$# 不包含 {} 就排除
// 可以尽量排除 Snippet Filters
if (
/(?:\w|\*|]|^)#\$#/.test(rule) &&
!/{\s*[A-Za-z-]+\s*:\s*.+}\s*$/.test(rule)
) {
return;
}
// ## -> #?#
if (
/(?:\w|\*|]|^)#@?\$?#/.test(rule) &&
hasSome(rule, [
":has(",
":-abp-has(",
"[-ext-has=",
":has-text(",
":contains(",
":-abp-contains(",
"[-ext-contains=",
":matches-css(",
"[-ext-matches-css=",
":matches-css-before(",
"[-ext-matches-css-before=",
":matches-css-after(",
"[-ext-matches-css-after=",
":matches-attr(",
":nth-ancestor(",
":upward(",
":xpath(",
":remove()",
":not(",
])
) {
rule = rule.replace(/(\w|\*|]|^)#(@?\$?)#/, "$1#$2?#");
}
// :style(...) 转换
// example.com#?##id:style(color: red)
// example.com#$?##id { color: red }
if (rule.includes(":style(")) {
rule = rule
.replace(/(\w|\*|]|^)#(@?)(\??)#/, "$1#$2$$$3#")
.replace(/:style\(\s*/, " { ")
.replace(/\s*\)$/, " }");
}
// 解构
const group = CRRE.exec(rule);
if (group) {
const [, isDomain, place = "*", flag, sel] = group,
type = CRFlags.indexOf(flag as (typeof CRFlags)[number]),
[match, generic] =
place === "*"
? [true, true]
: domainChecker(place.split(isDomain ? "|" : ","));
if (sel && match) {
return {
black: type % 2 ? "white" : "black",
type: Math.floor(type / 2) as 0 | 1 | 2 | 3,
place: (isDomain ? "|" : "") + place,
generic,
sel,
};
}
}
}
/**
* 转换 Rule 对象为 CSS 规则或选择器
* @param {Rule} rule Rule 对象
* @param {string} preset 默认 CSS 声明,需要带 {}
* @param {boolean} full css 值,`true` CSS 规则,`false` 选择器带逗号
* @returns 返回如下对象
* ```ts
* type cssO = {
* // CSS 规则或选择器
* css: string;
* // 选择器
* sel: string;
* // Rule 对象中是否包含 CSS 声明
* isStyle: boolean;
* }
* ```
*/
export function ruleToCss(
rule: Rule,
preset: string,
full: boolean,
): { css: string; sel: string; isStyle: boolean } {
const isStyle = /}\s*$/.test(rule.sel);
return {
css: `/* ${String(rule.type)}${rule.place} */ ${
rule.sel +
(isStyle
? ""
: full
? " " + preset.replaceAll(/^\s{2,}/g, " ").replaceAll("\n", "")
: ",")
} \n`,
sel: isStyle ? (CSRE.exec(rule.sel)?.[1] ?? rule.sel) : rule.sel,
isStyle,
};
}
/**
* 转换 CSS 规则为 ABP 规则 AdGuard 格式
* @param {string} css CSS 规则
* @returns 返回如下对象,失败返回 null
* ```ts
* type abpO = {
* // ABP 规则
* abp: string;
* // 选择器
* sel: string;
* // 规则类型
* type: 0 | 1 | 2 | 3;
* }
* ```
*/
export function cssToAbp(
css: string,
): { abp: string; type: Rule["type"]; sel: string } | null {
const flags = ["##", "#?#", "#$#", "#$?#"];
const [, typeString, isDomain, place, style, sel] = CCRE.exec(css) ?? [];
if (typeString === void 0) {
return null;
}
const type = Number.parseInt(typeString) as Rule["type"];
return {
abp: `${place === "*" ? "" : isDomain ? `[$domain=${place}]` : place}${
flags[type]
}${type >= 2 ? style : sel}`,
type,
sel,
};
}
/**
* 给 URL 添加时间戳,防止缓存
* @see https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest_API/Using_XMLHttpRequest#%E7%BB%95%E8%BF%87%E7%BC%93%E5%AD%98
* @param {string} url 原始 URL
* @returns {string} 处理后的 URL
*/
export function addTimeParameter(url: string): string {
return url + (url.includes("?") ? "&" : "?") + String(Date.now());
}
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
NodeJS
1
https://gitee.com/lemon399/tampermonkey-cli.git
git@gitee.com:lemon399/tampermonkey-cli.git
lemon399
tampermonkey-cli
TamperMonkey-CLI
master

搜索帮助