您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Cover-Hover: rahmenlos, performant, fix 200px nach rechts verschoben, passt sich automatisch an Bildschirmgröße an
// ==UserScript== // @name SceneNZBs Thumbnail Hover Zoom // @description Cover-Hover: rahmenlos, performant, fix 200px nach rechts verschoben, passt sich automatisch an Bildschirmgröße an // @version 6.1.10 // @match https://*.scenenzbs.com/* // @icon https://cdn.scenenzbs.com/assets/static/favicon.ico // @namespace https://scenenzbs.com/ // @author Baumeister // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; // Fixe Einstellungen const ZOOMFACTOR = 4.2; // feste Vergrößerung const BASE_WIDTH = 340; const BASE_HEIGHT = 480; const H_OFFSET_PX = 200; // feste Verschiebung nach rechts // CSS nur einmal einfügen if (!window.__overlayCSS) { const style = document.createElement('style'); style.textContent = ` img.thumb-zoom:hover, .thumb-zoom-wrap:hover img.thumb-zoom, img.thumb-zoom:active, img.thumb-zoom:focus { transform: none !important; transition: none !important; z-index: auto !important; position: static !important; outline: none !important; filter: none !important; box-shadow: none !important; } .ultra-overlay-img { position: fixed; top: 50%; left: 50%; z-index: 99999; opacity: 0; pointer-events: none; display: block; max-width: 90vw; max-height: 90vh; object-fit: contain; transition: opacity 0.18s cubic-bezier(.19,1,.22,1); } .ultra-hint-text { position: fixed; top: 50%; left: 50%; transform: translate(calc(-50% + 200px), -50%); z-index: 99999; opacity: 0; pointer-events: none; background: rgba(0, 0, 0, 0.85); color: white; padding: 20px 30px; border-radius: 8px; font-size: 16px; font-weight: bold; text-align: center; transition: opacity 0.18s cubic-bezier(.19,1,.22,1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); } `; document.head.appendChild(style); window.__overlayCSS = true; } // Overlay-Bild erzeugen if (!window.__overlayImg) { const overlayImg = document.createElement('img'); overlayImg.className = 'ultra-overlay-img'; overlayImg.style.opacity = '0'; document.body.appendChild(overlayImg); window.__overlayImg = overlayImg; } // Hinweistext für Platzhalter erzeugen if (!window.__hintText) { const hintText = document.createElement('div'); hintText.className = 'ultra-hint-text'; hintText.textContent = 'Bildersuche nach Coverbild'; hintText.style.opacity = '0'; document.body.appendChild(hintText); window.__hintText = hintText; } // Platzhalter erkennen function isPlaceholder(img) { const src = (img.getAttribute('src') || '').toLowerCase(); return src.includes('category_') || src.includes('placeholder.webp') || src.includes('no-cover.webp'); } // Release-Namen aus der Seite extrahieren function getReleaseName(img) { // Suche nach dem nächsten Release-Link let element = img.closest('.row, .col, .card, .item'); if (!element) element = img.parentElement; // Suche nach dem Release-Namen in verschiedenen möglichen Strukturen const releaseLink = element?.querySelector('a.fw-bold.text-decoration-none[href*="/details/"]'); if (releaseLink) { return releaseLink.textContent.trim(); } // Fallback: Suche im gesamten Container const allLinks = element?.querySelectorAll('a[href*="/details/"]'); if (allLinks && allLinks.length > 0) { return allLinks[0].textContent.trim(); } return ''; } // Kategorie aus der Seite extrahieren function getCategory(img) { let element = img.closest('.row, .col, .card, .item'); if (!element) element = img.parentElement; // Suche nach Kategorie-Link const categoryLink = element?.querySelector('.text-muted a[href*="/browse?t="]'); if (categoryLink) { const fullCategory = categoryLink.textContent.trim(); // Nur den Teil vor ">" zurückgeben const mainCategory = fullCategory.split('>')[0].trim(); return mainCategory; } return ''; } // NFO-URL aus der Seite extrahieren function getNfoUrl(img) { let element = img.closest('.row, .col, .card, .item'); if (!element) element = img.parentElement; // Suche nach NFO-Button const nfoButton = element?.querySelector('a[data-url*="/nfo?id="]'); if (nfoButton) { return nfoButton.getAttribute('data-url'); } return ''; } // URL aus NFO-Text extrahieren async function extractUrlFromNfo(nfoUrl) { try { const response = await fetch(nfoUrl); const html = await response.text(); // Parse HTML und extrahiere den NFO-Text const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const nfoText = doc.body.textContent || ''; // Suche nach URLs in verschiedenen Formaten const urlPatterns = [ /URL[.\s:]+([^\s\n]+)/i, /https?:\/\/(play\.napster\.com|tidal\.com|open\.spotify\.com|music\.apple\.com|www\.deezer\.com|music\.youtube\.com|open\.qobuz\.com)[^\s\n]*/gi ]; for (const pattern of urlPatterns) { const match = nfoText.match(pattern); if (match) { // Wenn es ein URL:-Feld ist, nimm die zweite Gruppe if (match[1] && !match[0].startsWith('http')) { return match[1].trim(); } // Sonst nimm die ganze URL return match[0].trim(); } } } catch (error) { console.error('Fehler beim Abrufen der NFO:', error); } return ''; } // Streaming-Button hinzufügen async function addStreamingButton(img) { const nfoUrl = getNfoUrl(img); if (!nfoUrl) return; // Finde den Button-Container let element = img.closest('.row, .col, .card, .item'); if (!element) element = img.parentElement; const buttonContainer = element?.querySelector('.mt-2.d-flex.flex-wrap.gap-1'); if (!buttonContainer) return; // Prüfe, ob Button bereits existiert if (buttonContainer.parentElement?.querySelector('.ultra-streaming-btn')) return; // Erstelle den Button-Container (eine Zeile unter den anderen Buttons) const linkContainer = document.createElement('div'); linkContainer.className = 'mt-1'; // Erstelle den Button const streamButton = document.createElement('a'); streamButton.href = '#'; streamButton.className = 'btn btn-sm btn-tag btn-outline-primary ultra-streaming-btn'; streamButton.textContent = 'URL aus NFO'; streamButton.title = 'Medienlink aus NFO öffnen'; // Klick-Handler: Erst beim Klick NFO abrufen streamButton.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); // Zeige Lade-Indikator streamButton.textContent = 'Lädt...'; streamButton.disabled = true; try { const extractedUrl = await extractUrlFromNfo(nfoUrl); if (extractedUrl) { // Öffne die URL window.open(extractedUrl, '_blank'); // Aktualisiere Button-Text mit Service-Namen let serviceName = 'Link'; if (extractedUrl.includes('napster.com')) serviceName = 'Napster'; else if (extractedUrl.includes('tidal.com')) serviceName = 'Tidal'; else if (extractedUrl.includes('spotify.com')) serviceName = 'Spotify'; else if (extractedUrl.includes('apple.com')) serviceName = 'Apple Music'; else if (extractedUrl.includes('deezer.com')) serviceName = 'Deezer'; else if (extractedUrl.includes('youtube.com')) serviceName = 'YouTube Music'; else if (extractedUrl.includes('qobuz.com')) serviceName = 'Qobuz'; streamButton.textContent = serviceName; streamButton.href = extractedUrl; } else { // Keine URL gefunden streamButton.textContent = 'Kein Link'; streamButton.classList.remove('btn-outline-primary'); streamButton.classList.add('btn-outline-secondary'); } } catch (error) { console.error('Fehler beim Abrufen der NFO:', error); streamButton.textContent = 'Fehler'; streamButton.classList.remove('btn-outline-primary'); streamButton.classList.add('btn-outline-danger'); } finally { streamButton.disabled = false; } }); linkContainer.appendChild(streamButton); // Füge Container direkt nach dem Button-Container ein buttonContainer.parentElement.insertBefore(linkContainer, buttonContainer.nextSibling); } // Events für Thumbs binden function bindOverlay() { // Original: Bilder mit thumb-zoom Klasse const thumbZoomImages = document.querySelectorAll('img.thumb-zoom'); // Neu: Bilder in wiederkehrenden Row-Containern (movies, books, console, music) const rowImages = document.querySelectorAll('.row.border-bottom img.img-fluid.rounded.shadow-sm'); // Kombiniere beide Listen const allImages = [...thumbZoomImages, ...rowImages]; allImages.forEach(img => { if (img.dataset.ultraBound) return; img.dataset.ultraBound = '1'; const isPlaceholderImg = isPlaceholder(img); img.addEventListener('mouseenter', () => { if (isPlaceholderImg) { // Zeige Hinweistext statt Zoom window.__overlayImg.style.opacity = '0'; // Aktualisiere Hinweistext mit Release-Namen const releaseName = getReleaseName(img); if (releaseName) { window.__hintText.innerHTML = `Bildersuche nach<br><strong>${releaseName}</strong>`; } else { window.__hintText.textContent = 'Bildersuche nach Coverbild'; } window.__hintText.style.opacity = '1'; return; } if (window.__overlayImg.src !== img.src) { window.__overlayImg.src = img.src; } // feste Verschiebung nach rechts window.__overlayImg.style.transform = `translate(calc(-50% + ${H_OFFSET_PX}px), -50%)`; window.__overlayImg.style.opacity = '1'; }); img.addEventListener('mouseleave', () => { window.__overlayImg.style.opacity = '0'; window.__hintText.style.opacity = '0'; }); // Klick-Event für Platzhalter: Google Bildersuche if (isPlaceholderImg) { img.style.cursor = 'pointer'; img.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); const releaseName = getReleaseName(img); const category = getCategory(img); // Fallback: Google Bildersuche if (releaseName) { // Kombiniere Kategorie und Release-Name für bessere Suchergebnisse let searchTerm = releaseName; if (category) { searchTerm = `${category}: ${releaseName}`; } const searchQuery = encodeURIComponent(searchTerm + ' cover'); const googleImageSearchUrl = `https://www.google.com/search?tbm=isch&q=${searchQuery}`; window.open(googleImageSearchUrl, '_blank'); } else { console.log('Kein Release-Name gefunden'); } }); } }); // Füge Streaming-Button für ALLE Einträge hinzu (nicht nur Platzhalter) allImages.forEach(img => { addStreamingButton(img); }); } // Scroll-Guard window.addEventListener('scroll', () => { if (window.__overlayImg && window.__overlayImg.style.opacity === '1') { window.__overlayImg.style.opacity = '0'; } }); // Start bindOverlay(); const observer = new MutationObserver(() => { bindOverlay(); }); observer.observe(document.body, { childList: true, subtree: true }); })();