// ==UserScript== // @name AdBlock Script for WebView // @name:zh-CN 套壳油猴的广告拦截脚本 // @author Lemon399 // @version 2.1.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 // @match *://*/* // @resource jiekouAD https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt // @resource abpmerge https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/abpmerge.txt // @run-at document-start // @grant GM_getValue // @grant GM_deleteValue // @grant GM_setValue // @grant unsafeWindow // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @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 // @history 2.0.1 兼容 Tampermonkey 4.18,代码兼容改为 ES6 // @history 2.0.2 修复多个 iframe 首次执行重复下载规则,改进清空功能 // @history 2.0.3 继续改进清空功能 // @history 2.1.0 @resource 内置规则,兼容 X 和 Via // @downloadURL none // ==/UserScript== (function (tm, ExtendedCss) { "use strict"; function _interopDefaultLegacy(e) { return e && typeof e === "object" && "default" in e ? e : { default: e }; } var ExtendedCss__default = /*#__PURE__*/ _interopDefaultLegacy(ExtendedCss); 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 = [ "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt", "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/abpmerge.txt", ], defaultRules = ` ! 没有 ## #@# #?# #@?# ! #$# #@$# #$?# #@$?# 的行和 ! 开头为 ! 的行会忽略 ! ! 由于语法限制,内置规则中 ! 一个反斜杠需要改成两个,像这样 \\ ! ! 若要修改地址,请注意同步修改 ! 头部的 @connect 和 @resource baidu.com##.ec_wise_ad `; const id = "placeholder"; function runOnce(fn, key) { const uniqId = "BEXT_UNIQ_ID_" + id + (key ? key : ""); if (uniqId in window) return; window[uniqId] = true; fn === null || fn === void 0 ? void 0 : fn.call(null); } function isObj(o) { return ( typeof o == "object" && (o === null || o === void 0 ? void 0 : o.toString()) === "[object Object]" ); } function runNeed(condition, fn, option, ...args) { let ok = false, sleep = (time) => { return new Promise((r) => setTimeout(r, time)); }, defaultOption = { count: 20, delay: 200, failFn: () => null, }; if (isObj(option)) Object.assign(defaultOption, option); new Promise(async (resolve, reject) => { for (let c = 0; !ok && c < defaultOption.count; c++) { await sleep(defaultOption.delay); ok = condition.call(null, c + 1); } ok ? resolve() : reject(); }).then(fn.bind(null, ...args), defaultOption.failFn); } `BEXT_LAST_CHECK_KEY_${id}`; function getName(path) { const reer = /\/([^\/]+)$/.exec(path); return reer ? reer[1] : null; } function getEtag(header) { const reer = /etag: \"(\w+)\"/.exec(header); const reerVia = /Etag: \[\"(\w+)\"\]/.exec(header); return reer ? reer[1] : reerVia ? reerVia[1] : null; } function getDay(date) { const reer = /\/(\d{1,2}) /.exec(date); return reer ? parseInt(reer[1]) : 0; } function makeRuleBox() { return { black: [], white: [], apply: "", }; } function domainChecker(domains) { const results = [], hasTLD = /\.+?[\w-]+$/, urlSuffix = hasTLD.exec(location.hostname); let invert = false, result = false, mostMatch = { long: 0, result: undefined, }; domains.forEach((domain) => { if (domain.endsWith(".*") && Array.isArray(urlSuffix)) { domain = domain.replace(".*", urlSuffix[0]); } if (domain.startsWith("~")) { invert = true; domain = domain.slice(1); } else invert = false; 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 ruleChecker(matches) { const index = matches.findIndex((i) => i !== null); if ( index >= 0 && (!matches[index][1] || domainChecker(matches[index][1].split(","))) ) { return [index % 2 == 0, Math.floor(index / 2), matches[index].pop()]; } } function extraChecker(sel) { const unsupported = [ ":matches-path(", ":min-text-length(", ":watch-attr(", ":style(", ]; let pass = true; unsupported.forEach((cls) => { if (sel.indexOf(cls) >= 0) pass = false; }); return pass; } function ruleSpliter(rule) { const result = ruleChecker([ rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?##([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@#([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\?#([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\?#([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$#([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$#([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$\?#([^\s^+].*)/ ), rule.match( /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$\?#([^\s^+].*)/ ), ]); if (result && result[2] && extraChecker(result[2])) { return { black: result[0], type: result[1], sel: result[2], }; } } const selectors = makeRuleBox(), extSelectors = makeRuleBox(), styles = makeRuleBox(), extStyles = makeRuleBox(), values = { get black() { const v = tm.GM_getValue("ajs_disabled_domains", ""); return typeof v == "string" ? v : ""; }, set black(v) { v === null ? tm.GM_deleteValue("ajs_disabled_domains") : tm.GM_setValue("ajs_disabled_domains", v); }, get rules() { const v = tm.GM_getValue("ajs_saved_abprules", "{}"); return typeof v == "string" ? JSON.parse(v) : {}; }, set rules(v) { v === null ? tm.GM_deleteValue("ajs_saved_abprules") : tm.GM_setValue("ajs_saved_abprules", JSON.stringify(v)); }, get time() { const v = tm.GM_getValue("ajs_rules_ver", "0/0/0 0:0:0"); return typeof v == "string" ? v : "0/0/0 0:0:0"; }, set time(v) { v === null ? tm.GM_deleteValue("ajs_rules_ver") : tm.GM_setValue("ajs_rules_ver", v); }, get etags() { const v = tm.GM_getValue("ajs_rules_etags", "{}"); return typeof v == "string" ? JSON.parse(v) : {}; }, set etags(v) { v === null ? tm.GM_deleteValue("ajs_rules_etags") : tm.GM_setValue("ajs_rules_etags", JSON.stringify(v)); }, }, data = { disabled: false, updating: false, receivedRules: "", allRules: "", genericStyle: document.createElement("style"), presetCss: " {display: none !important;width: 0 !important;height: 0 !important;} ", supportedCount: 0, appliedCount: 0, isFrame: unsafeWindow.self !== unsafeWindow.top, isClean: false, mutex: "__lemon__abp__parser__$__", debug: false, }, 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() { return data.isClean ? "已清空,点击刷新重新加载规则" : `点击清空: ${data.appliedCount} / ${data.supportedCount} / ${ data.allRules.split("\n").length }`; }, }, }; function gmMenu(name, cb) { if ( typeof tm.GM_registerMenuCommand !== "function" || typeof tm.GM_unregisterMenuCommand !== "function" || data.isFrame ) return false; const id = menus[name].id; if (typeof id !== "undefined") { tm.GM_unregisterMenuCommand(id); menus[name].id = undefined; } if (typeof cb == "function") { menus[name].id = tm.GM_registerMenuCommand(menus[name].text, cb); } return typeof menus[name].id !== "undefined"; } function promiseXhr(details) { let loaded = false; return new Promise((resolve, reject) => { tm.GM_xmlhttpRequest( Object.assign( { onload(e) { loaded = true; resolve(e); }, onabort: reject.bind(null, "abort"), onerror: reject.bind(null, "error"), ontimeout: reject.bind(null, "timeout"), onreadystatechange(e) { // X 浏览器超时中断 if (e.readyState === 4) { setTimeout(() => { if (!loaded) reject("X timeout"); }, 300); } // Via 浏览器超时中断,不给成功状态,3 秒没反应就放弃... if (e.readyState === 3) { setTimeout(() => { if (!loaded) reject("Via timeout"); }, 3000); } }, }, details ) ); }).catch((r) => {}); } function storeRule(name, resp) { const savedRules = values.rules, savedEtags = values.etags; if (resp.responseHeaders) { const etag = getEtag(resp.responseHeaders); if (etag) { savedEtags[name] = etag; values.etags = savedEtags; } } if (resp.responseText) { savedRules[name] = resp.responseText; values.rules = savedRules; } } function fetchRule(url) { var _a; const name = (_a = getName(url)) !== null && _a !== void 0 ? _a : `${url.length}.${url.slice(-5)}`; return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { if (!name) reject(); const headResp = yield promiseXhr({ method: "HEAD", responseType: "text", url: url, }); if (!headResp) { reject(); } else { if (headResp.responseText) { storeRule(name, headResp); resolve(); } else { if (headResp.responseHeaders) { const etag = getEtag(headResp.responseHeaders), savedEtags = values.etags; if (etag !== savedEtags[name]) { const getResp = yield promiseXhr({ method: "GET", responseType: "text", url: url, }); if (getResp) { storeRule(name, getResp); resolve(); } else reject(); } else reject(); } } } }) ); } function fetchRules() { return __awaiter(this, void 0, void 0, function* () { data.updating = true; gmMenu("update", () => undefined); for (const url of onlineRules) yield fetchRule(url).catch((r) => {}); values.time = new Date().toLocaleString("zh-CN"); gmMenu("count", cleanRules); initRules(); }); } function performUpdate(force) { if (force) { return fetchRules(); } else { return getDay(values.time) !== new Date().getDate() ? fetchRules() : Promise.resolve(); } } function switchDisabledStat() { const disaList = values.black.split(","), disaResult = disaList.includes(location.hostname); data.disabled = !disaResult; if (data.disabled) { disaList.push(location.hostname); } else { disaList.splice(disaList.indexOf(location.hostname), 1); } values.black = disaList.join(","); gmMenu("disable", switchDisabledStat); } function checkDisableStat() { const disaResult = values.black.split(",").includes(location.hostname); data.disabled = disaResult; gmMenu("disable", switchDisabledStat); return disaResult; } function initRules() { const abpRules = values.rules; if (typeof tm.GM_getResourceText == "function") { onlineRules.forEach((url) => { const ruleName = getName(url), resName = ruleName.split(".")[0], resRule = tm.GM_getResourceText(resName); if (resRule && !abpRules[ruleName]) abpRules[ruleName] = resRule; }); } const abpKeys = Object.keys(abpRules); abpKeys.forEach((name) => { data.receivedRules += "\n" + abpRules[name] + "\n"; }); data.allRules = defaultRules + data.receivedRules; if (abpKeys.length !== 0) { data.updating = false; gmMenu("update", () => __awaiter(this, void 0, void 0, function* () { yield performUpdate(true); location.reload(); }) ); } return data.receivedRules.length; } function styleApply() { const css = styles.apply + (selectors.apply.length > 0 ? selectors.apply + data.presetCss : ""), ecss = extStyles.apply + (extSelectors.apply.length > 0 ? extSelectors.apply + data.presetCss : ""); if (css.length > 0) { if (typeof tm.GM_addStyle == "function") { tm.GM_addStyle(css); } else { runNeed( () => !!document.documentElement, () => { data.genericStyle.textContent = css; document.documentElement.appendChild(data.genericStyle); } ); } } if (ecss.length > 0) new ExtendedCss__default.default({ styleSheet: ecss }).apply(); } function cleanRules() { if (confirm("是否清空存储规则 ?")) { values.rules = {}; values.time = "0/0/0 0:0:0"; values.etags = {}; data.appliedCount = 0; data.supportedCount = 0; data.allRules = ""; data.isClean = true; gmMenu("update"); gmMenu("count", () => location.reload()); } } function parseRules() { [selectors, extSelectors].forEach((obj) => { obj.black .filter((v) => !obj.white.includes(v)) .forEach((sel) => { obj.apply += `${obj.apply.length == 0 ? "" : ","}${sel}`; data.appliedCount++; }); }); [styles, extStyles].forEach((obj) => { obj.black .filter((v) => !obj.white.includes(v)) .forEach((sel) => { obj.apply += ` ${sel}`; data.appliedCount++; }); }); gmMenu("count", cleanRules); styleApply(); } function main() { return __awaiter(this, void 0, void 0, function* () { if (checkDisableStat() || (initRules() === 0 && data.isFrame)) return; if (data.receivedRules.length === 0) yield performUpdate(true); data.allRules.split("\n").forEach((rule) => { const ruleObj = ruleSpliter(rule); let arr = ""; if (typeof ruleObj !== "undefined") { arr = ruleObj.black ? "black" : "white"; switch (ruleObj.type) { case 0: selectors[arr].push(ruleObj.sel); break; case 1: extSelectors[arr].push(ruleObj.sel); break; case 2: styles[arr].push(ruleObj.sel); break; case 3: extStyles[arr].push(ruleObj.sel); break; } data.supportedCount++; } }); parseRules(); performUpdate(false); }); } runOnce(main, data.mutex); })( { GM_getValue, GM_deleteValue, GM_setValue, unsafeWindow, GM_registerMenuCommand, GM_unregisterMenuCommand, GM_xmlhttpRequest, GM_getResourceText, GM_addStyle, }, ExtendedCss );