// ==UserScript== // @name YouTube Channel Hover Popup // @namespace http://tampermonkey.net/ // @version 0.6 // @description Display a hover popup with channel info on YouTube after dynamic content load, with immediate loading indicator // @author @dmtri // @match https://www.youtube.com/* // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function() { 'use strict'; const MAGIC_NUMBER = 2500; const initTag = '.youtube-popup-desc-init'; const createPopup = () => { const popup = document.createElement('div'); popup.style.position = 'fixed'; popup.style.zIndex = '1000'; popup.style.width = '300px'; popup.style.background = 'white'; popup.style.border = '1px solid black'; popup.style.borderRadius = '8px'; popup.style.padding = '16px'; popup.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)'; popup.style.display = 'none'; popup.style.fontSize = '16px'; document.body.appendChild(popup); return popup; }; const popup = createPopup(); // Initialize the popup const showLoadingPopup = (popup, x, y) => { popup.innerHTML = 'Loading...'; popup.style.left = `${x}px`; popup.style.top = `${y}px`; popup.style.display = 'block'; }; const updatePopupContent = (popup, content) => { popup.innerHTML = content; // Close button const closeButton = document.createElement('button'); closeButton.textContent = 'X'; closeButton.style.position = 'absolute'; closeButton.style.top = '5px'; closeButton.style.right = '10px'; closeButton.style.border = 'none'; closeButton.style.background = 'none'; closeButton.style.cursor = 'pointer'; closeButton.style.color = '#333'; closeButton.style.fontSize = '16px'; closeButton.style.fontWeight = 'bold'; closeButton.onclick = () => { popup.style.display = 'none'; }; popup.appendChild(closeButton); }; const hidePopup = (popup) => { popup.style.display = 'none'; }; const fetchChannelInfo = async (url) => { try { const response = await fetch(url); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const meta = doc.querySelector('meta[property="og:description"]'); const description = meta ? meta.getAttribute('content') : 'No description available.'; return `Description: ${description}
View Channel`; } catch (error) { return 'Failed to load description.'; } }; const init = () => { document.querySelectorAll('.ytd-channel-name#text-container').forEach(channelElement => { let popupTimeout; channelElement.addEventListener('mouseenter', async (e) => { clearTimeout(popupTimeout); popupTimeout = setTimeout(async () => { const url = channelElement.querySelector('a').href; showLoadingPopup(popup, e.clientX, e.clientY + 20); const content = await fetchChannelInfo(url); updatePopupContent(popup, content); }, 500); // Reduced the delay to show the loading message sooner }); channelElement.addEventListener('mouseleave', setTimeout(() => { clearTimeout(popupTimeout); hidePopup(popup); }, 1000)); channelElement.addEventListener('mousemove', (e) => { if (popup.style.display !== 'none') { popup.style.left = `${e.clientX}px`; popup.style.top = `${e.clientY + 20}px`; } }); }); }; const tryInit = () => { if (!document.querySelector(initTag)) { setTimeout(() => { init(); tryInit(); }, MAGIC_NUMBER); } }; tryInit(); })();