1
0
Files
Userscripts/youtube-restore-nav-links.user.js
2025-11-30 19:39:46 +01:00

199 lines
7.2 KiB
JavaScript

// ==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
});
}
})();