AdBlock Script for WebView

Parse ABP Cosmetic rules to CSS and apply it.

目前為 2022-10-06 提交的版本,檢視 最新版本

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