// ==UserScript== // @name 套壳油猴的广告拦截脚本.副本 // @author Lemon399 // @version 2.4.0.4 // @description 将 ABP 中的元素隐藏规则转换为 CSS 使用 // @require https://greasyfork.org/scripts/452263-extended-css/code/extended-css.js?version=1124696 // @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 (tm, 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 = [], invResults = [], urlSuffix = /\.+?[\w-]+$/.exec(location.hostname); let totalResult = [0, false], black = false, white = 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); 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) { return false; } else if (invResults.length > 0 && !white) { return true; } else { results.forEach((r) => { if (r[0] >= totalResult[0] && r[1]) { totalResult = r; } }); invResults.forEach((r) => { if (r[0] >= totalResult[0] && !r[1]) { totalResult = r; } }); return totalResult[1]; } } 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(", "if-not(", ]) ) return; // 去掉开头空格 rule = rule.replace(/^ +/, ""); // 如果 #$# 不包含 {} 就排除 // 可以尽量排除 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(", ]) ) { 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 logger(type, ...data) { switch (type) { case "info": console.info(...data); break; case "warn": console.warn(...data); break; case "error": console.error(...data); break; case "data": console.table(data[0]); break; case "color": console.log( `%c ${data[0]} `, `display: inline-block; color: white; background: ${data[1]}; border-radius: .75em; padding: 2px 5px`, ...data.splice(2) ); break; case "count": console.count(data[0]); break; } } var _a, _b, _c; 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 drlen() { return gmValue("get", false, "ajs_drule_length", 0); }, set drlen(v) { gmValue("set", false, "ajs_drule_length", 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: "", customRules: defaultRules, allRules: "", presetCss: " {display: none !important;width: 0 !important;height: 0 !important;} ", hideCss: "", extraCss: "", appliedCount: 0, isFrame: tm.unsafeWindow.self !== tm.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 },点击清空规则`; }, }, export: { id: undefined, text: "导出此网站规则为独立脚本", }, }; data.customRules += "\n" + ((_c = (_b = (_a = tm.GM_info.script) === null || _a === void 0 ? void 0 : _a.options) === null || _b === void 0 ? void 0 : _b.comment) !== null && _c !== void 0 ? _c : "") + "\n"; function gmMenu(name, cb) { var _a; const id = (_a = menus[name].id) !== null && _a !== void 0 ? _a : false; if ( typeof tm.GM_registerMenuCommand != "function" || typeof tm.GM_unregisterMenuCommand != "function" || data.isFrame ) return; if (typeof id == "number") { tm.GM_unregisterMenuCommand(id); menus[name].id = undefined; } if (typeof cb == "function") { menus[name].id = tm.GM_registerMenuCommand(menus[name].text, cb); } } function gmValue(action, json, key, value) { switch (action) { case "get": let v; try { v = tm.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 ? tm.GM_deleteValue(key) : tm.GM_setValue(key, json ? JSON.stringify(value) : value); } catch (error) { tm.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) => { tm.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) return 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 tm.GM_getResourceText == "function") { onlineRules.forEach((rule) => { let resRule; try { resRule = tm.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"; }); values.drlen = data.customRules.length; data.allRules = data.customRules + data.receivedRules; data.updating = false; makeInitMenu(); if (apply) splitRules(); return data.receivedRules.length; } function styleApply() { if (data.hideCss.length > 0) { if (typeof tm.GM_addStyle == "function") { tm.GM_addStyle(data.hideCss); } else { const elem = document.createElement("style"); elem.textContent = data.hideCss; document.documentElement.appendChild(elem); } } if (data.extraCss.length > 0) { data.extraCss.split("\n").forEach((css) => { new ExtendedCss({ styleSheet: css }).apply(); }); } gmMenu("export", downScript); } function genManifest() { const header = [], tmpl = { name: `用于 ${location.hostname} 的广告拦截脚本`, namespace: "exported_css_script", version: new Date().getTime(), description: `用于 ${location.hostname} 的广告拦截脚本`, author: "You", match: `${location.protocol}//${location.hostname}/*`, grant: "GM_addStyle", "run-at": "document-start", require: "https://greasyfork.org/scripts/452263-extended-css/code/extended-css.js?version=1124696", }; let result = ""; header.push("==UserScript=="); Object.keys(tmpl).forEach((prop) => header.push(`@${prop} ${tmpl[prop]}`)); header.push("==/UserScript=="); header.forEach((line) => { result += `// ${line}\n`; }); return result; } function genScript() { return `${genManifest()} (function() { 'use strict'; const hc = "${data.presetCss}", data = { hide: ${JSON.stringify( data.hideCss.split("\n").map((v) => v.replaceAll(data.presetCss, "")), undefined, 2 )}, extra: ${JSON.stringify( data.extraCss.split("\n").map((v) => v.replaceAll(data.presetCss, "")), undefined, 2 )}, }; if (typeof GM_addStyle !== "function") { const GM_addStyle = css => { const elem = document.createElement("style"); elem.textContent = css; document.documentElement.appendChild(elem); }; } function cssApply() { GM_addStyle(data.hide.map(r => r.endsWith("} ") ? r : r + hc).join(" ")); data.extra.map(r => r.endsWith("} ") ? r : r + hc).forEach((css) => { new ExtendedCss({ styleSheet: css }).apply(); }); } cssApply(); })();`; } function downScript() { const a = document.createElement("a"), b = new Blob([genScript()]); a.href = URL.createObjectURL(b); a.download = `${location.hostname}.user.js`; Object.assign(a.style, { position: "fixed", top: "200%", }); document.body.appendChild(a); setTimeout(() => { a.click(); a.remove(); }, 0); } function cleanRules() { if (confirm(`是否清空存储规则 (${Object.keys(values.rules).length}) ?`)) { const has = values.hasSave; values.rules = {}; values.time = "0/0/0 0:0:0"; values.drlen = 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("export"); 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) => { const checkResult = ExtendedCss.validate(s.split("{")[0]); if (checkResult.ok) { data[boxes[t]] += `${s} \n`; data.appliedCount++; } else { logger( "color", "选择器检查错误:", "maroon", s.split("{")[0], checkResult.error ); } }); }); [selectors, extSelectors].forEach((r, t) => { r.black .filter((v) => !r.white.includes(v)) .forEach((s, i) => { const checkResult = ExtendedCss.validate(s); if (checkResult.ok) { data[boxes[t]] += `${i == 0 ? "" : "\n"}${s + data.presetCss}`; data.appliedCount++; } else { logger("color", "选择器检查错误:", "maroon", s, checkResult.error); } }); }); 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 = tm.unsafeWindow.mbrowser) === null || _a === void 0 ? void 0 : _a.getVersionCode) === null || _b === void 0 ? void 0 : _b.call(_a)) >= 662 ) { const vars = [ "ajs_disabled_domains", "ajs_saved_abprules", "ajs_rules_etags", "ajs_rules_ver", ], stor = tm.unsafeWindow.localStorage; vars.forEach((key) => { if (stor.getItem(key)) { stor.removeItem(key); } }); } data.disabled = values.black.includes(location.hostname); gmMenu("disable", switchDisabledStat); if (data.disabled) return; if (values.drlen === data.customRules.length) readCss(); saved: { if (data.saved) { styleApply(); makeInitMenu(); if (!data.update) break saved; } if (initRules(false) === 0) yield performUpdate(true, true); splitRules(); } try { yield performUpdate(false, false); } catch (error) { logger("warn", "iframe: ", location.href, " 取消更新"); } }); } function runOnce(key, func) { if (key in tm.unsafeWindow) return; tm.unsafeWindow[key] = true; func(); } runOnce(data.mutex, main); })( { GM_info: typeof GM_info == "object" ? GM_info : {}, 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 );