您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
View Original Avatar, Banner, Video and Shorts Thumbnails.
// ==UserScript== // @name YouTube Enhancer (Thumbnail Preview) // @description View Original Avatar, Banner, Video and Shorts Thumbnails. // @icon https://raw.githubusercontent.com/exyezed/youtube-enhancer/refs/heads/main/extras/youtube-enhancer.png // @version 1.6 // @author exyezed // @namespace https://github.com/exyezed/youtube-enhancer/ // @supportURL https://github.com/exyezed/youtube-enhancer/issues // @license MIT // @match https://www.youtube.com/* // @grant none // ==/UserScript== (function () { "use strict"; const css = ` .thumbnail-overlay-container { position: absolute; bottom: 8px; left: 8px; z-index: 9999; opacity: 0; transition: opacity 0.2s ease; } .thumbnail-overlay-button { width: 28px; height: 28px; background: rgba(0, 0, 0, 0.7); border: none; border-radius: 4px; cursor: pointer; display: flex; align-items: center; justify-content: center; color: white; position: relative; } .thumbnail-overlay-button:hover { background: rgba(0, 0, 0, 0.9); } .thumbnail-overlay-button svg { width: 18px; height: 18px; fill: currentColor; } .thumbnail-dropdown { position: absolute; bottom: 100%; left: 0; background: rgba(0, 0, 0, 0.9); border-radius: 4px; padding: 4px; margin-bottom: 4px; display: none; flex-direction: column; min-width: 140px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); z-index: 10000; } .thumbnail-dropdown.show { display: flex !important; } .thumbnail-dropdown-item { background: none; border: none; color: white; padding: 8px 12px; cursor: pointer; border-radius: 2px; font-size: 12px; text-align: left; white-space: nowrap; transition: background-color 0.2s ease; } .thumbnail-dropdown-item:hover { background: rgba(255, 255, 255, 0.1); } /* Sidebar thumbnails */ .yt-lockup-view-model-wiz__content-image:hover .thumbnail-overlay-container { opacity: 1; } .yt-lockup-view-model-wiz__content-image { position: relative; } .shortsLockupViewModelHostEndpoint:hover .thumbnail-overlay-container { opacity: 1; } .shortsLockupViewModelHostEndpoint { position: relative; } /* Channel page thumbnails */ ytd-thumbnail:hover .thumbnail-overlay-container { opacity: 1; } ytd-thumbnail { position: relative; } ytm-shorts-lockup-view-model:hover .thumbnail-overlay-container { opacity: 1; } ytm-shorts-lockup-view-model { position: relative; } /* For shorts in channel page */ .shortsLockupViewModelHostThumbnailContainer:hover .thumbnail-overlay-container { opacity: 1; } .shortsLockupViewModelHostThumbnailContainer { position: relative; } /* For shorts containers in general */ ytm-shorts-lockup-view-model:hover .thumbnail-overlay-container { opacity: 1; } ytm-shorts-lockup-view-model { position: relative; } /* Watch page custom thumbnail */ #thumbnailPreview-custom-image { width: 100%; height: auto; margin-bottom: 10px; box-sizing: border-box; border-radius: 10px; cursor: pointer; } /* Avatar and banner preview buttons */ .thumbnailPreview-button { position: absolute; bottom: 10px; left: 5px; background-color: rgba(0, 0, 0, 0.75); color: white; border: none; border-radius: 3px; padding: 3px; font-size: 18px; cursor: pointer; z-index: 2000; opacity: 0; transition: opacity 0.3s; display: flex; align-items: center; justify-content: center; } .thumbnailPreview-container { position: relative; } .thumbnailPreview-container:hover .thumbnailPreview-button { opacity: 1; } `; const style = document.createElement("style"); style.textContent = css; document.head.appendChild(style); function extractVideoId(url) { const regularMatch = url.match(/[?&]v=([^&]+)/); if (regularMatch) { return regularMatch[1]; } const shortsMatch = url.match(/\/shorts\/([^?&]+)/); if (shortsMatch) { return shortsMatch[1]; } return null; } function openImageInNewTab(url) { window.open(url, "_blank"); } function createSVGElement(pathD) { const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); svg.setAttribute("width", "1em"); svg.setAttribute("height", "1em"); svg.setAttribute("viewBox", "0 0 24 24"); path.setAttribute("fill", "currentColor"); path.setAttribute("d", pathD); svg.appendChild(path); return svg; } const defaultIconPath = "M18 20H4V6h9V4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2zm-7.79-3.17l-1.96-2.36L5.5 18h11l-3.54-4.71zM20 4V1h-2v3h-3c.01.01 0 2 0 2h3v2.99c.01.01 2 0 2 0V6h3V4"; const hoverIconPath = "M19 7v2.99s-1.99.01-2 0V7h-3s.01-1.99 0-2h3V2h2v3h3v2zm-3 4V8h-3V5H5c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2v-8zM5 19l3-4l2 3l3-4l4 5z"; let thumbnailPreviewCurrentVideoId = ""; let thumbnailInsertionAttempts = 0; const MAX_ATTEMPTS = 10; const RETRY_DELAY = 500; function createOverlayButton(videoId, isShorts = false) { const container = document.createElement("div"); container.className = "thumbnail-overlay-container"; const button = document.createElement("button"); button.className = "thumbnail-overlay-button"; const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); svg.setAttribute("viewBox", "0 0 24 24"); svg.setAttribute("xmlns", "http://www.w3.org/2000/svg"); const path = document.createElementNS("http://www.w3.org/2000/svg", "path"); path.setAttribute( "d", "M18 20H4V6h9V4H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2v-9h-2zm-7.79-3.17l-1.96-2.36L5.5 18h11l-3.54-4.71zM20 4V1h-2v3h-3c.01.01 0 2 0 2h3v2.99c.01.01 2 0 2 0V6h3V4" ); svg.appendChild(path); button.appendChild(svg); const dropdown = document.createElement("div"); dropdown.className = "thumbnail-dropdown"; let thumbnailOptions; if (isShorts) { thumbnailOptions = [ { name: "Default", filename: "oardefault.jpg" }, { name: "Alternative 1", filename: "oar1.jpg" }, { name: "Alternative 2 (Default)", filename: "oar2.jpg" }, { name: "Alternative 3", filename: "oar3.jpg" }, ]; } else { thumbnailOptions = [ { name: "Default (120x90)", filename: "default.jpg" }, { name: "Medium (320x180)", filename: "mqdefault.jpg" }, { name: "High (480x360)", filename: "hqdefault.jpg" }, { name: "Standard (640x480)", filename: "sddefault.jpg" }, { name: "Max Res (1280x720)", filename: "maxresdefault.jpg" }, ]; } thumbnailOptions.forEach((option) => { const item = document.createElement("button"); item.className = "thumbnail-dropdown-item"; item.textContent = option.name; item.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); const thumbnailUrl = `https://i.ytimg.com/vi/${videoId}/${option.filename}`; openImageInNewTab(thumbnailUrl); dropdown.classList.remove("show"); }); dropdown.appendChild(item); }); button.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); document.querySelectorAll(".thumbnail-dropdown.show").forEach((d) => { if (d !== dropdown) d.classList.remove("show"); }); dropdown.classList.toggle("show"); }); document.addEventListener("click", function (e) { if (!container.contains(e.target)) { dropdown.classList.remove("show"); } }); document.addEventListener("keydown", function (e) { if (e.key === "Escape") { dropdown.classList.remove("show"); } }); container.appendChild(dropdown); container.appendChild(button); return container; } function addButtonToElement(element, getFullSizeUrl) { if (!element.closest(".thumbnailPreview-container")) { const container = document.createElement("div"); container.className = "thumbnailPreview-container"; element.parentNode.insertBefore(container, element); container.appendChild(element); const button = document.createElement("button"); button.className = "thumbnailPreview-button"; const defaultIcon = createSVGElement(defaultIconPath); button.appendChild(defaultIcon); button.addEventListener("mouseenter", () => { button.innerHTML = ""; button.appendChild(createSVGElement(hoverIconPath)); }); button.addEventListener("mouseleave", () => { button.innerHTML = ""; button.appendChild(createSVGElement(defaultIconPath)); }); button.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); const url = getFullSizeUrl(element.src); if (url) { openImageInNewTab(url); } }); container.appendChild(button); } } function isWatchPage() { const url = new URL(window.location.href); return url.pathname === "/watch" && url.searchParams.has("v"); } function addOrUpdateThumbnailImage() { if (!isWatchPage()) return; const newVideoId = new URLSearchParams(window.location.search).get("v"); if (!newVideoId || newVideoId === thumbnailPreviewCurrentVideoId) { return; } thumbnailPreviewCurrentVideoId = newVideoId; function attemptInsertion() { const targetElement = document.querySelector("#secondary-inner #panels"); const existingImg = document.getElementById( "thumbnailPreview-custom-image" ); if (existingImg) { existingImg.src = `https://i.ytimg.com/vi/${thumbnailPreviewCurrentVideoId}/mqdefault.jpg`; thumbnailInsertionAttempts = 0; return; } if (!targetElement) { thumbnailInsertionAttempts++; if (thumbnailInsertionAttempts < MAX_ATTEMPTS) { setTimeout(attemptInsertion, RETRY_DELAY); } else { thumbnailInsertionAttempts = 0; } return; } const img = document.createElement("img"); img.id = "thumbnailPreview-custom-image"; img.src = `https://i.ytimg.com/vi/${thumbnailPreviewCurrentVideoId}/mqdefault.jpg`; img.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); const maxResUrl = `https://i.ytimg.com/vi/${thumbnailPreviewCurrentVideoId}/maxresdefault.jpg`; openImageInNewTab(maxResUrl); }); targetElement.parentNode.insertBefore(img, targetElement); thumbnailInsertionAttempts = 0; } attemptInsertion(); } function processAvatars() { const avatars = document.querySelectorAll( "yt-avatar-shape img, yt-img-shadow#avatar img" ); avatars.forEach((img) => { if (!img.closest(".thumbnailPreview-container")) { addButtonToElement(img, (src) => src.replace(/=s\d+-c-k-c0x00ffffff-no-rj.*/, "=s0") ); if (isWatchPage()) { const button = img .closest(".thumbnailPreview-container") .querySelector(".thumbnailPreview-button"); if (button) { button.style.display = "none"; } } } }); } function processChannelBanners() { const banners = document.querySelectorAll("yt-image-banner-view-model img"); banners.forEach((img) => { if (!img.closest(".thumbnailPreview-container")) { addButtonToElement(img, (src) => src.replace(/=w\d+-.*/, "=s0")); } }); } function cleanupDuplicateButtons() { const shortsWithMultipleButtons = document.querySelectorAll( "ytm-shorts-lockup-view-model" ); shortsWithMultipleButtons.forEach((shortsContainer) => { const buttons = shortsContainer.querySelectorAll( ".thumbnail-overlay-container" ); if (buttons.length > 1) { const thumbnailContainer = shortsContainer.querySelector( ".shortsLockupViewModelHostThumbnailContainer" ); const preferredButton = thumbnailContainer ? thumbnailContainer.querySelector(".thumbnail-overlay-container") : buttons[0]; buttons.forEach((button) => { if (button !== preferredButton) { button.remove(); } }); } }); } function addOverlayButtons() { const sidebarThumbnails = document.querySelectorAll( ".yt-lockup-view-model-wiz__content-image:not([data-overlay-added])" ); sidebarThumbnails.forEach((container) => { const href = container.getAttribute("href"); if (href) { const videoId = extractVideoId(href); if (videoId) { const overlayButton = createOverlayButton(videoId, false); container.appendChild(overlayButton); container.setAttribute("data-overlay-added", "true"); } } }); const channelThumbnails = document.querySelectorAll( "ytd-thumbnail:not([data-overlay-added])" ); channelThumbnails.forEach((thumbnail) => { const link = thumbnail.querySelector('a[href*="/watch?v="]'); if (link) { const href = link.getAttribute("href"); const videoId = extractVideoId(href); if (videoId) { const overlayButton = createOverlayButton(videoId, false); thumbnail.appendChild(overlayButton); thumbnail.setAttribute("data-overlay-added", "true"); } } }); const allShortsContainers = document.querySelectorAll( "ytm-shorts-lockup-view-model:not([data-overlay-added])" ); console.log("Found shorts containers:", allShortsContainers.length); allShortsContainers.forEach((shortsContainer) => { const link = shortsContainer.querySelector('a[href*="/shorts/"]'); if (link) { const href = link.getAttribute("href"); const videoId = extractVideoId(href); console.log("Processing shorts:", videoId, href); if (videoId) { const overlayButton = createOverlayButton(videoId, true); const thumbnailContainer = shortsContainer.querySelector( ".shortsLockupViewModelHostThumbnailContainer" ); if (thumbnailContainer) { console.log("Adding button to thumbnail container"); thumbnailContainer.appendChild(overlayButton); } else { console.log("Adding button to main container"); shortsContainer.appendChild(overlayButton); } shortsContainer.setAttribute("data-overlay-added", "true"); } } }); const sidebarShorts = document.querySelectorAll( ".shortsLockupViewModelHostEndpoint:not([data-overlay-added])" ); sidebarShorts.forEach((container) => { if (container.closest("ytm-shorts-lockup-view-model")) { return; } const href = container.getAttribute("href"); if (href) { const videoId = extractVideoId(href); if (videoId) { const overlayButton = createOverlayButton(videoId, true); container.appendChild(overlayButton); container.setAttribute("data-overlay-added", "true"); } } }); addOrUpdateThumbnailImage(); processAvatars(); processChannelBanners(); cleanupDuplicateButtons(); } function observeChanges() { const observer = new MutationObserver(function (mutations) { let shouldCheck = false; mutations.forEach(function (mutation) { if (mutation.type === "childList" && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach(function (node) { if (node.nodeType === Node.ELEMENT_NODE) { if ( node.querySelector && (node.querySelector( ".yt-lockup-view-model-wiz__content-image" ) || node.classList.contains( "yt-lockup-view-model-wiz__content-image" ) || node.querySelector(".shortsLockupViewModelHostEndpoint") || node.classList.contains( "shortsLockupViewModelHostEndpoint" ) || node.querySelector("ytd-thumbnail") || node.classList.contains("ytd-thumbnail") || node.querySelector("ytm-shorts-lockup-view-model") || node.classList.contains("ytm-shorts-lockup-view-model") || node.querySelector("yt-avatar-shape") || node.classList.contains("yt-avatar-shape") || node.querySelector("yt-image-banner-view-model") || node.classList.contains("yt-image-banner-view-model")) ) { shouldCheck = true; } } }); } }); if (shouldCheck) { setTimeout(addOverlayButtons, 100); } }); observer.observe(document.body, { childList: true, subtree: true, }); } function setupUrlChangeDetection() { let currentUrl = location.href; const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function () { originalPushState.apply(history, arguments); setTimeout(() => { if (location.href !== currentUrl) { currentUrl = location.href; setTimeout(addOverlayButtons, 500); } }, 100); }; history.replaceState = function () { originalReplaceState.apply(history, arguments); setTimeout(() => { if (location.href !== currentUrl) { currentUrl = location.href; setTimeout(addOverlayButtons, 500); } }, 100); }; window.addEventListener("popstate", function () { setTimeout(() => { if (location.href !== currentUrl) { currentUrl = location.href; setTimeout(addOverlayButtons, 500); } }, 100); }); setInterval(function () { if (location.href !== currentUrl) { currentUrl = location.href; setTimeout(addOverlayButtons, 300); } }, 500); document.addEventListener("yt-navigate-start", function () { setTimeout(addOverlayButtons, 1000); }); document.addEventListener("yt-navigate-finish", function () { setTimeout(addOverlayButtons, 500); }); } function observePageChanges() { const contentObserver = new MutationObserver((mutations) => { let shouldProcessRegular = false; mutations.forEach((mutation) => { if (mutation.addedNodes.length > 0) { shouldProcessRegular = true; } }); if (shouldProcessRegular) { processAvatars(); processChannelBanners(); } }); const panelObserver = new MutationObserver((mutations) => { for (const mutation of mutations) { if ( mutation.type === "childList" && (mutation.target.id === "secondary" || mutation.target.id === "secondary-inner") ) { addOrUpdateThumbnailImage(); } } }); contentObserver.observe(document.body, { childList: true, subtree: true, }); const observeSecondary = () => { const secondary = document.getElementById("secondary"); if (secondary) { panelObserver.observe(secondary, { childList: true, subtree: true, }); } else { setTimeout(observeSecondary, 1000); } }; observeSecondary(); } function init() { addOverlayButtons(); observeChanges(); observePageChanges(); setupUrlChangeDetection(); window.addEventListener("yt-navigate-finish", () => { addOrUpdateThumbnailImage(); setTimeout(addOverlayButtons, 1000); }); setTimeout(addOverlayButtons, 2000); setTimeout(addOverlayButtons, 5000); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();