Enlarges YouTube profile pictures on mouse over, shows HD version, Caches HD images for faster display using localStorage caching. Enlarges profile picture when a creator hearts a comment.
// ==UserScript== // @name Enhance YouTube Profile Pictures (HD Version with Caching) // @namespace typpi.online // @version 5.3 // @description Enlarges YouTube profile pictures on mouse over, shows HD version, Caches HD images for faster display using localStorage caching. Enlarges profile picture when a creator hearts a comment. // @author Nick2bad4u // @match https://www.youtube.com/* // @grant none // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @license UnLicense // @tag youtube // ==/UserScript== (function () { 'use strict'; let debounceTimeout; const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds const preloadedImages = new Map(); // Load cache from localStorage function loadCache() { const cache = JSON.parse(localStorage.getItem('profilePicCache') || '{}'); const now = Date.now(); // Clear out expired cache entries Object.keys(cache).forEach((key) => { if (now - cache[key].timestamp > CACHE_TTL_MS) { delete cache[key]; // Remove expired entry } }); localStorage.setItem('profilePicCache', JSON.stringify(cache)); // Update cache after removing expired entries return cache; } // Save cache to localStorage function saveCache(cache) { localStorage.setItem('profilePicCache', JSON.stringify(cache)); } let cache = loadCache(); // Load the cache once when the script runs // Preload HD image function preloadHDImage(src) { const hdSrc = src.replace(/=s(32|88|48)-c/, '=s800-c'); // Adjust as needed for HD if (!preloadedImages.has(hdSrc)) { if (cache[hdSrc]) { // If in persistent cache, load directly from cache preloadedImages.set(hdSrc, cache[hdSrc].url); } else { // Preload HD image and store in cache const img = new Image(); img.src = hdSrc; preloadedImages.set(hdSrc, hdSrc); // Store in memory cache[hdSrc] = { url: hdSrc, timestamp: Date.now(), }; // Cache with timestamp saveCache(cache); // Save the updated cache } } } // Function to enlarge profile pictures, show HD image, add black outline, and shift position function enlargeProfilePic(event) { clearTimeout(debounceTimeout); const img = event.target; // If the image is already enlarged, skip further processing if (img.dataset.enlarged === 'true') return; debounceTimeout = setTimeout(() => { const originalSrc = img.src; const hdSrc = originalSrc.replace(/=s(32|88|48)-c/, '=s800-c'); // Increase the size to 800px img.dataset.originalSrc = originalSrc; // Store the original src img.src = preloadedImages.get(hdSrc) || hdSrc; // Get the position of the original image const rect = img.getBoundingClientRect(); // Set fixed size, position relative to the original image if ( img.classList.contains( 'h-5.w-5.inline.align-middle.rounded-full.flex-none', ) ) { img.style.transform = 'scale(6) translateX(20px)'; img.style.transition = 'transform 0.2s ease'; img.style.border = '1px solid black'; img.style.zIndex = '9999'; img.style.position = 'relative'; } else { img.style.width = '260px'; // Adjust width as needed img.style.height = '260px'; // Adjust height as needed img.style.borderRadius = '50%'; // Make the image circular img.style.position = 'fixed'; img.style.top = `${rect.top - 20}px`; // Adjust vertical position as needed img.style.left = `${rect.left + 70}px`; // Offset to the right img.style.border = '2px solid black'; img.style.zIndex = '9999'; } img.dataset.enlarged = 'true'; // Mark as enlarged to prevent re-enlarging // Reset after 3 seconds setTimeout(() => { resetProfilePic(img); }, 3000); }, 100); } // Function to reset profile pictures to original size and source function resetProfilePic(img) { img.src = img.dataset.originalSrc || img.src; // Restore the original src if it was replaced img.style.width = ''; // Clear custom width img.style.height = ''; // Clear custom height img.style.borderRadius = ''; // Clear circular style img.style.position = ''; // Reset position to default img.style.top = ''; // Clear top position img.style.left = ''; // Clear left position img.style.border = 'none'; // Remove any border img.style.zIndex = 'auto'; // Reset z-index img.style.transform = ''; // Remove any transform applied delete img.dataset.enlarged; // Remove the enlarged flag } // Add event listeners to profile pictures function addEventListeners() { const profilePicsChat = document.querySelectorAll( '.h-5.w-5.inline.align-middle.rounded-full.flex-none', ); const profilePicsComments = document.querySelectorAll( '.style-scope yt-img-shadow img:not(#avatar-btn > yt-img-shadow img)', ); const heartedThumbnails = document.querySelectorAll( '#creator-heart-button yt-img-shadow img, #creator-heart-button img', ); profilePicsChat.forEach((pic) => { preloadHDImage(pic.src); // Preload HD image pic.addEventListener('mouseenter', enlargeProfilePic); }); profilePicsComments.forEach((pic) => { preloadHDImage(pic.src); // Preload HD image pic.addEventListener('mouseenter', enlargeProfilePic); }); heartedThumbnails.forEach((pic) => { preloadHDImage(pic.src); // Preload HD image pic.addEventListener('mouseenter', enlargeProfilePic); // Add hover event }); } // Observe changes in the chat and comments section to dynamically add event listeners const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length > 0) { addEventListeners(); } }); }); observer.observe(document.body, { childList: true, subtree: true, attributes: true, // Observe attribute changes attributeFilter: ['src'], // Only track changes in `src` attribute }); // Initial call to add event listeners addEventListeners(); })();