常用函数(用户脚本)

自用函数

当前为 2023-01-30 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/456034/1143737/Basic%20Functions%20%28For%20userscripts%29.js

  1. /* eslint-disable no-multi-spaces */
  2.  
  3. // ==UserScript==
  4. // @name Basic Functions (For userscripts)
  5. // @name:zh-CN 常用函数(用户脚本)
  6. // @name:en Basic Functions (For userscripts)
  7. // @namespace PY-DNG Userscripts
  8. // @version 0.3.2
  9. // @description Useful functions for myself
  10. // @description:zh-CN 自用函数
  11. // @description:en Useful functions for myself
  12. // @author PY-DNG
  13. // @license GPL-license
  14. // ==/UserScript==
  15.  
  16. let [
  17. // Console & Debug
  18. LogLevel, DoLog, Err,
  19.  
  20. // DOM
  21. $, $All, $CrE, $AEL, addStyle, destroyEvent,
  22.  
  23. // Data
  24. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  25.  
  26. // Environment & Browser
  27. getUrlArgv, dl_browser, dl_GM,
  28.  
  29. // Logic & Task
  30. AsyncManager,
  31. ] = (function() {
  32. // function DoLog() {}
  33. // Arguments: level=LogLevel.Info, logContent, trace=false
  34. const [LogLevel, DoLog] = (function() {
  35. const LogLevel = {
  36. None: 0,
  37. Error: 1,
  38. Success: 2,
  39. Warning: 3,
  40. Info: 4,
  41. };
  42.  
  43. return [LogLevel, DoLog];
  44. function DoLog() {
  45. // Get window
  46. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
  47.  
  48. const LogLevelMap = {};
  49. LogLevelMap[LogLevel.None] = {
  50. prefix: '',
  51. color: 'color:#ffffff'
  52. }
  53. LogLevelMap[LogLevel.Error] = {
  54. prefix: '[Error]',
  55. color: 'color:#ff0000'
  56. }
  57. LogLevelMap[LogLevel.Success] = {
  58. prefix: '[Success]',
  59. color: 'color:#00aa00'
  60. }
  61. LogLevelMap[LogLevel.Warning] = {
  62. prefix: '[Warning]',
  63. color: 'color:#ffa500'
  64. }
  65. LogLevelMap[LogLevel.Info] = {
  66. prefix: '[Info]',
  67. color: 'color:#888888'
  68. }
  69. LogLevelMap[LogLevel.Elements] = {
  70. prefix: '[Elements]',
  71. color: 'color:#000000'
  72. }
  73.  
  74. // Current log level
  75. DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  76.  
  77. // Log counter
  78. DoLog.logCount === undefined && (DoLog.logCount = 0);
  79.  
  80. // Get args
  81. let [level, logContent, trace] = parseArgs([...arguments], [
  82. [2],
  83. [1,2],
  84. [1,2,3]
  85. ], [LogLevel.Info, 'DoLog initialized.', false]);
  86.  
  87. // Log when log level permits
  88. if (level <= DoLog.logLevel) {
  89. let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
  90. let subst = LogLevelMap[level].color;
  91.  
  92. switch (typeof(logContent)) {
  93. case 'string':
  94. msg += '%s';
  95. break;
  96. case 'number':
  97. msg += '%d';
  98. break;
  99. default:
  100. msg += '%o';
  101. break;
  102. }
  103.  
  104. if (++DoLog.logCount > 512) {
  105. console.clear();
  106. DoLog.logCount = 0;
  107. }
  108. console[trace ? 'trace' : 'log'](msg, subst, logContent);
  109. }
  110. }
  111. }) ();
  112.  
  113. // type: [Error, TypeError]
  114. function Err(msg, type=0) {
  115. throw new [Error, TypeError][type]((typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + msg);
  116. }
  117.  
  118. // Basic functions
  119. // querySelector
  120. function $() {
  121. switch(arguments.length) {
  122. case 2:
  123. return arguments[0].querySelector(arguments[1]);
  124. break;
  125. default:
  126. return document.querySelector(arguments[0]);
  127. }
  128. }
  129. // querySelectorAll
  130. function $All() {
  131. switch(arguments.length) {
  132. case 2:
  133. return arguments[0].querySelectorAll(arguments[1]);
  134. break;
  135. default:
  136. return document.querySelectorAll(arguments[0]);
  137. }
  138. }
  139. // createElement
  140. function $CrE() {
  141. switch(arguments.length) {
  142. case 2:
  143. return arguments[0].createElement(arguments[1]);
  144. break;
  145. default:
  146. return document.createElement(arguments[0]);
  147. }
  148. }
  149. // addEventListener
  150. function $AEL(...args) {
  151. const target = args.shift();
  152. return target.addEventListener.apply(target, args);
  153. }
  154.  
  155. // Append a style text to document(<head>) with a <style> element
  156. // arguments: css | parentElement, css | parentElement, css, attributes
  157. function addStyle() {
  158. // Get arguments
  159. const [parentElement, css, attributes] = parseArgs([...arguments], [
  160. [2],
  161. [1,2],
  162. [1,2,3]
  163. ], [document.head, '', {}]);
  164.  
  165. // Make <style>
  166. const style = $CrE("style");
  167. style.textContent = css;
  168. for (const [name, val] of Object.entries(attributes)) {
  169. style.setAttribute(name, val);
  170. }
  171.  
  172. // Append to parentElement
  173. parentElement.appendChild(style);
  174. return style;
  175. }
  176.  
  177. // Just stopPropagation and preventDefault
  178. function destroyEvent(e) {
  179. if (!e) {return false;};
  180. if (!e instanceof Event) {return false;};
  181. e.stopPropagation();
  182. e.preventDefault();
  183. }
  184.  
  185. // Object1[prop] ==> Object2[prop]
  186. function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
  187. function copyProps(obj1, obj2, props) {(props || Object.keys(obj1)).forEach((prop) => (copyProp(obj1, obj2, prop)));}
  188.  
  189. function parseArgs(args, rules, defaultValues=[]) {
  190. // args and rules should be array, but not just iterable (string is also iterable)
  191. if (!Array.isArray(args) || !Array.isArray(rules)) {
  192. throw new TypeError('parseArgs: args and rules should be array')
  193. }
  194.  
  195. // fill rules[0]
  196. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  197.  
  198. // max arguments length
  199. const count = rules.length - 1;
  200.  
  201. // args.length must <= count
  202. if (args.length > count) {
  203. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  204. }
  205.  
  206. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  207. for (let i = 1; i <= count; i++) {
  208. const rule = rules[i];
  209. if (Array.isArray(rule)) {
  210. if (rule.length !== i) {
  211. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  212. }
  213. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  214. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  215. }
  216. } else if (typeof rule !== 'function') {
  217. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  218. }
  219. }
  220.  
  221. // Parse
  222. const rule = rules[args.length];
  223. let parsed;
  224. if (Array.isArray(rule)) {
  225. parsed = [...defaultValues];
  226. for (let i = 0; i < rule.length; i++) {
  227. parsed[rule[i]-1] = args[i];
  228. }
  229. } else {
  230. parsed = rule(args, defaultValues);
  231. }
  232. return parsed;
  233. }
  234.  
  235. // escape str into javascript written format
  236. function escJsStr(str, quote='"') {
  237. str = str.replaceAll('\\', '\\\\').replaceAll(quote, '\\' + quote).replaceAll('\t', '\\t');
  238. str = quote === '`' ? str.replaceAll(/(\$\{[^\}]*\})/g, '\\$1') : str.replaceAll('\r', '\\r').replaceAll('\n', '\\n');
  239. return quote + str + quote;
  240. }
  241.  
  242. // Replace model text with no mismatching of replacing replaced text
  243. // e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee'
  244. // replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA'
  245. // replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}'
  246. // replaceText('abcd', {}) === 'abcd'
  247. /* Note:
  248. replaceText will replace in sort of replacer's iterating sort
  249. e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT'
  250. but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was
  251. not always the case, and the order is complex. As a result, it's best not to rely on property order.
  252. So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to
  253. replace irrelevance replacer keys only.
  254. */
  255. function replaceText(text, replacer) {
  256. if (Object.entries(replacer).length === 0) {return text;}
  257. const [models, targets] = Object.entries(replacer);
  258. const len = models.length;
  259. let text_arr = [{text: text, replacable: true}];
  260. for (const [model, target] of Object.entries(replacer)) {
  261. text_arr = replace(text_arr, model, target);
  262. }
  263. return text_arr.map((text_obj) => (text_obj.text)).join('');
  264.  
  265. function replace(text_arr, model, target) {
  266. const result_arr = [];
  267. for (const text_obj of text_arr) {
  268. if (text_obj.replacable) {
  269. const splited = text_obj.text.split(model);
  270. for (const part of splited) {
  271. result_arr.push({text: part, replacable: true});
  272. result_arr.push({text: target, replacable: false});
  273. }
  274. result_arr.pop();
  275. } else {
  276. result_arr.push(text_obj);
  277. }
  278. }
  279. return result_arr;
  280. }
  281. }
  282.  
  283. // Get a url argument from lacation.href
  284. // also recieve a function to deal the matched string
  285. // returns defaultValue if name not found
  286. // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name'
  287. function getUrlArgv(details) {
  288. typeof(details) === 'string' && (details = {name: details});
  289. typeof(details) === 'undefined' && (details = {});
  290. if (!details.name) {return null;};
  291.  
  292. const url = details.url ? details.url : location.href;
  293. const name = details.name ? details.name : '';
  294. const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
  295. const defaultValue = details.defaultValue ? details.defaultValue : null;
  296. const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)');
  297. const result = url.match(matcher);
  298. const argv = result ? dealFunc(result[1]) : defaultValue;
  299.  
  300. return argv;
  301. }
  302.  
  303. // Save dataURL to file
  304. function dl_browser(dataURL, filename) {
  305. const a = document.createElement('a');
  306. a.href = dataURL;
  307. a.download = filename;
  308. a.click();
  309. }
  310.  
  311. // File download function
  312. // details looks like the detail of GM_xmlhttpRequest
  313. // onload function will be called after file saved to disk
  314. function dl_GM(details) {
  315. if (!details.url || !details.name) {return false;};
  316.  
  317. // Configure request object
  318. const requestObj = {
  319. url: details.url,
  320. responseType: 'blob',
  321. onload: function(e) {
  322. // Save file
  323. dl_browser(URL.createObjectURL(e.response), details.name);
  324.  
  325. // onload callback
  326. details.onload ? details.onload(e) : function() {};
  327. }
  328. }
  329. if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
  330. if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
  331. if (details.onerror ) {requestObj.onerror = details.onerror;};
  332. if (details.onabort ) {requestObj.onabort = details.onabort;};
  333. if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
  334. if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
  335.  
  336. // Send request
  337. GM_xmlhttpRequest(requestObj);
  338. }
  339.  
  340. function AsyncManager() {
  341. const AM = this;
  342.  
  343. // Ongoing xhr count
  344. this.taskCount = 0;
  345.  
  346. // Whether generate finish events
  347. let finishEvent = false;
  348. Object.defineProperty(this, 'finishEvent', {
  349. configurable: true,
  350. enumerable: true,
  351. get: () => (finishEvent),
  352. set: (b) => {
  353. finishEvent = b;
  354. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  355. }
  356. });
  357.  
  358. // Add one task
  359. this.add = () => (++AM.taskCount);
  360.  
  361. // Finish one task
  362. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  363. }
  364.  
  365. return [
  366. // Console & Debug
  367. LogLevel, DoLog, Err,
  368.  
  369. // DOM
  370. $, $All, $CrE, $AEL, addStyle, destroyEvent,
  371.  
  372. // Data
  373. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  374.  
  375. // Environment & Browser
  376. getUrlArgv, dl_browser, dl_GM,
  377.  
  378. // Logic & Task
  379. AsyncManager,
  380. ];
  381. })();

QingJ © 2025

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