套壳油猴的广告拦截脚本

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

当前为 2022-12-20 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name               AdBlock Script for WebView
// @name:zh-CN         套壳油猴的广告拦截脚本
// @author             Lemon399
// @version            2.4.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=1130140
// @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              https://*/*
// @match              http://*/*
// @run-at             document-start
// @grant              unsafeWindow
// @grant              GM_registerMenuCommand
// @grant              GM_unregisterMenuCommand
// @grant              GM_getValue
// @grant              GM_deleteValue
// @grant              GM_setValue
// @grant              GM_addStyle
// @grant              GM_xmlhttpRequest
// @grant              GM_getResourceText
// @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 (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 CRRE =
      /^(~?[\w-]+(?:\.[\w-]+)*(?:\.[\w-]+|\.\*)(?:,~?[\w-]+(?:\.[\w-]+)*(?:\.[\w-]+|\.\*))*)?(#@?\$?\??#)([^\s^+].*)/,
    BRRE =
      /^(?:@@?)(\|\|?)?(https?:\/\/)?([^\s"<>`]+?[|^]?)\$((?:~?[\w-]+(?:=[\s\w'":.-]+)?|_+)(?:,(?:~?[\w-]+(?:=[\s\w'":.-]+)?|_+))*)$/,
    CCRE = /^\/\* (.+?) \*\/ /,
    BROpts = [
      "elemhide",
      "ehide",
      "specifichide",
      "shide",
      "generichide",
      "ghide",
    ];
  const CRFlags = ["##", "#@#", "#?#", "#@?#", "#$#", "#@$#", "#$?#", "#@$?#"];
  function bRuleSpliter(rule) {
    const group = rule.match(BRRE),
      body = group[3],
      options = group[4].split(","),
      sepChar = "[!#&'()+,/:;=?@~|{}$]",
      anyChar = '([^\\s"<>`]*)',
      eh = hasSome(options, ["elemhide", "ehide"]),
      sh = hasSome(options, ["specifichide", "shide"]),
      gh = hasSome(options, ["generichide", "ghide"]);
    let urlres = "";
    urlres += group[1]
      ? group[2]
        ? `^${group[2]}`
        : `^https?://((${anyChar}:)?(${anyChar}@))?([\\w-]+\\.)*?`
      : `^${anyChar}`;
    urlres += body
      .replace(/[-\\$+.()[\]{}]/g, "\\$&")
      .replace(/\|$/, "$")
      .replace(/\|/g, "\\|")
      .replace(/\^$/, `(${sepChar}|$)`)
      .replace(/\^/g, sepChar)
      .replace(/\*$/g, "")
      .replace(/\*/g, anyChar);
    return {
      rule: rule,
      match: urlres,
      level: eh ? 3 : gh && sh ? 3 : sh ? 2 : gh ? 1 : 0,
    };
  }
  function isBasicRule(rule) {
    return BRRE.test(rule) && hasSome(rule, BROpts);
  }
  function bRuleParser(rule, url = location.href) {
    return new RegExp(rule.match).test(url) ? rule.level : 0;
  }
  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,
      match = 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) {
      match = false;
    } else if (invResults.length > 0 && !white) {
      match = 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;
        }
      });
      match = totalResult[1];
    }
    return [match, results.length == 0];
  }
  function hasSome(str, arr) {
    return arr.some((word) => str.includes(word));
  }
  function ruleSpliter(rule) {
    var _a;
    const group = rule.match(CRRE);
    if (group) {
      const sel = group[3],
        type = CRFlags.indexOf(group[2]),
        matchResult = !group[1]
          ? [true, true]
          : domainChecker(group[1].split(","));
      if (sel && matchResult[0]) {
        return {
          black: type % 2 ? "white" : "black",
          type: Math.floor(type / 2),
          place: (_a = group[1]) !== null && _a !== void 0 ? _a : "*",
          generic: matchResult[1],
          sel,
        };
      }
    }
  }
  function ruleLoader(rule) {
    if (
      hasSome(rule, [
        ":matches-path(",
        ":min-text-length(",
        ":watch-attr(",
        ":-abp-properties(",
        ":matches-property(",
      ])
    )
      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 textToBlobUrl(text) {
    return URL.createObjectURL(new Blob([text]));
  }
  function downUrl(url, name) {
    const a = document.createElement("a");
    a.href = url;
    a.download = name;
    Object.assign(a.style, {
      position: "fixed",
      top: "200%",
    });
    document.body.appendChild(a);
    setTimeout(() => {
      a.click();
      a.remove();
    }, 0);
  }

  const data = {
    disabled: false,
    saved: false,
    update: true,
    updating: false,
    receivedRules: "",
    customRules: defaultRules,
    allRules: "",
    presetCss:
      " {display: none !important;width: 0 !important;height: 0 !important;} ",
    genHideCss: "",
    genExtraCss: "",
    spcHideCss: "",
    spcExtraCss: "",
    bRules: {
      levels: [],
      rules: [],
    },
    appliedLevel: 0,
    appliedCount: 0,
    records: [],
    isFrame: tm.unsafeWindow.self !== tm.unsafeWindow.top,
    isClean: false,
    mutex: "__lemon__abp__parser__$__",
    timeout: 6000,
    xTimeout: 700,
    tryCount: 3,
    tryTimeout: 200,
  };

  const 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,
          genHideCss: "",
          genExtraCss: "",
          spcHideCss: "",
          spcExtraCss: "",
        });
      },
      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);
      },
      get brules() {
        return gmValue("get", true, "ajs_modifier_rules", []);
      },
      set brules(v) {
        gmValue("set", true, "ajs_modifier_rules", v);
      },
    },
    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() {
          var _a;
          let cssCount = "";
          if ((data.appliedLevel & 1) == 0)
            cssCount += data.genHideCss + data.genExtraCss;
          if ((data.appliedLevel & 2) == 0)
            cssCount += data.spcHideCss + data.spcExtraCss;
          return data.isClean
            ? "已清空,点击刷新重新加载规则"
            : `${
                data.saved
                  ? "CSS: " +
                    ((_a = cssCount.match(/{/g)) === null || _a === void 0
                      ? void 0
                      : _a.length)
                  : "规则: " +
                    data.appliedCount +
                    "/" +
                    data.allRules.split("\n").length
              },点击清空规则`;
        },
      },
      export: {
        id: undefined,
        text: "下载统计报告",
      },
    };
  function gmMenu(name, cb) {
    var _a;
    const id = (_a = menus[name].id) !== null && _a !== void 0 ? _a : undefined;
    if (
      typeof tm.GM_registerMenuCommand != "function" ||
      typeof tm.GM_unregisterMenuCommand != "function" ||
      data.isFrame
    )
      return;
    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);
    }
  }
  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 addStyle(css, pass = 0) {
    let el;
    if (pass >= data.tryCount) return;
    if (typeof tm.GM_addStyle == "function") {
      el = tm.GM_addStyle(css);
    } else {
      el = document.createElement("style");
      el.textContent = css;
      document.documentElement.appendChild(el);
    }
    if (el) {
      setTimeout(() => {
        if (!document.documentElement.contains(el)) {
          addStyle(css, pass + 1);
        }
      }, data.tryTimeout);
    }
  }

  var _a, _b, _c;
  const selectors = makeRuleBox(),
    extSelectors = makeRuleBox(),
    styles = makeRuleBox(),
    extStyles = makeRuleBox(),
    styleBoxes = ["genHideCss", "genExtraCss", "spcHideCss", "spcExtraCss"];
  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 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 (CRRE.test(rule) || isBasicRule(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 styleInject(csss, extra) {
    if (extra) {
      csss.split("\n").forEach((css) => {
        new ExtendedCss({
          styleSheet: css.replace(CCRE, ""),
        }).apply();
      });
    } else {
      addStyle(csss);
    }
  }
  function styleApply() {
    if (data.appliedLevel === 3) return;
    if (data.genHideCss.length > 0 && data.appliedLevel !== 1) {
      styleInject(data.genHideCss, false);
    }
    if (data.spcHideCss.length > 0 && data.appliedLevel !== 2) {
      styleInject(data.spcHideCss, false);
    }
    if (data.genExtraCss.length > 0 && data.appliedLevel !== 1) {
      styleInject(data.genExtraCss, true);
    }
    if (data.spcExtraCss.length > 0 && data.appliedLevel !== 2) {
      styleInject(data.spcExtraCss, true);
    }
    gmMenu("export", reportRecord);
  }
  function reportRecord() {
    const flags = ["##", "#?#", "#$#", "#$?#"];
    let text = "! 应用地址: \n! " + location.href + "\n";
    function pushRecord(css) {
      if (css.match(CCRE) === null) return void 0;
      const extra = css.match(CCRE)[1],
        rule = css.replace(CCRE, ""),
        sel = rule.replace(/ {.+$/, ""),
        fi = parseInt(extra.slice(0, 1)),
        place = extra.slice(1),
        count =
          fi % 2 == 1
            ? ExtendedCss.query(sel).length
            : document.querySelectorAll(sel).length,
        item = (place == "*" ? "" : place) + flags[fi] + (fi >= 2 ? rule : sel);
      if (count > 0) {
        text += `\n! 匹配元素数量: ${count}\n${item}\n`;
        data.records.push([item, count]);
      }
    }
    if (data.bRules.levels.length > 0) {
      data.bRules.levels.forEach((l, i) => {
        if (l > 0) {
          text += `\n! 禁用${l == 2 ? "特定" : "通用"}元素隐藏\n${
            data.bRules.rules[i]
          }\n`;
        }
      });
    }
    styleBoxes.forEach((box, i) => {
      if ((data.appliedLevel & (i >= 2 ? 2 : 1)) == 0 && data[box].length > 0) {
        data[box].split("\n").forEach((css) => pushRecord(css));
      }
    });
    console.log("地址: " + location.href);
    console.table(data.records);
    downUrl(textToBlobUrl(text), `拦截报告_${location.hostname}.txt`);
  }
  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 = {};
      values.brules = [];
      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() {
    styleBoxes.forEach((box) => {
      data[box] = "";
    });
    [styles, extStyles].forEach((r, t) => {
      r.black
        .filter((v) => !r.white.includes(v))
        .forEach((s) => {
          const checkResult = ExtendedCss.validate(s.sel.split("{")[0]);
          if (checkResult.ok) {
            data[
              styleBoxes[s.generic ? t : t + 2]
            ] += `/* ${s.type}${s.place} */ ${s.sel} \n`;
            data.appliedCount++;
          } else {
            console.error(
              "选择器检查错误:",
              s.sel.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.sel);
          if (checkResult.ok) {
            data[styleBoxes[s.generic ? t : t + 2]] += `${
              i == 0 ? "" : "\n"
            }/* ${s.type}${s.place} */ ${s.sel + data.presetCss}`;
            data.appliedCount++;
          } else {
            console.error("选择器检查错误:", s.sel, checkResult.error);
          }
        });
    });
    if (values.brules.length > 0) parseBRules();
    gmMenu("count", cleanRules);
    saveCss();
    if (!data.saved) styleApply();
  }
  function splitRules() {
    const bRules = [];
    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)
        )
          return;
        boxes[ruleObj.type][ruleObj.black].push(ruleObj);
      } else if (isBasicRule(rule)) {
        bRules.push(bRuleSpliter(rule));
      }
    });
    values.brules = bRules;
    parseRules();
  }
  function parseBRules() {
    const mrules = values.brules;
    data.bRules.levels = [];
    data.bRules.rules = [];
    mrules.forEach((br) => {
      data.bRules.levels.push(bRuleParser(br));
      data.bRules.rules.push(br.rule);
    });
    data.appliedLevel = Math.max(...data.bRules.levels);
  }
  function saveCss() {
    const styles = {
        needUpdate: false,
        genHideCss: data.genHideCss,
        genExtraCss: data.genExtraCss,
        spcHideCss: data.spcHideCss,
        spcExtraCss: data.spcExtraCss,
      },
      has = values.hasSave;
    values.css = styles;
    if (!has.includes(location.hostname)) has.push(location.hostname);
    values.hasSave = has;
  }
  function readCss() {
    const styles = values.css;
    if (!hasSome(Object.keys(styles), styleBoxes)) {
      values.css = {
        needUpdate: true,
        genHideCss: "",
        genExtraCss: "",
        spcHideCss: "",
        spcExtraCss: "",
      };
      return false;
    }
    if (values.brules.length > 0) parseBRules();
    styleBoxes.forEach((sname) => {
      var _a;
      if (styles[sname].length > 0) {
        data.saved = true;
        data.update =
          (_a = styles.needUpdate) !== null && _a !== void 0 ? _a : true;
        data[sname] = styles[sname];
      }
    });
    return data.saved;
  }
  function main() {
    var _a, _b;
    return __awaiter(this, void 0, void 0, function* () {
      if (
        location.protocol.indexOf("http") !== 0 ||
        location.hostname.length < 4
      )
        return;
      data.disabled = values.black.includes(location.hostname);
      gmMenu("disable", switchDisabledStat);
      if (data.disabled) return;
      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);
          }
        });
      }
      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) {
        console.warn("iframe: ", location.href, " 取消更新");
      }
    });
  }
  function runOnce(key, func) {
    return __awaiter(this, void 0, void 0, function* () {
      if (key in tm.unsafeWindow) return;
      tm.unsafeWindow[key] = true;
      return yield 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_addStyle: typeof GM_addStyle == "function" ? GM_addStyle : undefined,
    GM_xmlhttpRequest:
      typeof GM_xmlhttpRequest == "function" ? GM_xmlhttpRequest : undefined,
    GM_getResourceText:
      typeof GM_getResourceText == "function" ? GM_getResourceText : undefined,
  },
  ExtendedCss
);