// ==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 // @downloadURL none // ==/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) => { // 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 = `

${desc}

${url}

`; 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(); })();