套壳油猴的广告拦截脚本

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

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

  1. // ==UserScript==
  2. // @name AdBlock Script for WebView
  3. // @name:zh-CN 套壳油猴的广告拦截脚本
  4. // @author Lemon399
  5. // @version 2.0.2
  6. // @description Parse ABP Cosmetic rules to CSS and apply it.
  7. // @description:zh-CN 将 ABP 元素中的隐藏规则转换为 CSS 使用
  8. // @require https://greasyfork.org/scripts/452263-extended-css/code/extended-css.js?version=1099366
  9. // @match *://*/*
  10. // @run-at document-start
  11. // @grant GM_getValue
  12. // @grant GM_deleteValue
  13. // @grant GM_setValue
  14. // @grant GM_registerMenuCommand
  15. // @grant GM_unregisterMenuCommand
  16. // @grant GM_xmlhttpRequest
  17. // @grant GM_addStyle
  18. // @namespace https://lemon399-bitbucket-io.vercel.app/
  19. // @source https://gitee.com/lemon399/tampermonkey-cli/tree/master/projects/abp_parse
  20. // @connect code.gitlink.org.cn
  21. // @copyright GPL-3.0
  22. // @license GPL-3.0
  23. // @history 2.0.1 兼容 Tampermonkey 4.18,代码兼容改为 ES6
  24. // @history 2.0.2 修复多个 iframe 首次执行重复下载规则,改进清空功能
  25. // ==/UserScript==
  26.  
  27. (function (tm, ExtendedCss) {
  28. "use strict";
  29.  
  30. function _interopDefaultLegacy(e) {
  31. return e && typeof e === "object" && "default" in e
  32. ? e
  33. : {
  34. default: e,
  35. };
  36. }
  37.  
  38. var ExtendedCss__default = _interopDefaultLegacy(ExtendedCss);
  39.  
  40. function __awaiter(thisArg, _arguments, P, generator) {
  41. function adopt(value) {
  42. return value instanceof P
  43. ? value
  44. : new P(function (resolve) {
  45. resolve(value);
  46. });
  47. }
  48.  
  49. return new (P || (P = Promise))(function (resolve, reject) {
  50. function fulfilled(value) {
  51. try {
  52. step(generator.next(value));
  53. } catch (e) {
  54. reject(e);
  55. }
  56. }
  57.  
  58. function rejected(value) {
  59. try {
  60. step(generator["throw"](value));
  61. } catch (e) {
  62. reject(e);
  63. }
  64. }
  65.  
  66. function step(result) {
  67. result.done
  68. ? resolve(result.value)
  69. : adopt(result.value).then(fulfilled, rejected);
  70. }
  71.  
  72. step((generator = generator.apply(thisArg, _arguments || [])).next());
  73. });
  74. }
  75.  
  76. const onlineRules = [
  77. "https://code.gitlink.org.cn/damengzhu/banad/raw/branch/main/jiekouAD.txt",
  78. "https://code.gitlink.org.cn/damengzhu/abpmerge/raw/branch/main/abpmerge.txt",
  79. ],
  80. defaultRules = `
  81. ! 没有两个 # 的行和 开头为 ! 的行会忽略
  82. ! baidu.com##.ec_wise_ad
  83. !
  84. ! :remove() 会用 js 移除元素,:remove() 必须放在行尾
  85. ! baidu.com###ad:remove()
  86. !
  87. ! 由于语法限制,内置规则中
  88. ! 一个反斜杠需要改成两个,像这样 \\
  89. !
  90. ! 脚本会首先尝试从上面的地址数组获取规则
  91. ! 获取到的规则将会与内置规则合并
  92. ! 所有规则获取完毕以后才会应用规则
  93. !
  94. ! 若要修改地址,请注意同步修改头部的 @connect 的域名
  95. !2.3.1
  96. vercel.app#?#blockquote:has(.mymoney)
  97. vercel.app#?#blockquote:-abp-has(.myhoney)
  98. vercel.app#?#blockquote[-ext-has=".mytony"]
  99. !2.3.2
  100. vercel.app#?#blockquote:has-text(烦恼)
  101. vercel.app#?#blockquote:has-text(/区分\\d/)
  102. vercel.app#?#blockquote:contains(滑块)
  103. vercel.app#?#blockquote:-abp-contains(红日)
  104. vercel.app#?#blockquote[-ext-contains="媒体"]
  105. !2.3.3
  106. vercel.app#?#blockquote:matches-css(background-color: rgb\\(135, 206, 235\\))
  107. vercel.app#?#blockquote:matches-css(background-color: rgb\\(200, 206, 214\\))
  108. vercel.app#?#blockquote[-ext-matches-css="background-color: rgb\\(240, 255, 240\\)"]
  109. vercel.app#?#blockquote:matches-css(background-color: /^rgb\\(255,/)
  110. !2.3.4
  111. vercel.app#?#blockquote:matches-css-before(content: 我是广告啊)
  112. vercel.app#?#blockquote[-ext-matches-css-before="content: 我是广告呢"]
  113. !2.3.5
  114. vercel.app#?#blockquote:matches-css-after(content: 我是广告哟)
  115. vercel.app#?#blockquote[-ext-matches-css-after="content: 我是广告哦"]
  116. !2.3.6
  117. vercel.app#?#[type=range]:matches-attr("disabled")
  118. vercel.app#?#[type=range]:matches-attr("min"="5")
  119. vercel.app#?#[type=range]:matches-attr("max"="/^3/")
  120. !2.3.9
  121. vercel.app#?#[src$="up.gif"]:nth-ancestor(2)
  122. !2.3.10
  123. vercel.app#?#[src$="up2.gif"]:upward(2)
  124. vercel.app#?#p > em:upward(.box)
  125. !2.3.12
  126. vercel.app#?##close:xpath(../../*[1])
  127. !2.3.13
  128. vercel.app#?##remo:remove()
  129. !2.3.15
  130. vercel.app#?##not > blockquote:not(:has(.ok))
  131. vercel.app#?##abpnot > blockquote:not(:-abp-has(.ok))
  132. !2.3.16
  133. vercel.app#?##ifnot > blockquote:if-not(.ok)
  134. !2.2.4
  135. vercel.app#?#blockquote:has(.yes)
  136. vercel.app#@?#blockquote:has(.yes)
  137. !2.2.10
  138. vercel.app#$##turq { color: turquoise !important }
  139. !2.2.10@
  140. vercel.app#$##seag { color: seagreen !important }
  141. vercel.app#@$##seag { color: seagreen !important }
  142. !2.2.11
  143. vercel.app#$?#span:contains(真的是) { display: none!important; }
  144. !2.2.11@
  145. vercel.app#$?#span:contains(真不是) { display: none!important; }
  146. vercel.app#@$?#span:contains(真不是) { display: none!important; }
  147. `;
  148. const id = "placeholder";
  149.  
  150. function isObj(o) {
  151. return (
  152. typeof o == "object" &&
  153. (o === null || o === void 0 ? void 0 : o.toString()) === "[object Object]"
  154. );
  155. }
  156.  
  157. function runNeed(condition, fn, option, ...args) {
  158. let ok = false,
  159. sleep = (time) => {
  160. return new Promise((r) => setTimeout(r, time));
  161. },
  162. defaultOption = {
  163. count: 20,
  164. delay: 200,
  165. failFn: () => null,
  166. };
  167.  
  168. if (isObj(option)) Object.assign(defaultOption, option);
  169. new Promise(async (resolve, reject) => {
  170. for (let c = 0; !ok && c < defaultOption.count; c++) {
  171. await sleep(defaultOption.delay);
  172. ok = condition.call(null, c + 1);
  173. }
  174.  
  175. ok ? resolve() : reject();
  176. }).then(fn.bind(null, ...args), defaultOption.failFn);
  177. }
  178.  
  179. `BEXT_LAST_CHECK_KEY_${id}`;
  180.  
  181. function getName(path) {
  182. const reer = /\/([^\/]+)$/.exec(path);
  183. return reer ? reer[1] : null;
  184. }
  185.  
  186. function getEtag(header) {
  187. const reer = /etag: \"(\w+)\"/.exec(header);
  188. return reer ? reer[1] : null;
  189. }
  190.  
  191. function getDay(date) {
  192. const reer = /\/(\d{1,2}) /.exec(date);
  193. return reer ? parseInt(reer[1]) : 0;
  194. }
  195.  
  196. function makeRuleBox() {
  197. return {
  198. black: [],
  199. white: [],
  200. apply: "",
  201. };
  202. }
  203.  
  204. function domainChecker(domains) {
  205. const results = [],
  206. hasTLD = /\.+?[\w-]+$/,
  207. urlSuffix = hasTLD.exec(location.hostname);
  208. let invert = false,
  209. result = false,
  210. mostMatch = {
  211. long: 0,
  212. result: undefined,
  213. };
  214. domains.forEach((domain) => {
  215. if (domain.endsWith(".*") && Array.isArray(urlSuffix)) {
  216. domain = domain.replace(".*", urlSuffix[0]);
  217. }
  218.  
  219. if (domain.startsWith("~")) {
  220. invert = true;
  221. domain = domain.slice(1);
  222. } else invert = false;
  223.  
  224. result = location.hostname.endsWith(domain);
  225. results.push(result !== invert);
  226.  
  227. if (result) {
  228. if (domain.length > mostMatch.long) {
  229. mostMatch = {
  230. long: domain.length,
  231. result: result !== invert,
  232. };
  233. }
  234. }
  235. });
  236. return mostMatch.long > 0 ? mostMatch.result : results.includes(true);
  237. }
  238.  
  239. function ruleChecker(matches) {
  240. const index = matches.findIndex((i) => i !== null);
  241.  
  242. if (
  243. index >= 0 &&
  244. (!matches[index][1] || domainChecker(matches[index][1].split(",")))
  245. ) {
  246. return [index % 2 == 0, Math.floor(index / 2), matches[index].pop()];
  247. }
  248. }
  249.  
  250. function ruleSpliter(rule) {
  251. const result = ruleChecker([
  252. rule.match(
  253. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?##([^\s^+].*)/
  254. ),
  255. rule.match(
  256. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@#([^\s^+].*)/
  257. ),
  258. rule.match(
  259. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\?#([^\s^+].*)/
  260. ),
  261. rule.match(
  262. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\?#([^\s^+].*)/
  263. ),
  264. rule.match(
  265. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$#([^\s^+].*)/
  266. ),
  267. rule.match(
  268. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$#([^\s^+].*)/
  269. ),
  270. rule.match(
  271. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#\$\?#([^\s^+].*)/
  272. ),
  273. rule.match(
  274. /^(~?[\w-]+\.([\w-]+|\*)(,~?[\w-]+\.([\w-]+|\*))*)?#@\$\?#([^\s^+].*)/
  275. ),
  276. ]);
  277.  
  278. if (result && result[2]) {
  279. return {
  280. black: result[0],
  281. type: result[1],
  282. sel: result[2],
  283. };
  284. }
  285. }
  286.  
  287. const selectors = makeRuleBox(),
  288. extSelectors = makeRuleBox(),
  289. styles = makeRuleBox(),
  290. extStyles = makeRuleBox(),
  291. values = {
  292. get black() {
  293. const v = tm.GM_getValue("ajs_disabled_domains", "");
  294. return typeof v == "string" ? v : "";
  295. },
  296.  
  297. set black(v) {
  298. v === null
  299. ? tm.GM_deleteValue("ajs_disabled_domains")
  300. : tm.GM_setValue("ajs_disabled_domains", v);
  301. },
  302.  
  303. get rules() {
  304. const v = tm.GM_getValue("ajs_saved_abprules", "{}");
  305. return typeof v == "string" ? JSON.parse(v) : {};
  306. },
  307.  
  308. set rules(v) {
  309. v === null
  310. ? tm.GM_deleteValue("ajs_saved_abprules")
  311. : tm.GM_setValue("ajs_saved_abprules", JSON.stringify(v));
  312. },
  313.  
  314. get time() {
  315. const v = tm.GM_getValue("ajs_rules_ver", "0/0/0 0:0:0");
  316. return typeof v == "string" ? v : "0/0/0 0:0:0";
  317. },
  318.  
  319. set time(v) {
  320. v === null
  321. ? tm.GM_deleteValue("ajs_rules_ver")
  322. : tm.GM_setValue("ajs_rules_ver", v);
  323. },
  324.  
  325. get etags() {
  326. const v = tm.GM_getValue("ajs_rules_etags", "{}");
  327. return typeof v == "string" ? JSON.parse(v) : {};
  328. },
  329.  
  330. set etags(v) {
  331. v === null
  332. ? tm.GM_deleteValue("ajs_rules_etags")
  333. : tm.GM_setValue("ajs_rules_etags", JSON.stringify(v));
  334. },
  335. },
  336. data = {
  337. disabled: false,
  338. updating: false,
  339. receivedRules: "",
  340. allRules: "",
  341. genericStyle: document.createElement("style"),
  342. presetCss:
  343. " {display: none !important;width: 0 !important;height: 0 !important;} ",
  344. supportedCount: 0,
  345. appliedCount: 0,
  346. isFrame: window.self !== window.top,
  347. isClean: false,
  348. },
  349. menus = {
  350. disable: {
  351. id: undefined,
  352.  
  353. get text() {
  354. return data.disabled ? "在此网站启用拦截" : "在此网站禁用拦截";
  355. },
  356. },
  357. update: {
  358. id: undefined,
  359.  
  360. get text() {
  361. const time = values.time;
  362. return data.updating
  363. ? "正在更新..."
  364. : `点击更新: ${time.slice(0, 1) === "0" ? "未知时间" : time}`;
  365. },
  366. },
  367. count: {
  368. id: undefined,
  369.  
  370. get text() {
  371. return data.isClean
  372. ? "已清空规则,点击刷新页面"
  373. : `点击清空: ${data.appliedCount} / ${data.supportedCount} / ${
  374. data.allRules.split("\n").length
  375. }`;
  376. },
  377. },
  378. };
  379.  
  380. function gmMenu(name, cb) {
  381. if (
  382. typeof tm.GM_registerMenuCommand !== "function" ||
  383. typeof tm.GM_unregisterMenuCommand !== "function" ||
  384. data.isFrame
  385. )
  386. return false;
  387. const id = menus[name].id;
  388.  
  389. if (typeof id !== "undefined") {
  390. tm.GM_unregisterMenuCommand(id);
  391. menus[name].id = undefined;
  392. }
  393.  
  394. if (typeof cb == "function") {
  395. menus[name].id = tm.GM_registerMenuCommand(menus[name].text, cb);
  396. }
  397.  
  398. return typeof menus[name].id !== "undefined";
  399. }
  400.  
  401. function promiseXhr(details) {
  402. return new Promise((resolve, reject) => {
  403. tm.GM_xmlhttpRequest(
  404. Object.assign(
  405. {
  406. onload(e) {
  407. resolve(e);
  408. },
  409.  
  410. onabort: reject.bind(null),
  411. onerror: reject.bind(null),
  412. ontimeout: reject.bind(null),
  413. },
  414. details
  415. )
  416. );
  417. });
  418. }
  419.  
  420. function storeRule(name, resp) {
  421. const savedRules = values.rules,
  422. savedEtags = values.etags;
  423.  
  424. if (resp.responseHeaders) {
  425. const etag = getEtag(resp.responseHeaders);
  426.  
  427. if (etag) {
  428. savedEtags[name] = etag;
  429. values.etags = savedEtags;
  430. }
  431. }
  432.  
  433. if (resp.responseText) {
  434. savedRules[name] = resp.responseText;
  435. values.rules = savedRules;
  436. }
  437. }
  438.  
  439. function fetchRule(url) {
  440. var _a;
  441.  
  442. const name =
  443. (_a = getName(url)) !== null && _a !== void 0
  444. ? _a
  445. : `${url.length}.${url.slice(-5)}`;
  446. return new Promise((resolve, reject) =>
  447. __awaiter(this, void 0, void 0, function* () {
  448. if (!name) reject();
  449. const headResp = yield promiseXhr({
  450. method: "HEAD",
  451. responseType: "text",
  452. url: url,
  453. });
  454.  
  455. if (headResp.responseText) {
  456. storeRule(name, headResp);
  457. resolve();
  458. } else {
  459. if (headResp.responseHeaders) {
  460. const etag = getEtag(headResp.responseHeaders),
  461. savedEtags = values.etags;
  462.  
  463. if (etag !== savedEtags[name]) {
  464. storeRule(
  465. name,
  466. yield promiseXhr({
  467. method: "GET",
  468. responseType: "text",
  469. url: url,
  470. })
  471. );
  472. resolve();
  473. } else reject();
  474. }
  475. }
  476. })
  477. );
  478. }
  479.  
  480. function fetchRules() {
  481. return __awaiter(this, void 0, void 0, function* () {
  482. const pArray = [];
  483. data.updating = true;
  484. gmMenu("update", fetchRules);
  485. onlineRules.forEach((url) => {
  486. pArray.push(fetchRule(url));
  487. });
  488. yield Promise.allSettled(pArray);
  489. values.time = new Date().toLocaleString("zh-CN");
  490. data.isClean = false;
  491. gmMenu("count", cleanRules);
  492. initRules();
  493. });
  494. }
  495.  
  496. function performUpdate(force) {
  497. if (data.isFrame && initRules() === 0) return Promise.reject();
  498.  
  499. if (force) {
  500. return fetchRules();
  501. } else {
  502. return getDay(values.time) !== new Date().getDate()
  503. ? fetchRules()
  504. : Promise.resolve();
  505. }
  506. }
  507.  
  508. function switchDisabledStat() {
  509. const disaList = values.black.split(","),
  510. disaResult = disaList.includes(location.hostname);
  511. data.disabled = !disaResult;
  512.  
  513. if (data.disabled) {
  514. disaList.push(location.hostname);
  515. } else {
  516. disaList.splice(disaList.indexOf(location.hostname), 1);
  517. }
  518.  
  519. values.black = disaList.join(",");
  520. gmMenu("disable", switchDisabledStat);
  521. }
  522.  
  523. function checkDisableStat() {
  524. const disaResult = values.black.split(",").includes(location.hostname);
  525. data.disabled = disaResult;
  526. gmMenu("disable", switchDisabledStat);
  527. return disaResult;
  528. }
  529.  
  530. function initRules() {
  531. const abpRules = values.rules,
  532. abpKeys = Object.keys(abpRules);
  533. abpKeys.forEach((name) => {
  534. data.receivedRules += "\n" + abpRules[name] + "\n";
  535. });
  536. data.allRules = defaultRules + data.receivedRules;
  537.  
  538. if (abpKeys.length !== 0) {
  539. data.updating = false;
  540. gmMenu("update", fetchRules);
  541. }
  542.  
  543. return data.receivedRules.length;
  544. }
  545.  
  546. function styleApply() {
  547. const css =
  548. styles.apply +
  549. (selectors.apply.length > 0 ? selectors.apply + data.presetCss : ""),
  550. ecss =
  551. extStyles.apply +
  552. (extSelectors.apply.length > 0
  553. ? extSelectors.apply + data.presetCss
  554. : "");
  555.  
  556. if (css.length > 0) {
  557. if (typeof tm.GM_addStyle == "function") {
  558. tm.GM_addStyle(css);
  559. } else {
  560. runNeed(
  561. () => !!document.documentElement,
  562. () => {
  563. data.genericStyle.textContent = css;
  564. document.documentElement.appendChild(data.genericStyle);
  565. }
  566. );
  567. }
  568. }
  569.  
  570. if (ecss.length > 0)
  571. new ExtendedCss__default.default({
  572. styleSheet: ecss,
  573. }).apply();
  574. }
  575.  
  576. function cleanRules() {
  577. if (confirm("是否清空存储规则 ?")) {
  578. values.rules = {};
  579. values.time = "0/0/0 0:0:0";
  580. values.etags = {};
  581. data.appliedCount = 0;
  582. data.supportedCount = 0;
  583. data.allRules = "";
  584. data.isClean = true;
  585. gmMenu("update", performUpdate.bind(this, true));
  586. gmMenu("count", location.reload.bind(this));
  587. }
  588. }
  589.  
  590. function parseRules() {
  591. [selectors, extSelectors].forEach((obj) => {
  592. obj.black
  593. .filter((v) => !obj.white.includes(v))
  594. .forEach((sel) => {
  595. obj.apply += `${obj.apply.length == 0 ? "" : ","}${sel}`;
  596. data.appliedCount++;
  597. });
  598. });
  599. [styles, extStyles].forEach((obj) => {
  600. obj.black
  601. .filter((v) => !obj.white.includes(v))
  602. .forEach((sel) => {
  603. obj.apply += ` ${sel}`;
  604. data.appliedCount++;
  605. });
  606. });
  607. gmMenu("count", cleanRules);
  608. styleApply();
  609. }
  610.  
  611. function main() {
  612. return __awaiter(this, void 0, void 0, function* () {
  613. if (checkDisableStat()) return;
  614. if (initRules() === 0) yield performUpdate(true);
  615. data.allRules.split("\n").forEach((rule) => {
  616. const ruleObj = ruleSpliter(rule);
  617. let arr = "";
  618.  
  619. if (typeof ruleObj !== "undefined") {
  620. arr = ruleObj.black ? "black" : "white";
  621.  
  622. switch (ruleObj.type) {
  623. case 0:
  624. selectors[arr].push(ruleObj.sel);
  625. break;
  626.  
  627. case 1:
  628. extSelectors[arr].push(ruleObj.sel);
  629. break;
  630.  
  631. case 2:
  632. styles[arr].push(ruleObj.sel);
  633. break;
  634.  
  635. case 3:
  636. extStyles[arr].push(ruleObj.sel);
  637. break;
  638. }
  639.  
  640. data.supportedCount++;
  641. }
  642. });
  643. parseRules();
  644. performUpdate(false);
  645. });
  646. }
  647.  
  648. main();
  649. })(self, ExtendedCss);