套壳油猴的广告拦截脚本

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

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

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