套壳油猴的广告拦截脚本

将 ABP 元素隐藏规则转换为 CSS 并且应用

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

// ==UserScript==
// @name               AdBlock Script for WebView
// @name:zh-CN         套壳油猴的广告拦截脚本
// @author             Lemon399
// @version            2.0
// @description        Parse ABP Cosmetic rules to CSS and apply it.
// @description:zh-CN  将 ABP 元素隐藏规则转换为 CSS 并且应用
// @require            https://greasyfork.org/scripts/452263-extended-css/code/extended-css.js?version=1099366
// @match              *://*/*
// @run-at             document-start
// @grant              GM_getValue
// @grant              GM_deleteValue
// @grant              GM_setValue
// @grant              GM_registerMenuCommand
// @grant              GM_unregisterMenuCommand
// @grant              GM_xmlhttpRequest
// @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 (tm, ExtendedCss) {
  "use strict";

  function _interopDefaultLegacy(e) {
    return e && typeof e === "object" && "default" in e
      ? e
      : {
          default: e,
        };
  }

  var ExtendedCss__default = _interopDefaultLegacy(ExtendedCss);

  var __assign = function () {
    __assign =
      Object.assign ||
      function __assign(t) {
        for (var s, i = 1, n = arguments.length; i < n; i++) {
          s = arguments[i];

          for (var p in s)
            if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
        }

        return t;
      };

    return __assign.apply(this, arguments);
  };

  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());
    });
  }

  function __generator(thisArg, body) {
    var _ = {
        label: 0,
        sent: function () {
          if (t[0] & 1) throw t[1];
          return t[1];
        },
        trys: [],
        ops: [],
      },
      f,
      y,
      t,
      g;
    return (
      (g = {
        next: verb(0),
        throw: verb(1),
        return: verb(2),
      }),
      typeof Symbol === "function" &&
        (g[Symbol.iterator] = function () {
          return this;
        }),
      g
    );

    function verb(n) {
      return function (v) {
        return step([n, v]);
      };
    }

    function step(op) {
      if (f) throw new TypeError("Generator is already executing.");

      while (_)
        try {
          if (
            ((f = 1),
            y &&
              (t =
                op[0] & 2
                  ? y["return"]
                  : op[0]
                  ? y["throw"] || ((t = y["return"]) && t.call(y), 0)
                  : y.next) &&
              !(t = t.call(y, op[1])).done)
          )
            return t;
          if (((y = 0), t)) op = [op[0] & 2, t.value];

          switch (op[0]) {
            case 0:
            case 1:
              t = op;
              break;

            case 4:
              _.label++;
              return {
                value: op[1],
                done: false,
              };

            case 5:
              _.label++;
              y = op[1];
              op = [0];
              continue;

            case 7:
              op = _.ops.pop();

              _.trys.pop();

              continue;

            default:
              if (
                !((t = _.trys), (t = t.length > 0 && t[t.length - 1])) &&
                (op[0] === 6 || op[0] === 2)
              ) {
                _ = 0;
                continue;
              }

              if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) {
                _.label = op[1];
                break;
              }

              if (op[0] === 6 && _.label < t[1]) {
                _.label = t[1];
                t = op;
                break;
              }

              if (t && _.label < t[2]) {
                _.label = t[2];

                _.ops.push(op);

                break;
              }

              if (t[2]) _.ops.pop();

              _.trys.pop();

              continue;
          }

          op = body.call(thisArg, _);
        } catch (e) {
          op = [6, e];
          y = 0;
        } finally {
          f = t = 0;
        }

      if (op[0] & 5) throw op[1];
      return {
        value: op[0] ? op[1] : void 0,
        done: true,
      };
    }
  }

  var onlineRules = [
      "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
      "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/abpmerge.txt",
    ],
    defaultRules =
      '\n! \u6CA1\u6709\u4E24\u4E2A # \u7684\u884C\u548C \u5F00\u5934\u4E3A ! \u7684\u884C\u4F1A\u5FFD\u7565\n! baidu.com##.ec_wise_ad\n!\n! :remove() \u4F1A\u7528 js \u79FB\u9664\u5143\u7D20\uFF0C:remove() \u5FC5\u987B\u653E\u5728\u884C\u5C3E\n! baidu.com###ad:remove()\n!\n! \u7531\u4E8E\u8BED\u6CD5\u9650\u5236\uFF0C\u5185\u7F6E\u89C4\u5219\u4E2D\n! \u4E00\u4E2A\u53CD\u659C\u6760\u9700\u8981\u6539\u6210\u4E24\u4E2A\uFF0C\u50CF\u8FD9\u6837 \\\n!\n! \u811A\u672C\u4F1A\u9996\u5148\u5C1D\u8BD5\u4ECE\u4E0A\u9762\u7684\u5730\u5740\u6570\u7EC4\u83B7\u53D6\u89C4\u5219\n! \u83B7\u53D6\u5230\u7684\u89C4\u5219\u5C06\u4F1A\u4E0E\u5185\u7F6E\u89C4\u5219\u5408\u5E76\n! \u6240\u6709\u89C4\u5219\u83B7\u53D6\u5B8C\u6BD5\u4EE5\u540E\u624D\u4F1A\u5E94\u7528\u89C4\u5219\n!\n! \u82E5\u8981\u4FEE\u6539\u5730\u5740\uFF0C\u8BF7\u6CE8\u610F\u540C\u6B65\u4FEE\u6539\u5934\u90E8\u7684 @connect \u7684\u57DF\u540D\n!2.3.1\nvercel.app#?#blockquote:has(.mymoney)\nvercel.app#?#blockquote:-abp-has(.myhoney)\nvercel.app#?#blockquote[-ext-has=".mytony"]\n!2.3.2\nvercel.app#?#blockquote:has-text(\u70E6\u607C)\nvercel.app#?#blockquote:has-text(/\u533A\u5206\\d/)\nvercel.app#?#blockquote:contains(\u6ED1\u5757)\nvercel.app#?#blockquote:-abp-contains(\u7EA2\u65E5)\nvercel.app#?#blockquote[-ext-contains="\u5A92\u4F53"]\n!2.3.3\nvercel.app#?#blockquote:matches-css(background-color: rgb\\(135, 206, 235\\))\nvercel.app#?#blockquote:matches-css(background-color: rgb\\(200, 206, 214\\))\nvercel.app#?#blockquote[-ext-matches-css="background-color: rgb\\(240, 255, 240\\)"]\nvercel.app#?#blockquote:matches-css(background-color: /^rgb\\(255,/)\n!2.3.4\nvercel.app#?#blockquote:matches-css-before(content: \u6211\u662F\u5E7F\u544A\u554A)\nvercel.app#?#blockquote[-ext-matches-css-before="content: \u6211\u662F\u5E7F\u544A\u5462"]\n!2.3.5\nvercel.app#?#blockquote:matches-css-after(content: \u6211\u662F\u5E7F\u544A\u54DF)\nvercel.app#?#blockquote[-ext-matches-css-after="content: \u6211\u662F\u5E7F\u544A\u54E6"]\n!2.3.6\nvercel.app#?#[type=range]:matches-attr("disabled")\nvercel.app#?#[type=range]:matches-attr("min"="5")\nvercel.app#?#[type=range]:matches-attr("max"="/^3/")\n!2.3.9\nvercel.app#?#[src$="up.gif"]:nth-ancestor(2)\n!2.3.10\nvercel.app#?#[src$="up2.gif"]:upward(2)\nvercel.app#?#p > em:upward(.box)\n!2.3.12\nvercel.app#?##close:xpath(../../*[1])\n!2.3.13\nvercel.app#?##remo:remove()\n!2.3.15\nvercel.app#?##not > blockquote:not(:has(.ok))\nvercel.app#?##abpnot > blockquote:not(:-abp-has(.ok))\n!2.3.16\nvercel.app#?##ifnot > blockquote:if-not(.ok)\n!2.2.4\nvercel.app#?#blockquote:has(.yes)\nvercel.app#@?#blockquote:has(.yes)\n!2.2.10\nvercel.app#$##turq { color: turquoise !important }\n!2.2.10@\nvercel.app#$##seag { color: seagreen !important }\nvercel.app#@$##seag { color: seagreen !important }\n!2.2.11\nvercel.app#$?#span:contains(\u771F\u7684\u662F) { display: none!important; }\n!2.2.11@\nvercel.app#$?#span:contains(\u771F\u4E0D\u662F) { display: none!important; }\nvercel.app#@$?#span:contains(\u771F\u4E0D\u662F) { display: none!important; }\n';
  const id = "placeholder";

  function isObj(o) {
    return (
      typeof o == "object" &&
      (o === null || o === void 0 ? void 0 : o.toString()) === "[object Object]"
    );
  }

  function runNeed(condition, fn, option, ...args) {
    let ok = false,
      sleep = (time) => {
        return new Promise((r) => setTimeout(r, time));
      },
      defaultOption = {
        count: 20,
        delay: 200,
        failFn: () => null,
      };

    if (isObj(option)) Object.assign(defaultOption, option);
    new Promise(async (resolve, reject) => {
      for (let c = 0; !ok && c < defaultOption.count; c++) {
        await sleep(defaultOption.delay);
        ok = condition.call(null, c + 1);
      }

      ok ? resolve() : reject();
    }).then(fn.bind(null, ...args), defaultOption.failFn);
  }

  `BEXT_LAST_CHECK_KEY_${id}`;

  function getName(path) {
    var reer = /\/([^\/]+)$/.exec(path);
    return reer ? reer[1] : null;
  }

  function getEtag(header) {
    var reer = /etag: \"(\w+)\"/.exec(header);
    return reer ? reer[1] : null;
  }

  function getDay(date) {
    var reer = /\/(\d{1,2}) /.exec(date);
    return reer ? parseInt(reer[1]) : 0;
  }

  function makeRuleBox() {
    return {
      black: [],
      white: [],
      apply: "",
    };
  }

  function domainChecker(domains) {
    var results = [],
      hasTLD = /\.+?[\w-]+$/,
      urlSuffix = hasTLD.exec(location.hostname);
    var invert = false,
      result = false,
      mostMatch = {
        long: 0,
        result: undefined,
      };
    domains.forEach(function (domain) {
      if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
        domain = domain.replace(".*", urlSuffix[0]);
      }

      if (domain.startsWith("~")) {
        invert = true;
        domain = domain.slice(1);
      } else invert = false;

      result = location.hostname.endsWith(domain);
      results.push(result !== invert);

      if (result) {
        if (domain.length > mostMatch.long) {
          mostMatch = {
            long: domain.length,
            result: result !== invert,
          };
        }
      }
    });
    return mostMatch.long > 0 ? mostMatch.result : results.includes(true);
  }

  function ruleChecker(matches) {
    var index = matches.findIndex(function (i) {
      return 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 ruleSpliter(rule) {
    var result = ruleChecker([
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?##([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\?#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\?#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$\?#([^\s^+].*)/
      ),
      rule.match(
        /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$\?#([^\s^+].*)/
      ),
    ]);

    if (result && result[2]) {
      return {
        black: result[0],
        type: result[1],
        sel: result[2],
      };
    }
  }

  var selectors = makeRuleBox(),
    extSelectors = makeRuleBox(),
    styles = makeRuleBox(),
    extStyles = makeRuleBox(),
    values = {
      get black() {
        var v = tm.GM_getValue("ajs_disabled_domains", "");
        return typeof v == "string" ? v : "";
      },

      set black(v) {
        v === null
          ? tm.GM_deleteValue("ajs_disabled_domains")
          : tm.GM_setValue("ajs_disabled_domains", v);
      },

      get rules() {
        var v = tm.GM_getValue("ajs_saved_abprules", "{}");
        return typeof v == "string" ? JSON.parse(v) : {};
      },

      set rules(v) {
        v === null
          ? tm.GM_deleteValue("ajs_saved_abprules")
          : tm.GM_setValue("ajs_saved_abprules", JSON.stringify(v));
      },

      get time() {
        var v = tm.GM_getValue("ajs_rules_ver", "0/0/0 0:0:0");
        return typeof v == "string" ? v : "0/0/0 0:0:0";
      },

      set time(v) {
        v === null
          ? tm.GM_deleteValue("ajs_rules_ver")
          : tm.GM_setValue("ajs_rules_ver", v);
      },

      get etags() {
        var v = tm.GM_getValue("ajs_rules_etags", "{}");
        return typeof v == "string" ? JSON.parse(v) : {};
      },

      set etags(v) {
        v === null
          ? tm.GM_deleteValue("ajs_rules_etags")
          : tm.GM_setValue("ajs_rules_etags", JSON.stringify(v));
      },
    },
    data = {
      disabled: false,
      updating: false,
      receivedRules: "",
      allRules: "",
      genericStyle: document.createElement("style"),
      presetCss:
        " {display: none !important;width: 0 !important;height: 0 !important;} ",
      supportedCount: 0,
      appliedCount: 0,
    },
    menus = {
      disable: {
        id: undefined,

        get text() {
          return data.disabled ? "在此网站启用拦截" : "在此网站禁用拦截";
        },
      },
      update: {
        id: undefined,

        get text() {
          var time = values.time;
          return data.updating
            ? "正在更新..."
            : "\u70B9\u51FB\u66F4\u65B0: ".concat(
                time.slice(0, 1) === "0" ? "未知时间" : time
              );
        },
      },
      count: {
        id: undefined,

        get text() {
          return "\u70B9\u51FB\u6E05\u7A7A: "
            .concat(data.appliedCount, " / ")
            .concat(data.supportedCount, " / ")
            .concat(data.allRules.split("\n").length);
        },
      },
    };

  function gmMenu(name, cb) {
    if (
      typeof tm.GM_registerMenuCommand !== "function" ||
      typeof tm.GM_unregisterMenuCommand !== "function" ||
      window.self !== window.top
    )
      return false;
    var id = menus[name].id;

    if (typeof id !== "undefined") {
      menus[name].id = tm.GM_unregisterMenuCommand(id);
    }

    if (typeof cb == "function") {
      menus[name].id = tm.GM_registerMenuCommand(menus[name].text, cb);
    }

    return typeof menus[name].id !== "undefined";
  }

  function promiseXhr(details) {
    return new Promise(function (resolve, reject) {
      tm.GM_xmlhttpRequest(
        __assign(
          {
            onload: function (e) {
              resolve(e);
            },
            onabort: reject.bind(null),
            onerror: reject.bind(null),
            ontimeout: reject.bind(null),
          },
          details
        )
      );
    });
  }

  function storeRule(name, resp) {
    var savedRules = values.rules,
      savedEtags = values.etags;

    if (resp.responseHeaders) {
      var etag = getEtag(resp.responseHeaders);

      if (etag) {
        savedEtags[name] = etag;
        values.etags = savedEtags;
      }
    }

    if (resp.responseText) {
      savedRules[name] = resp.responseText;
      values.rules = savedRules;
    }
  }

  function fetchRule(url) {
    var _this = this;

    var _a;

    var name =
      (_a = getName(url)) !== null && _a !== void 0
        ? _a
        : "".concat(url.length, ".").concat(url.slice(-5));
    return new Promise(function (resolve, reject) {
      return __awaiter(_this, void 0, void 0, function () {
        var headResp, etag, savedEtags, _a, _b;

        return __generator(this, function (_c) {
          switch (_c.label) {
            case 0:
              if (!name) reject();
              return [
                4,
                promiseXhr({
                  method: "HEAD",
                  responseType: "text",
                  url: url,
                }),
              ];

            case 1:
              headResp = _c.sent();
              if (!headResp.responseText) return [3, 2];
              storeRule(name, headResp);
              resolve();
              return [3, 5];

            case 2:
              if (!headResp.responseHeaders) return [3, 5];
              (etag = getEtag(headResp.responseHeaders)),
                (savedEtags = values.etags);
              if (!(etag !== savedEtags[name])) return [3, 4];
              _a = storeRule;
              _b = [name];
              return [
                4,
                promiseXhr({
                  method: "GET",
                  responseType: "text",
                  url: url,
                }),
              ];

            case 3:
              _a.apply(void 0, _b.concat([_c.sent()]));

              resolve();
              return [3, 5];

            case 4:
              reject();
              _c.label = 5;

            case 5:
              return [2];
          }
        });
      });
    });
  }

  function fetchRules() {
    return __awaiter(this, void 0, void 0, function () {
      var pArray;
      return __generator(this, function (_a) {
        switch (_a.label) {
          case 0:
            pArray = [];
            data.updating = true;
            gmMenu("update", fetchRules);
            onlineRules.forEach(function (url) {
              pArray.push(fetchRule(url));
            });
            return [4, Promise.allSettled(pArray)];

          case 1:
            _a.sent();

            values.time = new Date().toLocaleString("zh-CN");
            initRules();
            return [2];
        }
      });
    });
  }

  function performUpdate(force) {
    if (force) {
      return fetchRules();
    } else {
      return getDay(values.time) !== new Date().getDate()
        ? fetchRules()
        : Promise.resolve();
    }
  }

  function switchDisabledStat() {
    var disaList = values.black.split(","),
      disaResult = disaList.includes(location.hostname);
    data.disabled = !disaResult;

    if (data.disabled) {
      disaList.push(location.hostname);
    } else {
      disaList.splice(disaList.indexOf(location.hostname), 1);
    }

    values.black = disaList.join(",");
    gmMenu("disable", switchDisabledStat);
  }

  function checkDisableStat() {
    var disaResult = values.black.split(",").includes(location.hostname);
    data.disabled = disaResult;
    gmMenu("disable", switchDisabledStat);
    return disaResult;
  }

  function initRules() {
    var abpRules = values.rules,
      abpKeys = Object.keys(abpRules);
    abpKeys.forEach(function (name) {
      data.receivedRules += "\n" + abpRules[name] + "\n";
    });
    data.allRules = defaultRules + data.receivedRules;

    if (abpKeys.length !== 0) {
      data.updating = false;
      gmMenu("update", fetchRules);
    }

    return data.receivedRules.length;
  }

  function styleApply() {
    var css =
        styles.apply +
        (selectors.apply.length > 0 ? selectors.apply + data.presetCss : ""),
      ecss =
        extStyles.apply +
        (extSelectors.apply.length > 0
          ? extSelectors.apply + data.presetCss
          : "");

    if (css.length > 0) {
      if (typeof tm.GM_addStyle == "function") {
        tm.GM_addStyle(css);
      } else {
        runNeed(
          function () {
            return !!document.documentElement;
          },
          function () {
            data.genericStyle.textContent = css;
            document.documentElement.appendChild(data.genericStyle);
          }
        );
      }
    }

    if (ecss.length > 0)
      new ExtendedCss__default.default({
        styleSheet: ecss,
      }).apply();
  }

  function parseRules() {
    var _this = this;

    [selectors, extSelectors].forEach(function (obj) {
      obj.black
        .filter(function (v) {
          return !obj.white.includes(v);
        })
        .forEach(function (sel) {
          obj.apply += "".concat(obj.apply.length == 0 ? "" : ",").concat(sel);
          data.appliedCount++;
        });
    });
    [styles, extStyles].forEach(function (obj) {
      obj.black
        .filter(function (v) {
          return !obj.white.includes(v);
        })
        .forEach(function (sel) {
          obj.apply += " ".concat(sel);
          data.appliedCount++;
        });
    });
    gmMenu("count", function () {
      if (confirm("是否清空存储规则 ?")) {
        values.rules = {};
        values.time = "0/0/0 0:0:0";
        values.etags = {};
        gmMenu("update", performUpdate.bind(_this, true));
      }
    });
    styleApply();
  }

  function main() {
    return __awaiter(this, void 0, void 0, function () {
      return __generator(this, function (_a) {
        switch (_a.label) {
          case 0:
            if (checkDisableStat()) return [2];
            if (!(initRules() === 0)) return [3, 2];
            return [4, performUpdate(true)];

          case 1:
            _a.sent();

            _a.label = 2;

          case 2:
            data.allRules.split("\n").forEach(function (rule) {
              var ruleObj = ruleSpliter(rule);
              var arr = "";

              if (typeof ruleObj !== "undefined") {
                arr = ruleObj.black ? "black" : "white";

                switch (ruleObj.type) {
                  case 0:
                    selectors[arr].push(ruleObj.sel);
                    break;

                  case 1:
                    extSelectors[arr].push(ruleObj.sel);
                    break;

                  case 2:
                    styles[arr].push(ruleObj.sel);
                    break;

                  case 3:
                    extStyles[arr].push(ruleObj.sel);
                    break;
                }

                data.supportedCount++;
              }
            });
            parseRules();
            performUpdate(false);
            return [2];
        }
      });
    });
  }

  main();
})(self, ExtendedCss);