套壳油猴的广告拦截脚本

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

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

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