套壳油猴的广告拦截脚本

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

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

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

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址