Observe

Observe and wait for elements

当前为 2025-10-11 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/552301/1675911/Observe.js

// ==UserScript==
// @name         Observe
// @license      MIT
// @version      1.3
// @description  Observe and wait for elements
// @author       tukars
// @match        *://*/*
// ==/UserScript==

(function () {
	// Define the namespace structure
	const NAMESPACE_ROOT = "userscript";
	const NAMESPACE_DOMAIN = "com";
	const NAMESPACE_AUTHOR = "tukars";
	const NAMESPACE_LIB_NAME = "Observe";

	window[NAMESPACE_ROOT] = window[NAMESPACE_ROOT] || {};
	window[NAMESPACE_ROOT][NAMESPACE_DOMAIN] =
		window[NAMESPACE_ROOT][NAMESPACE_DOMAIN] || {};
	window[NAMESPACE_ROOT][NAMESPACE_DOMAIN][NAMESPACE_AUTHOR] =
		window[NAMESPACE_ROOT][NAMESPACE_DOMAIN][NAMESPACE_AUTHOR] || {};

	if (
		window[NAMESPACE_ROOT][NAMESPACE_DOMAIN][NAMESPACE_AUTHOR][
		NAMESPACE_LIB_NAME
		]
	) {
		return;
	}

	const timeStamp = function () {
		const now = new Date();
		const pad = (num, size) => String(num).padStart(size, "0");

		const hours = pad(now.getHours(), 2);
		const minutes = pad(now.getMinutes(), 2);
		const seconds = pad(now.getSeconds(), 2);
		const milliseconds = pad(now.getMilliseconds(), 3);

		return `${hours}:${minutes}:${seconds}.${milliseconds}`;
	};

	const contextPrint = function (message, ENABLE_DEBUG_LOGGING) {
		const messageTime = () => `[${message}] ${timeStamp()} -`;

		const log = function (...args) {
			console.log(messageTime(), ...args);
		};
		const warn = function (...args) {
			console.warn(messageTime(), ...args);
		};
		const error = function (...args) {
			console.error(messageTime(), ...args);
		};
		const info = function (...args) {
			console.info(messageTime(), ...args);
		};
		var debug = function (...args) {
			console.debug(messageTime(), ...args);
		};
		if (!ENABLE_DEBUG_LOGGING) {
			debug = function () { };
		}

		return { log, warn, error, info, debug };
	};

	const observeChildren = function (element, config, callback) {
		const observer = new MutationObserver((mutationsList) => {
			for (const mutation of mutationsList) {
				if (mutation.type === "childList") {
					callback(Array.from(mutation.addedNodes));
				}
			}
		});
		const use_config = config || { childList: true, subtree: false };
		observer.observe(element, use_config);
		return observer;
	};

	const observeChildrenWithFilter = function (element, config, filter, callback) {
		return observeChildren(element, config, (addedNodes) =>
			callback(addedNodes.filter(filter))
		);
	};

	const observeChildrenWithTags = function (element, config, filterTags, callback) {
		return observeChildrenWithFilter(
			element,
			config,
			(node) =>
				node.nodeType === Node.ELEMENT_NODE &&
				filterTags.includes(node.tagName.toLowerCase()),
			callback
		);
	};

	const observeAndHandle = function (element, tags, fun, config) {
		const use_config = config || { childList: true, subtree: true };
		observeChildrenWithTags(element, use_config, tags, (nodes) =>
			nodes.forEach((node) => fun(node))
		);
	};

	function waitForElement(selector, timeoutMs = 0) {
		return new Promise((resolve, reject) => {
			const element = document.querySelector(selector);
			if (element) {
				resolve(element);
				return;
			}
			const observer = observeChildren(document.body, { childList: true, subtree: true }, () => {
				const el = document.querySelector(selector);
				if (!el) { return; }
				
				observer.disconnect();
				resolve(el);
			});
			
			if (timeoutMs == 0) { return; }
			
			timeoutId = setTimeout(() => {
				observer.disconnect();
				reject(new Error(`waitForElement timed out after ${timeoutMs}ms for selector: "${selector}"`));
			}, timeoutMs);
		});
	}

	window[NAMESPACE_ROOT][NAMESPACE_DOMAIN][NAMESPACE_AUTHOR][NAMESPACE_LIB_NAME] = {
		timeStamp,
		contextPrint,
		observeChildren,
		observeChildrenWithFilter,
		observeChildrenWithTags,
		observeAndHandle,
		waitForElement,
	};
})();