DMS-UserScripts-Toolkit

GreasyMonkey 脚本工具类

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/408776/842323/DMS-UserScripts-Toolkit.js

  1. // ==UserScript==
  2. // @name DMS UserScripts Toolkit
  3. // @author 稻米鼠
  4. // @description A lib for easy to write userscript.
  5. // @version 0.0.4
  6. // ==UserScript==
  7.  
  8. // modules are defined as an array
  9. // [ module function, map of requires ]
  10. //
  11. // map of requires is short require name -> numeric require
  12. //
  13. // anything defined in a previous bundle is accessed via the
  14. // orig method which is the require for previous bundles
  15. parcelRequire = (function (modules, cache, entry, globalName) {
  16. // Save the require from previous bundle to this closure if any
  17. var previousRequire = typeof parcelRequire === 'function' && parcelRequire;
  18. var nodeRequire = typeof require === 'function' && require;
  19.  
  20. function newRequire(name, jumped) {
  21. if (!cache[name]) {
  22. if (!modules[name]) {
  23. // if we cannot find the module within our internal map or
  24. // cache jump to the current global require ie. the last bundle
  25. // that was added to the page.
  26. var currentRequire = typeof parcelRequire === 'function' && parcelRequire;
  27. if (!jumped && currentRequire) {
  28. return currentRequire(name, true);
  29. }
  30.  
  31. // If there are other bundles on this page the require from the
  32. // previous one is saved to 'previousRequire'. Repeat this as
  33. // many times as there are bundles until the module is found or
  34. // we exhaust the require chain.
  35. if (previousRequire) {
  36. return previousRequire(name, true);
  37. }
  38.  
  39. // Try the node require function if it exists.
  40. if (nodeRequire && typeof name === 'string') {
  41. return nodeRequire(name);
  42. }
  43.  
  44. var err = new Error('Cannot find module \'' + name + '\'');
  45. err.code = 'MODULE_NOT_FOUND';
  46. throw err;
  47. }
  48.  
  49. localRequire.resolve = resolve;
  50. localRequire.cache = {};
  51.  
  52. var module = cache[name] = new newRequire.Module(name);
  53.  
  54. modules[name][0].call(module.exports, localRequire, module, module.exports, this);
  55. }
  56.  
  57. return cache[name].exports;
  58.  
  59. function localRequire(x){
  60. return newRequire(localRequire.resolve(x));
  61. }
  62.  
  63. function resolve(x){
  64. return modules[name][1][x] || x;
  65. }
  66. }
  67.  
  68. function Module(moduleName) {
  69. this.id = moduleName;
  70. this.bundle = newRequire;
  71. this.exports = {};
  72. }
  73.  
  74. newRequire.isParcelRequire = true;
  75. newRequire.Module = Module;
  76. newRequire.modules = modules;
  77. newRequire.cache = cache;
  78. newRequire.parent = previousRequire;
  79. newRequire.register = function (id, exports) {
  80. modules[id] = [function (require, module) {
  81. module.exports = exports;
  82. }, {}];
  83. };
  84.  
  85. var error;
  86. for (var i = 0; i < entry.length; i++) {
  87. try {
  88. newRequire(entry[i]);
  89. } catch (e) {
  90. // Save first error but execute all entries
  91. if (!error) {
  92. error = e;
  93. }
  94. }
  95. }
  96.  
  97. if (entry.length) {
  98. // Expose entry point to Node, AMD or browser globals
  99. // Based on https://github.com/ForbesLindesay/umd/blob/master/template.js
  100. var mainExports = newRequire(entry[entry.length - 1]);
  101.  
  102. // CommonJS
  103. if (typeof exports === "object" && typeof module !== "undefined") {
  104. module.exports = mainExports;
  105.  
  106. // RequireJS
  107. } else if (typeof define === "function" && define.amd) {
  108. define(function () {
  109. return mainExports;
  110. });
  111.  
  112. // <script>
  113. } else if (globalName) {
  114. this[globalName] = mainExports;
  115. }
  116. }
  117.  
  118. // Override the current require with this new one
  119. parcelRequire = newRequire;
  120.  
  121. if (error) {
  122. // throw error from earlier, _after updating parcelRequire_
  123. throw error;
  124. }
  125.  
  126. return newRequire;
  127. })({"epB2":[function(require,module,exports) {
  128. "use strict";
  129.  
  130. Object.defineProperty(exports, "__esModule", {
  131. value: true
  132. });
  133. exports.Toolkit = void 0;
  134.  
  135. function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  136.  
  137. /**
  138. * @file DMS UserScripts Toolkit
  139. *
  140. * @author 稻米鼠
  141. * @version 0.0.3
  142. * @update 2020-8-29 09:06:16
  143. */
  144.  
  145. /**
  146. * @classdesc GreasyMonkey 脚本工具类
  147. *
  148. * 因脚本中一些基础功能函数需要复用,所以写了这个小的工具库。为对用户安全负责,并符合 GreasyFork 审核规则,代码未作压缩,仅用 Parcel 简单过了一下,一方面是为了用 babel 转码,另一方为后期分文件书写不同类别功能做准备。**这导致代码中注释未能精确对应,使用方法请依照此说明**。
  149. *
  150. * @export
  151. * @class Toolkit
  152. */
  153. class Toolkit {
  154. /**
  155. * @summary 当前库的版本号
  156. * @name version
  157. * @memberof Toolkit
  158. */
  159.  
  160. /**
  161. * @summary 当前是否处于 debug 状态。
  162. * @description **Debug 模式开启方法:** 手动为此脚本设置一个数据: `is_debug: 1`(在脚本设置页面中)。值可以任意,因为会当作字符串处理,所以不为空即为真。
  163. *
  164. * **注意:** 这需要 `GM_getValue` API 的支持
  165. * @memberof Toolkit
  166. */
  167. // debug 状态
  168.  
  169. /**
  170. * @summary 引入方法
  171. * @description GreasyFork 发布地址: https://gf.qytechs.cn/zh-CN/scripts/408776
  172. *
  173. * 请依照说明,在脚本元数据部分引入对应的最新地址。此工具库仍在更新中,所以请注意保持更新。
  174. *
  175. * * 会在控制台输出当前工具库的名称+版本号徽章
  176. * * 如果在初始化时传入了 `GM_info` 接口,并可用,会输出当前脚本的名称+版本号徽章
  177. * * 框架中页面不输出,避免重复输出
  178. * @constructor
  179. * @param {object} [GM={}] 其中放入会用到的 GreasyMonkey Api 对象
  180. * @example const DMSTookit = new DMS_UserScripts.Toolkit({GM_info, GM_getValue})
  181. * @memberof Toolkit
  182. */
  183. constructor() {
  184. let GM = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  185.  
  186. _defineProperty(this, "version", '0.0.3');
  187.  
  188. _defineProperty(this, "is_debug", false);
  189.  
  190. // 获取需要用到的 API
  191. for (const key in GM) {
  192. this[key] = GM[key];
  193. } // 设定是否是开发状态
  194.  
  195.  
  196. this.is_debug = this.GM_getValue && this.GM_getValue('is_debug', false) ? true : false; // 输出版本标记
  197.  
  198. if (self != top) return; // 框架中页面不输出,避免重复
  199.  
  200. this.badge('DMS UserScripts Toolkit', this.version, 'https://script.izyx.xyz');
  201.  
  202. if (this.GM_info && this.GM_info.script && this.GM_info.script.name && this.GM_info.script.version) {
  203. this.badge(this.GM_info.script.name, this.GM_info.script.version, this.GM_info.script.description ? this.GM_info.script.description : '', {
  204. rightBGColor: '#71baeb'
  205. });
  206. } // Debug 时输出获得的 API
  207.  
  208.  
  209. const ApiKeys = Object.keys(this).filter(key => /^GM_/.test(key));
  210. this.dblog.apply(this, ['Constructor - No. of Apis = ' + ApiKeys.length].concat(ApiKeys));
  211. }
  212. /* ====== 输出相关 ====== */
  213.  
  214. /**
  215. * @summary 日志输出
  216. * @description 用 `console.info` 方法输出信息。如多条内容,则以折叠组的形式输出。主要为了便于 debug。
  217. * @param {string} by 由信息来源标识
  218. * @param {...any} args 输出内容,任意多个参数
  219. * @example
  220. * DMSTookit.info('Demo', 'Some thing want to print to console.') // 仅一项需输出内容
  221. * DMSTookit.info('Demo', 'Some thing want to print to console.', 'And more.') // 多项输出内容
  222. * @memberof Toolkit
  223. */
  224.  
  225.  
  226. info(by) {
  227. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  228. args[_key - 1] = arguments[_key];
  229. }
  230.  
  231. if (args.length > 1) {
  232. console.groupCollapsed(by + ': ');
  233.  
  234. for (const arg of args) {
  235. console.info(arg);
  236. }
  237.  
  238. console.groupEnd();
  239. } else if (args.length === 1) {
  240. console.info(by + ': ', args[0]);
  241. }
  242. }
  243. /**
  244. * @summary Debug 输出,仅开发时输出
  245. * @description 用 `.info` 方法输出。但仅在 `.is_debug` 为真时输出。这样在发布版本时可不删除这些调试内容。参数同 {@link Toolkit#info}
  246. * @param {string} by 由信息来源标识
  247. * @param {...any} args 输出内容,任意多个参数
  248. * @memberof Toolkit
  249. */
  250.  
  251.  
  252. dblog() {
  253. if (this.is_debug) {
  254. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  255. args[_key2] = arguments[_key2];
  256. }
  257.  
  258. this.info.apply(this, args);
  259. }
  260. }
  261. /**
  262. * @summary 徽章输出
  263. * @description 类似这种效果 ![](https://img.shields.io/badge/%E5%B7%A6%E4%BE%A7%E6%96%87%E5%AD%97-%E5%8F%B3%E4%BE%A7%E6%96%87%E5%AD%97-%23ffc107?style=flat&labelColor=555555), 但并不一致。具体可在引用此库,并正确初始化后看控制台输出。其实除了好看也没什么特别的用途。
  264. * @param {string} leftText 徽章左侧文字
  265. * @param {string} rightText 徽章右侧文字
  266. * @param {string} endText 徽章后描述
  267. * @param {object} options={} 徽章配置项
  268. * * options.leftBGColor 左侧文字背景色
  269. * * options.leftColor 左侧文字颜色
  270. * * options.rightBGColor 右侧文字背景色
  271. * * options.rightColor 右侧文字颜色
  272. * @example
  273. * DMSTookit.badge(
  274. * 'DMS UserScripts Toolkit',
  275. * DMSTookit.version,
  276. * 'https://gf.qytechs.cn/zh-CN/scripts/408776'
  277. * )
  278. * @memberof Toolkit
  279. */
  280.  
  281.  
  282. badge(leftText, rightText, endText) {
  283. let options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
  284. const opt = Object.assign({
  285. type: 'log',
  286. leftBGColor: '#555555',
  287. leftColor: '#ededed',
  288. rightBGColor: '#ffc107',
  289. rightColor: '#262318'
  290. }, options);
  291. console[opt.type]('%c' + leftText + '%c' + rightText + (endText ? '%c\n' + endText : ''), 'color: ' + opt.leftColor + '; ' + 'background-color: ' + opt.leftBGColor + '; ' + 'border-radius: 2px 0 0 2px;' + 'padding: 0 5px', 'color: ' + opt.rightColor + '; ' + 'background-color: ' + opt.rightBGColor + '; ' + 'border-radius: 0 2px 2px 0;' + 'padding: 0 5px;', '');
  292. }
  293. /* ====== 数据相关 ====== */
  294.  
  295. /**
  296. * @summary 【私有】验证是否具有所需的 Api
  297. * @description 如无法获取此 API,且处于 debug 状态,则在控制台输出提示
  298. * @param {string} apiName API 的名称
  299. * @param {string} by='DMS_Toolkit' (可选)调用来源
  300. * @returns {boolean}
  301. * @memberof Toolkit
  302. * @private
  303. */
  304.  
  305.  
  306. hasGMApi(apiName) {
  307. let by = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'DMS_Toolkit';
  308. if (this[apiName]) return true;
  309. this.dblog(by + ' - 未能获取 ' + apiName + ' 接口', '* 请检查初始化时是否传入对应接口', '* 脚本元数据中是否声明对该接口的使用');
  310. return false;
  311. }
  312. /**
  313. * @summary 获取存储中的数据
  314. * @description 基础方法,当接口不可用时会直接抛出错误,不建议直接使用。提供如下语法糖:
  315. * * `getGMData` 将 `type` 指定为 `GM`,然后省略此参数
  316. * * `getLocalData` 将 `type` 指定为 `local`,然后省略此参数
  317. * * `getSessionData` 将 `type` 指定为 `session`,然后省略此参数
  318. * @param {string} type 存储类型,可用值如下:
  319. * * `GM` 脚本管理器提供的数据存储
  320. * * `local` localStorage 数据
  321. * * `session` sessionStorage 数据
  322. * * `cookie` cookie 数据(暂未提供
  323. * @param {string} dataName 数据名称
  324. * @param {any} defaultValue=false 默认值,可省略
  325. */
  326.  
  327.  
  328. getData(type, dataName) {
  329. let defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
  330.  
  331. switch (type) {
  332. case 'GM':
  333. // 脚本管理器同宫的全局存储
  334. if (!this.hasGMApi('GM_getValue', 'getData')) {
  335. throw new Error("This API is not available.");
  336. } else {
  337. return this.GM_getValue(dataName, defaultValue);
  338. }
  339.  
  340. break;
  341.  
  342. case 'local':
  343. // localStorage
  344. if (!window.localStorage) {
  345. throw new Error("This API is not available.");
  346. } else {
  347. if (!localStorage.getItem(dataName)) {
  348. return defaultValue;
  349. } else {
  350. return JSON.parse(localStorage.getItem(dataName));
  351. }
  352. }
  353.  
  354. break;
  355.  
  356. case 'session':
  357. // sessionStorage
  358. if (!window.sessionStorage) {
  359. throw new Error("This API is not available.");
  360. } else {
  361. if (!sessionStorage.getItem(dataName)) {
  362. return defaultValue;
  363. } else {
  364. return JSON.parse(sessionStorage.getItem(dataName));
  365. }
  366. }
  367.  
  368. break;
  369.  
  370. case 'cookie':
  371. // cookies
  372. this.dblog('getData', 'The method of cookie operation is not yet available.');
  373. break;
  374.  
  375. default:
  376. this.dblog('getData', 'Probably set the wrong type of get method.');
  377. throw new Error("Probably set the wrong type of get method.");
  378. break;
  379. }
  380. }
  381.  
  382. getGMData() {
  383. try {
  384. for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
  385. args[_key3] = arguments[_key3];
  386. }
  387.  
  388. return this.getData.apply(this, ['GM'].concat(args));
  389. } catch (error) {
  390. this.dblog('getGMData', error.message);
  391. }
  392. }
  393.  
  394. getLocalData() {
  395. try {
  396. for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
  397. args[_key4] = arguments[_key4];
  398. }
  399.  
  400. return this.getData.apply(this, ['local'].concat(args));
  401. } catch (error) {
  402. this.dblog('getLocalData', error.message);
  403. }
  404. }
  405.  
  406. getSessionData() {
  407. try {
  408. for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
  409. args[_key5] = arguments[_key5];
  410. }
  411.  
  412. return this.getData.apply(this, ['session'].concat(args));
  413. } catch (error) {
  414. this.dblog('getSessionData', error.message);
  415. }
  416. }
  417. /**
  418. * @summary 设置存储中的数据
  419. * @description 基础方法,当接口不可用时会直接抛出错误,不建议直接使用。提供如下语法糖:
  420. * * `setGMData` 将 `type` 指定为 `GM`,然后省略此参数
  421. * * `setLocalData` 将 `type` 指定为 `local`,然后省略此参数
  422. * * `setSessionData` 将 `type` 指定为 `session`,然后省略此参数
  423. * @param {string} type 存储类型,可用值如下:
  424. * * `GM` 脚本管理器提供的数据存储
  425. * * `local` localStorage 数据
  426. * * `session` sessionStorage 数据
  427. * * `cookie` cookie 数据(暂未提供
  428. * @param {string} dataName 数据名称
  429. * @param {any} value 要写入的值,如不存在则删除该数据
  430. */
  431.  
  432.  
  433. setData(type, dataName, value) {
  434. // 如果不存在值则删除此数据
  435. if (typeof value === 'undefined') {
  436. this.removeData(type, dataName);
  437. this.dblog('setData', 'No value, and removed ' + dataName);
  438. return;
  439. }
  440.  
  441. switch (type) {
  442. case 'GM':
  443. // 脚本管理器同宫的全局存储
  444. if (!this.hasGMApi('GM_setValue', 'setData')) {
  445. throw new Error("This API is not available.");
  446. } else {
  447. this.GM_setValue(dataName, value);
  448. }
  449.  
  450. break;
  451.  
  452. case 'local':
  453. // localStorage
  454. if (!window.localStorage) {
  455. throw new Error("This API is not available.");
  456. } else {
  457. localStorage.setItem(dataName, JSON.stringify(value));
  458. }
  459.  
  460. break;
  461.  
  462. case 'session':
  463. // sessionStorage
  464. if (!window.sessionStorage) {
  465. throw new Error("This API is not available.");
  466. } else {
  467. sessionStorage.setItem(dataName, JSON.stringify(value));
  468. }
  469.  
  470. break;
  471.  
  472. case 'cookie':
  473. // cookies
  474. this.dblog('setData', 'The method of cookie operation is not yet available.');
  475. break;
  476.  
  477. default:
  478. this.dblog('setData', 'Probably set the wrong type of get method.');
  479. throw new Error("Probably set the wrong type of get method.");
  480. break;
  481. }
  482. }
  483.  
  484. setGMData() {
  485. try {
  486. for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
  487. args[_key6] = arguments[_key6];
  488. }
  489.  
  490. this.setData.apply(this, ['GM'].concat(args));
  491. } catch (error) {
  492. this.dblog('setGMData', error.message);
  493. }
  494. }
  495.  
  496. setLocalData() {
  497. try {
  498. for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
  499. args[_key7] = arguments[_key7];
  500. }
  501.  
  502. this.setData.apply(this, ['local'].concat(args));
  503. } catch (error) {
  504. this.dblog('setLocalData', error.message);
  505. }
  506. }
  507.  
  508. setSessionData() {
  509. try {
  510. for (var _len8 = arguments.length, args = new Array(_len8), _key8 = 0; _key8 < _len8; _key8++) {
  511. args[_key8] = arguments[_key8];
  512. }
  513.  
  514. this.setData.apply(this, ['session'].concat(args));
  515. } catch (error) {
  516. this.dblog('setSessionData', error.message);
  517. }
  518. }
  519. /**
  520. * @summary 移除存储中的数据
  521. * @description 基础方法,当接口不可用时会直接抛出错误,不建议直接使用。提供如下语法糖:
  522. * * `removeGMData` 将 `type` 指定为 `GM`,然后省略此参数
  523. * * `removeLocalData` 将 `type` 指定为 `local`,然后省略此参数
  524. * * `removeSessionData` 将 `type` 指定为 `session`,然后省略此参数
  525. * @param {string} type 存储类型,可用值如下:
  526. * * `GM` 脚本管理器提供的数据存储
  527. * * `local` localStorage 数据
  528. * * `session` sessionStorage 数据
  529. * * `cookie` cookie 数据(暂未提供
  530. * @param {string} dataName 数据名称
  531. */
  532.  
  533.  
  534. removeData(type, dataName) {
  535. switch (type) {
  536. case 'GM':
  537. // 脚本管理器同宫的全局存储
  538. if (!this.hasGMApi('GM_deleteValue', 'removeData')) {
  539. throw new Error("This API is not available.");
  540. } else {
  541. this.GM_deleteValue(dataName);
  542. }
  543.  
  544. break;
  545.  
  546. case 'local':
  547. // localStorage
  548. if (!window.localStorage) {
  549. throw new Error("This API is not available.");
  550. } else {
  551. localStorage.removeItem(dataName);
  552. }
  553.  
  554. break;
  555.  
  556. case 'session':
  557. // sessionStorage
  558. if (!window.sessionStorage) {
  559. throw new Error("This API is not available.");
  560. } else {
  561. sessionStorage.removeItem(dataName);
  562. }
  563.  
  564. break;
  565.  
  566. case 'cookie':
  567. // cookies
  568. this.dblog('removeData', 'The method of cookie operation is not yet available.');
  569. break;
  570.  
  571. default:
  572. this.dblog('removeData', 'Probably set the wrong type of get method.');
  573. throw new Error("Probably set the wrong type of get method.");
  574. break;
  575. }
  576. }
  577.  
  578. removeGMData() {
  579. try {
  580. for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
  581. args[_key9] = arguments[_key9];
  582. }
  583.  
  584. this.removeData.apply(this, ['GM'].concat(args));
  585. } catch (error) {
  586. this.dblog('removeGMData', error.message);
  587. }
  588. }
  589.  
  590. removeLocalData() {
  591. try {
  592. for (var _len10 = arguments.length, args = new Array(_len10), _key10 = 0; _key10 < _len10; _key10++) {
  593. args[_key10] = arguments[_key10];
  594. }
  595.  
  596. this.removeData.apply(this, ['local'].concat(args));
  597. } catch (error) {
  598. this.dblog('removeLocalData', error.message);
  599. }
  600. }
  601.  
  602. removeSessionData() {
  603. try {
  604. for (var _len11 = arguments.length, args = new Array(_len11), _key11 = 0; _key11 < _len11; _key11++) {
  605. args[_key11] = arguments[_key11];
  606. }
  607.  
  608. this.removeData.apply(this, ['session'].concat(args));
  609. } catch (error) {
  610. this.dblog('removeSessionData', error.message);
  611. }
  612. }
  613. /**
  614. * @summary 移除存储中的数据
  615. * @description 基础方法,当接口不可用时会直接抛出错误,不建议直接使用。提供如下语法糖:
  616. * * `removeGMData` 将 `type` 指定为 `GM`,然后省略此参数
  617. * * `removeLocalData` 将 `type` 指定为 `local`,然后省略此参数
  618. * * `removeSessionData` 将 `type` 指定为 `session`,然后省略此参数
  619. * @param {string} type 存储类型,可用值如下:
  620. * * `GM` 脚本管理器提供的数据存储
  621. * * `local` localStorage 数据
  622. * * `session` sessionStorage 数据
  623. * * `cookie` cookie 数据(暂未提供
  624. * @param {string} dataName 数据名称
  625. */
  626.  
  627.  
  628. listData(type) {
  629. switch (type) {
  630. case 'GM':
  631. // 脚本管理器同宫的全局存储
  632. if (!this.hasGMApi('GM_listValues', 'removeData')) {
  633. throw new Error("This API is not available.");
  634. } else {
  635. return this.GM_listValues();
  636. }
  637.  
  638. break;
  639.  
  640. case 'local':
  641. // localStorage
  642. if (!window.localStorage) {
  643. throw new Error("This API is not available.");
  644. } else {
  645. return new Array(localStorage.length).fill(0).map((e, i) => localStorage.key(i));
  646. }
  647.  
  648. break;
  649.  
  650. case 'session':
  651. // sessionStorage
  652. if (!window.sessionStorage) {
  653. throw new Error("This API is not available.");
  654. } else {
  655. return new Array(sessionStorage.length).fill(0).map((e, i) => sessionStorage.key(i));
  656. }
  657.  
  658. break;
  659.  
  660. case 'cookie':
  661. // cookies
  662. this.dblog('setData', 'The method of cookie operation is not yet available.');
  663. break;
  664.  
  665. default:
  666. this.dblog('listData', 'Probably set the wrong type of get method.');
  667. throw new Error("Probably set the wrong type of get method.");
  668. break;
  669. }
  670. }
  671.  
  672. listGMData() {
  673. try {
  674. return this.listData.apply(this, ['GM']);
  675. } catch (error) {
  676. this.dblog('listGMData', error.message);
  677. }
  678. }
  679.  
  680. listLocalData() {
  681. try {
  682. return this.listData.apply(this, ['local']);
  683. } catch (error) {
  684. this.dblog('listLocalData', error.message);
  685. }
  686. }
  687.  
  688. listSessionData() {
  689. try {
  690. return this.listData.apply(this, ['session']);
  691. } catch (error) {
  692. this.dblog('listSessionData', error.message);
  693. }
  694. }
  695. /**
  696. * @summary 代理存储中的数据对象
  697. * @description 基础方法,不建议直接使用。仅用 `new Proxy()` 方法简单代理了数据的 `get` 和 `set`,可以满足简单的数据操作需求,复杂情况请自行书写代理。提供如下语法糖:
  698. * * `proxyGMData` 将 `type` 指定为 `GM`,然后省略此参数
  699. * * `proxyLocalData` 将 `type` 指定为 `local`,然后省略此参数
  700. * * `proxySessionData` 将 `type` 指定为 `session`,然后省略此参数
  701. * * `proxyDataAuto` 脚本数据存储 API 可用的话 `type` 指定为 `GM`,否则为 `local` 即自动选择 `type`,然后省略此参数
  702. * @param {string} type 存储类型,可用值如下:
  703. * * `GM` 脚本管理器提供的数据存储
  704. * * `local` localStorage 数据
  705. * * `session` sessionStorage 数据
  706. * * `cookie` cookie 数据(暂未提供
  707. * @param {string} dataName 数据名称
  708. * @param {any} defaultValue={} 默认值,可省略
  709. */
  710.  
  711.  
  712. proxyData(type, dataName) {
  713. let defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  714.  
  715. if (typeof defaultValue !== 'undefined' && !(defaultValue instanceof Object)) {
  716. this.dblog('The defaultValue must be an object, and the proxy method can only be used to observe a change in an object\'s property value.');
  717. return;
  718. }
  719.  
  720. return new Proxy(defaultValue, {
  721. get: (target, property, receiver) => {
  722. return Object.assign(target, this.getData(type, dataName, {}))[property];
  723. },
  724. set: (target, property, value, receiver) => {
  725. Object.assign(target, this.getData(type, dataName, {}))[property] = value;
  726.  
  727. try {
  728. this.setData(type, dataName, target);
  729. return true;
  730. } catch (error) {
  731. this.dblog('proxyData', error);
  732. return false;
  733. }
  734. }
  735. });
  736. }
  737.  
  738. proxyGMData() {
  739. try {
  740. for (var _len12 = arguments.length, args = new Array(_len12), _key12 = 0; _key12 < _len12; _key12++) {
  741. args[_key12] = arguments[_key12];
  742. }
  743.  
  744. return this.proxyData.apply(this, ['GM'].concat(args));
  745. } catch (error) {
  746. this.dblog('proxyGMData', error.message);
  747. }
  748. }
  749.  
  750. proxyLocalData() {
  751. try {
  752. for (var _len13 = arguments.length, args = new Array(_len13), _key13 = 0; _key13 < _len13; _key13++) {
  753. args[_key13] = arguments[_key13];
  754. }
  755.  
  756. return this.proxyData.apply(this, ['local'].concat(args));
  757. } catch (error) {
  758. this.dblog('proxyLocalData', error.message);
  759. }
  760. }
  761.  
  762. proxySessionData() {
  763. try {
  764. for (var _len14 = arguments.length, args = new Array(_len14), _key14 = 0; _key14 < _len14; _key14++) {
  765. args[_key14] = arguments[_key14];
  766. }
  767.  
  768. return this.proxyData.apply(this, ['session'].concat(args));
  769. } catch (error) {
  770. this.dblog('proxySessionData', error.message);
  771. }
  772. }
  773.  
  774. proxyDataAuto() {
  775. let type = 'local';
  776.  
  777. if (this.hasGMApi('GM_getValue', 'proxyDataAuto') && this.hasGMApi('GM_setValue', 'proxyDataAuto')) {
  778. if (this.getGMData('GM_Can_be_Used', false)) {
  779. type = 'GM';
  780. } else {
  781. this.setGMData('GM_Can_be_Used', true);
  782.  
  783. if (this.getGMData('GM_Can_be_Used', false)) {
  784. type = 'GM';
  785. }
  786. }
  787. }
  788.  
  789. try {
  790. for (var _len15 = arguments.length, args = new Array(_len15), _key15 = 0; _key15 < _len15; _key15++) {
  791. args[_key15] = arguments[_key15];
  792. }
  793.  
  794. return this.proxyData.apply(this, [type].concat(args));
  795. } catch (error) {
  796. this.dblog('proxyDataAuto', error.message);
  797. }
  798. }
  799. /**
  800. * @summary 代理存储中的数据
  801. * @description 基础方法,不建议直接使用。针对存储的单个数据进行简单代理。提供如下语法糖:
  802. * * `proxyGMKey` 将 `type` 指定为 `GM`,然后省略此参数
  803. * * `proxyLocalKey` 将 `type` 指定为 `local`,然后省略此参数
  804. * * `proxySessionKey` 将 `type` 指定为 `session`,然后省略此参数
  805. * * `proxyKeyAuto` 脚本数据存储 API 可用的话 `type` 指定为 `GM`,否则为 `local` 即自动选择 `type`,然后省略此参数
  806. * @param {string} type 存储类型,可用值如下:
  807. * * `GM` 脚本管理器提供的数据存储
  808. * * `local` localStorage 数据
  809. * * `session` sessionStorage 数据
  810. * * `cookie` cookie 数据(暂未提供
  811. * @param {string} dataName 数据名称
  812. * @param {any} defaultValue=false 默认值,可省略
  813. * @returns {function} 返回一个方法,无参数运行则获取对应存储的值,有参数运行则将参数值写入存储
  814. */
  815.  
  816.  
  817. proxyKey(type, dataName) {
  818. let defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
  819. return value => {
  820. if (typeof value !== 'undefined') {
  821. this.setData(type, dataName, value);
  822. } else {
  823. return this.getData(type, dataName, defaultValue);
  824. }
  825. };
  826. }
  827.  
  828. proxyGMKey() {
  829. try {
  830. for (var _len16 = arguments.length, args = new Array(_len16), _key16 = 0; _key16 < _len16; _key16++) {
  831. args[_key16] = arguments[_key16];
  832. }
  833.  
  834. return this.proxyKey.apply(this, ['GM'].concat(args));
  835. } catch (error) {
  836. this.dblog('proxyGMKey', error.message);
  837. }
  838. }
  839.  
  840. proxyLocalKey() {
  841. try {
  842. for (var _len17 = arguments.length, args = new Array(_len17), _key17 = 0; _key17 < _len17; _key17++) {
  843. args[_key17] = arguments[_key17];
  844. }
  845.  
  846. return this.proxyKey.apply(this, ['local'].concat(args));
  847. } catch (error) {
  848. this.dblog('proxyLocalKey', error.message);
  849. }
  850. }
  851.  
  852. proxySessionKey() {
  853. try {
  854. for (var _len18 = arguments.length, args = new Array(_len18), _key18 = 0; _key18 < _len18; _key18++) {
  855. args[_key18] = arguments[_key18];
  856. }
  857.  
  858. return this.proxyKey.apply(this, ['session'].concat(args));
  859. } catch (error) {
  860. this.dblog('proxySessionKey', error.message);
  861. }
  862. }
  863.  
  864. proxyKeyAuto() {
  865. let type = 'local';
  866.  
  867. if (this.hasGMApi('GM_getValue', 'proxyKeyAuto') && this.hasGMApi('GM_setValue', 'proxyKeyAuto')) {
  868. if (this.getGMKey('GM_Can_be_Used', false)) {
  869. type = 'GM';
  870. } else {
  871. this.setGMKey('GM_Can_be_Used', true);
  872.  
  873. if (this.getGMKey('GM_Can_be_Used', false)) {
  874. type = 'GM';
  875. }
  876. }
  877. }
  878.  
  879. try {
  880. for (var _len19 = arguments.length, args = new Array(_len19), _key19 = 0; _key19 < _len19; _key19++) {
  881. args[_key19] = arguments[_key19];
  882. }
  883.  
  884. return this.proxyKey.apply(this, [type].concat(args));
  885. } catch (error) {
  886. this.dblog('proxyKeyAuto', error.message);
  887. }
  888. }
  889. /* ====== 菜单相关 ====== */
  890.  
  891. /**
  892. * @summary 注册(不可用)一个链接菜单
  893. * @description 需要如下 API:
  894. * * `GM_registerMenuCommand` 【必须】否则无法注册(不可用)脚本菜单
  895. * * `GM_openInTab` 【可选】可以更稳定的在新窗口打开链接
  896. * @param {string} name 菜单的名称
  897. * @param {url} url 链接地址,含协议部分(`http://`,`https://`),否则可能按相对地址处理
  898. * @example DMSTookit.menuLink('更多脚本', 'https://script.izyx.xyz/')
  899. * @memberof Toolkit
  900. */
  901.  
  902.  
  903. menuLink(name, url) {
  904. if (!this.hasGMApi('GM_registerMenuCommand', 'menuLink')) return;
  905. GM_registerMenuCommand(name, () => {
  906. if (this.hasGMApi('GM_openInTab', 'menuLink')) {
  907. GM_openInTab(url);
  908. return;
  909. }
  910.  
  911. window.open(url, '_blank');
  912. });
  913. }
  914. /**
  915. * @summary 注册(不可用)一个切换菜单
  916. * @description 因为不同管理器下菜单的反注册(不可用)方式不太一样,所以最稳妥的方法是切换后刷新页面。如果不刷新页面,会用两种方法尝试反注册(不可用)菜单。需要如下 API:
  917. * * `GM_registerMenuCommand` 【必须】否则无法注册(不可用)脚本菜单
  918. * * `GM_unregisterMenuCommand` 【可选】无刷新切换时用来反注册(不可用)菜单
  919. * 提供如下语法糖:
  920. * * `menuToggle` 菜单标记位于某个数据对象的属性上,用此方法可略显简便,详细参数在后面
  921. * * `menuDebug` 注册(不可用)一个 debug 状态的切换菜单,此方法无参数
  922. * @param {function} getter 获取菜单状态标记
  923. * @param {function} setter 设置菜单状态标记
  924. * @param {string} onName 启用状态菜单文字
  925. * @param {string} offName 禁用状态菜单文字
  926. * @param {boolean} reload=true 是否需要刷新页面,默认为是。当为否时会尝试两种方法反注册(不可用)菜单,但并不推荐。
  927. * @param {function} callback 回调函数,当 `reload=false` 时可用,会传入当前菜单状态(`true`,`false`)
  928. * @memberof Toolkit
  929. */
  930.  
  931.  
  932. menuToggleBasic(getter, setter, onName, offName) {
  933. let reload = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
  934. let callback = arguments.length > 5 ? arguments[5] : undefined;
  935. if (!this.hasGMApi('GM_registerMenuCommand', 'toggleMenu')) return;
  936. if (!reload && !this.hasGMApi('GM_unregisterMenuCommand', 'toggleMenu')) return;
  937.  
  938. const registerMenu = () => {
  939. const nowState = getter();
  940. const menuName = nowState ? onName : offName;
  941. let menuMarkID;
  942. menuMarkID = GM_registerMenuCommand(menuName, () => {
  943. setter(!nowState);
  944.  
  945. if (reload) {
  946. window.location.reload(1);
  947. return;
  948. }
  949.  
  950. if (menuMarkID) {
  951. // Tampermonkey
  952. GM_unregisterMenuCommand(menuMarkID);
  953. } else {
  954. // Violentmonkey
  955. GM_unregisterMenuCommand(menuName);
  956. }
  957.  
  958. registerMenu();
  959. if (callback) callback(!nowState);
  960. });
  961. };
  962.  
  963. registerMenu();
  964. }
  965. /**
  966. * @summary 注册(不可用)一个切换菜单,菜单标记位于储存数据对象的某个属性上
  967. * @param {object} optionsObject 选项数据对象,推荐使用上面 `proxyData` 方法代理的数据,这样改变后可以直接存储
  968. * @param {string} optionsProperty 选项对象中对应的属性名称
  969. * @param {string} onName 启用状态菜单文字
  970. * @param {string} offName 禁用状态菜单文字
  971. * @param {boolean} reload=true 是否需要刷新页面,默认为是。当为否时会尝试两种方法反注册(不可用)菜单,但并不推荐。
  972. * @param {function} callback 回调函数,当 `reload=false` 时可用,会传入当前菜单状态(`true`,`false`)
  973. * @memberof Toolkit
  974. */
  975.  
  976.  
  977. menuToggle(optionsObject, optionsProperty) {
  978. for (var _len20 = arguments.length, args = new Array(_len20 > 2 ? _len20 - 2 : 0), _key20 = 2; _key20 < _len20; _key20++) {
  979. args[_key20 - 2] = arguments[_key20];
  980. }
  981.  
  982. this.menuToggleBasic.apply(this, [() => {
  983. return optionsObject[optionsProperty];
  984. }, state => {
  985. optionsObject[optionsProperty] = state;
  986. }].concat(args));
  987. }
  988.  
  989. menuDebug() {
  990. const isdebug = this.proxyGMKey('is_debug', false);
  991. this.menuToggleBasic(isdebug, isdebug, 'Debug opening……', 'Debug closed.');
  992. }
  993. /* ====== 页面变化监控 ====== */
  994.  
  995. /**
  996. * @summary 创建页面元素变化监控
  997. * @description 用来实时跟踪页面元素变化,及时进行相应修改,但可能和页面程序产生冲突,在执行回调函数期间可能错过某些变化。仅作为对页面的元素的粗略追踪方法。
  998. * @param {object} targetEl 要监控的元素对象
  999. * @param {function} callback 元素发生变化时执行的函数,应该为一个异步函数,会在执行此函数之间停止监控,等待此函数执行结束后再次启动监控
  1000. * @param {object} [obOptions={}] 监控设置项,参见: https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserverInit
  1001. * @return {object} 一个对象,提供两个方法,`start` 开始此监控,`stop` 停止此监控
  1002. * @memberof Toolkit
  1003. */
  1004.  
  1005.  
  1006. pageObserverInit(targetEl, callback) {
  1007. let obOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  1008. const options = Object.assign({
  1009. childList: true,
  1010. subtree: true,
  1011. attributes: true,
  1012. characterData: true,
  1013. attributeOldValue: false,
  1014. characterDataOldValue: false,
  1015. attributeFilter: []
  1016. }, obOptions);
  1017. const observer = new MutationObserver(async (records, observer) => {
  1018. observer.disconnect();
  1019. await callback(records); // 页面处理完成之后重新监控页面变化
  1020.  
  1021. observer.observe(targetEl, options);
  1022. });
  1023. return {
  1024. start: () => {
  1025. observer.observe(targetEl, options);
  1026. },
  1027. stop: () => {
  1028. observer.disconnect();
  1029. }
  1030. };
  1031. }
  1032. /**
  1033. * @summary 对 `MutationRecord` 对象的预处理
  1034. * @description 根据变化类型将受到影响的元素放入数组并标记变化类型,方便后期直接通过对数组的遍历处理。
  1035. * @param {*} records `MutationRecord` 对象。参见: https://developer.mozilla.org/zh-CN/docs/Web/API/MutationRecord
  1036. * @memberof Toolkit
  1037. */
  1038.  
  1039.  
  1040. recordsPreProcessing(records) {
  1041. const result = [];
  1042. records.forEach(el => {
  1043. // 属性变化
  1044. if (/^attributes$/i.test(el.type)) {
  1045. result.push({
  1046. 'type': 'attributes',
  1047. 'el': el.target
  1048. });
  1049. return;
  1050. } // 内容变化
  1051.  
  1052.  
  1053. if (/^characterData$/i.test(el.type)) {
  1054. result.push({
  1055. 'type': 'characterData',
  1056. 'el': el.target
  1057. });
  1058. return;
  1059. } // 后代元素变化
  1060.  
  1061.  
  1062. if (/^childList$/i.test(el.type)) {
  1063. el.addedNodes.forEach(node => result.push({
  1064. 'type': 'childListAdd',
  1065. 'el': node
  1066. }));
  1067. el.removedNodes.forEach(node => result.push({
  1068. 'type': 'childListRemove',
  1069. 'el': node.parentElement
  1070. }));
  1071. return;
  1072. }
  1073. });
  1074. return result;
  1075. }
  1076. /* ====== 基础 API 替代 ====== */
  1077.  
  1078. /**
  1079. * @summary 为页面添加样式
  1080. * @description 当没有 `GM_addStyle` 接口或执行错误时,使用替代方法
  1081. * @param {string} cssString 要注入页面的 CSS 字符串
  1082. * @memberof Toolkit
  1083. */
  1084.  
  1085.  
  1086. addStyle(cssString) {
  1087. if (this.hasGMApi('GM_addStyle', 'addStyle')) {
  1088. try {
  1089. GM_addStyle(cssString);
  1090. return;
  1091. } catch (error) {
  1092. this.dblog('addStyle', error);
  1093. }
  1094. } // 重写添加 style 功能,以兼容无接口的运行环境
  1095.  
  1096.  
  1097. const style = document.createElement('style'); // 添加额外 ID 便于识别
  1098.  
  1099. style.id = 'expand-the-article-for-me';
  1100. style.innerHTML = cssString;
  1101. document.head.appendChild(style);
  1102. }
  1103.  
  1104. }
  1105.  
  1106. exports.Toolkit = Toolkit;
  1107. },{}]},{},["epB2"], "DMS_UserScripts")

QingJ © 2025

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