常用函数

自用函数

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

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