套壳油猴的广告拦截脚本

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

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

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