常用函数

自用函数 For wenku8++

目前为 2022-11-29 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/449412/1122431/Basic%20Functions.js

  1. /* eslint-disable no-multi-spaces */
  2.  
  3. // ==UserScript==
  4. // @name Basic Functions
  5. // @name:zh-CN 常用函数
  6. // @name:en Basic Functions
  7. // @namespace Wenku8++
  8. // @version 0.5
  9. // @description 自用函数 For wenku8++
  10. // @description:zh-CN 自用函数 For wenku8++
  11. // @description:en Useful functions for myself
  12. // @author PY-DNG
  13. // @license GPL-license
  14. // @grant GM_info
  15. // @grant GM_addStyle
  16. // @grant GM_addElement
  17. // @grant GM_deleteValue
  18. // @grant GM_listValues
  19. // @grant GM_addValueChangeListener
  20. // @grant GM_removeValueChangeListener
  21. // @grant GM_setValue
  22. // @grant GM_getValue
  23. // @grant GM_log
  24. // @grant GM_getResourceText
  25. // @grant GM_getResourceURL
  26. // @grant GM_registerMenuCommand
  27. // @grant GM_unregisterMenuCommand
  28. // @grant GM_openInTab
  29. // @grant GM_xmlhttpRequest
  30. // @grant GM_download
  31. // @grant GM_getTab
  32. // @grant GM_saveTab
  33. // @grant GM_getTabs
  34. // @grant GM_notification
  35. // @grant GM_setClipboard
  36. // @grant GM_info
  37. // @grant unsafeWindow
  38. // ==/UserScript==
  39.  
  40. const LogLevel = {
  41. None: 0,
  42. Error: 1,
  43. Success: 2,
  44. Warning: 3,
  45. Info: 4,
  46. }
  47.  
  48. // Arguments: level=LogLevel.Info, logContent, asObject=false
  49. // Needs one call "DoLog();" to get it initialized before using it!
  50. function DoLog() {
  51. // Get window
  52. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
  53.  
  54. const LogLevelMap = {};
  55. LogLevelMap[LogLevel.None] = {
  56. prefix: '',
  57. color: 'color:#ffffff'
  58. }
  59. LogLevelMap[LogLevel.Error] = {
  60. prefix: '[Error]',
  61. color: 'color:#ff0000'
  62. }
  63. LogLevelMap[LogLevel.Success] = {
  64. prefix: '[Success]',
  65. color: 'color:#00aa00'
  66. }
  67. LogLevelMap[LogLevel.Warning] = {
  68. prefix: '[Warning]',
  69. color: 'color:#ffa500'
  70. }
  71. LogLevelMap[LogLevel.Info] = {
  72. prefix: '[Info]',
  73. color: 'color:#888888'
  74. }
  75. LogLevelMap[LogLevel.Elements] = {
  76. prefix: '[Elements]',
  77. color: 'color:#000000'
  78. }
  79.  
  80. // Current log level
  81. DoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  82.  
  83. // Log counter
  84. DoLog.logCount === undefined && (DoLog.logCount = 0);
  85.  
  86. // Get args
  87. let level, logContent, asObject;
  88. switch (arguments.length) {
  89. case 1:
  90. level = LogLevel.Info;
  91. logContent = arguments[0];
  92. asObject = false;
  93. break;
  94. case 2:
  95. level = arguments[0];
  96. logContent = arguments[1];
  97. asObject = false;
  98. break;
  99. case 3:
  100. level = arguments[0];
  101. logContent = arguments[1];
  102. asObject = arguments[2];
  103. break;
  104. default:
  105. level = LogLevel.Info;
  106. logContent = 'DoLog initialized.';
  107. asObject = false;
  108. break;
  109. }
  110.  
  111. // Log when log level permits
  112. if (level <= DoLog.logLevel) {
  113. let msg = '%c' + LogLevelMap[level].prefix + (typeof MODULE_DATA === 'object' ? '[' + MODULE_DATA.name + ']' : '');
  114. let subst = LogLevelMap[level].color;
  115.  
  116. if (asObject) {
  117. msg += ' %o';
  118. } else {
  119. switch (typeof(logContent)) {
  120. case 'string':
  121. msg += ' %s';
  122. break;
  123. case 'number':
  124. msg += ' %d';
  125. break;
  126. case 'object':
  127. msg += ' %o';
  128. break;
  129. }
  130. }
  131.  
  132. if (++DoLog.logCount > 512) {
  133. console.clear();
  134. DoLog.logCount = 0;
  135. }
  136. console.log(msg, subst, logContent);
  137. }
  138. }
  139. DoLog();
  140.  
  141. // Basic functions
  142. // querySelector
  143. function $() {
  144. switch (arguments.length) {
  145. case 2:
  146. return arguments[0].querySelector(arguments[1]);
  147. break;
  148. default:
  149. return document.querySelector(arguments[0]);
  150. }
  151. }
  152. // querySelectorAll
  153. function $All() {
  154. switch (arguments.length) {
  155. case 2:
  156. return arguments[0].querySelectorAll(arguments[1]);
  157. break;
  158. default:
  159. return document.querySelectorAll(arguments[0]);
  160. }
  161. }
  162. // createElement
  163. function $CrE() {
  164. switch (arguments.length) {
  165. case 2:
  166. return arguments[0].createElement(arguments[1]);
  167. break;
  168. default:
  169. return document.createElement(arguments[0]);
  170. }
  171. }
  172. // addEventListener
  173. function $AEL(...args) {
  174. const target = args.shift();
  175. return target.addEventListener.apply(target, args);
  176. }
  177. // Object1[prop] ==> Object2[prop]
  178. function copyProp(obj1, obj2, prop) {
  179. obj1.hasOwnProperty(prop) && (obj2[prop] = obj1[prop]);
  180. }
  181. function copyProps(obj1, obj2, props) {
  182. props.forEach((prop) => (copyProp(obj1, obj2, prop)));
  183. }
  184.  
  185. function clearChildNodes(elm) {
  186. for (const el of elm.childNodes) {
  187. elm.removeChild(el);
  188. }
  189. }
  190.  
  191. // Just stopPropagation and preventDefault
  192. function destroyEvent(e) {
  193. if (!e) {
  194. return false;
  195. };
  196. if (!e instanceof Event) {
  197. return false;
  198. };
  199. e.stopPropagation();
  200. e.preventDefault();
  201. }
  202.  
  203. // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR
  204. // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting)
  205. // (If the request is invalid, such as url === '', will return false and will NOT make this request)
  206. // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event
  207. // Requires: function delItem(){...} & function uniqueIDMaker(){...}
  208. function GMXHRHook(maxXHR = 5) {
  209. const GM_XHR = GM_xmlhttpRequest;
  210. const getID = uniqueIDMaker();
  211. let todoList = [],
  212. ongoingList = [];
  213. GM_xmlhttpRequest = safeGMxhr;
  214.  
  215. function safeGMxhr() {
  216. // Get an id for this request, arrange a request object for it.
  217. const id = getID();
  218. const request = {
  219. id: id,
  220. args: arguments,
  221. aborter: null
  222. };
  223.  
  224. // Deal onload function first
  225. dealEndingEvents(request);
  226.  
  227. /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!
  228. // Stop invalid requests
  229. if (!validCheck(request)) {
  230. return false;
  231. }
  232. */
  233.  
  234. // Judge if we could start the request now or later?
  235. todoList.push(request);
  236. checkXHR();
  237. return makeAbortFunc(id);
  238.  
  239. // Decrease activeXHRCount while GM_XHR onload;
  240. function dealEndingEvents(request) {
  241. const e = request.args[0];
  242.  
  243. // onload event
  244. const oriOnload = e.onload;
  245. e.onload = function() {
  246. reqFinish(request.id);
  247. checkXHR();
  248. oriOnload ? oriOnload.apply(null, arguments) : function() {};
  249. }
  250.  
  251. // onerror event
  252. const oriOnerror = e.onerror;
  253. e.onerror = function() {
  254. reqFinish(request.id);
  255. checkXHR();
  256. oriOnerror ? oriOnerror.apply(null, arguments) : function() {};
  257. }
  258.  
  259. // ontimeout event
  260. const oriOntimeout = e.ontimeout;
  261. e.ontimeout = function() {
  262. reqFinish(request.id);
  263. checkXHR();
  264. oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};
  265. }
  266.  
  267. // onabort event
  268. const oriOnabort = e.onabort;
  269. e.onabort = function() {
  270. reqFinish(request.id);
  271. checkXHR();
  272. oriOnabort ? oriOnabort.apply(null, arguments) : function() {};
  273. }
  274. }
  275.  
  276. // Check if the request is invalid
  277. function validCheck(request) {
  278. const e = request.args[0];
  279.  
  280. if (!e.url) {
  281. return false;
  282. }
  283.  
  284. return true;
  285. }
  286.  
  287. // Call a XHR from todoList and push the request object to ongoingList if called
  288. function checkXHR() {
  289. if (ongoingList.length >= maxXHR) {
  290. return false;
  291. };
  292. if (todoList.length === 0) {
  293. return false;
  294. };
  295. const req = todoList.shift();
  296. const reqArgs = req.args;
  297. const aborter = GM_XHR.apply(null, reqArgs);
  298. req.aborter = aborter;
  299. ongoingList.push(req);
  300. return req;
  301. }
  302.  
  303. // Make a function that aborts a certain request
  304. function makeAbortFunc(id) {
  305. return function() {
  306. let i;
  307.  
  308. // Check if the request haven't been called
  309. for (i = 0; i < todoList.length; i++) {
  310. const req = todoList[i];
  311. if (req.id === id) {
  312. // found this request: haven't been called
  313. delItem(todoList, i);
  314. return true;
  315. }
  316. }
  317.  
  318. // Check if the request is running now
  319. for (i = 0; i < ongoingList.length; i++) {
  320. const req = todoList[i];
  321. if (req.id === id) {
  322. // found this request: running now
  323. req.aborter();
  324. reqFinish(id);
  325. checkXHR();
  326. }
  327. }
  328.  
  329. // Oh no, this request is already finished...
  330. return false;
  331. }
  332. }
  333.  
  334. // Remove a certain request from ongoingList
  335. function reqFinish(id) {
  336. let i;
  337. for (i = 0; i < ongoingList.length; i++) {
  338. const req = ongoingList[i];
  339. if (req.id === id) {
  340. ongoingList = delItem(ongoingList, i);
  341. return true;
  342. }
  343. }
  344. return false;
  345. }
  346. }
  347. }
  348.  
  349. // Get a url argument from lacation.href
  350. // also recieve a function to deal the matched string
  351. // returns defaultValue if name not found
  352. // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name'
  353. function getUrlArgv(details) {
  354. typeof(details) === 'string' && (details = {
  355. name: details
  356. });
  357. typeof(details) === 'undefined' && (details = {});
  358. if (!details.name) {
  359. return null;
  360. };
  361.  
  362. const url = details.url ? details.url : location.href;
  363. const name = details.name ? details.name : '';
  364. const dealFunc = details.dealFunc ? details.dealFunc : ((a) => {
  365. return a;
  366. });
  367. const defaultValue = details.defaultValue ? details.defaultValue : null;
  368. const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)');
  369. const result = url.match(matcher);
  370. const argv = result ? dealFunc(result[1]) : defaultValue;
  371.  
  372. return argv;
  373. }
  374.  
  375. // Append a style text to document(<head>) with a <style> element
  376. function addStyle(css, id) {
  377. const style = document.createElement("style");
  378. id && (style.id = id);
  379. style.textContent = css;
  380. for (const elm of $All(document, '#' + id)) {
  381. elm.parentElement && elm.parentElement.removeChild(elm);
  382. }
  383. document.head.appendChild(style);
  384. }
  385.  
  386. // Save dataURL to file
  387. function saveFile(dataURL, filename) {
  388. const a = document.createElement('a');
  389. a.href = dataURL;
  390. a.download = filename;
  391. a.click();
  392. }
  393.  
  394. // File download function
  395. // details looks like the detail of GM_xmlhttpRequest
  396. // onload function will be called after file saved to disk
  397. function downloadFile(details) {
  398. if (!details.url || !details.name) {
  399. return false;
  400. };
  401.  
  402. // Configure request object
  403. const requestObj = {
  404. url: details.url,
  405. responseType: 'blob',
  406. onload: function(e) {
  407. // Save file
  408. saveFile(URL.createObjectURL(e.response), details.name);
  409.  
  410. // onload callback
  411. details.onload ? details.onload(e) : function() {};
  412. }
  413. }
  414. if (details.onloadstart) {
  415. requestObj.onloadstart = details.onloadstart;
  416. };
  417. if (details.onprogress) {
  418. requestObj.onprogress = details.onprogress;
  419. };
  420. if (details.onerror) {
  421. requestObj.onerror = details.onerror;
  422. };
  423. if (details.onabort) {
  424. requestObj.onabort = details.onabort;
  425. };
  426. if (details.onreadystatechange) {
  427. requestObj.onreadystatechange = details.onreadystatechange;
  428. };
  429. if (details.ontimeout) {
  430. requestObj.ontimeout = details.ontimeout;
  431. };
  432.  
  433. // Send request
  434. GM_xmlhttpRequest(requestObj);
  435. }
  436.  
  437. // get '/' splited API array from a url
  438. function getAPI(url = location.href) {
  439. return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
  440. }
  441.  
  442. // get host part from a url(includes '^https://', '/$')
  443. function getHost(url = location.href) {
  444. const match = location.href.match(/https?:\/\/[^\/]+\//);
  445. return match ? match[0] : match;
  446. }
  447.  
  448. function AsyncManager() {
  449. const AM = this;
  450.  
  451. // Ongoing xhr count
  452. this.taskCount = 0;
  453.  
  454. // Whether generate finish events
  455. let finishEvent = false;
  456. Object.defineProperty(this, 'finishEvent', {
  457. configurable: true,
  458. enumerable: true,
  459. get: () => (finishEvent),
  460. set: (b) => {
  461. finishEvent = b;
  462. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  463. }
  464. });
  465.  
  466. // Add one task
  467. this.add = () => (++AM.taskCount);
  468.  
  469. // Finish one task
  470. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  471. }
  472.  
  473. // Polyfill String.prototype.replaceAll
  474. // replaceValue does NOT support regexp match groups($1, $2, etc.)
  475. function polyfill_replaceAll() {
  476. String.prototype.replaceAll = String.prototype.replaceAll ? String.prototype.replaceAll : PF_replaceAll;
  477.  
  478. function PF_replaceAll(searchValue, replaceValue) {
  479. const str = String(this);
  480.  
  481. if (searchValue instanceof RegExp) {
  482. const global = RegExp(searchValue, 'g');
  483. if (/\$/.test(replaceValue)) {
  484. console.error('Error: Polyfilled String.protopype.replaceAll does support regexp groups');
  485. };
  486. return str.replace(global, replaceValue);
  487. } else {
  488. return str.split(searchValue).join(replaceValue);
  489. }
  490. }
  491. }
  492.  
  493. function randint(min, max) {
  494. return Math.floor(Math.random() * (max - min + 1)) + min;
  495. }
  496.  
  497. function parseArgs(args, rules, defaultValues=[]) {
  498. // args and rules should be array, but not just iterable (string is also iterable)
  499. if (!Array.isArray(args) || !Array.isArray(rules)) {
  500. throw new TypeError('parseArgs: args and rules should be array')
  501. }
  502.  
  503. // fill rules[0]
  504. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  505.  
  506. // max arguments length
  507. const count = rules.length - 1;
  508.  
  509. // args.length must <= count
  510. if (args.length > count) {
  511. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  512. }
  513.  
  514. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  515. for (let i = 1; i <= count; i++) {
  516. const rule = rules[i];
  517. if (Array.isArray(rule)) {
  518. if (rule.length !== i) {
  519. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  520. }
  521. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  522. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  523. }
  524. } else if (typeof rule !== 'function') {
  525. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  526. }
  527. }
  528.  
  529. // Parse
  530. const rule = rules[args.length];
  531. let parsed;
  532. if (Array.isArray(rule)) {
  533. parsed = [...defaultValues];
  534. for (let i = 0; i < rule.length; i++) {
  535. parsed[rule[i]-1] = args[i];
  536. }
  537. } else {
  538. parsed = rule(args, defaultValues);
  539. }
  540. return parsed;
  541. }
  542.  
  543. // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!
  544. function delItem(arr, delIndex) {
  545. arr = arr.slice(0, delIndex).concat(arr.slice(delIndex + 1));
  546. return arr;
  547. }
  548.  
  549. // type: [Error, TypeError]
  550. function Err(msg, type=0) {
  551. throw new [Error, TypeError][type]((typeof MODULE_DATA === 'object' ? '[' + MODULE_DATA.name + ']' : '') + msg);
  552. }