您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
simple toolkit to help me create userscripts
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/526417/1639004/USToolkit.js
- // ==UserScript==
- // @name USToolkit
- // @namespace https://gf.qytechs.cn/pt-BR/users/821661
- // @version 0.0.5
- // @run-at document-start
- // @author hdyzen
- // @description simple toolkit to help me create userscripts
- // @license MIT
- // ==/UserScript==
- /**
- * Some functions are strongly inspired by:
- * github.com/violentmonkey/
- * github.com/gorhill/uBlock/
- *
- */
- (() => {
- /**
- * Sets up a MutationObserver to watch for DOM changes and executes a callback function.
- * @param {function(MutationRecord[]): (boolean|void)} func The callback function to execute on mutation.
- * It receives an array of MutationRecord objects. If the function returns `true`, the observer is disconnected.
- * @param {MutationObserverInit} [options={ childList: true, subtree: true }] The options object for the MutationObserver.
- * @param {Node} [scope=document] The target node to observe.
- * @returns {MutationObserver} The created MutationObserver instance.
- */
- function observe(func, options = { childList: true, subtree: true }, scope = document) {
- const observer = new MutationObserver((mut) => {
- const shouldDisconnect = func(mut);
- if (shouldDisconnect === true) {
- observer.disconnect();
- }
- });
- observer.observe(scope, options);
- return observer;
- }
- class ObserverEverywhere extends MutationObserver {
- #callback;
- #options;
- #connectedInstances = new Set();
- constructor(callback) {
- const wrapper = (mutations, observer) => {
- this.#handleMutations(mutations);
- if (this.#callback) {
- this.#callback(mutations, observer);
- }
- };
- super(wrapper);
- this.#callback = callback;
- }
- #handleMutations(mutations) {
- for (const mutation of mutations) {
- for (const node of mutation.addedNodes) {
- if (node.nodeType === Node.ELEMENT_NODE) {
- this.#scanNodeForShadows(node);
- }
- }
- }
- }
- #scanNodeForShadows(node) {
- if (node.shadowRoot) {
- this.#createInstance(node.shadowRoot);
- }
- const elementsWithShadow = node.querySelectorAll("*");
- elementsWithShadow.forEach((el) => {
- if (el.shadowRoot) {
- this.#createInstance(el.shadowRoot);
- }
- });
- }
- #createInstance(root) {
- const newObserver = new ObserverEverywhere(this.#callback, root);
- newObserver.observe(root, this.#options);
- this.#connectedInstances.add(newObserver);
- }
- observe(targetNode, options) {
- this.#options = options;
- super.observe(targetNode, this.#options);
- this.#scanNodeForShadows(targetNode);
- }
- disconnect() {
- super.disconnect();
- this.#connectedInstances.forEach((instance) => instance.disconnect());
- this.#connectedInstances.clear();
- }
- }
- class OnElement {
- #rules = new Map();
- #combinedSelector = "";
- #processedChildList = new WeakSet();
- #scope;
- constructor(scope = document.documentElement) {
- this.#scope = scope;
- this.#observe();
- }
- add(selector, fn) {
- this.#scope.querySelectorAll(selector).forEach((element) => {
- fn(element);
- this.#processedChildList.add(element);
- });
- if (!this.#rules.has(selector)) {
- this.#rules.set(selector, []);
- }
- this.#rules.get(selector).push(fn);
- this.#updateSelector();
- }
- once(selector, fn) {
- const onceFn = (element) => {
- fn(element);
- this.remove(selector, onceFn);
- };
- this.add(selector, onceFn);
- }
- remove(selector, fn) {
- const fns = this.#rules.get(selector);
- if (!fns) return;
- if (!fn) {
- this.#rules.delete(selector);
- this.#updateSelector();
- return;
- }
- const filteredFns = fns.filter((existingFn) => existingFn !== fn);
- if (!filteredFns.length) {
- this.#rules.delete(selector);
- this.#updateSelector();
- return;
- }
- this.#rules.set(selector, filteredFns);
- this.#updateSelector();
- }
- #observe() {
- const processedInBatch = new Set();
- const unprocessElement = (elementToForget) => {
- this.#processedChildList.delete(elementToForget);
- };
- const processElement = (element) => {
- if (processedInBatch.has(element) || this.#processedChildList.has(element)) return;
- this.#applyRulesToElement(element);
- processedInBatch.add(element);
- this.#processedChildList.add(element);
- };
- const observer = new MutationObserver((mutations) => {
- if (this.#rules.size === 0) return;
- for (const mutation of mutations) {
- if (
- mutation.type === "attributes" &&
- this.#combinedSelector !== "" &&
- mutation.target.matches(this.#combinedSelector) &&
- !processedInBatch.has(mutation.target) &&
- mutation.oldValue !== mutation.target.getAttribute(mutation.attributeName)
- ) {
- this.#applyRulesToElement(mutation.target);
- processedInBatch.add(mutation.target);
- continue;
- }
- for (const node of mutation.removedNodes) {
- if (node.nodeType !== Node.ELEMENT_NODE) continue;
- unprocessElement(node);
- node.querySelectorAll("*").forEach(unprocessElement);
- }
- for (const node of mutation.addedNodes) {
- if (node.nodeType !== Node.ELEMENT_NODE || this.#combinedSelector === "") continue;
- if (node.matches(this.#combinedSelector)) processElement(node);
- node.querySelectorAll(this.#combinedSelector).forEach(processElement);
- }
- }
- processedInBatch.clear();
- });
- observer.observe(this.#scope, { childList: true, subtree: true, attributes: true, attributeOldValue: true });
- }
- #applyRulesToElement(element) {
- for (const [selector, fns] of this.#rules.entries()) {
- if (!element.matches(selector)) continue;
- fns.forEach((fn) => {
- if (fn(element)) this.remove(selector, fn);
- });
- }
- }
- #updateSelector() {
- this.#combinedSelector = [...this.#rules.keys()].join(",");
- }
- }
- function* queryAll(scope, selector) {
- for (const element of scope.querySelectorAll("*")) {
- if (element.matches(selector)) {
- yield element;
- }
- if (element.shadowRoot) {
- yield* queryAll(element.shadowRoot, selector);
- }
- }
- }
- function query(scope, selector) {
- const iterator = queryAll(scope, selector);
- const result = iterator.next();
- return result.done ? null : result.value;
- }
- function injectScriptInline(code) {
- const script = document.createElement("script");
- script.textContent = code;
- (document.head || document.documentElement).appendChild(script);
- script.remove();
- return;
- }
- /**
- * Waits for an element that matches a given CSS selector to appear in the DOM.
- * @param {string} selector The CSS selector for the element to wait for.
- * @param {number} [timeout=5000] The maximum time to wait in milliseconds.
- * @param {Node} [scope=document] The scope within which to search.
- * @returns {Promise<HTMLElement>} A promise that resolves with the found element, or rejects on timeout.
- */
- function wait(selector, timeout = 5000, scope = document) {
- return new Promise((resolve, reject) => {
- const element = scope.querySelector(selector);
- if (element) {
- resolve(element);
- return true;
- }
- const onMutation = (mutations) => {
- for (const mutation of mutations) {
- if (mutation.type === "attributes" && mutation.target.matches(selector)) {
- resolve(mutation.target);
- return true;
- }
- for (const node of mutation.addedNodes) {
- if (node.nodeType !== Node.ELEMENT_NODE) {
- continue;
- }
- if (node.matches(selector)) {
- resolve(node);
- return true;
- }
- const target = node.querySelector(selector);
- if (target) {
- resolve(target);
- return true;
- }
- }
- }
- };
- const options = {
- childList: true,
- subtree: true,
- attributes: true,
- };
- if (onMutation([]) !== true) {
- observe(onMutation, options, scope);
- }
- setTimeout(() => reject(`Element ${selector} not found`), timeout);
- });
- }
- /**
- * Attaches a delegated event listener to a scope.
- * @param {string} event The name of the event (e.g., 'click').
- * @param {string} selector A CSS selector to filter the event target.
- * @param {function(Event): void} handler The event handler function.
- * @param {Node} [scope=document] The parent element to attach the listener to.
- */
- function on(event, selector, handler, scope = document) {
- scope.addEventListener(event, (e) => {
- if (e.target.matches(selector)) handler(e);
- });
- }
- function createDeepProxy(target) {
- return new Proxy(target, {
- get(obj, prop) {
- console.log(obj, prop);
- const val = Reflect.get(obj, prop);
- if (typeof val === "object" && val !== null) {
- return createDeepProxy(val);
- }
- if (typeof val === "function") {
- return (...args) => {
- const res = val.apply(obj, args);
- return typeof res === "object" && res !== null ? createDeepProxy(res) : res;
- };
- }
- return val;
- },
- });
- }
- /**
- * Safely retrieves a nested property from an object using a string path.
- * Supports special wildcards for arrays ('[]') and objects ('{}' or '*').
- * @param {object} obj The source object.
- * @param {string} chain A dot-separated string for the property path (e.g., 'user.address.street').
- * @returns {*} The value of the nested property, or undefined if not found.
- */
- function safeGet(obj, chain) {
- if (!obj || typeof chain !== "string" || chain === "") {
- return;
- }
- const props = chain.split(".");
- // const props = propChain.match(/'[^']*'|"[^"]*"|\[[^\]]*]|\([^)]*\)|{[^}]*}|[^.()[\]{}\n]+/g);
- // const props = parsePropChain(propChain);
- console.log("Tokens:", props);
- let current = obj;
- for (let i = 0; i < props.length; i++) {
- const prop = props[i];
- if (current === undefined || current === null) {
- break;
- }
- // console.log(current, prop);
- if (prop === "[]") {
- i++;
- current = handleArray(current, props[i]);
- continue;
- }
- if (prop === "{}" || prop === "*") {
- i++;
- current = handleObject(current, props[i]);
- continue;
- }
- if (startsEndsWith(prop, "(", ")")) {
- current = handleFunction(current, prop);
- continue;
- }
- if (startsEndsWith(prop, "[", "]")) {
- current = handleArrayQuery(current, prop);
- continue;
- }
- if (startsEndsWith(prop, "{", "}")) {
- current = handleObjectQuery(current, prop);
- continue;
- }
- if (startsEndsWith(prop, "'") || startsEndsWith(prop, '"')) {
- current = handleQuoted(current, prop);
- continue;
- }
- if (prop.startsWith("/") && /^\/.*\/[gmiyuvsd]+$/.test(prop)) {
- current = handlePattern(current, prop);
- continue;
- }
- current = current[prop];
- }
- return current;
- }
- function handlePattern(obj, nextProp) {
- const lastIndexSlash = nextProp.lastIndexOf("/");
- const pattenString = nextProp.slice(1, lastIndexSlash);
- const flags = nextProp.slice(lastIndexSlash + 1);
- const pattern = new RegExp(pattenString, flags);
- const results = [];
- for (const key of Object.keys(obj)) {
- pattern.lastIndex = 0;
- if (pattern.test(key)) {
- results.push(obj[key]);
- }
- }
- return results;
- }
- /**
- * Safely handles function calls from the property chain.
- * It parses arguments as JSON.
- * @param {function} fn The function to call.
- * @param {string} prop The string containing arguments, e.g., '({"name": "test"})'.
- * @returns {*} The result of the function call.
- */
- function handleFunction(fn, prop) {
- const argString = prop.slice(1, -1).trim().replaceAll("'", '"');
- let args;
- if (argString === "") {
- return fn();
- }
- try {
- args = JSON.parse(`[${argString}]`);
- } catch (err) {
- console.error(`[UST.safeGet] Failed to execute function in property chain "${prop}":`, err);
- }
- return typeof fn === "function" ? fn(...args) : undefined;
- }
- function handleQuoted(obj, prop) {
- return obj[prop.slice(1, -1)];
- }
- /**
- * Filters an array of objects based on a query string in the format "[key=value]" or "[key]".
- *
- * @param {Array<Object>} arr - The array of objects to filter.
- * @param {string} rawQuery - The query string, expected to be in the format "[key=value]" or "[key]".
- * @returns {Array<Object>|undefined} The filtered array of objects if any match the query, otherwise undefined.
- */
- function handleArrayQuery(arr, rawQuery) {
- const query = rawQuery.slice(1, -1);
- const match = query.match(/[!?*=<>]+/);
- const operator = match[0];
- console.log("Operator:", operator);
- const [key, rawValue] = query.split(operator || "");
- const arrayFiltered = [];
- // TODO: Terminar de filtrar a query / Suportar regex na query / Suportar operador para pegar o objeto mais aninhado disponivel
- for (const item of arr) {
- const propValue = safeGet(item, key);
- // if (!operator && propValue !== undefined) {
- // arrayFiltered.push(item);
- // continue;
- // }
- const queryValue = parseValue(rawValue);
- console.log(arr, propValue, queryValue);
- if (
- (operator === "=" && propValue === queryValue) ||
- (operator === "!=" && propValue !== queryValue) ||
- (operator === ">=" && propValue >= queryValue) ||
- (operator === "<=" && propValue <= queryValue) ||
- (operator === "<" && propValue < queryValue) ||
- (operator === ">" && propValue > queryValue) ||
- (operator === "!" && propValue === undefined)
- ) {
- console.log("Retornando objeto");
- arrayFiltered.push(item);
- }
- }
- return arrayFiltered;
- // const query = rawQuery.slice(1, -1);
- // const [key, rawValue] = query.split("=");
- // const filteredArray = [];
- // for (const item of arr) {
- // const propValue = safeGet(item, key);
- // if (
- // (rawValue === undefined && propValue !== undefined) ||
- // (rawValue !== undefined && propValue === parseValue(rawValue))
- // ) {
- // filteredArray.push(item);
- // }
- // }
- // return filteredArray.length ? filteredArray : undefined;
- }
- /**
- * Handles a query on an object, checking if the object's property matches the query.
- *
- * @param {Object} obj - The object to query.
- * @param {string} rawQuery - The query string in the format "[key=value]" or "[key]".
- * @returns {Object|undefined} The object if the query matches, otherwise undefined.
- */
- function handleObjectQuery(obj, rawQuery) {
- const query = rawQuery.slice(1, -1);
- const [operator] = query.match(/[!?*=<>]+/);
- const [key, rawValue] = query.split(operator);
- const propValue = safeGet(obj, key);
- if (!operator && propValue !== undefined) {
- return obj;
- }
- const queryValue = parseValue(rawValue);
- if (
- (operator === "=" && propValue === queryValue) ||
- (operator === "!=" && propValue !== queryValue) ||
- (operator === ">=" && propValue >= queryValue) ||
- (operator === "<=" && propValue <= queryValue) ||
- (operator === "<" && propValue < queryValue) ||
- (operator === ">" && propValue > queryValue) ||
- (operator === "!" && propValue === undefined)
- ) {
- console.log("Retornando objeto");
- return obj;
- }
- }
- function parseValue(value) {
- if (value === "true") {
- return true;
- }
- if (value === "false") {
- return false;
- }
- if (value === "null") {
- return null;
- }
- if (value === "undefined") {
- return undefined;
- }
- if (typeof value === "string" && (startsEndsWith(value, "'") || startsEndsWith(value, '"'))) {
- return value.slice(1, -1);
- }
- if (typeof value === "string" && value.trim() !== "") {
- const num = Number(value);
- return !Number.isNaN(num) ? num : value;
- }
- return value;
- }
- /**
- * Helper for `prop` to handle array wildcards. It maps over an array and extracts a property from each item.
- * @param {Array<object>} arr The array to process.
- * @param {string} nextProp The property to extract from each item.
- * @returns {*} An array of results, or a single result if only one is found.
- */
- function handleArray(arr, nextProp) {
- const results = [];
- for (const item of arr) {
- if (getProp(item, nextProp) !== undefined) {
- results.push(item);
- }
- }
- return results;
- }
- /**
- * Helper for `prop` to handle object wildcards. It maps over an object's values and extracts a property.
- * @param {object} obj The object to process.
- * @param {string} nextProp The property to extract from each value.
- * @returns {*} An array of results, or a single result if only one is found.
- */
- function handleObject(obj, nextProp) {
- const keys = Object.keys(obj);
- const results = [];
- for (const key of keys) {
- if (getProp(obj[key], nextProp) !== undefined) {
- results.push(obj[key]);
- }
- }
- return results;
- }
- /**
- * Safely gets an own property from an object.
- * @param {object} obj The source object.
- * @param {string} prop The property name.
- * @returns {*} The property value or undefined if it doesn't exist.
- */
- function getProp(obj, prop) {
- if (obj && Object.hasOwn(obj, prop)) {
- return obj[prop];
- }
- return;
- }
- /**
- * Checks if a value is a plain JavaScript object.
- * @param {*} val The value to check.
- * @returns {boolean} True if the value is a plain object, otherwise false.
- */
- function isObject(val) {
- return Object.prototype.toString.call(val) === "[object Object]";
- }
- // function compareProps(objToCompare, obj) {
- // return Object.entries(obj).every(([prop, value]) => {
- // return Object.hasOwn(objToCompare, prop) && objToCompare[prop] === value;
- // });
- // }
- /**
- * Checks if all properties and their values in the targetObject exist and are equal in the referenceObject.
- * @param {Object} referenceObject The object to compare against.
- * @param {Object} targetObject The object whose properties and values are checked for equality.
- * @returns {boolean} Returns true if all properties and values in targetObject are present and equal in referenceObject, otherwise false.
- */
- function checkPropertyEquality(referenceObject, targetObject) {
- const entries = Object.entries(targetObject);
- for (const [prop, value] of entries) {
- if (!Object.hasOwn(referenceObject, prop)) {
- return false;
- }
- if (referenceObject[prop] !== value) {
- return false;
- }
- }
- return true;
- }
- function containsValue(valueReference, ...values) {
- for (const value of values) {
- if (valueReference === value) return true;
- }
- return false;
- }
- function startsEndsWith(string, ...searchs) {
- const [startSearch, endSearch] = searchs;
- const firstChar = string[0];
- const lastChar = string[string.length - 1];
- if (endSearch === undefined) {
- return firstChar === startSearch && lastChar === startSearch;
- }
- return firstChar === startSearch && lastChar === endSearch;
- }
- /**
- * Gets a more specific type of a value than `typeof`.
- * @param {*} val The value whose type is to be determined.
- * @returns {string} The type of the value (e.g., 'string', 'array', 'object', 'class', 'null').
- */
- function valType(val) {
- if (val?.prototype?.constructor === val) {
- return "class";
- }
- return Object.prototype.toString.call(val).slice(8, -1).toLowerCase();
- }
- /**
- * Returns the length or size of the given target based on its type.
- *
- * Supported types:
- * - string: Returns the string's length.
- * - array: Returns the array's length.
- * - object: Returns the number of own enumerable properties.
- * - set: Returns the number of elements in the Set.
- * - map: Returns the number of elements in the Map.
- * - null: Returns 0.
- *
- * @param {*} target - The value whose length or size is to be determined.
- * @returns {number} The length or size of the target.
- * @throws {Error} If the type of target is unsupported.
- */
- function len(target) {
- const type = valType(target);
- const types = {
- string: () => target.length,
- object: () => Object.keys(target).length,
- array: () => target.length,
- set: () => target.size,
- map: () => target.size,
- null: () => 0,
- };
- if (types[type]) {
- return types[type]();
- } else {
- throw new Error(`Unsupported type: ${type}`);
- }
- }
- /**
- * Repeatedly calls a function with a delay until it returns `true`.
- * Uses `requestAnimationFrame` for scheduling.
- * @param {function(): (boolean|void)} func The function to run. The loop stops if it returns `true`.
- * @param {number} [time=250] The delay in milliseconds between executions.
- */
- function update(func, time = 250) {
- const exec = () => {
- if (func() === true) {
- return;
- }
- setTimeout(() => {
- requestAnimationFrame(exec);
- }, time);
- };
- requestAnimationFrame(exec);
- }
- /**
- * Runs a function on every animation frame until the function returns `true`.
- * @param {function(): (boolean|void)} func The function to execute. The loop stops if it returns `true`.
- */
- function loop(func) {
- const exec = () => {
- if (func() === true) {
- return;
- }
- requestAnimationFrame(exec);
- };
- requestAnimationFrame(exec);
- }
- /**
- * Injects a CSS string into the document by creating a `<style>` element.
- * @param {string} css The CSS text to apply.
- * @returns {Promise<HTMLStyleElement>} A promise that resolves with the created style element.
- */
- function style(css) {
- return new Promise((resolve) => {
- const toAppend = document.documentElement;
- const styleElement = document.createElement("style");
- styleElement.className = "ust-style";
- styleElement.innerHTML = css;
- toAppend.appendChild(styleElement);
- resolve(styleElement);
- });
- }
- /**
- * Intercepts calls to an object's method using a Proxy, allowing modification of its behavior.
- * @param {object} owner The object that owns the method.
- * @param {string} methodName The name of the method to hook.
- * @param {ProxyHandler<function>} handler The proxy handler to intercept the method call.
- * @returns {function(): void} A function that, when called, reverts the method to its original implementation.
- */
- function hook(owner, methodName, handler) {
- const originalMethod = owner[methodName];
- // if (typeof originalMethod !== "function") {
- // throw new Error(`[UST.patch] The method “${methodName}” was not found in the object "${owner}".`);
- // }
- const proxy = new Proxy(originalMethod, handler);
- owner[methodName] = proxy;
- return () => {
- owner[methodName] = originalMethod;
- };
- }
- /**
- * An object to execute callbacks based on changes in the page URL, useful for Single Page Applications (SPAs).
- */
- const watchUrl = {
- _enabled: false,
- _onUrlRules: [],
- /**
- * Adds a URL pattern and a callback to execute when the URL matches.
- * @param {string|RegExp} pattern The URL pattern to match against. Can be a string or a RegExp.
- * @param {function(): void} func The callback to execute on match.
- */
- add(pattern, func) {
- const isRegex = pattern instanceof RegExp;
- const patternRule = pattern.startsWith("/") ? unsafeWindow.location.origin + pattern : pattern;
- this._onUrlRules.push({ pattern: patternRule, func, isRegex });
- if (this._enabled === false) {
- this._enabled = true;
- this.init();
- }
- },
- /**
- * @private
- * Initializes the URL watching mechanism.
- */
- init() {
- const exec = (currentUrl) => {
- const ruleFound = this._onUrlRules.find((rule) =>
- rule.isRegex ? rule.pattern.test(currentUrl) : rule.pattern === currentUrl,
- );
- if (ruleFound) {
- ruleFound.func();
- }
- };
- watchLocation(exec);
- },
- };
- /**
- * Monitors `location.href` for changes and triggers a callback. It handles history API changes (pushState, replaceState)
- * and popstate events, making it suitable for SPAs.
- * @param {function(string): void} callback The function to call with the new URL when a change is detected.
- */
- function watchLocation(callback) {
- let previousUrl = location.href;
- const observer = new MutationObserver(() => checkForChanges());
- observer.observe(unsafeWindow.document, { childList: true, subtree: true });
- const checkForChanges = () => {
- requestAnimationFrame(() => {
- const currentUrl = location.href;
- if (currentUrl !== previousUrl) {
- previousUrl = currentUrl;
- callback(currentUrl);
- }
- });
- };
- const historyHandler = {
- apply(target, thisArg, args) {
- const result = Reflect.apply(target, thisArg, args);
- checkForChanges();
- return result;
- },
- };
- hook(history, "pushState", historyHandler);
- hook(history, "replaceState", historyHandler);
- unsafeWindow.addEventListener("popstate", checkForChanges);
- callback(previousUrl);
- }
- /**
- * A promise-based wrapper for the Greasemonkey `GM_xmlhttpRequest` function.
- * @param {object} options The options for the request, matching the `GM_xmlhttpRequest` specification.
- * @returns {Promise<object>} A promise that resolves with the response object on success or rejects on error/timeout.
- */
- function request(options) {
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- onload: resolve,
- onerror: reject,
- ontimeout: reject,
- ...options,
- });
- });
- }
- /**
- * Extracts data from an element based on an array of property path definitions.
- * @param {HTMLElement} element The root element to extract properties from.
- * @param {Array<string>} propsArray Array of property definitions, e.g., ["name:innerText", "link:href"].
- * @returns {object} An object containing the extracted data.
- */
- function extractProps(element, propsArray) {
- const data = {};
- for (const propDefinition of propsArray) {
- const [label, valuePath] = propDefinition.split(":");
- if (valuePath) {
- data[label] = safeGet(element, valuePath);
- } else {
- data[label] = safeGet(element, label);
- }
- }
- return data;
- }
- /**
- * @private
- * Handles a string rule in the scrape schema.
- * @param {HTMLElement} container The container element.
- * @param {string} rule The CSS selector for the target element.
- * @returns {string|null} The text content of the found element, or null.
- */
- function _handleStringRule(container, rule) {
- const element = container.querySelector(rule);
- return element ? element.textContent.trim() : null;
- }
- /**
- * @private
- * Handles an array rule in the scrape schema.
- * @param {HTMLElement} container The container element.
- * @param {Array<string>} rule An array where the first item is a sub-selector and the rest are property definitions.
- * @returns {object} The extracted properties from the sub-element.
- */
- function _handleArrayRule(container, rule) {
- const [subSelector, ...propsToGet] = rule;
- if (!subSelector) {
- throw new Error("[UST.scrape] No subselector provided as the first item in the rule");
- }
- const element = container.querySelector(subSelector);
- return extractProps(element, propsToGet);
- }
- const ruleHandlers = {
- string: _handleStringRule,
- array: _handleArrayRule,
- };
- /**
- * @private
- * Determines the type of a scrape rule.
- * @param {*} rule The rule to check.
- * @returns {string} The type of the rule ('string', 'array', or 'unknown').
- */
- function _getRuleType(rule) {
- if (typeof rule === "string") return "string";
- if (Array.isArray(rule)) return "array";
- return "unknown";
- }
- /**
- * @private
- * Processes an object schema for scraping.
- * @param {HTMLElement} container The container element.
- * @param {object} schema The schema object.
- * @returns {object} The scraped data object.
- */
- function _processObjectSchema(container, schema) {
- const item = {};
- for (const key in schema) {
- const rule = schema[key];
- const ruleType = _getRuleType(rule);
- const handler = ruleHandlers[ruleType];
- if (handler) {
- item[key] = handler(container, rule);
- continue;
- }
- console.warn(`[UST.scrape] Rule for key “${key}” has an unsupported type.`);
- }
- return item;
- }
- /**
- * @private
- * Processes a single container element based on the provided schema.
- * @param {HTMLElement} container The container element to process.
- * @param {object|Array<string>} schema The schema to apply.
- * @returns {object} The scraped data.
- */
- function _processContainer(container, schema) {
- if (Array.isArray(schema)) {
- return extractProps(container, schema);
- }
- if (isObject(schema)) {
- return _processObjectSchema(container, schema);
- }
- console.warn("[UST.scrape] Invalid schema format.");
- return {};
- }
- /**
- * Scrapes structured data from the DOM based on a selector and a schema.
- * @param {string} selector CSS selector for the container elements to scrape.
- * @param {object|Array<string>} schema Defines the data to extract from each container.
- * @param {function(HTMLElement, object): void} func A callback for each scraped item, receiving the container element and the extracted data object.
- * @param {Node} [scope=document] The scope within which to search for containers.
- * @returns {Array<object>} An array of the scraped data objects.
- */
- function scrape(selector, schema, func, scope = document) {
- const containers = scope.querySelectorAll(selector);
- const results = [];
- for (const container of containers) {
- const item = _processContainer(container, schema);
- func(container, item);
- results.push(item);
- }
- return results;
- }
- /**
- * Iterates over all elements matching a selector and applies a function to each.
- * @param {string} selector A CSS selector.
- * @param {function(Node): void} func The function to execute for each matching element.
- * @returns {NodeListOf<Element>} The list of nodes found.
- */
- function each(selector, func) {
- const nodes = document.querySelectorAll(selector);
- for (const node of nodes) {
- func(node);
- }
- return nodes;
- }
- /**
- * Chains multiple iterables together into a single sequence.
- * @param {...Iterable} iterables One or more iterable objects (e.g., arrays, sets).
- * @returns {Generator} A generator that yields values from each iterable in order.
- */
- function* chain(...iterables) {
- for (const it of iterables) {
- yield* it;
- }
- }
- /**
- * Creates a debounced version of a function that delays its execution until after a certain time has passed
- * without it being called.
- * @param {function} func The function to debounce.
- * @param {number} wait The debounce delay in milliseconds.
- * @returns {function} The new debounced function.
- */
- function debounce(func, wait) {
- let timeout;
- return function (...args) {
- clearTimeout(timeout);
- timeout = setTimeout(() => func.apply(this, args), wait);
- };
- }
- /**
- * Pauses execution for a specified number of milliseconds.
- * @param {number} ms The number of milliseconds to sleep.
- * @returns {Promise<void>} A promise that resolves after the specified time.
- */
- function sleep(ms) {
- return new Promise((resolve) => setTimeout(resolve, ms));
- }
- /**
- * A simple template engine that extends Map. It replaces `{{placeholder}}` syntax in strings.
- * @extends Map
- */
- class Templates extends Map {
- /**
- * Fills a template with the provided data.
- * @param {*} key The key of the template stored in the map.
- * @param {object} [data={}] An object with key-value pairs to replace placeholders.
- * @returns {string|null} The template string with placeholders filled, or null if the template is not found.
- */
- fill(key, data = {}) {
- const template = super.get(key);
- if (!template) {
- console.warn(`[UST.Templates] Template with key “${key}” not found.`);
- return null;
- }
- return template.replace(/\{\{(\s*\w+\s*)\}\}/g, (match, placeholder) =>
- Object.hasOwn(data, placeholder) ? data[placeholder] : match,
- );
- }
- /**
- * Renders a template into a DocumentFragment.
- * @param {*} key The key of the template stored in the map.
- * @param {object} [data={}] An object with data to fill the placeholders.
- * @returns {DocumentFragment|null} A document fragment containing the rendered HTML, or null if the template is not found.
- */
- render(key, data = {}) {
- const filledHtml = this.fill(key, data);
- if (filledHtml === null) {
- return null;
- }
- const templateElement = document.createElement("template");
- templateElement.innerHTML = filledHtml;
- return templateElement.content.cloneNode(true);
- }
- }
- /**
- * Factory function to create a new Templates instance.
- * @returns {Templates} A new instance of the Templates class.
- */
- function templates() {
- return new Templates();
- }
- /**
- * A class for creating lazy, chainable operations (map, filter, take) on iterables.
- * Operations are only executed when the sequence is consumed.
- */
- class LazySequence extends Array {
- /**
- * @param {Iterable<any>} iterable The initial iterable.
- */
- constructor(iterable) {
- super();
- this.iterable = iterable;
- }
- /**
- * Creates a new lazy sequence with a mapping function.
- * @param {function(*): *} func The mapping function.
- * @returns {LazySequence} A new LazySequence instance.
- */
- map(func) {
- const self = this;
- return new LazySequence({
- *[Symbol.iterator]() {
- for (const value of self.iterable) {
- yield func(value);
- }
- },
- });
- }
- /**
- * Creates a new lazy sequence with a filtering function.
- * @param {function(*): boolean} func The filtering function.
- * @returns {LazySequence} A new LazySequence instance.
- */
- filter(func) {
- const self = this;
- return new LazySequence({
- *[Symbol.iterator]() {
- for (const value of self.iterable) {
- if (func(value)) {
- yield value;
- }
- }
- },
- });
- }
- /**
- * Creates a new lazy sequence that takes only the first n items.
- * @param {number} n The number of items to take.
- * @returns {LazySequence} A new LazySequence instance.
- */
- take(n) {
- const self = this;
- return new LazySequence({
- *[Symbol.iterator]() {
- let count = 0;
- for (const value of self.iterable) {
- if (count >= n) break;
- yield value;
- count++;
- }
- },
- });
- }
- /**
- * Makes the LazySequence itself iterable.
- */
- *[Symbol.iterator]() {
- yield* this.iterable;
- }
- /**
- * Executes all lazy operations and returns the results as an array.
- * @returns {Array<*>} An array containing all values from the processed iterable.
- */
- collect() {
- return [...this.iterable];
- }
- }
- /**
- * Factory function to create a new LazySequence.
- * @param {Iterable<any>} iterable An iterable to wrap.
- * @returns {LazySequence} A new LazySequence instance.
- */
- function lazy(iterable) {
- return new LazySequence(iterable);
- }
- /**
- * Creates a DocumentFragment and populates it using a callback.
- * This is useful for building a piece of DOM in memory before attaching it to the live DOM.
- * @param {function(DocumentFragment): void} builderCallback A function that receives a document fragment and can append nodes to it.
- * @returns {DocumentFragment} The populated document fragment.
- */
- function createFromFragment(builderCallback) {
- const fragment = document.createDocumentFragment();
- builderCallback(fragment);
- return fragment;
- }
- /**
- * Detaches an element from the DOM, runs a callback to perform modifications, and then re-attaches it.
- * This can improve performance by preventing multiple browser reflows and repaints during manipulation.
- * @param {HTMLElement|string} elementOrSelector The element or its CSS selector.
- * @param {function(HTMLElement): void} callback The function to execute with the detached element.
- */
- function withDetached(elementOrSelector, callback) {
- const element = typeof elementOrSelector === "string" ? document.querySelector(elementOrSelector) : elementOrSelector;
- if (!element || !element.parentElement) return;
- const parent = element.parentElement;
- const nextSibling = element.nextElementSibling;
- parent.removeChild(element);
- try {
- callback(element);
- } finally {
- parent.insertBefore(element, nextSibling);
- }
- }
- window.UST = window.UST || {};
- Object.assign(window.UST, {
- observe,
- OnElement,
- queryAll,
- query,
- injectScriptInline,
- wait,
- on,
- createDeepProxy,
- safeGet,
- handleArray,
- handleObject,
- checkPropertyEquality,
- getProp,
- isObject,
- containsValue,
- valType,
- len,
- update,
- loop,
- style,
- hook,
- watchUrl,
- watchLocation,
- request,
- extractProps,
- scrape,
- each,
- chain,
- debounce,
- sleep,
- templates,
- lazy,
- createFromFragment,
- withDetached,
- });
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址