套壳油猴的广告拦截脚本

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

当前为 2022-10-01 提交的版本,查看 最新版本

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

QingJ © 2025

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