常用函数

自用函数 For wenku8++

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

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/449412/1083543/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.2
  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. // Global log levels set
  55. LogLevel = {
  56. None: 0,
  57. Error: 1,
  58. Success: 2,
  59. Warning: 3,
  60. Info: 4,
  61. }
  62. const LogLevelMap = {};
  63. LogLevelMap[LogLevel.None] = {
  64. prefix: '',
  65. color: 'color:#ffffff'
  66. }
  67. LogLevelMap[LogLevel.Error] = {
  68. prefix: '[Error]',
  69. color: 'color:#ff0000'
  70. }
  71. LogLevelMap[LogLevel.Success] = {
  72. prefix: '[Success]',
  73. color: 'color:#00aa00'
  74. }
  75. LogLevelMap[LogLevel.Warning] = {
  76. prefix: '[Warning]',
  77. color: 'color:#ffa500'
  78. }
  79. LogLevelMap[LogLevel.Info] = {
  80. prefix: '[Info]',
  81. color: 'color:#888888'
  82. }
  83. LogLevelMap[LogLevel.Elements] = {
  84. prefix: '[Elements]',
  85. color: 'color:#000000'
  86. }
  87.  
  88. // Current log level
  89. DoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  90.  
  91. // Log counter
  92. DoLog.logCount === undefined && (DoLog.logCount = 0);
  93.  
  94. // Get args
  95. let level, logContent, asObject;
  96. switch (arguments.length) {
  97. case 1:
  98. level = LogLevel.Info;
  99. logContent = arguments[0];
  100. asObject = false;
  101. break;
  102. case 2:
  103. level = arguments[0];
  104. logContent = arguments[1];
  105. asObject = false;
  106. break;
  107. case 3:
  108. level = arguments[0];
  109. logContent = arguments[1];
  110. asObject = arguments[2];
  111. break;
  112. default:
  113. level = LogLevel.Info;
  114. logContent = 'DoLog initialized.';
  115. asObject = false;
  116. break;
  117. }
  118.  
  119. // Log when log level permits
  120. if (level <= DoLog.logLevel) {
  121. let msg = '%c' + LogLevelMap[level].prefix + (typeof MODULE_DATA === 'object' ? '[' + MODULE_DATA.name + ']' : '');
  122. let subst = LogLevelMap[level].color;
  123.  
  124. if (asObject) {
  125. msg += ' %o';
  126. } else {
  127. switch (typeof(logContent)) {
  128. case 'string':
  129. msg += ' %s';
  130. break;
  131. case 'number':
  132. msg += ' %d';
  133. break;
  134. case 'object':
  135. msg += ' %o';
  136. break;
  137. }
  138. }
  139.  
  140. if (++DoLog.logCount > 512) {
  141. console.clear();
  142. DoLog.logCount = 0;
  143. }
  144. console.log(msg, subst, logContent);
  145. }
  146. }
  147. DoLog();
  148.  
  149. // Basic functions
  150. // querySelector
  151. function $() {
  152. switch (arguments.length) {
  153. case 2:
  154. return arguments[0].querySelector(arguments[1]);
  155. break;
  156. default:
  157. return document.querySelector(arguments[0]);
  158. }
  159. }
  160. // querySelectorAll
  161. function $All() {
  162. switch (arguments.length) {
  163. case 2:
  164. return arguments[0].querySelectorAll(arguments[1]);
  165. break;
  166. default:
  167. return document.querySelectorAll(arguments[0]);
  168. }
  169. }
  170. // createElement
  171. function $CrE() {
  172. switch (arguments.length) {
  173. case 2:
  174. return arguments[0].createElement(arguments[1]);
  175. break;
  176. default:
  177. return document.createElement(arguments[0]);
  178. }
  179. }
  180. // Object1[prop] ==> Object2[prop]
  181. function copyProp(obj1, obj2, prop) {
  182. obj1.hasOwnProperty(prop) && (obj2[prop] = obj1[prop]);
  183. }
  184. function copyProps(obj1, obj2, props) {
  185. props.forEach((prop) => (copyProp(obj1, obj2, prop)));
  186. }
  187.  
  188. // Just stopPropagation and preventDefault
  189. function destroyEvent(e) {
  190. if (!e) {
  191. return false;
  192. };
  193. if (!e instanceof Event) {
  194. return false;
  195. };
  196. e.stopPropagation();
  197. e.preventDefault();
  198. }
  199.  
  200. // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR
  201. // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting)
  202. // (If the request is invalid, such as url === '', will return false and will NOT make this request)
  203. // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event
  204. // Requires: function delItem(){...} & function uniqueIDMaker(){...}
  205. function GMXHRHook(maxXHR = 5) {
  206. const GM_XHR = GM_xmlhttpRequest;
  207. const getID = uniqueIDMaker();
  208. let todoList = [],
  209. ongoingList = [];
  210. GM_xmlhttpRequest = safeGMxhr;
  211.  
  212. function safeGMxhr() {
  213. // Get an id for this request, arrange a request object for it.
  214. const id = getID();
  215. const request = {
  216. id: id,
  217. args: arguments,
  218. aborter: null
  219. };
  220.  
  221. // Deal onload function first
  222. dealEndingEvents(request);
  223.  
  224. /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!
  225. // Stop invalid requests
  226. if (!validCheck(request)) {
  227. return false;
  228. }
  229. */
  230.  
  231. // Judge if we could start the request now or later?
  232. todoList.push(request);
  233. checkXHR();
  234. return makeAbortFunc(id);
  235.  
  236. // Decrease activeXHRCount while GM_XHR onload;
  237. function dealEndingEvents(request) {
  238. const e = request.args[0];
  239.  
  240. // onload event
  241. const oriOnload = e.onload;
  242. e.onload = function() {
  243. reqFinish(request.id);
  244. checkXHR();
  245. oriOnload ? oriOnload.apply(null, arguments) : function() {};
  246. }
  247.  
  248. // onerror event
  249. const oriOnerror = e.onerror;
  250. e.onerror = function() {
  251. reqFinish(request.id);
  252. checkXHR();
  253. oriOnerror ? oriOnerror.apply(null, arguments) : function() {};
  254. }
  255.  
  256. // ontimeout event
  257. const oriOntimeout = e.ontimeout;
  258. e.ontimeout = function() {
  259. reqFinish(request.id);
  260. checkXHR();
  261. oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};
  262. }
  263.  
  264. // onabort event
  265. const oriOnabort = e.onabort;
  266. e.onabort = function() {
  267. reqFinish(request.id);
  268. checkXHR();
  269. oriOnabort ? oriOnabort.apply(null, arguments) : function() {};
  270. }
  271. }
  272.  
  273. // Check if the request is invalid
  274. function validCheck(request) {
  275. const e = request.args[0];
  276.  
  277. if (!e.url) {
  278. return false;
  279. }
  280.  
  281. return true;
  282. }
  283.  
  284. // Call a XHR from todoList and push the request object to ongoingList if called
  285. function checkXHR() {
  286. if (ongoingList.length >= maxXHR) {
  287. return false;
  288. };
  289. if (todoList.length === 0) {
  290. return false;
  291. };
  292. const req = todoList.shift();
  293. const reqArgs = req.args;
  294. const aborter = GM_XHR.apply(null, reqArgs);
  295. req.aborter = aborter;
  296. ongoingList.push(req);
  297. return req;
  298. }
  299.  
  300. // Make a function that aborts a certain request
  301. function makeAbortFunc(id) {
  302. return function() {
  303. let i;
  304.  
  305. // Check if the request haven't been called
  306. for (i = 0; i < todoList.length; i++) {
  307. const req = todoList[i];
  308. if (req.id === id) {
  309. // found this request: haven't been called
  310. delItem(todoList, i);
  311. return true;
  312. }
  313. }
  314.  
  315. // Check if the request is running now
  316. for (i = 0; i < ongoingList.length; i++) {
  317. const req = todoList[i];
  318. if (req.id === id) {
  319. // found this request: running now
  320. req.aborter();
  321. reqFinish(id);
  322. checkXHR();
  323. }
  324. }
  325.  
  326. // Oh no, this request is already finished...
  327. return false;
  328. }
  329. }
  330.  
  331. // Remove a certain request from ongoingList
  332. function reqFinish(id) {
  333. let i;
  334. for (i = 0; i < ongoingList.length; i++) {
  335. const req = ongoingList[i];
  336. if (req.id === id) {
  337. ongoingList = delItem(ongoingList, i);
  338. return true;
  339. }
  340. }
  341. return false;
  342. }
  343. }
  344. }
  345.  
  346. // Get a url argument from lacation.href
  347. // also recieve a function to deal the matched string
  348. // returns defaultValue if name not found
  349. // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name'
  350. function getUrlArgv(details) {
  351. typeof(details) === 'string' && (details = {
  352. name: details
  353. });
  354. typeof(details) === 'undefined' && (details = {});
  355. if (!details.name) {
  356. return null;
  357. };
  358.  
  359. const url = details.url ? details.url : location.href;
  360. const name = details.name ? details.name : '';
  361. const dealFunc = details.dealFunc ? details.dealFunc : ((a) => {
  362. return a;
  363. });
  364. const defaultValue = details.defaultValue ? details.defaultValue : null;
  365. const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)');
  366. const result = url.match(matcher);
  367. const argv = result ? dealFunc(result[1]) : defaultValue;
  368.  
  369. return argv;
  370. }
  371.  
  372. // Append a style text to document(<head>) with a <style> element
  373. function addStyle(css, id) {
  374. const style = document.createElement("style");
  375. id && (style.id = id);
  376. style.textContent = css;
  377. for (const elm of document.querySelectorAll('#' + id)) {
  378. elm.parentElement && elm.parentElement.removeChild(elm);
  379. }
  380. document.head.appendChild(style);
  381. }
  382.  
  383. // Save dataURL to file
  384. function saveFile(dataURL, filename) {
  385. const a = document.createElement('a');
  386. a.href = dataURL;
  387. a.download = filename;
  388. a.click();
  389. }
  390.  
  391. // File download function
  392. // details looks like the detail of GM_xmlhttpRequest
  393. // onload function will be called after file saved to disk
  394. function downloadFile(details) {
  395. if (!details.url || !details.name) {
  396. return false;
  397. };
  398.  
  399. // Configure request object
  400. const requestObj = {
  401. url: details.url,
  402. responseType: 'blob',
  403. onload: function(e) {
  404. // Save file
  405. saveFile(URL.createObjectURL(e.response), details.name);
  406.  
  407. // onload callback
  408. details.onload ? details.onload(e) : function() {};
  409. }
  410. }
  411. if (details.onloadstart) {
  412. requestObj.onloadstart = details.onloadstart;
  413. };
  414. if (details.onprogress) {
  415. requestObj.onprogress = details.onprogress;
  416. };
  417. if (details.onerror) {
  418. requestObj.onerror = details.onerror;
  419. };
  420. if (details.onabort) {
  421. requestObj.onabort = details.onabort;
  422. };
  423. if (details.onreadystatechange) {
  424. requestObj.onreadystatechange = details.onreadystatechange;
  425. };
  426. if (details.ontimeout) {
  427. requestObj.ontimeout = details.ontimeout;
  428. };
  429.  
  430. // Send request
  431. GM_xmlhttpRequest(requestObj);
  432. }
  433.  
  434. // get '/' splited API array from a url
  435. function getAPI(url = location.href) {
  436. return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
  437. }
  438.  
  439. // get host part from a url(includes '^https://', '/$')
  440. function getHost(url = location.href) {
  441. const match = location.href.match(/https?:\/\/[^\/]+\//);
  442. return match ? match[0] : match;
  443. }
  444.  
  445. function AsyncManager() {
  446. const AM = this;
  447.  
  448. // Ongoing xhr count
  449. this.taskCount = 0;
  450.  
  451. // Whether generate finish events
  452. let finishEvent = false;
  453. Object.defineProperty(this, 'finishEvent', {
  454. configurable: true,
  455. enumerable: true,
  456. get: () => (finishEvent),
  457. set: (b) => {
  458. finishEvent = b;
  459. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  460. }
  461. });
  462.  
  463. // Add one task
  464. this.add = () => (++AM.taskCount);
  465.  
  466. // Finish one task
  467. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  468. }
  469.  
  470. // Polyfill String.prototype.replaceAll
  471. // replaceValue does NOT support regexp match groups($1, $2, etc.)
  472. function polyfill_replaceAll() {
  473. String.prototype.replaceAll = String.prototype.replaceAll ? String.prototype.replaceAll : PF_replaceAll;
  474.  
  475. function PF_replaceAll(searchValue, replaceValue) {
  476. const str = String(this);
  477.  
  478. if (searchValue instanceof RegExp) {
  479. const global = RegExp(searchValue, 'g');
  480. if (/\$/.test(replaceValue)) {
  481. console.error('Error: Polyfilled String.protopype.replaceAll does support regexp groups');
  482. };
  483. return str.replace(global, replaceValue);
  484. } else {
  485. return str.split(searchValue).join(replaceValue);
  486. }
  487. }
  488. }
  489.  
  490. function randint(min, max) {
  491. return Math.floor(Math.random() * (max - min + 1)) + min;
  492. }
  493.  
  494. // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!
  495. function delItem(arr, delIndex) {
  496. arr = arr.slice(0, delIndex).concat(arr.slice(delIndex + 1));
  497. return arr;
  498. }