Enhance YouTube Profile Pictures (HD Version with Caching)

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.

目前為 2024-11-05 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name Enhance YouTube Profile Pictures (HD Version with Caching)
  3. // @namespace https://github.com/Nick2bad4u/UserStyles
  4. // @version 5.2
  5. // @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.
  6. // @author Nick2bad4u
  7. // @match https://www.youtube.com/*
  8. // @grant none
  9. // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
  10. // @license UnLicense
  11. // ==/UserScript==
  12.  
  13. (function () {
  14. "use strict";
  15.  
  16. let debounceTimeout;
  17. const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
  18. const preloadedImages = new Map();
  19.  
  20. // Load cache from localStorage
  21. function loadCache() {
  22. const cache = JSON.parse(localStorage.getItem("profilePicCache") || "{}");
  23. const now = Date.now();
  24. // Clear out expired cache entries
  25. Object.keys(cache).forEach((key) => {
  26. if (now - cache[key].timestamp > CACHE_TTL_MS) {
  27. delete cache[key]; // Remove expired entry
  28. }
  29. });
  30. localStorage.setItem("profilePicCache", JSON.stringify(cache)); // Update cache after removing expired entries
  31. return cache;
  32. }
  33.  
  34. // Save cache to localStorage
  35. function saveCache(cache) {
  36. localStorage.setItem("profilePicCache", JSON.stringify(cache));
  37. }
  38.  
  39. let cache = loadCache(); // Load the cache once when the script runs
  40.  
  41. // Preload HD image
  42. function preloadHDImage(src) {
  43. const hdSrc = src.replace(/=s(32|88|48)-c/, "=s800-c"); // Adjust as needed for HD
  44. if (!preloadedImages.has(hdSrc)) {
  45. if (cache[hdSrc]) {
  46. // If in persistent cache, load directly from cache
  47. preloadedImages.set(hdSrc, cache[hdSrc].url);
  48. } else {
  49. // Preload HD image and store in cache
  50. const img = new Image();
  51. img.src = hdSrc;
  52. preloadedImages.set(hdSrc, hdSrc); // Store in memory
  53. cache[hdSrc] = {
  54. url: hdSrc,
  55. timestamp: Date.now(),
  56. }; // Cache with timestamp
  57. saveCache(cache); // Save the updated cache
  58. }
  59. }
  60. }
  61.  
  62. // Function to enlarge profile pictures, show HD image, add black outline, and shift position
  63. function enlargeProfilePic(event) {
  64. clearTimeout(debounceTimeout);
  65.  
  66. const img = event.target;
  67.  
  68. // If the image is already enlarged, skip further processing
  69. if (img.dataset.enlarged === "true") return;
  70.  
  71. debounceTimeout = setTimeout(() => {
  72. const originalSrc = img.src;
  73. const hdSrc = originalSrc.replace(/=s(32|88|48)-c/, "=s800-c"); // Increase the size to 800px
  74. img.dataset.originalSrc = originalSrc; // Store the original src
  75. img.src = preloadedImages.get(hdSrc) || hdSrc;
  76.  
  77. // Get the position of the original image
  78. const rect = img.getBoundingClientRect();
  79.  
  80. // Set fixed size, position relative to the original image
  81. if (
  82. img.classList.contains(
  83. "h-5.w-5.inline.align-middle.rounded-full.flex-none",
  84. )
  85. ) {
  86. img.style.transform = "scale(6) translateX(20px)";
  87. img.style.transition = "transform 0.2s ease";
  88. img.style.border = "1px solid black";
  89. img.style.zIndex = "9999";
  90. img.style.position = "relative";
  91. } else {
  92. img.style.width = "260px"; // Adjust width as needed
  93. img.style.height = "260px"; // Adjust height as needed
  94. img.style.borderRadius = "50%"; // Make the image circular
  95. img.style.position = "fixed";
  96. img.style.top = `${rect.top - 20}px`; // Adjust vertical position as needed
  97. img.style.left = `${rect.left + 70}px`; // Offset to the right
  98. img.style.border = "2px solid black";
  99. img.style.zIndex = "9999";
  100. }
  101.  
  102. img.dataset.enlarged = "true"; // Mark as enlarged to prevent re-enlarging
  103.  
  104. // Reset after 3 seconds
  105. setTimeout(() => {
  106. resetProfilePic(img);
  107. }, 3000);
  108. }, 100);
  109. }
  110.  
  111. // Function to reset profile pictures to original size and source
  112. function resetProfilePic(img) {
  113. img.src = img.dataset.originalSrc || img.src; // Restore the original src if it was replaced
  114. img.style.width = ""; // Clear custom width
  115. img.style.height = ""; // Clear custom height
  116. img.style.borderRadius = ""; // Clear circular style
  117. img.style.position = ""; // Reset position to default
  118. img.style.top = ""; // Clear top position
  119. img.style.left = ""; // Clear left position
  120. img.style.border = "none"; // Remove any border
  121. img.style.zIndex = "auto"; // Reset z-index
  122. img.style.transform = ""; // Remove any transform applied
  123. delete img.dataset.enlarged; // Remove the enlarged flag
  124. }
  125.  
  126. // Add event listeners to profile pictures
  127. function addEventListeners() {
  128. const profilePicsChat = document.querySelectorAll(
  129. ".h-5.w-5.inline.align-middle.rounded-full.flex-none",
  130. );
  131. const profilePicsComments = document.querySelectorAll(
  132. ".style-scope yt-img-shadow img:not(#avatar-btn > yt-img-shadow img)",
  133. );
  134. const heartedThumbnails = document.querySelectorAll(
  135. "#creator-heart-button yt-img-shadow img, #creator-heart-button img",
  136. );
  137.  
  138. profilePicsChat.forEach((pic) => {
  139. preloadHDImage(pic.src); // Preload HD image
  140. pic.addEventListener("mouseenter", enlargeProfilePic);
  141. });
  142.  
  143. profilePicsComments.forEach((pic) => {
  144. preloadHDImage(pic.src); // Preload HD image
  145. pic.addEventListener("mouseenter", enlargeProfilePic);
  146. });
  147.  
  148. heartedThumbnails.forEach((pic) => {
  149. preloadHDImage(pic.src); // Preload HD image
  150. pic.addEventListener("mouseenter", enlargeProfilePic); // Add hover event
  151. });
  152. }
  153.  
  154. // Observe changes in the chat and comments section to dynamically add event listeners
  155. const observer = new MutationObserver((mutations) => {
  156. mutations.forEach((mutation) => {
  157. if (mutation.addedNodes.length > 0) {
  158. addEventListeners();
  159. }
  160. });
  161. });
  162. observer.observe(document.body, {
  163. childList: true,
  164. subtree: true,
  165. attributes: true, // Observe attribute changes
  166. attributeFilter: ["src"], // Only track changes in `src` attribute
  167. });
  168.  
  169. // Initial call to add event listeners
  170. addEventListeners();
  171. })();