// ==UserScript== // @name YouTube Navigation-Links Fixer // @namespace https://git.ponywave.de/Akamaru/Userscripts // @version 1.0 // @description Stellt Links für Abos und Verlauf in der Sidebar wieder her // @author Akamaru // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?domain=youtube.com&sz=32 // @grant none // @updateURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/youtube-restore-nav-links.user.js // @downloadURL https://git.ponywave.de/Akamaru/Userscripts/raw/branch/master/youtube-restore-nav-links.user.js // ==/UserScript== (function() { 'use strict'; // SVG Icons für die Navigation (d-Attribute) const ICONS = { subscriptions: 'M10 18v-6l5 3-5 3zm7-15H7v1h10V3zm3 3H4v1h16V6zm2 3H2v12h20V9zM3 10h18v10H3V10z', history: 'M11.9 3.75c-4.55 0-8.23 3.7-8.23 8.25H.92l3.57 3.57.04.13 3.7-3.7H5.5c0-3.54 2.87-6.42 6.42-6.42 3.54 0 6.4 2.88 6.4 6.42s-2.86 6.42-6.4 6.42c-1.78 0-3.38-.73-4.54-1.9l-1.3 1.3c1.5 1.5 3.55 2.43 5.83 2.43 4.58 0 8.28-3.7 8.28-8.25 0-4.56-3.7-8.25-8.26-8.25zM11 8.33v4.6l3.92 2.3.66-1.1-3.2-1.9v-3.9H11z' }; // Erstellt einen neuen Navigation-Eintrag als einfachen HTML-Link mit Styling function createNavEntry(title, url, iconPath) { // Erstelle einen Container const container = document.createElement('div'); container.style.cssText = 'display: flex; align-items: center; padding: 10px 12px; cursor: pointer; border-radius: 10px; color: var(--yt-spec-text-primary, #0f0f0f);'; container.className = 'yt-nav-custom-entry'; // Icon SVG (manuell erstellen wegen Trusted Types) const iconDiv = document.createElement('div'); iconDiv.style.cssText = 'width: 24px; height: 24px; margin-right: 24px;'; const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); svg.setAttribute('height', '24'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('width', '24'); svg.setAttribute('focusable', 'false'); svg.setAttribute('aria-hidden', 'true'); svg.style.cssText = 'pointer-events: none; display: block; width: 100%; height: 100%;'; const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); path.setAttribute('d', iconPath); path.setAttribute('fill', 'currentColor'); svg.appendChild(path); iconDiv.appendChild(svg); // Text const textSpan = document.createElement('span'); textSpan.textContent = title; textSpan.style.cssText = 'font-size: 14px; font-weight: 400; line-height: 20px;'; container.appendChild(iconDiv); container.appendChild(textSpan); // Click-Handler container.addEventListener('click', () => { window.location.href = url; }); // Hover-Effekt container.addEventListener('mouseenter', () => { container.style.backgroundColor = 'var(--yt-spec-10-percent-layer, rgba(255,255,255,0.1))'; }); container.addEventListener('mouseleave', () => { container.style.backgroundColor = ''; }); return container; } // Fügt die Navigation-Links hinzu function addNavLinks() { // Finde die erste Section (enthält Startseite und Shorts) const firstSection = document.querySelector('ytd-guide-section-renderer[guide-persistent-and-visible]'); if (!firstSection) { console.log('YouTube Nav Restore: Section nicht gefunden, versuche erneut...'); return false; } const itemsContainer = firstSection.querySelector('#items'); if (!itemsContainer) { console.log('YouTube Nav Restore: Items Container nicht gefunden'); return false; } // Prüfe ob bereits hinzugefügt const existingEntry = itemsContainer.querySelector('.yt-nav-custom-entry'); if (existingEntry) { console.log('YouTube Nav Restore: Links bereits vorhanden'); return true; } // Finde Startseite und Shorts Einträge const entries = itemsContainer.querySelectorAll('ytd-guide-entry-renderer'); if (entries.length < 2) { console.log('YouTube Nav Restore: Nicht genug Einträge gefunden'); return false; } // Suche nach dem Shorts-Eintrag (nach Titel) let shortsEntry = null; for (const entry of entries) { const title = entry.querySelector('.title'); if (title && title.textContent.trim() === 'Shorts') { shortsEntry = entry; break; } } // Fallback: Nimm den zweiten Eintrag if (!shortsEntry && entries.length >= 2) { shortsEntry = entries[1]; } if (!shortsEntry) { console.log('YouTube Nav Restore: Shorts-Eintrag nicht gefunden'); return false; } // Erstelle die neuen Einträge const subscriptionsEntry = createNavEntry('Abos', '/feed/subscriptions', ICONS.subscriptions); const historyEntry = createNavEntry('Verlauf', '/feed/history', ICONS.history); if (!subscriptionsEntry || !historyEntry) { console.log('YouTube Nav Restore: Konnte Einträge nicht erstellen'); return false; } // Füge die Einträge zwischen Startseite und Shorts ein itemsContainer.insertBefore(subscriptionsEntry, shortsEntry); itemsContainer.insertBefore(historyEntry, shortsEntry); console.log('YouTube Nav Restore: Navigation-Links erfolgreich hinzugefügt'); return true; } // Debounce und Lock um Endlosschleifen zu vermeiden let debounceTimer = null; let isAdding = false; let lastUrl = location.href; function debouncedAddNavLinks() { if (isAdding) return; if (debounceTimer) { clearTimeout(debounceTimer); } debounceTimer = setTimeout(() => { if (!isAdding) { isAdding = true; addNavLinks(); isAdding = false; } }, 300); } // Versuche mehrmals die Links hinzuzufügen function init() { let attempts = 0; const maxAttempts = 20; const interval = setInterval(() => { attempts++; if (addNavLinks()) { clearInterval(interval); } else if (attempts >= maxAttempts) { console.log('YouTube Nav Restore: Maximale Versuche erreicht'); clearInterval(interval); } }, 500); } // Starte wenn die Seite geladen ist if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } // Beobachte URL-Änderungen für SPA-Navigation const titleElement = document.querySelector('title'); if (titleElement) { new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; debouncedAddNavLinks(); } }).observe(titleElement, { childList: true, subtree: true }); } })();