套壳油猴的广告拦截脚本(副本)

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

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

// ==UserScript==
// @name             套壳油猴的广告拦截脚本(副本)
// @author             Lemon399
// @version            2.2.5
// @description        将 ABP 中的元素隐藏规则转换为 CSS 使用
// @require            https://gf.qytechs.cn/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 = [
      {
        标识: "jiekouAD",
        地址: "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
        在线更新: !!1,
      },
      {
        标识: "CSSRule",
        地址: "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/CSSRule.txt",
        在线更新: !!1,
      },
    ],
    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 (option && 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 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, [
        /^(~?[\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].*)/,
      ]),
      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;
    // ## -> #?#
    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);
  }

  const selectors = makeRuleBox(),
    extSelectors = makeRuleBox(),
    styles = makeRuleBox(),
    extStyles = makeRuleBox(),
    values = {
      get black() {
        return gmValue("get", false, "ajs_disabled_domains", "");
      },
      set black(v) {
        gmValue("set", false, "ajs_disabled_domains", v);
      },
      get rules() {
        return gmValue("get", true, "ajs_saved_abprules", {});
      },
      set rules(v) {
        gmValue("set", true, "ajs_saved_abprules", v);
      },
      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,
      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: 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.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
            ? 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_1) {
                  // X 浏览器超时中断
                  if (e_1.readyState === 4) {
                    setTimeout(() => {
                      if (!loaded)
                        reject({
                          error: "X timeout",
                          resp: e_1,
                        });
                    }, data.xTimeout);
                  }
                  // Via 浏览器超时中断,不给成功状态...
                  if (e_1.readyState === 3) {
                    setTimeout(() => {
                      if (!loaded)
                        reject({
                          error: "Via timeout",
                          resp: e_1,
                        });
                    }, 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("HEAD 失败");
        } 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("GET 失败");
              } else reject("ETag 一致");
            } else {
              (yield fetchRuleBody(rule)) ? resolve() : reject("GET 失败");
            }
          }
        }
      })
    );
  }
  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 new Date(values.time).getDate() !== new Date().getDate()
        ? fetchRules()
        : Promise.resolve();
    }
  }
  function switchDisabledStat() {
    const disaList = values.black.length === 0 ? [] : 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 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() {
    if (data.hideCss.length > 0) {
      if (typeof vm.GM_addStyle == "function") {
        vm.GM_addStyle(data.hideCss);
      } else {
        runNeed(
          () => !!document.documentElement,
          () => {
            const elem = document.createElement("style");
            elem.textContent = data.hideCss;
            document.documentElement.appendChild(elem);
          }
        );
      }
    }
    if (data.extraCss.length > 0) {
      runNeed(
        () => !!document.documentElement,
        () => new ExtendedCss({ styleSheet: data.extraCss }).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.allRules = "";
      data.isClean = true;
      gmMenu("update");
      gmMenu("count", () => location.reload());
    }
  }
  function parseRules() {
    styles.black
      .filter((v) => !styles.white.includes(v))
      .forEach((s) => {
        data.hideCss += `${s} `;
        data.appliedCount++;
      });
    extStyles.black
      .filter((v) => !extStyles.white.includes(v))
      .forEach((s) => {
        data.extraCss += `${s} `;
        data.appliedCount++;
      });
    selectors.black
      .filter((v) => !selectors.white.includes(v))
      .forEach((s, i, a) => {
        data.hideCss += `${i == 0 ? "" : ","}${s}`;
        if (i == a.length - 1) data.hideCss += data.presetCss;
        data.appliedCount++;
      });
    extSelectors.black
      .filter((v) => !extSelectors.white.includes(v))
      .forEach((s, i, a) => {
        data.extraCss += `${i == 0 ? "" : ","}${s}`;
        if (i == a.length - 1) data.extraCss += data.presetCss;
        data.appliedCount++;
      });
    gmMenu("count", cleanRules);
    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 main() {
    return __awaiter(this, void 0, void 0, function* () {
      data.disabled = values.black.split(",").includes(location.hostname);
      gmMenu("disable", switchDisabledStat);
      if (data.disabled || (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);
})(
  {
    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
);

QingJ © 2025

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