// ==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 // @downloadURL none // ==/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 );