Greasy Fork is available in English.

Basic Functions (For userscripts)

Useful functions for myself

Od 28.07.2023.. Pogledajte najnovija verzija.

Ovu skriptu ne treba izravno instalirati. To je biblioteka za druge skripte koje se uključuju u meta direktivu // @require https://update.greatest.deepsurf.us/scripts/456034/1226884/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.7.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, $$CrE, addStyle, detectDom, 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, logger='log'
  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, logger] = parseArgs([...arguments], [
  82. [2],
  83. [1,2],
  84. [1,2,3]
  85. ], [LogLevel.Info, 'DoLog initialized.', 'log']);
  86.  
  87. let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
  88. let subst = LogLevelMap[level].color;
  89.  
  90. switch (typeof(logContent)) {
  91. case 'string':
  92. msg += '%s';
  93. break;
  94. case 'number':
  95. msg += '%d';
  96. break;
  97. default:
  98. msg += '%o';
  99. break;
  100. }
  101.  
  102. // Log when log level permits
  103. if (level <= DoLog.logLevel) {
  104. // Log to console when log level permits
  105. if (level <= DoLog.logLevel) {
  106. if (++DoLog.logCount > 512) {
  107. console.clear();
  108. DoLog.logCount = 0;
  109. }
  110. console[logger](msg, subst, logContent);
  111. }
  112. }
  113. }
  114. }) ();
  115.  
  116. // type: [Error, TypeError]
  117. function Err(msg, type=0) {
  118. throw new [Error, TypeError][type]((typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + msg);
  119. }
  120.  
  121. // Basic functions
  122. // querySelector
  123. function $() {
  124. switch(arguments.length) {
  125. case 2:
  126. return arguments[0].querySelector(arguments[1]);
  127. break;
  128. default:
  129. return document.querySelector(arguments[0]);
  130. }
  131. }
  132. // querySelectorAll
  133. function $All() {
  134. switch(arguments.length) {
  135. case 2:
  136. return arguments[0].querySelectorAll(arguments[1]);
  137. break;
  138. default:
  139. return document.querySelectorAll(arguments[0]);
  140. }
  141. }
  142. // createElement
  143. function $CrE() {
  144. switch(arguments.length) {
  145. case 2:
  146. return arguments[0].createElement(arguments[1]);
  147. break;
  148. default:
  149. return document.createElement(arguments[0]);
  150. }
  151. }
  152. // addEventListener
  153. function $AEL(...args) {
  154. const target = args.shift();
  155. return target.addEventListener.apply(target, args);
  156. }
  157. function $$CrE() {
  158. const [tagName, props, attrs, classes, styles, listeners] = parseArgs([...arguments], [
  159. function(args, defaultValues) {
  160. const arg = args[0];
  161. return {
  162. 'string': () => [arg, ...defaultValues.filter((arg, i) => i > 0)],
  163. 'object': () => ['tagName', 'props', 'attrs', 'classes', 'styles', 'listeners'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i])
  164. }[typeof arg]();
  165. },
  166. [1,2],
  167. [1,2,3],
  168. [1,2,3,4],
  169. [1,2,3,4,5]
  170. ], ['div', {}, {}, [], {}, []]);
  171. const elm = $CrE(tagName);
  172. for (const [name, val] of Object.entries(props)) {
  173. elm[name] = val;
  174. }
  175. for (const [name, val] of Object.entries(attrs)) {
  176. elm.setAttribute(name, val);
  177. }
  178. for (const cls of Array.isArray(classes) ? classes : [classes]) {
  179. elm.classList.add(cls);
  180. }
  181. for (const [name, val] of Object.entries(styles)) {
  182. elm.style[name] = val;
  183. }
  184. for (const listener of listeners) {
  185. $AEL(...[elm, ...listener]);
  186. }
  187. return elm;
  188. }
  189.  
  190. // Append a style text to document(<head>) with a <style> element
  191. // arguments: css | css, id | parentElement, css, id
  192. // remove old one when id duplicates with another element in document
  193. function addStyle() {
  194. // Get arguments
  195. const [parentElement, css, id] = parseArgs([...arguments], [
  196. [2],
  197. [2,3],
  198. [1,2,3]
  199. ], [document.head, '', null]);
  200.  
  201. // Make <style>
  202. const style = $CrE("style");
  203. style.textContent = css;
  204. id !== null && (style.id = id);
  205. id !== null && $(`#${id}`) && $(`#${id}`).remove();
  206.  
  207. // Append to parentElement
  208. parentElement.appendChild(style);
  209. return style;
  210. }
  211.  
  212. // Get callback when specific dom/element loaded
  213. // detectDom({[root], selector, callback[, once]}) | detectDom(selector, callback) | detectDom(root, selector, callback) | detectDom(root, selector, callback, once)
  214. function detectDom() {
  215. const [root, selector, callback, once] = parseArgs([...arguments], [
  216. function(args, defaultValues) {
  217. const arg = args[0];
  218. return ['root', 'selector', 'callback', 'once'].map((prop, i) => arg.hasOwnProperty(prop) ? arg[prop] : defaultValues[i]);
  219. },
  220. [2,3],
  221. [1,2,3],
  222. [1,2,3,4]
  223. ], [document, '', e => Err('detectDom: callback not found'), true]);
  224.  
  225. if ($(root, selector)) {
  226. for (const elm of $All(root, selector)) {
  227. callback(elm);
  228. if (once) {
  229. return null;
  230. }
  231. }
  232. }
  233.  
  234. const observer = new MutationObserver(mCallback);
  235. observer.observe(root, {
  236. childList: true,
  237. subtree: true
  238. });
  239.  
  240. function mCallback(mutationList, observer) {
  241. const addedNodes = mutationList.reduce((an, mutation) => ((an.push.apply(an, mutation.addedNodes), an)), []);
  242. const addedSelectorNodes = addedNodes.reduce((nodes, anode) => {
  243. if (anode.matches && anode.matches(selector)) {
  244. nodes.add(anode);
  245. }
  246. const childMatches = anode.querySelectorAll ? $All(anode, selector) : [];
  247. for (const cm of childMatches) {
  248. nodes.add(cm);
  249. }
  250. return nodes;
  251. }, new Set());
  252. for (const node of addedSelectorNodes) {
  253. callback(node);
  254. if (once) {
  255. observer.disconnect();
  256. break;
  257. }
  258. }
  259. }
  260.  
  261. return observer;
  262. }
  263.  
  264. // Just stopPropagation and preventDefault
  265. function destroyEvent(e) {
  266. if (!e) {return false;};
  267. if (!e instanceof Event) {return false;};
  268. e.stopPropagation();
  269. e.preventDefault();
  270. }
  271.  
  272. // Object1[prop] ==> Object2[prop]
  273. function copyProp(obj1, obj2, prop) {obj1[prop] !== undefined && (obj2[prop] = obj1[prop]);}
  274. function copyProps(obj1, obj2, props) {(props || Object.keys(obj1)).forEach((prop) => (copyProp(obj1, obj2, prop)));}
  275.  
  276. function parseArgs(args, rules, defaultValues=[]) {
  277. // args and rules should be array, but not just iterable (string is also iterable)
  278. if (!Array.isArray(args) || !Array.isArray(rules)) {
  279. throw new TypeError('parseArgs: args and rules should be array')
  280. }
  281.  
  282. // fill rules[0]
  283. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  284.  
  285. // max arguments length
  286. const count = rules.length - 1;
  287.  
  288. // args.length must <= count
  289. if (args.length > count) {
  290. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  291. }
  292.  
  293. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  294. for (let i = 1; i <= count; i++) {
  295. const rule = rules[i];
  296. if (Array.isArray(rule)) {
  297. if (rule.length !== i) {
  298. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  299. }
  300. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  301. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  302. }
  303. } else if (typeof rule !== 'function') {
  304. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  305. }
  306. }
  307.  
  308. // Parse
  309. const rule = rules[args.length];
  310. let parsed;
  311. if (Array.isArray(rule)) {
  312. parsed = [...defaultValues];
  313. for (let i = 0; i < rule.length; i++) {
  314. parsed[rule[i]-1] = args[i];
  315. }
  316. } else {
  317. parsed = rule(args, defaultValues);
  318. }
  319. return parsed;
  320. }
  321.  
  322. // escape str into javascript written format
  323. function escJsStr(str, quote='"') {
  324. str = str.replaceAll('\\', '\\\\').replaceAll(quote, '\\' + quote).replaceAll('\t', '\\t');
  325. str = quote === '`' ? str.replaceAll(/(\$\{[^\}]*\})/g, '\\$1') : str.replaceAll('\r', '\\r').replaceAll('\n', '\\n');
  326. return quote + str + quote;
  327. }
  328.  
  329. // Replace model text with no mismatching of replacing replaced text
  330. // e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee'
  331. // replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA'
  332. // replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}'
  333. // replaceText('abcd', {}) === 'abcd'
  334. /* Note:
  335. replaceText will replace in sort of replacer's iterating sort
  336. e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT'
  337. but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was
  338. not always the case, and the order is complex. As a result, it's best not to rely on property order.
  339. So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to
  340. replace irrelevance replacer keys only.
  341. */
  342. function replaceText(text, replacer) {
  343. if (Object.entries(replacer).length === 0) {return text;}
  344. const [models, targets] = Object.entries(replacer);
  345. const len = models.length;
  346. let text_arr = [{text: text, replacable: true}];
  347. for (const [model, target] of Object.entries(replacer)) {
  348. text_arr = replace(text_arr, model, target);
  349. }
  350. return text_arr.map((text_obj) => (text_obj.text)).join('');
  351.  
  352. function replace(text_arr, model, target) {
  353. const result_arr = [];
  354. for (const text_obj of text_arr) {
  355. if (text_obj.replacable) {
  356. const splited = text_obj.text.split(model);
  357. for (const part of splited) {
  358. result_arr.push({text: part, replacable: true});
  359. result_arr.push({text: target, replacable: false});
  360. }
  361. result_arr.pop();
  362. } else {
  363. result_arr.push(text_obj);
  364. }
  365. }
  366. return result_arr;
  367. }
  368. }
  369.  
  370. // Get a url argument from lacation.href
  371. // also recieve a function to deal the matched string
  372. // returns defaultValue if name not found
  373. // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name'
  374. function getUrlArgv(details) {
  375. typeof(details) === 'string' && (details = {name: details});
  376. typeof(details) === 'undefined' && (details = {});
  377. if (!details.name) {return null;};
  378.  
  379. const url = details.url ? details.url : location.href;
  380. const name = details.name ? details.name : '';
  381. const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
  382. const defaultValue = details.defaultValue ? details.defaultValue : null;
  383. const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)');
  384. const result = url.match(matcher);
  385. const argv = result ? dealFunc(result[1]) : defaultValue;
  386.  
  387. return argv;
  388. }
  389.  
  390. // Save dataURL to file
  391. function dl_browser(dataURL, filename) {
  392. const a = document.createElement('a');
  393. a.href = dataURL;
  394. a.download = filename;
  395. a.click();
  396. }
  397.  
  398. // File download function
  399. // details looks like the detail of GM_xmlhttpRequest
  400. // onload function will be called after file saved to disk
  401. function dl_GM(details) {
  402. if (!details.url || !details.name) {return false;};
  403.  
  404. // Configure request object
  405. const requestObj = {
  406. url: details.url,
  407. responseType: 'blob',
  408. onload: function(e) {
  409. // Save file
  410. dl_browser(URL.createObjectURL(e.response), details.name);
  411.  
  412. // onload callback
  413. details.onload ? details.onload(e) : function() {};
  414. }
  415. }
  416. if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
  417. if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
  418. if (details.onerror ) {requestObj.onerror = details.onerror;};
  419. if (details.onabort ) {requestObj.onabort = details.onabort;};
  420. if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
  421. if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
  422.  
  423. // Send request
  424. GM_xmlhttpRequest(requestObj);
  425. }
  426.  
  427. function AsyncManager() {
  428. const AM = this;
  429.  
  430. // Ongoing xhr count
  431. this.taskCount = 0;
  432.  
  433. // Whether generate finish events
  434. let finishEvent = false;
  435. Object.defineProperty(this, 'finishEvent', {
  436. configurable: true,
  437. enumerable: true,
  438. get: () => (finishEvent),
  439. set: (b) => {
  440. finishEvent = b;
  441. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  442. }
  443. });
  444.  
  445. // Add one task
  446. this.add = () => (++AM.taskCount);
  447.  
  448. // Finish one task
  449. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  450. }
  451.  
  452. return [
  453. // Console & Debug
  454. LogLevel, DoLog, Err,
  455.  
  456. // DOM
  457. $, $All, $CrE, $AEL, $$CrE, addStyle, detectDom, destroyEvent,
  458.  
  459. // Data
  460. copyProp, copyProps, parseArgs, escJsStr, replaceText,
  461.  
  462. // Environment & Browser
  463. getUrlArgv, dl_browser, dl_GM,
  464.  
  465. // Logic & Task
  466. AsyncManager,
  467. ];
  468. })();