UserUtils

Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more

As of 2023-09-04. See the latest version.

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/472956/1245169/UserUtils.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         UserUtils
// @description  Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, modify the DOM more easily and more 
// @namespace    https://github.com/Sv443-Network/UserUtils
// @version      0.5.3
// @license      MIT
// @author       Sv443
// @copyright    Sv443 (https://github.com/Sv443)
// @supportURL   https://github.com/Sv443-Network/UserUtils/issues
// @homepageURL  https://github.com/Sv443-Network/UserUtils#readme
// ==/UserScript==

var UserUtils = (function (exports) {
  var __defProp = Object.defineProperty;
  var __defProps = Object.defineProperties;
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
  var __getOwnPropSymbols = Object.getOwnPropertySymbols;
  var __hasOwnProp = Object.prototype.hasOwnProperty;
  var __propIsEnum = Object.prototype.propertyIsEnumerable;
  var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
  var __spreadValues = (a, b) => {
    for (var prop in b || (b = {}))
      if (__hasOwnProp.call(b, prop))
        __defNormalProp(a, prop, b[prop]);
    if (__getOwnPropSymbols)
      for (var prop of __getOwnPropSymbols(b)) {
        if (__propIsEnum.call(b, prop))
          __defNormalProp(a, prop, b[prop]);
      }
    return a;
  };
  var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
  var __async = (__this, __arguments, generator) => {
    return new Promise((resolve, reject) => {
      var fulfilled = (value) => {
        try {
          step(generator.next(value));
        } catch (e) {
          reject(e);
        }
      };
      var rejected = (value) => {
        try {
          step(generator.throw(value));
        } catch (e) {
          reject(e);
        }
      };
      var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
      step((generator = generator.apply(__this, __arguments)).next());
    });
  };

  // lib/math.ts
  function clamp(value, min, max) {
    return Math.max(Math.min(value, max), min);
  }
  function mapRange(value, range_1_min, range_1_max, range_2_min, range_2_max) {
    if (Number(range_1_min) === 0 && Number(range_2_min) === 0)
      return value * (range_2_max / range_1_max);
    return (value - range_1_min) * ((range_2_max - range_2_min) / (range_1_max - range_1_min)) + range_2_min;
  }
  function randRange(...args) {
    let min, max;
    if (typeof args[0] === "number" && typeof args[1] === "number") {
      [min, max] = args;
    } else if (typeof args[0] === "number" && typeof args[1] !== "number") {
      min = 0;
      max = args[0];
    } else
      throw new TypeError(`Wrong parameter(s) provided - expected: "number" and "number|undefined", got: "${typeof args[0]}" and "${typeof args[1]}"`);
    min = Number(min);
    max = Number(max);
    if (isNaN(min) || isNaN(max))
      throw new TypeError(`Parameters "min" and "max" can't be NaN`);
    if (min > max)
      throw new TypeError(`Parameter "min" can't be bigger than "max"`);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

  // lib/array.ts
  function randomItem(array) {
    return randomItemIndex(array)[0];
  }
  function randomItemIndex(array) {
    if (array.length === 0)
      return [void 0, void 0];
    const idx = randRange(array.length - 1);
    return [array[idx], idx];
  }
  function takeRandomItem(arr) {
    const [itm, idx] = randomItemIndex(arr);
    if (idx === void 0)
      return void 0;
    arr.splice(idx, 1);
    return itm;
  }
  function randomizeArray(array) {
    const retArray = [...array];
    if (array.length === 0)
      return array;
    for (let i = retArray.length - 1; i > 0; i--) {
      const j = Math.floor(randRange(0, 1e4) / 1e4 * (i + 1));
      [retArray[i], retArray[j]] = [retArray[j], retArray[i]];
    }
    return retArray;
  }

  // lib/dom.ts
  function getUnsafeWindow() {
    try {
      return unsafeWindow;
    } catch (e) {
      return window;
    }
  }
  function insertAfter(beforeElement, afterElement) {
    var _a;
    (_a = beforeElement.parentNode) == null ? void 0 : _a.insertBefore(afterElement, beforeElement.nextSibling);
    return afterElement;
  }
  function addParent(element2, newParent) {
    const oldParent = element2.parentNode;
    if (!oldParent)
      throw new Error("Element doesn't have a parent node");
    oldParent.replaceChild(newParent, element2);
    newParent.appendChild(element2);
    return newParent;
  }
  function addGlobalStyle(style) {
    const styleElem = document.createElement("style");
    styleElem.innerHTML = style;
    document.head.appendChild(styleElem);
  }
  function preloadImages(srcUrls, rejects = false) {
    const promises = srcUrls.map((src) => new Promise((res, rej) => {
      const image = new Image();
      image.src = src;
      image.addEventListener("load", () => res(image));
      image.addEventListener("error", (evt) => rejects && rej(evt));
    }));
    return Promise.allSettled(promises);
  }
  function openInNewTab(href) {
    const openElem = document.createElement("a");
    Object.assign(openElem, {
      className: "userutils-open-in-new-tab",
      target: "_blank",
      rel: "noopener noreferrer",
      href
    });
    openElem.style.display = "none";
    document.body.appendChild(openElem);
    openElem.click();
    setTimeout(openElem.remove, 50);
  }
  function interceptEvent(eventObject, eventName, predicate) {
    if (typeof Error.stackTraceLimit === "number" && Error.stackTraceLimit < 1e3) {
      Error.stackTraceLimit = 1e3;
    }
    (function(original) {
      element.__proto__.addEventListener = function(...args) {
        if (args[0] === eventName && predicate())
          return;
        else
          return original.apply(this, args);
      };
    })(eventObject.__proto__.addEventListener);
  }
  function interceptWindowEvent(eventName, predicate) {
    return interceptEvent(getUnsafeWindow(), eventName, predicate);
  }
  function amplifyMedia(mediaElement, multiplier = 1) {
    const context = new (window.AudioContext || window.webkitAudioContext)();
    const result = {
      mediaElement,
      amplify: (multiplier2) => {
        result.gain.gain.value = multiplier2;
      },
      getAmpLevel: () => result.gain.gain.value,
      context,
      source: context.createMediaElementSource(mediaElement),
      gain: context.createGain()
    };
    result.source.connect(result.gain);
    result.gain.connect(context.destination);
    result.amplify(multiplier);
    return result;
  }

  // lib/misc.ts
  function autoPlural(word, num) {
    if (Array.isArray(num) || num instanceof NodeList)
      num = num.length;
    return `${word}${num === 1 ? "" : "s"}`;
  }
  function pauseFor(time) {
    return new Promise((res) => {
      setTimeout(() => res(), time);
    });
  }
  function debounce(func, timeout = 300) {
    let timer;
    return function(...args) {
      clearTimeout(timer);
      timer = setTimeout(() => func.apply(this, args), timeout);
    };
  }
  function fetchAdvanced(_0) {
    return __async(this, arguments, function* (url, options = {}) {
      const { timeout = 1e4 } = options;
      const controller = new AbortController();
      const id = setTimeout(() => controller.abort(), timeout);
      const res = yield fetch(url, __spreadProps(__spreadValues({}, options), {
        signal: controller.signal
      }));
      clearTimeout(id);
      return res;
    });
  }

  // lib/onSelector.ts
  var selectorMap = /* @__PURE__ */ new Map();
  function onSelector(selector, options) {
    let selectorMapItems = [];
    if (selectorMap.has(selector))
      selectorMapItems = selectorMap.get(selector);
    selectorMapItems.push(options);
    selectorMap.set(selector, selectorMapItems);
    checkSelectorExists(selector, selectorMapItems);
  }
  function removeOnSelector(selector) {
    return selectorMap.delete(selector);
  }
  function checkSelectorExists(selector, options) {
    const deleteIndices = [];
    options.forEach((option, i) => {
      try {
        const elements = option.all ? document.querySelectorAll(selector) : document.querySelector(selector);
        if (elements !== null && elements instanceof NodeList && elements.length > 0 || elements !== null) {
          option.listener(elements);
          if (!option.continuous)
            deleteIndices.push(i);
        }
      } catch (err) {
        console.error(`Couldn't call listener for selector '${selector}'`, err);
      }
    });
    if (deleteIndices.length > 0) {
      const newOptsArray = options.filter((_, i) => !deleteIndices.includes(i));
      if (newOptsArray.length === 0)
        selectorMap.delete(selector);
      else {
        selectorMap.set(selector, newOptsArray);
      }
    }
  }
  function initOnSelector(options = {}) {
    const observer = new MutationObserver(() => {
      for (const [selector, options2] of selectorMap.entries())
        checkSelectorExists(selector, options2);
    });
    observer.observe(document.body, __spreadValues({
      subtree: true,
      childList: true
    }, options));
  }
  function getSelectorMap() {
    return selectorMap;
  }

  exports.addGlobalStyle = addGlobalStyle;
  exports.addParent = addParent;
  exports.amplifyMedia = amplifyMedia;
  exports.autoPlural = autoPlural;
  exports.clamp = clamp;
  exports.debounce = debounce;
  exports.fetchAdvanced = fetchAdvanced;
  exports.getSelectorMap = getSelectorMap;
  exports.getUnsafeWindow = getUnsafeWindow;
  exports.initOnSelector = initOnSelector;
  exports.insertAfter = insertAfter;
  exports.interceptEvent = interceptEvent;
  exports.interceptWindowEvent = interceptWindowEvent;
  exports.mapRange = mapRange;
  exports.onSelector = onSelector;
  exports.openInNewTab = openInNewTab;
  exports.pauseFor = pauseFor;
  exports.preloadImages = preloadImages;
  exports.randRange = randRange;
  exports.randomItem = randomItem;
  exports.randomItemIndex = randomItemIndex;
  exports.randomizeArray = randomizeArray;
  exports.removeOnSelector = removeOnSelector;
  exports.takeRandomItem = takeRandomItem;

  return exports;

})({});