套壳油猴的广告拦截脚本

将 ABP 中的元素隐藏规则转换为 CSS 使用

目前为 2022-10-07 提交的版本。查看 最新版本

// ==UserScript==
// @name               AdBlock Script for WebView
// @name:zh-CN         套壳油猴的广告拦截脚本
// @author             Lemon399
// @version            2.2.2
// @description        Parse ABP Cosmetic rules to CSS and apply it.
// @description:zh-CN  将 ABP 中的元素隐藏规则转换为 CSS 使用
// @require            https://gf.qytechs.cn/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
// @history            2.1.1 兼容 MDM
// @history            2.1.2 兼容 脚本猫
// @history            2.1.3 兼容 B 仔
// @history            2.1.4 兼容 Top,提高兼容能力
// @history            2.1.5 兼容 书签地球
// @history            2.2.0 更改规则地址数据类型,禁用后自动刷新页面
// @history            2.2.1 修复多个严重错误
// @history            2.2.2 转换 :style()
// ==/UserScript==

(function (vm, 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 = [
      {
        标识: "jiekouAD",
        地址: "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
        在线更新: !!1,
      },
      {
        标识: "abpmerge",
        地址: "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/abpmerge.txt",
        在线更新: !!0,
      },
    ],
    defaultRules = `
! 没有 ## #@# #?# #@?#
! #$# #@$# #$?# #@$?# 的行和
! 开头为 ! 的行会忽略
!
! 由于语法限制,内置规则中
! 一个反斜杠需要改成两个,像这样 \\
!
! 若要修改地址,请注意同步修改
! 头部的 @connect 和 @resource

`;

  function isValidConfig(obj, ref) {
    let valid = typeof obj == "object";
    if (valid)
      Object.getOwnPropertyNames(obj).forEach((k) => {
        if (!ref.hasOwnProperty(k)) valid = false;
      });
    return valid;
  }
  function sleep(time) {
    return new Promise((resolve) => setTimeout(resolve, time));
  }
  function runNeed(condition, fn, option, ...args) {
    let ok = false;
    const defaultOption = {
      count: 20,
      delay: 200,
      failFn: () => null,
    };
    if (isValidConfig(option, defaultOption))
      Object.assign(defaultOption, option);
    new Promise((resolve, reject) =>
      __awaiter(this, void 0, void 0, function* () {
        for (let c = 0; !ok && c < defaultOption.count; c++) {
          yield sleep(defaultOption.delay);
          ok = condition.call(null, c + 1);
        }
        ok ? resolve() : reject();
      })
    ).then(fn.bind(null, ...args), defaultOption.failFn);
  }
  function getEtag(header) {
    const reer = /etag: \"(\w+)\"/.exec(header);
    // WebMonkey 系
    const reerWM = /Etag: \[\"(\w+)\"\]/.exec(header);
    // 书签地球
    const reerDQ = /Etag=\"(\w+)\"/.exec(header);
    return reer ? reer[1] : reerWM ? reerWM[1] : reerDQ ? reerDQ[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 = [],
      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.startsWith("~");
      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 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) {
    let pass = true;
    [
      ":matches-path(",
      ":min-text-length(",
      ":watch-attr(",
      ":-abp-properties(",
      ":matches-property(",
    ].forEach((cls) => {
      if (sel.includes(cls)) pass = false;
    });
    return pass;
  }
  function ruleSpliter(rule) {
    // :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(/\)$/, " }");
    }
    const result = ruleChecker([
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?##([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@#([^\s+].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\?#([^\s].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\?#([^\s].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\$#([^\s].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#@\$#([^\s].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*)(,~?[\w-]+(\.[\w-]+)*(\.[\w-]+|\.\*))*)?#\$\?#([^\s].*)/
      ),
      rule.match(
        /^(~?[\w-]+(\.[\w-]+)*(\.[\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 = vm.GM_getValue("ajs_disabled_domains", "");
        return typeof v == "string" ? v : "";
      },
      set black(v) {
        v === null
          ? vm.GM_deleteValue("ajs_disabled_domains")
          : vm.GM_setValue("ajs_disabled_domains", v);
      },
      get rules() {
        let v;
        try {
          v = vm.GM_getValue("ajs_saved_abprules", "{}");
        } catch (error) {
          v = "{}";
        }
        return typeof v == "string" ? JSON.parse(v) : {};
      },
      set rules(v) {
        try {
          v === null
            ? vm.GM_deleteValue("ajs_saved_abprules")
            : vm.GM_setValue("ajs_saved_abprules", JSON.stringify(v));
        } catch (error) {
          vm.GM_deleteValue("ajs_saved_abprules");
        }
      },
      get time() {
        const v = vm.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
          ? vm.GM_deleteValue("ajs_rules_ver")
          : vm.GM_setValue("ajs_rules_ver", v);
      },
      get etags() {
        const v = vm.GM_getValue("ajs_rules_etags", "{}");
        return typeof v == "string" ? JSON.parse(v) : {};
      },
      set etags(v) {
        v === null
          ? vm.GM_deleteValue("ajs_rules_etags")
          : vm.GM_setValue("ajs_rules_etags", JSON.stringify(v));
      },
    },
    data = {
      disabled: false,
      updating: false,
      receivedRules: "",
      allRules: "",
      presetCss:
        " {display: none !important;width: 0 !important;height: 0 !important;} ",
      supportedCount: 0,
      appliedCount: 0,
      isFrame: vm.unsafeWindow.self !== vm.unsafeWindow.top,
      isClean: false,
      mutex: "__lemon__abp__parser__$__",
      debug: !!0,
      timeout: 5000,
      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() {
          return data.isClean
            ? "已清空,点击刷新重新加载规则"
            : `点击清空: ${data.appliedCount} / ${data.supportedCount} / ${
                data.allRules.split("\n").length
              }`;
        },
      },
    };
  function gmMenu(name, cb) {
    if (
      typeof vm.GM_registerMenuCommand != "function" ||
      typeof vm.GM_unregisterMenuCommand != "function" ||
      data.isFrame
    )
      return false;
    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);
    }
    return typeof menus[name].id != "undefined";
  }
  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: reject.bind(null, "error"),
                ontimeout: reject.bind(null, "timeout"),
                onreadystatechange(e_1) {
                  // X 浏览器超时中断
                  if (e_1.readyState === 4) {
                    setTimeout(() => {
                      if (!loaded) reject("X timeout");
                    }, data.xTimeout);
                  }
                  // Via 浏览器超时中断,不给成功状态...
                  if (e_1.readyState === 3) {
                    setTimeout(() => {
                      if (!loaded) reject("Via timeout");
                    }, data.timeout);
                  }
                },
                timeout: data.timeout,
              },
              details
            )
          );
        });
      } catch (error) {}
    });
  }
  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;
      if (Object.keys(values.rules).length === 0) {
        data.receivedRules += "\n" + resp.responseText + "\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, _c;
        const headResp = yield promiseXhr({
          method: "HEAD",
          responseType: "text",
          url: rule.地址,
        });
        if (!headResp) {
          reject();
        } else {
          if (
            (headResp === null || headResp === void 0
              ? void 0
              : headResp.responseText) &&
            ((_a = headResp.responseText) === null || _a === void 0
              ? void 0
              : _a.length) > 0
          ) {
            storeRule(rule.标识, headResp);
            resolve();
          } else {
            const etag = getEtag(
                typeof headResp.responseHeaders == "string"
                  ? headResp.responseHeaders
                  : (_c = (_b = headResp).getAllResponseHeaders) === null ||
                    _c === void 0
                  ? void 0
                  : _c.call(_b)
              ),
              savedEtags = values.etags;
            if (etag) {
              if (etag !== savedEtags[rule.标识]) {
                (yield fetchRuleBody(rule)) ? resolve() : reject();
              } else reject();
            } else {
              (yield fetchRuleBody(rule)) ? resolve() : reject();
            }
          }
        }
      })
    );
  }
  function fetchRules() {
    return __awaiter(this, void 0, void 0, function* () {
      data.updating = true;
      gmMenu("update", () => undefined);
      for (const rule of onlineRules) {
        rule.在线更新 && (yield fetchRule(rule).catch((error) => {}));
      }
      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(",");
    data.disabled = !disaList.includes(location.hostname);
    if (data.disabled) {
      disaList.push(location.hostname);
    } else {
      disaList.splice(disaList.indexOf(location.hostname), 1);
    }
    values.black = disaList.join(",");
    location.reload();
  }
  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 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;
    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.join(" ") +
        (selectors.apply.length > 0
          ? selectors.apply.join() + data.presetCss
          : ""),
      ecss =
        extStyles.apply.join(" ") +
        (extSelectors.apply.length > 0
          ? extSelectors.apply.join() + data.presetCss
          : "");
    if (css.length > 0) {
      if (typeof vm.GM_addStyle == "function") {
        vm.GM_addStyle(css);
      } else {
        runNeed(
          () => !!document.documentElement,
          () => {
            const elem = document.createElement("style");
            elem.textContent = css;
            document.documentElement.appendChild(elem);
          }
        );
      }
    }
    if (ecss.length > 0) {
      runNeed(
        () => !!document.documentElement,
        () => new ExtendedCss__default.default({ styleSheet: ecss }).apply()
      );
    }
  }
  function cleanRules() {
    if (confirm(`是否清空存储规则 (${Object.keys(values.rules).length}) ?`)) {
      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.push(sel);
          data.appliedCount++;
        });
    });
    [styles, extStyles].forEach((obj) => {
      obj.black
        .filter((v) => !obj.white.includes(v))
        .forEach((sel) => {
          obj.apply.push(sel);
          data.appliedCount++;
        });
    });
    gmMenu("count", cleanRules);
    styleApply();
  }
  function splitRules() {
    data.allRules.split("\n").forEach((rule) => {
      const ruleObj = ruleSpliter(rule);
      if (typeof ruleObj != "undefined") {
        const 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();
  }
  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);
      splitRules();
      yield performUpdate(false);
      if (data.appliedCount === 0) splitRules();
    });
  }
  function runOnce(key, func, ...params) {
    if (key in vm.unsafeWindow) return;
    vm.unsafeWindow[key] = true;
    func === null || func === void 0 ? void 0 : func(...params);
  }
  runOnce(data.mutex, main);
})(
  {
    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,
    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_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
);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址