Itsnotlupus' Tiny Utilities

small utilities that I'm tired of digging from old scripts to put in new ones.

Ezt a szkriptet nem ajánlott közvetlenül telepíteni. Ez egy könyvtár más szkriptek számára, amik tartalmazzák a // @require https://update.greatest.deepsurf.us/scripts/468394/1247001/Itsnotlupus%27%20Tiny%20Utilities.js hivatkozást.

  1. // ==UserScript==
  2. // @name Itsnotlupus' Tiny Utilities
  3. // @namespace Itsnotlupus Industries
  4. // @version 1.27.1
  5. // @description small utilities that I'm tired of digging from old scripts to put in new ones.
  6. // @author Itsnotlupus
  7. // @license MIT
  8. // ==/UserScript==
  9.  
  10. /* jshint esversion:11 */
  11. /* jshint -W138 */
  12.  
  13. /** DOM queries - CSS selectors and XPath */
  14. const $ = (q,d=document)=>d.querySelector(q);
  15. const $$ = (q,d=document)=>d.querySelectorAll(q);
  16. const $$$ = (q,d=document,x=d.evaluate(q,d),a=[],n=x.iterateNext()) => n ? (a.push(n), $$$(q,d,x,a)) : a;
  17.  
  18. /** calls a function whenever the DOM changes */
  19. const observeDOM = (fn, e=document, config = { attributes: 1, childList: 1, subtree: 1 }, o = new MutationObserver(fn)) => (o.observe(e,config),()=>o.disconnect());
  20.  
  21. /** check a condition upfront, and on every DOM change until true */
  22. const untilDOM = async (v, e=document, f=v.sup?()=>$(v,e):v) => f() || new Promise((r,_,d = observeDOM(() => (_=f()) && d() | r(_), e)) => 0);
  23.  
  24. /** promisify setTimeout and setInterval */
  25. const sleep = (w = 100) => new Promise(r=>setTimeout(r, w));
  26. const until = async (v, w=100, t, f=v.sup?()=>$(v):v) => f() || new Promise(r => t=setInterval((s=f()) => s && (clearInterval(t), r(s)), w));
  27.  
  28. /** slightly less painful syntax to create DOM trees */
  29. const crel = (name, attrs, ...children) => ((e = Object.assign(document.createElement(name), attrs)) => (children.length && e.append(...children), e))();
  30.  
  31. /** same, for SVG content. */
  32. const svg = (name, attrs={}, ...children) => ((e=document.createElementNS('http://www.w3.org/2000/svg', name), _=Object.keys(attrs).forEach(k=>e.setAttribute(k,attrs[k])),__=children.length && e.append(...children)) => e)();
  33.  
  34. /** create a shadow dom with an isolated stylesheet. tbh you're better off just creating a custom element. */
  35. const custom = (name, css, dom, e = crel(name), ss = e.attachShadow({mode:'closed'}), s = new CSSStyleSheet(), t = ss.adoptedStyleSheets = [ (s.replaceSync(css),s) ], u = dom.length && ss.append(...dom)) => e;
  36.  
  37. /** add a stylesheet */
  38. const addStyles = async css => (await untilDOM('head')).append(crel('style', { type: 'text/css', textContent: css }));
  39.  
  40. /** decode HTML entities in a string */
  41. const decodeEntities = str => crel('textarea', { innerHTML: str }).value;
  42.  
  43. /** stolen from https://gist.github.com/nmsdvid/8807205 */
  44. const slowDebounce = (a,b=250,c=0)=>(...d)=>clearTimeout(c,c=setTimeout(a,b,...d));
  45.  
  46. /** microtask debounce */
  47. const fastDebounce = (f, l, s=0) => async (...a) => (l = a, !s && (await (s=1), s = 0, f(...l)));
  48.  
  49. /** remember and shortcut what a (pure) function returns */
  50. const memoize = (f, mkKey=args=>args.join(), cache = Object.create(null)) => (...args) => cache[mkKey(args)] ??= f(...args);
  51.  
  52. /** given an acyclic graph `obj`, visit every node recursively, depth first.
  53. * call fn(obj[key], obj, key) on every non-root node. If fn returns `false`, don't traverse that section further. */
  54. const traverse = (obj, fn) => obj && typeof obj == 'object' && Object.keys(obj).forEach(key => fn(obj[key], obj, key) !== false && traverse(obj[key], fn) );
  55.  
  56. /** requestAnimationFrame wrapper that allows a callback to request another run without referencing itself
  57. * Use as:
  58. * rAF((time, next) => {
  59. * // cool animation code goes here.
  60. * next(); // run again next frame
  61. * });
  62. */
  63. const rAF = (f, n=t=>f(t,r), r=_=>requestAnimationFrame(n)) => r();
  64.  
  65. /** define a few event listeners in one shot - call the returned function to remove them. */
  66. const events = (o, t=window, opts, f=op=>Object.keys(o).forEach(e=>t[op](e,o[e],opts))) => (f("addEventListener"), () => f("removeEventListener"));
  67.  
  68. /** insta-drag handler. just add callbacks. */
  69. function makeDraggable(elt, update, init=update, final=()=>{}) {
  70. return events({
  71. pointerdown(e) { elt.setPointerCapture(e.pointerId, e.preventDefault(init(e))) },
  72. pointermove(e) { elt.hasPointerCapture(e.pointerId) && update(e) },
  73. pointerup(e) { elt.releasePointerCapture(e.pointerId, final(e)) }
  74. }, elt, true);
  75. }
  76.  
  77. /** promisify a @grant-less XHR. probably useless. */
  78. const xhr = (url, type='') => new Promise((r,e,x=Object.assign(new XMLHttpRequest(), {responseType: type,onload() { r(x.response); },onerror:e}),_=x.open('GET',url)) => x.send());
  79.  
  80. /** fetch and parse */
  81. const fetchDOM = (url, mimeType) => fetch(url).then(r=>r.text()).then(t=>new DOMParser().parseFromString(t,mimeType));
  82. const fetchHTML = url => fetchDOM(url, 'text/html');
  83. const fetchJSON = url => fetch(url).then(r=>r.json());
  84.  
  85. /** Prefetch a URL */
  86. const prefetch = url => document.head.append(crel('link', { rel: 'prefetch', href: url }));
  87.  
  88. /** Some sites break the `console` API. This attempts to restore a working console object. */
  89. const fixConsole = (i=crel('iframe',{style:'display:none'}),_=document.body.append(i),c=unsafeWindow.console) => console.log.name!='log' ? (unsafeWindow.console = i.contentWindow.console,()=>(i.remove(),unsafeWindow.console=c)):()=>{};
  90.  
  91. /** Another take on logging */
  92. let logger = console;
  93. /** a cheap way to get logs to show up on sites that damaged their console.log */
  94. async function withLogs(f) {
  95. if (logger.log.name == 'log') return await f();
  96. const iframe=crel('iframe', { style: 'display:none' });
  97. document.body.append(iframe);
  98. const prevLogger = logger;
  99. logger = iframe.contentWindow.console;
  100. try {
  101. return await f();
  102. } finally {
  103. iframe.remove();
  104. logger = prevLogger;
  105. }
  106. }
  107. const logg = (type, color={log:'#ccf',warn:'#fcf',error:'#fcc'}[type]) => (msg, ...args) => logger[type](`%c ${GM_info.script.name}: ${msg}`, 'font-weight:600;color:${color};background:#114;padding:.2em', ...args);
  108. const log = logg('log');
  109. const warn = logg('warn');
  110. const error = logg('error');
  111.  
  112. const logGroup = (msg, ...args) => {
  113. logger.groupCollapsed(`%c ${GM_info.script.name}: ${msg}`, 'font-weight:600;color:#ccf;background:#114;padding:.2em');
  114. args.forEach(arg=>Array.isArray(arg)?logger.log(...arg):logger.log(arg));
  115. logger.groupEnd();
  116. };