// ==UserScript==
// @name AdBlock Script for WebView
// @name:zh-CN 套壳油猴的广告拦截脚本.副本
// @author Lemon399
// @version 2.3.0
// @description Parse ABP Cosmetic rules to CSS and apply it.
// @description:zh-CN 将 ABP 中的元素隐藏规则转换为 CSS 使用
// @require https://greasyfork.org/scripts/452263-extended-css/code/extended-css.js?version=1099366
// @resource jiekouAD https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt
// @resource CSSRule https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/CSSRule.txt
// @match *://*/*
// @run-at document-start
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant GM_addStyle
// @namespace https://lemon399-bitbucket-io.vercel.app/
// @source https://gitee.com/lemon399/tampermonkey-cli/tree/master/projects/abp_parse
// @connect code.gitlink.org.cn
// @copyright GPL-3.0
// @license GPL-3.0
// ==/UserScript==
(function (vm, ExtendedCss) {
"use strict";
function __awaiter(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P
? value
: new P(function (resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done
? resolve(result.value)
: adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
}
const onlineRules = [];
onlineRules.push(
{
标识: "jiekouAD",
地址: "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
在线更新: !!1,
筛选后存储: !!1,
},
{
标识: "CSSRule",
地址: "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/CSSRule.txt",
在线更新: !!1,
筛选后存储: !!0,
}
);
let defaultRules = `
! 没有 ## #@# #?# #@?#
! #$# #@$# #$?# #@$?# 的行和
! 开头为 ! 的行会忽略
!
! 由于语法限制,内置规则中
! 一个反斜杠需要改成两个,像这样 \\
!
! 若要修改地址,请注意同步修改
! 头部的 @connect 和 @resource
`;
const ruleRE = [
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?##([^\s^+].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@#([^\s+].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\?#([^\s].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\?#([^\s].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\$#([^\s].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\$#([^\s].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\$\?#([^\s].*)/,
/^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\$\?#([^\s].*)/,
];
function findMatches(string, res) {
let result = [-1, null];
res.forEach((re, i) => {
const match = string.match(re);
if (match) result = [i, match];
});
return result;
}
function getEtag(header) {
const result = findMatches(header, [
/(e|E)tag: \"(\w+)\"/,
// WebMonkey 系
/(e|E)tag: \[\"(\w+)\"\]/,
// 书签地球
/(e|E)tag=\"(\w+)\"/,
]);
return result[1] ? result[1][2] : null;
}
function makeRuleBox() {
return {
black: [],
white: [],
};
}
function domainChecker(domains) {
const results = [],
urlSuffix = /\.+?[\w-]+$/.exec(location.hostname);
let mostMatch = {
long: 0,
result: false,
};
domains.forEach((domain) => {
if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
domain = domain.replace(".*", urlSuffix[0]);
}
const invert = domain[0] == "~";
if (invert) domain = domain.slice(1);
const result = location.hostname.endsWith(domain);
results.push(result !== invert);
if (result) {
if (domain.length > mostMatch.long) {
mostMatch = {
long: domain.length,
result: result !== invert,
};
}
}
});
return mostMatch.long > 0 ? mostMatch.result : results.includes(true);
}
function hasSome(str, arr) {
return arr.some((word) => str.includes(word));
}
function ruleSpliter(rule) {
const result = findMatches(rule, ruleRE),
group = result[1];
if (group && (!group[1] || domainChecker(group[1].split(",")))) {
const sel = group.pop();
if (sel) {
return {
black: result[0] % 2 ? "white" : "black",
type: Math.floor(result[0] / 2),
sel,
};
}
}
}
function ruleLoader(rule) {
if (
hasSome(rule, [
":matches-path(",
":min-text-length(",
":watch-attr(",
":-abp-properties(",
":matches-property(",
])
)
return;
// 如果 #$# 不包含 {} 就排除
// 可以尽量排除 Snippet Filters
if (/(\w|^)#\$#/.test(rule) && !/{.+}/.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(",
"if-not(",
])
) {
rule = rule.replace(/(\w|^)##/, "$1#?#").replace(/(\w|^)#@#/, "$1#@?#");
}
// :style(...) 转换
// example.com#?##id:style(color: red)
// example.com#$?##id { color: red }
if (rule.includes(":style(")) {
rule = rule
.replace(/(\w|^)##/, "$1#$#")
.replace(/(\w|^)#@#/, "$1#@$#")
.replace(/(\w|^)#\?#/, "$1#$?#")
.replace(/(\w|^)#@\?#/, "$1#@$?#")
.replace(/:style\(/, " { ")
.replace(/\)$/, " }");
}
return ruleSpliter(rule);
}
function storAutoClean() {
const vars = [
"ajs_disabled_domains",
"ajs_saved_abprules",
"ajs_rules_etags",
"ajs_rules_ver",
],
stor = vm.unsafeWindow.localStorage;
vars.forEach((key) => {
if (stor.getItem(key)) {
stor.removeItem(key);
}
});
}
const selectors = makeRuleBox(),
extSelectors = makeRuleBox(),
styles = makeRuleBox(),
extStyles = makeRuleBox(),
values = {
get black() {
const arrStr = gmValue("get", false, "ajs_disabled_domains", "");
return typeof arrStr == "string" && arrStr.length > 0
? arrStr.split(",")
: [];
},
set black(v) {
gmValue(
"set",
false,
"ajs_disabled_domains",
v === null || v === void 0 ? void 0 : v.join()
);
},
get rules() {
return gmValue("get", true, "ajs_saved_abprules", {});
},
set rules(v) {
gmValue("set", true, "ajs_saved_abprules", v);
},
get css() {
return gmValue("get", true, `ajs_saved_styles_${location.hostname}`, {
needUpdate: true,
hideCss: "",
extraCss: "",
});
},
set css(v) {
gmValue("set", true, `ajs_saved_styles_${location.hostname}`, v);
},
get hasSave() {
const arrStr = gmValue("get", false, "ajs_hasSave_domains", "");
return typeof arrStr == "string" && arrStr.length > 0
? arrStr.split(",")
: [];
},
set hasSave(v) {
gmValue(
"set",
false,
"ajs_hasSave_domains",
v === null || v === void 0 ? void 0 : v.join()
);
},
get time() {
return gmValue("get", false, "ajs_rules_ver", "0/0/0 0:0:0");
},
set time(v) {
gmValue("set", false, "ajs_rules_ver", v);
},
get etags() {
return gmValue("get", true, "ajs_rules_etags", {});
},
set etags(v) {
gmValue("set", true, "ajs_rules_etags", v);
},
},
data = {
disabled: false,
saved: false,
update: true,
updating: false,
receivedRules: "",
allRules: "",
presetCss:
" {display: none !important;width: 0 !important;height: 0 !important;} ",
hideCss: "",
extraCss: "",
appliedCount: 0,
isFrame: vm.unsafeWindow.self !== vm.unsafeWindow.top,
isClean: false,
mutex: "__lemon__abp__parser__$__",
timeout: 6000,
xTimeout: 700,
},
menus = {
disable: {
id: undefined,
get text() {
return data.disabled ? "在此网站启用拦截" : "在此网站禁用拦截";
},
},
update: {
id: undefined,
get text() {
const time = values.time;
return data.updating
? "正在更新..."
: `点击更新: ${time.slice(0, 1) === "0" ? "未知时间" : time}`;
},
},
count: {
id: undefined,
get text() {
const cssCount = (data.hideCss + data.extraCss).match(/,/g);
return data.isClean
? "已清空,点击刷新重新加载规则"
: `${
data.saved
? "CSS: " +
(cssCount === null || cssCount === void 0
? void 0
: cssCount.length)
: "规则: " +
data.appliedCount +
"/" +
data.allRules.split("\n").length
},点击清空规则`;
},
},
};
function gmMenu(name, cb) {
if (
typeof vm.GM_registerMenuCommand != "function" ||
typeof vm.GM_unregisterMenuCommand != "function" ||
data.isFrame
)
return;
if (typeof menus[name].id != "undefined") {
vm.GM_unregisterMenuCommand(menus[name].id);
menus[name].id = undefined;
}
if (typeof cb == "function") {
menus[name].id = vm.GM_registerMenuCommand(menus[name].text, cb);
}
}
function gmValue(action, json, key, value) {
switch (action) {
case "get":
let v;
try {
v = vm.GM_getValue(key, json ? JSON.stringify(value) : value);
} catch (error) {
return;
}
return json && typeof v == "string" ? JSON.parse(v) : v;
case "set":
try {
value === null || value === undefined
? vm.GM_deleteValue(key)
: vm.GM_setValue(key, json ? JSON.stringify(value) : value);
} catch (error) {
vm.GM_deleteValue(key);
}
break;
}
}
function promiseXhr(details) {
return __awaiter(this, void 0, void 0, function* () {
let loaded = false;
try {
return yield new Promise((resolve, reject) => {
vm.GM_xmlhttpRequest(
Object.assign(
{
onload(e) {
loaded = true;
resolve(e);
},
onabort: reject.bind(null, "abort"),
onerror(e) {
reject({
error: "error",
resp: e,
});
},
ontimeout: reject.bind(null, "timeout"),
onreadystatechange(e) {
// X 浏览器超时中断
if (e.readyState === 4) {
setTimeout(() => {
if (!loaded)
reject({
error: "X timeout",
resp: e,
});
}, data.xTimeout);
}
// Via 浏览器超时中断,不给成功状态...
if (e.readyState === 3) {
setTimeout(() => {
if (!loaded)
reject({
error: "Via timeout",
resp: e,
});
}, data.timeout);
}
},
timeout: data.timeout,
},
details
)
);
});
} catch (error) {}
});
}
function storeRule(rule, resp) {
const savedRules = values.rules,
savedEtags = values.etags;
if (resp.responseHeaders) {
const etag = getEtag(resp.responseHeaders);
if (etag) {
savedEtags[rule.标识] = etag;
values.etags = savedEtags;
}
}
if (resp.responseText) {
if (rule.筛选后存储) {
let parsed = "";
resp.responseText.split("\n").forEach((rule) => {
if (ruleRE.some((re) => re.test(rule))) parsed += rule + "\n";
});
savedRules[rule.标识] = parsed;
} else {
savedRules[rule.标识] = resp.responseText;
}
values.rules = savedRules;
if (Object.keys(values.rules).length === 0) {
data.receivedRules += "\n" + savedRules[rule.标识] + "\n";
}
}
}
function fetchRuleBody(rule) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
const getResp = yield promiseXhr({
method: "GET",
responseType: "text",
url: rule.地址,
});
if (
getResp &&
(getResp === null || getResp === void 0
? void 0
: getResp.responseText) &&
((_a = getResp.responseText) === null || _a === void 0
? void 0
: _a.length) > 0
) {
storeRule(rule, getResp);
return true;
} else return false;
});
}
function fetchRule(rule) {
return new Promise((resolve, reject) =>
__awaiter(this, void 0, void 0, function* () {
var _a, _b, _e;
const headResp = yield promiseXhr({
method: "HEAD",
responseType: "text",
url: rule.地址,
});
if (!headResp) {
reject("HEAD 失败");
} else {
const etag = getEtag(
typeof headResp.responseHeaders == "string"
? headResp.responseHeaders
: (_b = (_a = headResp).getAllResponseHeaders) === null ||
_b === void 0
? void 0
: _b.call(_a)
),
savedEtags = values.etags;
if (
(headResp === null || headResp === void 0
? void 0
: headResp.responseText) &&
((_e = headResp.responseText) === null || _e === void 0
? void 0
: _e.length) > 0
) {
storeRule(rule, headResp);
!etag || etag !== savedEtags[rule.标识]
? resolve()
: reject("ETag 一致");
} else {
if (!etag || etag !== savedEtags[rule.标识]) {
(yield fetchRuleBody(rule)) ? resolve() : reject("GET 失败");
} else reject("ETag 一致");
}
}
})
);
}
function fetchRules(apply) {
return __awaiter(this, void 0, void 0, function* () {
const has = values.hasSave;
let hasUpdate = onlineRules.length;
data.updating = true;
gmMenu("update", () => undefined);
for (const rule of onlineRules) {
if (rule.在线更新) {
yield fetchRule(rule).catch((error) => {
hasUpdate--;
});
}
}
values.time = new Date().toLocaleString("zh-CN");
if (has.length > 0 && hasUpdate > 0) {
has.forEach((host) => {
const save = gmValue("get", true, `ajs_saved_styles_${host}`);
save.needUpdate = true;
gmValue("set", true, `ajs_saved_styles_${host}`, save);
});
}
initRules(apply);
});
}
function performUpdate(force, apply) {
if (data.isFrame) Promise.reject();
return force || new Date(values.time).getDate() !== new Date().getDate()
? fetchRules(apply)
: Promise.resolve();
}
function switchDisabledStat() {
const disaList = values.black;
data.disabled = !disaList.includes(location.hostname);
if (data.disabled) {
disaList.push(location.hostname);
} else {
disaList.splice(disaList.indexOf(location.hostname), 1);
}
values.black = disaList;
location.reload();
}
function makeInitMenu() {
gmMenu("update", () =>
__awaiter(this, void 0, void 0, function* () {
yield performUpdate(true, false);
location.reload();
})
);
gmMenu("count", cleanRules);
}
function initRules(apply) {
const abpRules = values.rules;
if (typeof vm.GM_getResourceText == "function") {
onlineRules.forEach((rule) => {
let resRule;
try {
resRule = vm.GM_getResourceText(rule.标识);
} catch (error) {
resRule = "";
}
if (resRule && !abpRules[rule.标识]) abpRules[rule.标识] = resRule;
});
}
const abpKeys = Object.keys(abpRules);
abpKeys.forEach((name) => {
data.receivedRules += "\n" + abpRules[name] + "\n";
});
data.allRules = defaultRules + data.receivedRules;
data.updating = false;
makeInitMenu();
if (apply) splitRules();
return data.receivedRules.length;
}
function styleApply() {
if (data.hideCss.length > 0) {
if (typeof vm.GM_addStyle == "function") {
vm.GM_addStyle(data.hideCss);
} else {
const elem = document.createElement("style");
elem.textContent = data.hideCss;
document.documentElement.appendChild(elem);
}
}
if (data.extraCss.length > 0) {
new ExtendedCss({ styleSheet: data.extraCss }).apply();
}
}
function cleanRules() {
if (confirm(`是否清空存储规则 (${Object.keys(values.rules).length}) ?`)) {
const has = values.hasSave;
values.rules = {};
values.time = "0/0/0 0:0:0";
values.etags = {};
if (has.length > 0) {
has.forEach((host) => {
gmValue("set", true, `ajs_saved_styles_${host}`);
});
values.hasSave = null;
}
data.appliedCount = 0;
data.allRules = "";
data.isClean = true;
gmMenu("update");
gmMenu("count", () => location.reload());
}
}
function parseRules() {
const boxes = ["hideCss", "extraCss"];
data.hideCss = "";
data.extraCss = "";
[styles, extStyles].forEach((r, t) => {
r.black
.filter((v) => !r.white.includes(v))
.forEach((s, i, a) => {
data[boxes[t]] += `${s} `;
if (i == 0) data.appliedCount += a.length;
});
});
[selectors, extSelectors].forEach((r, t) => {
r.black
.filter((v) => !r.white.includes(v))
.forEach((s, i, a) => {
data[boxes[t]] += `${i == 0 ? "" : ","}${s}`;
if (i == a.length - 1) {
data[boxes[t]] += data.presetCss;
data.appliedCount += a.length;
}
});
});
gmMenu("count", cleanRules);
saveCss();
if (!data.saved) styleApply();
}
function splitRules() {
data.allRules.split("\n").forEach((rule) => {
const ruleObj = ruleLoader(rule),
boxes = [selectors, extSelectors, styles, extStyles];
if (typeof ruleObj != "undefined") {
if (
ruleObj.black == "black" &&
boxes[ruleObj.type].white.includes(ruleObj.sel)
)
return;
boxes[ruleObj.type][ruleObj.black].push(ruleObj.sel);
}
});
parseRules();
}
function saveCss() {
const styles = {
needUpdate: false,
hideCss: data.hideCss,
extraCss: data.extraCss,
},
has = values.hasSave;
values.css = styles;
if (!has.includes(location.hostname)) has.push(location.hostname);
values.hasSave = has;
}
function readCss() {
var _a;
const styles = values.css;
if (styles.hideCss.length > 0) {
data.saved = true;
data.update =
(_a = styles.needUpdate) !== null && _a !== void 0 ? _a : true;
data.hideCss = styles.hideCss;
data.extraCss = styles.extraCss;
return true;
} else return false;
}
function main() {
var _a, _b;
return __awaiter(this, void 0, void 0, function* () {
if (
((_b =
(_a = vm.unsafeWindow.mbrowser) === null || _a === void 0
? void 0
: _a.getVersionCode) === null || _b === void 0
? void 0
: _b.call(_a)) >= 662
)
storAutoClean();
data.disabled = values.black.includes(location.hostname);
gmMenu("disable", switchDisabledStat);
if (data.disabled) return;
readCss();
saved: {
if (data.saved) {
styleApply();
makeInitMenu();
if (!data.update) break saved;
}
if (initRules(false) === 0) yield performUpdate(true, true);
splitRules();
}
yield performUpdate(false, false);
});
}
function runOnce(key, func) {
if (key in vm.unsafeWindow) return;
vm.unsafeWindow[key] = true;
func();
}
runOnce(data.mutex, main);
})(
{
unsafeWindow: typeof unsafeWindow == "object" ? unsafeWindow : window,
GM_registerMenuCommand:
typeof GM_registerMenuCommand == "function"
? GM_registerMenuCommand
: undefined,
GM_unregisterMenuCommand:
typeof GM_unregisterMenuCommand == "function"
? GM_unregisterMenuCommand
: undefined,
GM_getValue: typeof GM_getValue == "function" ? GM_getValue : undefined,
GM_deleteValue:
typeof GM_deleteValue == "function" ? GM_deleteValue : undefined,
GM_setValue: typeof GM_setValue == "function" ? GM_setValue : undefined,
GM_xmlhttpRequest:
typeof GM_xmlhttpRequest == "function" ? GM_xmlhttpRequest : undefined,
GM_getResourceText:
typeof GM_getResourceText == "function" ? GM_getResourceText : undefined,
GM_addStyle: typeof GM_addStyle == "function" ? GM_addStyle : undefined,
},
ExtendedCss
);