coofoUtils

一些工具

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/442002/1541573/coofoUtils.js

  1. // ==UserScript==
  2. // @name coofoUtils
  3. // @namespace https://github.com/coofo/someScript
  4. // @version 0.3.4
  5. // @license MIT License
  6. // @description 一些工具
  7. // @author coofo
  8. // @downloadURL https://github.com/coofo/someScript/raw/main/tampermonkey/coofoUtils.user.js
  9. // @supportURL https://github.com/coofo/someScript/issues
  10. // @grant GM_download
  11. // @grant GM_xmlhttpRequest
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16. window.coofoUtils = {
  17. commonUtils: {
  18. format: {
  19. num: {
  20. fullNum: function (num, length) {
  21. return (Array(length).join('0') + num).slice(-length);
  22. },
  23. toThousands: function (value, seperator, digitNum) {
  24. if ((value = ((value = value + "").replace(/^\s*|\s*$|,*/g, ''))).match(/^\d*\.?\d*$/) == null)
  25. return value;
  26. value = digitNum >= 0 ? (Number(value).toFixed(digitNum) + "") : value;
  27. let r = [],
  28. tl = value.split(".")[0],
  29. tr = value.split(".")[1];
  30. tr = typeof tr !== "undefined" ? tr : "";
  31. if (seperator != null && seperator !== "") {
  32. while (tl.length >= 3) {
  33. r.push(tl.substring(tl.length - 3));
  34. tl = tl.substring(0, tl.length - 3);
  35. }
  36. if (tl.length > 0)
  37. r.push(tl);
  38. r.reverse();
  39. r = r.join(seperator);
  40. return tr === "" ? r : r + "." + tr;
  41. }
  42. return value;
  43. },
  44. percentAutoDigitNum: function (num, total, maxDigitNum) {
  45. let standard = 100;
  46. let digitNum = 0;
  47. while (standard > total && maxDigitNum < digitNum) {
  48. standard *= 10;
  49. digitNum++;
  50. }
  51. return this.toThousands(num / total * 100, null, digitNum) + "%";
  52. }
  53. },
  54. file: {
  55. getSuffix: function (name) {
  56. let index = name.lastIndexOf('.');
  57. if (index < 0) {
  58. return "";
  59. } else {
  60. return name.substring(index + 1);
  61. }
  62. }
  63. },
  64. string: {
  65. byMap: function (str, map, preprocessing) {
  66. let reg = new RegExp('\\${([a-z][a-zA-Z0-9_.]+)}', 'g');
  67. return str.replace(reg, function (match, pos, originalText) {
  68. let key = match.replace(reg, '$1');
  69. let value = map[key];
  70. if (value === null || value === undefined) {
  71. value = match;
  72. }
  73. if (typeof preprocessing === "function") {
  74. value = preprocessing(value, key, map);
  75. }
  76. return value;
  77. });
  78. },
  79. filePathByMap: function (str, map) {
  80. let preprocessing = function (value, key, map) {
  81. let match = key.match(/^(.*)_([a-zA-Z0-9]+)$/);
  82. let ext = null;
  83. if (match != null) {
  84. let rKey = match[1];
  85. ext = match[2];
  86. let rValue = map[rKey];
  87. if (rValue !== null && rValue !== undefined) {
  88. value = rValue;
  89. } else {
  90. value = "";
  91. }
  92. }
  93.  
  94. if (typeof value === "string") {
  95. value = value.replace(/[\/:?"<>*|~]/g, function (match, pos, originalText) {
  96. switch (match) {
  97. case "\\":
  98. return "\";
  99. case "/":
  100. return "/";
  101. case ":":
  102. return ":";
  103. case "?":
  104. return "?";
  105. case '"':
  106. return '"';
  107. case '<':
  108. return '<';
  109. case '>':
  110. return '>';
  111. case '*':
  112. return '*';
  113. case '|':
  114. return '|';
  115. case '~':
  116. return '~';
  117. }
  118. });
  119. }
  120.  
  121. if (ext !== null && value !== "") {
  122. switch (ext) {
  123. case "empty":
  124. break;
  125. case "path":
  126. value += '/';
  127. break;
  128. case "parenthesis":
  129. value = "(" + value + ")";
  130. break;
  131. case "squareBracket":
  132. value = "[" + value + "]";
  133. break;
  134. case "curlyBracket":
  135. value = "{" + value + "}";
  136. break;
  137. default:
  138. let indexMatch = ext.match(/index([0-9]+)/);
  139. if (indexMatch !== null && (typeof value === "number" || ('' + value).match(/^\d+$/))) {
  140. value = coofoUtils.commonUtils.format.num.fullNum(value, Number(indexMatch[1]));
  141. }
  142. break;
  143. }
  144. }
  145.  
  146. return value;
  147. };
  148. return coofoUtils.commonUtils.format.string.byMap(str, map, preprocessing);
  149. }
  150. },
  151. url: {
  152. fullUrl: function (url) {
  153. if (url.match(/^[a-zA-Z0-9]+:\/\//) !== null) {
  154. return url;
  155. } else if (url.match(/^\/\/[a-zA-Z0-9]+/) !== null) {
  156. return window.location.protocol + url;
  157. } else if (url.match(/^\/[a-zA-Z0-9]+/) !== null) {
  158. return window.location.origin + url;
  159. } else {
  160. return url;
  161. }
  162. }
  163. }
  164. },
  165. assert: {
  166. isTrue: function (value, message) {
  167. if (true !== value) {
  168. console.error(message);
  169. console.error(value);
  170. throw message;
  171. }
  172. },
  173. isNull: function (value, message) {
  174. if (value !== null) {
  175. console.error(message);
  176. console.error(value);
  177. throw message;
  178. }
  179. },
  180. notNull: function (value, message) {
  181. if (value === null) {
  182. console.error(message);
  183. console.error(value);
  184. throw message;
  185. }
  186. },
  187. hasLength: function (value, message) {
  188. if (!(value !== null && value.length > 0)) {
  189. console.error(message);
  190. console.error(value);
  191. throw message;
  192. }
  193. },
  194. },
  195. downloadHelp: {
  196. toBlob: {},
  197. toUser: {
  198. asTagA4Url: function (url, fileName) {
  199. let aLink = document.createElement('a');
  200. if (fileName) {
  201. aLink.download = fileName;
  202. } else {
  203. aLink.download = url.substring(url.lastIndexOf('/') + 1);
  204. }
  205. aLink.className = 'download-temp-node';
  206. aLink.target = "_blank";
  207. aLink.style = "display:none;";
  208. aLink.href = url;
  209. document.body.appendChild(aLink);
  210. if (document.all) {
  211. aLink.click(); //IE
  212. } else {
  213. let evt = document.createEvent("MouseEvents");
  214. evt.initEvent("click", true, true);
  215. aLink.dispatchEvent(evt); // 其它浏览器
  216. }
  217. document.body.removeChild(aLink);
  218. },
  219. asTagA4Blob: function (content, fileName) {
  220. if ('msSaveOrOpenBlob' in navigator) {
  221. navigator.msSaveOrOpenBlob(content, fileName);
  222. } else {
  223. let aLink = document.createElement('a');
  224. aLink.className = 'download-temp-node';
  225. aLink.download = fileName;
  226. aLink.style = "display:none;";
  227. let blob = new Blob([content], {type: content.type});
  228. aLink.href = window.URL.createObjectURL(blob);
  229. document.body.appendChild(aLink);
  230. if (document.all) {
  231. aLink.click(); //IE
  232. } else {
  233. let evt = document.createEvent("MouseEvents");
  234. evt.initEvent("click", true, true);
  235. aLink.dispatchEvent(evt); // 其它浏览器
  236. }
  237. window.URL.revokeObjectURL(aLink.href);
  238. document.body.removeChild(aLink);
  239. }
  240. },
  241. asHref4Blob: function (content, fileName) {
  242. let blob = new Blob([content], {type: content.type});
  243. let file = new File([blob], fileName, {type: blob.type});
  244. let url = window.URL.createObjectURL(file);
  245. window.open(url, "_self")
  246. },
  247. asForm: function (url, method, data = null) {
  248. //新建form表单
  249. let form = document.createElement("form");
  250. form.id = "eform";
  251. form.name = "eform";
  252. form.target = "_blank";
  253. document.body.appendChild(form);
  254. //添加参数
  255. if (data != null) {
  256. Object.keys(data).forEach(function (key) {
  257. let sdate_input = document.createElement("input");
  258. sdate_input.type = "text";
  259. sdate_input.name = key;
  260. sdate_input.value = data[key];
  261. form.appendChild(sdate_input);
  262. });
  263. }
  264. form.method = method;
  265. form.action = url;
  266. form.submit();
  267. document.body.removeChild(form);
  268. }
  269. },
  270.  
  271. },
  272. xss: {
  273. htmlEscape: function (text) {
  274. if (!text) {
  275. return text;
  276. }
  277. text = text + "";
  278. return text.replace(/[<>"&']/g, function (match, pos, originalText) {
  279. switch (match) {
  280. case "<":
  281. return "&lt;";
  282. case ">":
  283. return "&gt;";
  284. case "&":
  285. return "&amp;";
  286. case "\"":
  287. return "&quot;";
  288. case "'":
  289. return "&#39;";
  290. }
  291. })
  292. }
  293. },
  294. url: {
  295. //获取url中的参数
  296. getQueryVariable: function (variableKey, defaultValue = null) {
  297. let query = window.location.search.substring(1);
  298. let vars = query.split("&");
  299. for (let i = 0; i < vars.length; i++) {
  300. let pair = vars[i].split("=");
  301. if (pair[0] === variableKey) {
  302. return decodeURIComponent(pair[1]);
  303. }
  304. }
  305. return defaultValue;
  306. },
  307. //在url中添加参数keyValue格式[{key: xx, value: xx}]
  308. addVariable: function (url, keyValues) {
  309. if (!keyValues || keyValues.length === 0) return url;
  310. let add = (u) => {
  311. if (u.lastIndexOf("?") !== -1) {
  312. u += "&";
  313. } else {
  314. u += "?";
  315. }
  316. for (let i = 0; i < keyValues.length; i++) {
  317. if (!(keyValues[i].key || keyValues[i].value)) continue;
  318. u += keyValues[i].key + "=" + encodeURIComponent(keyValues[i].value);
  319. if (i !== keyValues.length - 1) {
  320. u += "&"
  321. }
  322. }
  323. return u;
  324. };
  325. //处理有url有hash的情况
  326. let index = url.indexOf("#");
  327. if (index !== -1) {
  328. let realUrl = url.substr(0, index);
  329. let hash = url.substr(index);
  330. url = add(realUrl) + hash;
  331. } else {
  332. url = add(url);
  333. }
  334. return url;
  335. },
  336. addVariableByData: function (url, data) {
  337. let keyValues = [];
  338. let i = 0;
  339. Object.keys(data).forEach(function (key) {
  340. keyValues[i] = {
  341. key: key,
  342. value: data[key]
  343. };
  344. i++;
  345. });
  346. return coofoUtils.commonUtils.url.addVariable(url, keyValues);
  347. },
  348. getBaseUrl: function () {
  349. return window.location.protocol + '//' + window.location.host;
  350. }
  351. },
  352. browser: {
  353. type: function () {
  354. let u = navigator.userAgent;
  355. let app = navigator.appVersion;
  356. let name = {};
  357.  
  358. name.isAndroid = /Android/i.test(u);
  359. name.isiPhone = /iPhone/i.test(u);
  360. name.isiPad = /iPad/i.test(u);
  361. name.isWindowsPc = /Windows/i.test(u);
  362. name.isWindowsPhone = /Windows Phone/i.test(u);
  363. return name;
  364. }(),
  365. },
  366. },
  367. service: {
  368. //可自动重试的Promise
  369. retryablePromise: {
  370. create: function (exec, retryTimes) {
  371. let taskInfo = {
  372. resolve: null,
  373. reject: null,
  374. retryTimes: retryTimes + 1
  375. };
  376. let p = new Promise((res, rej) => {
  377. taskInfo.resolve = res;
  378. taskInfo.reject = rej;
  379. });
  380.  
  381. let doTask = function () {
  382. new Promise((res, rej) => {
  383. exec(res, rej);
  384. }).then(
  385. r => taskInfo.resolve(r),
  386. r => {
  387. taskInfo.retryTimes--;
  388. if (taskInfo.retryTimes > 0) {
  389. doTask();
  390. } else {
  391. taskInfo.reject(r);
  392. }
  393. }
  394. );
  395. };
  396. doTask();
  397.  
  398. return p;
  399. }
  400. },
  401. task: {
  402. create: function () {
  403. let task = {
  404. runtime: {taskList: [], executing: [], nowExec: false},
  405. api: {
  406. addTask: function (exec, lastRetryTimes) {
  407. if (task.runtime.nowExec) {
  408. return;
  409. }
  410. let taskItem = {
  411. complete: false,
  412. lastFinishTime: 0,
  413. lastRetryTimes: lastRetryTimes + 1,
  414. exec: exec,
  415. success: null,
  416. failed: null
  417. };
  418. task.runtime.taskList.push(taskItem);
  419. },
  420. exec: async function (poolLimit) {
  421. if (task.runtime.nowExec) {
  422. return;
  423. }
  424. const executing = task.runtime.executing;
  425. for (const taskItem of task.runtime.taskList) {
  426. let createPromise = function (taskItem) {
  427. let p = new Promise((resolve, reject) => {
  428. taskItem.success = resolve;
  429. taskItem.failed = reject;
  430. taskItem.exec(taskItem)
  431. }).then(() => {
  432. taskItem.complete = true;
  433. executing.splice(executing.indexOf(p), 1)
  434. },
  435. () => {
  436. taskItem.lastFinishTime = Date.now();
  437. taskItem.lastRetryTimes--;
  438. if (taskItem.lastRetryTimes > 0) {
  439. executing.splice(executing.indexOf(p), 1, createPromise(taskItem));
  440. } else {
  441. executing.splice(executing.indexOf(p), 1)
  442. }
  443. }
  444. );
  445. return p;
  446. };
  447. executing.push(createPromise(taskItem));
  448. while (executing.length >= poolLimit) {
  449. await Promise.race(executing);
  450. }
  451. }
  452. while (executing.length > 0) {
  453. await Promise.race(executing);
  454. }
  455. let completeNum = 0;
  456. let retryTimesOutNum = 0;
  457. for (const taskItem of task.runtime.taskList) {
  458. if (taskItem.complete) {
  459. completeNum++;
  460. } else {
  461. retryTimesOutNum++;
  462. }
  463. }
  464. return {completeNum: completeNum, retryTimesOutNum: retryTimesOutNum};
  465. }
  466. }
  467. };
  468. return task;
  469. }
  470. },
  471. taskCount: {
  472. create: function () {
  473. let taskListInfo = {
  474. size: 0,
  475. finished: 0,
  476. };
  477. let callback = null;
  478. let callCallback = function () {
  479. try {
  480. if (typeof callback === 'function') callback(taskListInfo);
  481. } catch (e) {
  482. console.error(e);
  483. }
  484. };
  485. return {
  486. addTask: function (runnable) {
  487. taskListInfo.size++;
  488. let pending = true;
  489. callCallback();
  490. return function (resolve, reject) {
  491. let taskResolve = function (o) {
  492. if (!pending) {
  493. throw "taskCount task can not exec again"
  494. }
  495. pending = false;
  496. taskListInfo.finished++;
  497. callCallback();
  498. resolve(o);
  499. };
  500. let taskReject = function (o) {
  501. taskListInfo.finished++;
  502. callCallback();
  503. reject(o);
  504. };
  505. runnable(taskResolve, taskReject);
  506. }
  507. },
  508. setStatusCallback: function (thisCallback) {
  509. callback = thisCallback;
  510. },
  511. getInfo: function () {
  512. return taskListInfo;
  513. }
  514. };
  515. }
  516. },
  517. //线程池
  518. threadPoolTaskExecutor: {
  519. create: function (size) {
  520. let executing = [];
  521. let pending = [];
  522.  
  523. let execOne = function () {
  524. if (executing.length < size && pending.length > 0) {
  525. let pendingItem = pending.shift();
  526. let e = new Promise((r, s) => {
  527. pendingItem.runnable(r, s);
  528. }).then(r => {
  529. executing.splice(executing.indexOf(e), 1);
  530. execOne();
  531. pendingItem.resolve(r);
  532. }, r => {
  533. executing.splice(executing.indexOf(e), 1);
  534. execOne();
  535. pendingItem.reject(r);
  536. });
  537. executing.push(e);
  538. }
  539. };
  540.  
  541. return {
  542. execute: function (runnable) {
  543. let thisPendingItem = {runnable: runnable};
  544. let p = new Promise((resolve, reject) => {
  545. thisPendingItem.resolve = resolve;
  546. thisPendingItem.reject = reject;
  547. });
  548. pending.push(thisPendingItem);
  549.  
  550. execOne();
  551. return p;
  552. },
  553. cancelAll: function () {
  554. pending.splice(0);
  555. }
  556. };
  557. }
  558. }
  559. }
  560. };
  561. })();