套壳油猴的广告拦截脚本

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

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

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