Youtube channel description popup on hover

Cool stuffs!

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

// ==UserScript==
// @name         Youtube channel description popup on hover
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Cool stuffs!
// @author       Duc Trinh @dmtri
// @match        https://www.youtube.com/*
// @icon
// @grant        none
// @license MIT
// ==/UserScript==

(function () {
	'use strict';
	const initTag = '.youtube-popup-desc-init';

	// a tampermonkey script to get the profile details
	// upon hover a channel profile on youtube (after 1.5 sec)
	// and then display the channel desc in a popup

	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 wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

	// 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;
		}
		profileMetaDataElement.forEach((element) => {
			element.addEventListener('mouseenter', async () => {
				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);
			});
		});

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

		// try init when popstate event happen
		window.addEventListener('popstate', () => {
			tryInit(true);
		});
	};

	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 = (force = false) => {
		if (!document.querySelector(initTag)) {
			setTimeout(() => {
				init(force);
				tryInit(force);
			}, 1500);
		}
	};
	tryInit();
})();