AdBlock Script for WebView

Parse ABP Cosmetic rules to CSS and apply it.

As of 2022-10-07. See the latest version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==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://greasyfork.org/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
);