您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more
当前为
此脚本不应直接安装,它是供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/472956/1279362/UserUtils.js
// ==UserScript== // @namespace https://github.com/Sv443-Network/UserUtils // @exclude * // @author Sv443 // @supportURL https://github.com/Sv443-Network/UserUtils/issues // @homepageURL https://github.com/Sv443-Network/UserUtils#readme // @supportURL https://github.com/Sv443-Network/UserUtils/issues // ==UserLibrary== // @name UserUtils // @description Library with various utilities for userscripts - register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, modify the DOM more easily and more // @version 3.0.0 // @license MIT // @copyright Sv443 (https://github.com/Sv443) // ==/UserScript== // ==/UserLibrary== // ==OpenUserJS== // @author Sv443 // ==/OpenUserJS== 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 __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; 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, range1min, range1max, range2min, range2max) { if (Number(range1min) === 0 && Number(range2min) === 0) return value * (range2max / range1max); return (value - range1min) * ((range2max - range2min) / (range1max - range1min)) + range2min; } 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; } 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)) return NaN; if (min > max) throw new TypeError(`Parameter "min" can't be bigger than "max"`); return Math.floor(Math.random() * (max - min + 1)) + min; } function randomId(length = 16, radix = 16) { const arr = new Uint8Array(length); crypto.getRandomValues(arr); return Array.from( arr, (v) => mapRange(v, 0, 255, 0, radix).toString(radix).substring(0, 1) ).join(""); } // 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/ConfigManager.ts var ConfigManager = class { /** * Creates an instance of ConfigManager to manage a user configuration that is cached in memory and persistently saved across sessions. * Supports migrating data from older versions of the configuration to newer ones and populating the cache with default data if no persistent data is found. * * ⚠️ Requires the directives `@grant GM.getValue` and `@grant GM.setValue` * ⚠️ Make sure to call {@linkcode loadData()} at least once after creating an instance, or the returned data will be the same as `options.defaultConfig` * * @template TData The type of the data that is saved in persistent storage (will be automatically inferred from `config.defaultConfig`) - this should also be the type of the data format associated with the current `options.formatVersion` * @param options The options for this ConfigManager instance */ constructor(options) { __publicField(this, "id"); __publicField(this, "formatVersion"); __publicField(this, "defaultConfig"); __publicField(this, "cachedConfig"); __publicField(this, "migrations"); this.id = options.id; this.formatVersion = options.formatVersion; this.defaultConfig = options.defaultConfig; this.cachedConfig = options.defaultConfig; this.migrations = options.migrations; } /** * Loads the data saved in persistent storage into the in-memory cache and also returns it. * Automatically populates persistent storage with default data if it doesn't contain any data yet. * Also runs all necessary migration functions if the data format has changed since the last time the data was saved. */ loadData() { return __async(this, null, function* () { try { const gmData = yield GM.getValue(`_uucfg-${this.id}`, this.defaultConfig); let gmFmtVer = Number(yield GM.getValue(`_uucfgver-${this.id}`)); if (typeof gmData !== "string") { yield this.saveDefaultData(); return this.defaultConfig; } if (isNaN(gmFmtVer)) yield GM.setValue(`_uucfgver-${this.id}`, gmFmtVer = this.formatVersion); let parsed = JSON.parse(gmData); if (gmFmtVer < this.formatVersion && this.migrations) parsed = yield this.runMigrations(parsed, gmFmtVer); return this.cachedConfig = typeof parsed === "object" ? parsed : void 0; } catch (err) { yield this.saveDefaultData(); return this.defaultConfig; } }); } /** * Returns a copy of the data from the in-memory cache. * Use {@linkcode loadData()} to get fresh data from persistent storage (usually not necessary since the cache should always exactly reflect persistent storage). */ getData() { return this.deepCopy(this.cachedConfig); } /** Saves the data synchronously to the in-memory cache and asynchronously to the persistent storage */ setData(data) { this.cachedConfig = data; return new Promise((resolve) => __async(this, null, function* () { yield Promise.all([ GM.setValue(`_uucfg-${this.id}`, JSON.stringify(data)), GM.setValue(`_uucfgver-${this.id}`, this.formatVersion) ]); resolve(); })); } /** Saves the default configuration data passed in the constructor synchronously to the in-memory cache and asynchronously to persistent storage */ saveDefaultData() { return __async(this, null, function* () { this.cachedConfig = this.defaultConfig; return new Promise((resolve) => __async(this, null, function* () { yield Promise.all([ GM.setValue(`_uucfg-${this.id}`, JSON.stringify(this.defaultConfig)), GM.setValue(`_uucfgver-${this.id}`, this.formatVersion) ]); resolve(); })); }); } /** * Call this method to clear all persistently stored data associated with this ConfigManager instance. * The in-memory cache will be left untouched, so you may still access the data with {@linkcode getData()} * Calling {@linkcode loadData()} or {@linkcode setData()} after this method was called will recreate persistent storage with the cached or default data. * * ⚠️ This requires the additional directive `@grant GM.deleteValue` */ deleteConfig() { return __async(this, null, function* () { yield Promise.all([ GM.deleteValue(`_uucfg-${this.id}`), GM.deleteValue(`_uucfgver-${this.id}`) ]); }); } /** Runs all necessary migration functions consecutively - may be overwritten in a subclass */ runMigrations(oldData, oldFmtVer) { return __async(this, null, function* () { if (!this.migrations) return oldData; let newData = oldData; const sortedMigrations = Object.entries(this.migrations).sort(([a], [b]) => Number(a) - Number(b)); let lastFmtVer = oldFmtVer; for (const [fmtVer, migrationFunc] of sortedMigrations) { const ver = Number(fmtVer); if (oldFmtVer < this.formatVersion && oldFmtVer < ver) { try { const migRes = migrationFunc(newData); newData = migRes instanceof Promise ? yield migRes : migRes; lastFmtVer = oldFmtVer = ver; } catch (err) { console.error(`Error while running migration function for format version ${fmtVer}:`, err); } } } yield Promise.all([ GM.setValue(`_uucfg-${this.id}`, JSON.stringify(newData)), GM.setValue(`_uucfgver-${this.id}`, lastFmtVer) ]); return newData; }); } /** Copies a JSON-compatible object and loses its internal references */ deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); } }; // 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(element, newParent) { const oldParent = element.parentNode; if (!oldParent) throw new Error("Element doesn't have a parent node"); oldParent.replaceChild(newParent, element); newParent.appendChild(element); 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 = () => true) { if (typeof Error.stackTraceLimit === "number" && Error.stackTraceLimit < 1e3) { Error.stackTraceLimit = 1e3; } (function(original) { eventObject.__proto__.addEventListener = function(...args) { var _a, _b; const origListener = typeof args[1] === "function" ? args[1] : (_b = (_a = args[1]) == null ? void 0 : _a.handleEvent) != null ? _b : () => void 0; args[1] = function(...a) { if (args[0] === eventName && predicate(Array.isArray(a) ? a[0] : a)) return; else return origListener.apply(this, a); }; original.apply(this, args); }; })(eventObject.__proto__.addEventListener); } function interceptWindowEvent(eventName, predicate = () => true) { return interceptEvent(getUnsafeWindow(), eventName, predicate); } function amplifyMedia(mediaElement, initialGain = 1) { const context = new (window.AudioContext || window.webkitAudioContext)(); const props = { context, sourceNode: context.createMediaElementSource(mediaElement), gainNode: context.createGain(), /** Sets the gain of the amplifying GainNode */ setGain(gain) { props.gainNode.gain.value = gain; }, /** Returns the current gain of the amplifying GainNode */ getGain() { return props.gainNode.gain.value; }, /** Whether the amplification is currently enabled */ enabled: false, /** Enable the amplification for the first time or if it was disabled before */ enable() { if (props.enabled) return; props.enabled = true; props.sourceNode.connect(props.gainNode); props.gainNode.connect(props.context.destination); }, /** Disable the amplification */ disable() { if (!props.enabled) return; props.enabled = false; props.sourceNode.disconnect(props.gainNode); props.gainNode.disconnect(props.context.destination); props.sourceNode.connect(props.context.destination); } }; props.setGain(initialGain); return props; } function isScrollable(element) { const { overflowX, overflowY } = getComputedStyle(element); return { vertical: (overflowY === "scroll" || overflowY === "auto") && element.scrollHeight > element.clientHeight, horizontal: (overflowX === "scroll" || overflowX === "auto") && element.scrollWidth > element.clientWidth }; } // 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; }); } function insertValues(str, ...values) { return str.replace(/%\d/gm, (match) => { var _a, _b; const argIndex = Number(match.substring(1)) - 1; return (_b = (_a = values[argIndex]) != null ? _a : match) == null ? void 0 : _b.toString(); }); } // lib/SelectorObserver.ts var SelectorObserver = class { constructor(baseElement, options = {}) { __publicField(this, "enabled", false); __publicField(this, "baseElement"); __publicField(this, "observer"); __publicField(this, "observerOptions"); __publicField(this, "listenerMap"); this.baseElement = baseElement; this.listenerMap = /* @__PURE__ */ new Map(); this.observer = new MutationObserver(() => this.checkAllSelectors()); this.observerOptions = __spreadValues({ childList: true, subtree: true }, options); } checkAllSelectors() { for (const [selector, listeners] of this.listenerMap.entries()) this.checkSelector(selector, listeners); } checkSelector(selector, listeners) { var _a; if (!this.enabled) return; const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement; if (!baseElement) return; const all = listeners.some((listener) => listener.all); const one = listeners.some((listener) => !listener.all); const allElements = all ? baseElement.querySelectorAll(selector) : null; const oneElement = one ? baseElement.querySelector(selector) : null; for (const options of listeners) { if (options.all) { if (allElements && allElements.length > 0) { options.listener(allElements); if (!options.continuous) this.removeListener(selector, options); } } else { if (oneElement) { options.listener(oneElement); if (!options.continuous) this.removeListener(selector, options); } } if (((_a = this.listenerMap.get(selector)) == null ? void 0 : _a.length) === 0) this.listenerMap.delete(selector); } } debounce(func, time) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), time); }; } /** * Starts observing the children of the base element for changes to the given {@linkcode selector} according to the set {@linkcode options} * @param selector The selector to observe * @param options Options for the selector observation * @param options.listener Gets called whenever the selector was found in the DOM * @param [options.all] Whether to use `querySelectorAll()` instead - default is false * @param [options.continuous] Whether to call the listener continuously instead of just once - default is false * @param [options.debounce] Whether to debounce the listener to reduce calls to `querySelector` or `querySelectorAll` - set undefined or <=0 to disable (default) */ addListener(selector, options) { options = __spreadValues({ all: false, continuous: false, debounce: 0 }, options); if (options.debounce && options.debounce > 0 || this.observerOptions.defaultDebounce && this.observerOptions.defaultDebounce > 0) { options.listener = this.debounce( options.listener, options.debounce || this.observerOptions.defaultDebounce ); } if (this.listenerMap.has(selector)) this.listenerMap.get(selector).push(options); else this.listenerMap.set(selector, [options]); this.checkSelector(selector, [options]); } /** Disables the observation of the child elements */ disable() { if (!this.enabled) return; this.enabled = false; this.observer.disconnect(); } /** * Enables or reenables the observation of the child elements. * @param immediatelyCheckSelectors Whether to immediately check if all previously registered selectors exist (default is true) * @returns Returns true when the observation was enabled, false otherwise (e.g. when the base element wasn't found) */ enable(immediatelyCheckSelectors = true) { const baseElement = typeof this.baseElement === "string" ? document.querySelector(this.baseElement) : this.baseElement; if (this.enabled || !baseElement) return false; this.enabled = true; this.observer.observe(baseElement, this.observerOptions); if (immediatelyCheckSelectors) this.checkAllSelectors(); return true; } /** Returns whether the observation of the child elements is currently enabled */ isEnabled() { return this.enabled; } /** Removes all listeners that have been registered with {@linkcode addListener()} */ clearListeners() { this.listenerMap.clear(); } /** * Removes all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} * @returns Returns true when all listeners for the associated selector were found and removed, false otherwise */ removeAllListeners(selector) { return this.listenerMap.delete(selector); } /** * Removes a single listener for the given {@linkcode selector} and {@linkcode options} that has been registered with {@linkcode addListener()} * @returns Returns true when the listener was found and removed, false otherwise */ removeListener(selector, options) { const listeners = this.listenerMap.get(selector); if (!listeners) return false; const index = listeners.indexOf(options); if (index > -1) { listeners.splice(index, 1); return true; } return false; } /** Returns all listeners that have been registered with {@linkcode addListener()} */ getAllListeners() { return this.listenerMap; } /** Returns all listeners for the given {@linkcode selector} that have been registered with {@linkcode addListener()} */ getListeners(selector) { return this.listenerMap.get(selector); } }; // lib/translation.ts var trans = {}; var curLang; function tr(key, ...args) { var _a; if (!curLang) return key; const trText = (_a = trans[curLang]) == null ? void 0 : _a[key]; if (!trText) return key; if (args.length > 0 && trText.match(/%\d/)) { return insertValues(trText, ...args); } return trText; } tr.addLanguage = (language, translations) => { trans[language] = translations; }; tr.setLanguage = (language) => { curLang = language; }; tr.getLanguage = () => { return curLang; }; exports.ConfigManager = ConfigManager; exports.SelectorObserver = SelectorObserver; exports.addGlobalStyle = addGlobalStyle; exports.addParent = addParent; exports.amplifyMedia = amplifyMedia; exports.autoPlural = autoPlural; exports.clamp = clamp; exports.debounce = debounce; exports.fetchAdvanced = fetchAdvanced; exports.getUnsafeWindow = getUnsafeWindow; exports.insertAfter = insertAfter; exports.insertValues = insertValues; exports.interceptEvent = interceptEvent; exports.interceptWindowEvent = interceptWindowEvent; exports.isScrollable = isScrollable; exports.mapRange = mapRange; exports.openInNewTab = openInNewTab; exports.pauseFor = pauseFor; exports.preloadImages = preloadImages; exports.randRange = randRange; exports.randomId = randomId; exports.randomItem = randomItem; exports.randomItemIndex = randomItemIndex; exports.randomizeArray = randomizeArray; exports.takeRandomItem = takeRandomItem; exports.tr = tr; return exports; })({});
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址