YouTube channel description popup on hover

YouTube channel description popup on channel name hover!

目前為 2023-09-08 提交的版本,檢視 最新版本

// ==UserScript==
// @name         YouTube channel description popup on hover
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  YouTube channel description popup on channel name hover!
// @author       @dmtri
// @match        https://www.youtube.com/*
// @icon
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
	'use strict';

	const MAGIC_NUMBER = 1500;
	let allEventHandlers = [];

	const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

	const debounce = (mainFunction, delay) => {
		let timer;
		return function (...args) {
			clearTimeout(timer);
			timer = setTimeout(() => {
				mainFunction(...args);
			}, delay);
		};
	};

	const initTag = '.youtube-popup-desc-init';

	const profileIdentifierUrlContainer = '#container.ytd-channel-name';

	// get the url from element with profileIdentifier
	const getProfileUrl = (profileMetaDataElement) => {
		const anchor = profileMetaDataElement.getElementsByTagName('a')[0];
		return anchor.href;
	};

	const clearEvents = (profileMetaDataElement) => {
		// clear all existing event handlers
		allEventHandlers.forEach((handler) => {
			profileMetaDataElement.forEach((element) => {
				element.removeEventListener('mouseenter', handler);
			});
		});
		allEventHandlers = [];
	};

	// display a popup when hover over the profileMetaData element
	const init = (force = false) => {
		if (!force && document.querySelector(initTag)) {
			return;
		}

		const profileMetaDataElement = document.querySelectorAll(profileIdentifierUrlContainer);
		if (!profileMetaDataElement || !profileMetaDataElement.length) {
			return;
		}

		clearEvents(profileMetaDataElement);

		const inlineHandler = async (e) => {
			const element = e.target;
			let isValidGesture = true;

			const mouseLeaveHandler = () => {
				isValidGesture = false;
			};
			element.removeEventListener('mouseleave', mouseLeaveHandler);
			element.addEventListener('mouseleave', mouseLeaveHandler);

			await wait(1500);
			// valid gesture meaning a mouse enter event
			// is not followed by a mouse leave event
			// within 1.5 seconds
			handler(element, isValidGesture);
		};
		allEventHandlers.push(inlineHandler);

		profileMetaDataElement.forEach((element) => {
			element.addEventListener('mouseenter', inlineHandler);
		});

		// append init tag to document
		const initTagElement = document.createElement('div');
		initTagElement.classList.add(initTag.replace('.', ''));
		document.body.appendChild(initTagElement);

		// init when scrolled down on home page
		const grid = '#contents.ytd-rich-grid-renderer';
		const callback = (mutations) => {
			mutations.forEach((mutation) => {
				if (mutation.type === 'childList') {
					debouncedInit(true);
				}
			});
		};

		let observer;
		if (observer) observer.disconnect();
		observer = new MutationObserver(callback);
		const targetNode = document.querySelector(grid);
		const config = { childList: true, subtree: true };

		observer.observe(targetNode, config);
	};

	const debouncedInit = debounce(init, MAGIC_NUMBER);

	const handler = async (profileMetaDataElement, isValidGesture) => {
		if (!isValidGesture) return;

		// display a native dialog element
		const dialog = document.createElement('dialog');
		const url = getProfileUrl(profileMetaDataElement);

		let desc = '';

		// append a spinner next to a channel name, then close it after 3 sec
		const spinner = document.createElement('div');
		spinner.innerHTML = 'loading...';
		profileMetaDataElement.appendChild(spinner);
		setTimeout(() => {
			spinner.remove();
		}, 3000);

		await fetch(url)
			.then((response) => response.text())
			.then((html) => {
				// <meta property="og:description" content="This is a place for all the things that are awesome on stream. ">
				const parser = new DOMParser();
				const doc = parser.parseFromString(html, 'text/html');
				const meta = doc.querySelector('meta[property="og:description"]');

				desc = !meta ? (desc = 'No desc available') : meta.getAttribute('content');
			});

		dialog.innerHTML = `
    <div>
      <h2>${desc}</h2>
      <h1>
        <a href="${url}">${url}</h1>
      </h1>
    </div>
  `;
		document.body.appendChild(dialog);
		dialog.showModal();

		setTimeout(() => {
			dialog.close();
		}, 3000);
	};

	// while there is no init tag in the document, keep trying to init
	const tryInit = () => {
		if (!document.querySelector(initTag)) {
			setTimeout(() => {
				init();
				tryInit();
			}, MAGIC_NUMBER);
		}
	};
	tryInit();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址