// ==UserScript==
// @name YouTube Video Hider with 🚫 Icon and Shorts Toggle
// @name:de YouTube Video Ausblender mit 🚫 Symbol und Shorts Umschalter
// @name:es Ocultador de Videos de YouTube con Icono 🚫 y Alternador de Shorts
// @name:fr Masqueur de Vidéos YouTube avec Icône 🚫 et Basculeur de Shorts
// @name:it Nascondi Video YouTube con Icona 🚫 e Interruttore Shorts
// @namespace http://tampermonkey.net/
// @version 2025.7.7
// @description Adds a 🚫 symbol without background in the top-left corner of the thumbnail for natively hiding videos and a button in the topbar to toggle the Shorts section on YouTube
// @description:de Fügt ein 🚫 Symbol ohne Hintergrund in der oberen linken Ecke des Thumbnails hinzu, um Videos nativ auszublenden, und einen Button in der oberen Leiste, um den Shorts-Abschnitt auf YouTube ein-/auszublenden
// @description:es Agrega un símbolo 🚫 sin fondo en la esquina superior izquierda de la miniatura para ocultar videos de forma nativa y un botón en la barra superior para alternar la sección de Shorts en YouTube
// @description:fr Ajoute un symbole 🚫 sans fond dans le coin supérieur gauche de la miniature pour masquer nativement les vidéos et un bouton dans la barre supérieure pour activer/désactiver la section Shorts sur YouTube
// @description:it Aggiunge un simbolo 🚫 senza sfondo nell'angolo superiore sinistro della miniatura per nascondere nativamente i video e un pulsante nella barra superiore per attivare/disattivare la sezione Shorts su YouTube
// @icon https://youtube.com/favicon.ico
// @author Copiis
// @license MIT
// @match https://www.youtube.com/*
// @grant none
// ==/UserScript==
// If you find this script useful and would like to support my work, consider making a small donation!
// Bitcoin (BTC): bc1quc5mkudlwwkktzhvzw5u2nruxyepef957p68r7
// PayPal: https://www.paypal.com/paypalme/Coopiis?country.x=DE&locale.x=de_DE
(function () {
'use strict';
// Spracherkennung
const userLang = (navigator.language || navigator.languages[0] || 'en').substring(0, 2);
console.log(`[Initializer] Erkannte Sprache: ${userLang}`);
// Übersetzungsobjekt
const translations = {
en: {
hideVideosFound: 'Found videos: ${count}',
hideNoThumbnail: 'Video ${index}: No thumbnail or image found',
hideButtonAdded: 'Video ${index}: Button added',
hideNoMenuButton: 'Video ${index}: No menu button found',
hideMenuOpened: 'Video ${index}: Menu opened',
hideOptionClicked: 'Video ${index}: Hide option clicked',
hideOptionNotFound: 'Video ${index}: Hide option not found',
hideError: 'Video ${index}: Error while hiding: ${error}',
hideConfirmClicked: 'Video ${index}: Confirm button clicked',
hideConfirmNotFound: 'Video ${index}: Confirm button not found',
shortsNoTopbar: 'Topbar or YouTube logo not found',
shortsButtonExists: 'Toggle button already exists, skipping',
shortsButtonAdded: 'Toggle button added to topbar',
shortsNotFound: 'Shorts section not found',
shortsFound: 'Shorts section found: ${details}',
shortsSectionHidden: 'Shorts section: hidden',
shortsSectionShown: 'Shorts section: shown',
shortsButtonTextHide: 'Hide Shorts',
shortsButtonTextShow: 'Show Shorts',
shortsButtonTextUnavailable: 'Shorts unavailable',
shortsDebugPrimary: 'Primary selector ytd-rich-shelf-renderer[is-shorts]: ${result}',
shortsDebugSection: 'Checking section: ${details}',
shortsNoUpdate: 'No Shorts section found, button remains disabled',
initStarted: 'Script initialized',
initAttempt: 'Attempt ${current} of ${max} for Shorts section',
initMaxAttempts: 'Maximum attempts reached, no Shorts section found',
initError: 'Error during initialization: ${error}',
observerError: 'Error in MutationObserver: ${error}',
shortsTitles: ['shorts', 'short videos']
},
de: {
hideVideosFound: 'Gefundene Videos: ${count}',
hideNoThumbnail: 'Video ${index}: Kein Thumbnail oder Bild gefunden',
hideButtonAdded: 'Video ${index}: Button hinzugefügt',
hideNoMenuButton: 'Video ${index}: Kein Menü-Button gefunden',
hideMenuOpened: 'Video ${index}: Menü geöffnet',
hideOptionClicked: 'Video ${index}: Ausblenden geklickt',
hideOptionNotFound: 'Video ${index}: Ausblenden-Option nicht gefunden',
hideError: 'Video ${index}: Fehler beim Ausblenden: ${error}',
hideConfirmClicked: 'Video ${index}: Bestätigen-Button geklickt',
hideConfirmNotFound: 'Video ${index}: Bestätigen-Button nicht gefunden',
shortsNoTopbar: 'Obere Leiste oder YouTube-Logo nicht gefunden',
shortsButtonExists: 'Toggle-Button bereits vorhanden, überspringe',
shortsButtonAdded: 'Toggle-Button in oberer Leiste hinzugefügt',
shortsNotFound: 'Shorts-Abschnitt nicht gefunden',
shortsFound: 'Shorts-Abschnitt gefunden: ${details}',
shortsSectionHidden: 'Shorts-Abschnitt: ausgeblendet',
shortsSectionShown: 'Shorts-Abschnitt: eingeblendet',
shortsButtonTextHide: 'Shorts ausblenden',
shortsButtonTextShow: 'Shorts einblenden',
shortsButtonTextUnavailable: 'Shorts nicht verfügbar',
shortsDebugPrimary: 'Primärer Selektor ytd-rich-shelf-renderer[is-shorts]: ${result}',
shortsDebugSection: 'Prüfe Abschnitt: ${details}',
shortsNoUpdate: 'Kein Shorts-Abschnitt gefunden, Button bleibt deaktiviert',
initStarted: 'Skript initialisiert',
initAttempt: 'Versuch ${current} von ${max} für Shorts-Abschnitt',
initMaxAttempts: 'Maximale Versuche erreicht, kein Shorts-Abschnitt gefunden',
initError: 'Fehler bei der Initialisierung: ${error}',
observerError: 'Fehler im MutationObserver: ${error}',
shortsTitles: ['shorts', 'kurzvideos']
},
es: {
hideVideosFound: 'Videos encontrados: ${count}',
hideNoThumbnail: 'Video ${index}: No se encontró miniatura o imagen',
hideButtonAdded: 'Video ${index}: Botón añadido',
hideNoMenuButton: 'Video ${index}: No se encontró botón de menú',
hideMenuOpened: 'Video ${index}: Menú abierto',
hideOptionClicked: 'Video ${index}: Opción de ocultar clicada',
hideOptionNotFound: 'Video ${index}: Opción de ocultar no encontrada',
hideError: 'Video ${index}: Error al ocultar: ${error}',
hideConfirmClicked: 'Video ${index}: Botón de confirmar clicado',
hideConfirmNotFound: 'Video ${index}: Botón de confirmar no encontrado',
shortsNoTopbar: 'Barra superior o logo de YouTube no encontrados',
shortsButtonExists: 'Botón de alternancia ya existe, omitiendo',
shortsButtonAdded: 'Botón de alternancia añadido a la barra superior',
shortsNotFound: 'Sección de Shorts no encontrada',
shortsFound: 'Sección de Shorts encontrada: ${details}',
shortsSectionHidden: 'Sección de Shorts: oculta',
shortsSectionShown: 'Sección de Shorts: mostrada',
shortsButtonTextHide: 'Ocultar Shorts',
shortsButtonTextShow: 'Mostrar Shorts',
shortsButtonTextUnavailable: 'Shorts no disponibles',
shortsDebugPrimary: 'Selector primario ytd-rich-shelf-renderer[is-shorts]: ${result}',
shortsDebugSection: 'Verificando sección: ${details}',
shortsNoUpdate: 'No se encontró sección de Shorts, el botón permanece deshabilitado',
initStarted: 'Script inicializado',
initAttempt: 'Intento ${current} de ${max} para la sección de Shorts',
initMaxAttempts: 'Máximo de intentos alcanzado, no se encontró sección de Shorts',
initError: 'Error durante la inicialización: ${error}',
observerError: 'Error en MutationObserver: ${error}',
shortsTitles: ['shorts', 'cortos', 'videos cortos']
},
fr: {
hideVideosFound: 'Vidéos trouvées : ${count}',
hideNoThumbnail: 'Vidéo ${index} : Aucune vignette ou image trouvée',
hideButtonAdded: 'Vidéo ${index} : Bouton ajouté',
hideNoMenuButton: 'Vidéo ${index} : Aucun bouton de menu trouvé',
hideMenuOpened: 'Vidéo ${index} : Menu ouvert',
hideOptionClicked: 'Vidéo ${index} : Option de masquer cliquée',
hideOptionNotFound: 'Vidéo ${index} : Option de masquer non trouvée',
hideError: 'Vidéo ${index} : Erreur lors du masquage : ${error}',
hideConfirmClicked: 'Vidéo ${index} : Bouton de confirmation cliqué',
hideConfirmNotFound: 'Vidéo ${index} : Bouton de confirmation non trouvé',
shortsNoTopbar: 'Barre supérieure ou logo YouTube non trouvés',
shortsButtonExists: 'Bouton de bascule déjà présent, ignoré',
shortsButtonAdded: 'Bouton de bascule ajouté à la barre supérieure',
shortsNotFound: 'Section Shorts non trouvée',
shortsFound: 'Section Shorts trouvée : ${details}',
shortsSectionHidden: 'Section Shorts : masquée',
shortsSectionShown: 'Section Shorts : affichée',
shortsButtonTextHide: 'Masquer les Shorts',
shortsButtonTextShow: 'Afficher les Shorts',
shortsButtonTextUnavailable: 'Shorts indisponibles',
shortsDebugPrimary: 'Sélecteur principal ytd-rich-shelf-renderer[is-shorts] : ${result}',
shortsDebugSection: 'Vérification de la section : ${details}',
shortsNoUpdate: 'Aucune section Shorts trouvée, le bouton reste désactivé',
initStarted: 'Script initialisé',
initAttempt: 'Tentative ${current} sur ${max} pour la section Shorts',
initMaxAttempts: 'Nombre maximum de tentatives atteint, aucune section Shorts trouvée',
initError: 'Erreur lors de l’initialisation : ${error}',
observerError: 'Erreur dans MutationObserver : ${error}',
shortsTitles: ['shorts', 'vidéos courtes']
},
it: {
hideVideosFound: 'Video trovati: ${count}',
hideNoThumbnail: 'Video ${index}: Nessuna miniatura o immagine trovata',
hideButtonAdded: 'Video ${index}: Pulsante aggiunto',
hideNoMenuButton: 'Video ${index}: Nessun pulsante di menu trovato',
hideMenuOpened: 'Video ${index}: Menu aperto',
hideOptionClicked: 'Video ${index}: Opzione nascondi cliccata',
hideOptionNotFound: 'Video ${index}: Opzione nascondi non trovata',
hideError: 'Video ${index}: Errore durante la nascondazione: ${error}',
hideConfirmClicked: 'Video ${index}: Pulsante di conferma cliccato',
hideConfirmNotFound: 'Video ${index}: Pulsante di conferma non trovato',
shortsNoTopbar: 'Barra superiore o logo YouTube non trovati',
shortsButtonExists: 'Pulsante di attivazione già presente, salto',
shortsButtonAdded: 'Pulsante di attivazione aggiunto alla barra superiore',
shortsNotFound: 'Sezione Shorts non trovata',
shortsFound: 'Sezione Shorts trovata: ${details}',
shortsSectionHidden: 'Sezione Shorts: nascosta',
shortsSectionShown: 'Sezione Shorts: mostrata',
shortsButtonTextHide: 'Nascondi Shorts',
shortsButtonTextShow: 'Mostra Shorts',
shortsButtonTextUnavailable: 'Shorts non disponibili',
shortsDebugPrimary: 'Selettore primario ytd-rich-shelf-renderer[is-shorts]: ${result}',
shortsDebugSection: 'Controllo sezione: ${details}',
shortsNoUpdate: 'Nessuna sezione Shorts trovata, il pulsante rimane disabilitato',
initStarted: 'Script inizializzato',
initAttempt: 'Tentativo ${current} di ${max} per la sezione Shorts',
initMaxAttempts: 'Massimo numero di tentativi raggiunto, nessuna sezione Shorts trovata',
initError: 'Errore durante l’inizializzazione: ${error}',
observerError: 'Errore in MutationObserver: ${error}',
shortsTitles: ['shorts', 'video brevi']
}
};
// Wähle Übersetzungen basierend auf der Sprache (Fallback: Englisch)
const t = translations[userLang] || translations.en;
// Funktion zum Formatieren von Übersetzungen mit Platzhaltern
function formatTranslation(key, params = {}) {
let str = t[key] || translations.en[key] || key;
Object.keys(params).forEach(param => {
str = str.replace(`\${${param}}`, params[param]);
});
return str;
}
// Funktion zum Warten auf ein Element (optimiert für Performance)
async function waitForElement(selector, timeout = 2000, maxAttempts = 3) {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const start = Date.now();
while (Date.now() - start < timeout) {
const element = document.querySelector(selector);
if (element) return element;
await new Promise(resolve => setTimeout(resolve, 50));
}
console.log(`[Wait Debug] Versuch ${attempt}/${maxAttempts}: Element ${selector} nicht gefunden, warte...`);
await new Promise(resolve => setTimeout(resolve, 100));
}
return null;
}
// Funktion zum Simulieren eines robusten Klicks
function simulateClick(element) {
const rect = element.getBoundingClientRect();
const events = [
new PointerEvent('pointerdown', {
bubbles: true,
cancelable: true,
view: window,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2,
isTrusted: true,
pointerType: 'mouse'
}),
new MouseEvent('mousedown', {
bubbles: true,
cancelable: true,
view: window,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
}),
new MouseEvent('click', {
bubbles: true,
cancelable: true,
view: window,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
}),
new PointerEvent('pointerup', {
bubbles: true,
cancelable: true,
view: window,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2,
isTrusted: true,
pointerType: 'mouse'
}),
new MouseEvent('mouseup', {
bubbles: true,
cancelable: true,
view: window,
clientX: rect.left + rect.width / 2,
clientY: rect.top + rect.height / 2
})
];
element.focus();
events.forEach(event => {
element.dispatchEvent(event);
console.log(`[Click Debug] Event ${event.type} ausgelöst auf: ${element.tagName}`);
});
}
// Debounce-Funktion zur Begrenzung wiederholter Aufrufe
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
// Funktion zum Hinzufügen des Ausblende-Buttons für Videos
const addHideButton = debounce(() => {
// Erweiterter Selektor für verschiedene YouTube-Seiten
const thumbnailContainers = document.querySelectorAll(
'ytd-rich-grid-media:not([data-hide-button-added]) #thumbnail, ' +
'ytd-grid-video-renderer:not([data-hide-button-added]) #thumbnail, ' +
'ytd-compact-video-renderer:not([data-hide-button-added]) #thumbnail, ' +
'ytd-thumbnail:not([data-hide-button-added])'
);
if (thumbnailContainers.length > 0) {
console.log(formatTranslation('hideVideosFound', { count: thumbnailContainers.length }));
} else {
console.log('[Debug] Keine Thumbnails gefunden mit erweitertem Selektor');
}
thumbnailContainers.forEach((thumbnailContainer, index) => {
const video = thumbnailContainer.closest('ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer');
const thumbnailImage = thumbnailContainer.querySelector('img.yt-core-image, img.yt-img-shadow, img');
if (!thumbnailImage || !video) {
console.log(formatTranslation('hideNoThumbnail', { index }));
console.log(`[Hide Debug] Thumbnail-Container: ${thumbnailContainer.outerHTML}`);
return;
}
const hideButton = document.createElement('div');
hideButton.className = 'hide-video-btn';
hideButton.textContent = '🚫';
Object.assign(hideButton.style, {
position: 'absolute',
top: '8px',
left: '8px',
cursor: 'pointer',
zIndex: '10002 !important',
borderRadius: '50%',
width: '32px',
height: '32px',
display: 'flex !important',
alignItems: 'center',
justifyContent: 'center',
fontSize: '18px',
color: 'white !important',
backgroundColor: 'rgba(0, 0, 0, 0.7) !important',
pointerEvents: 'auto !important',
visibility: 'visible !important',
opacity: '1 !important'
});
// Versuche, das übergeordnete Element mit position: relative zu setzen
const parentContainer = thumbnailContainer.tagName === 'YTD-THUMBNAIL' ? thumbnailContainer : thumbnailContainer.parentElement;
if (parentContainer) {
parentContainer.style.position = 'relative';
parentContainer.appendChild(hideButton);
video.setAttribute('data-hide-button-added', 'true');
console.log(formatTranslation('hideButtonAdded', { index }));
} else {
console.log(`[Hide Debug] Kein parentContainer für Video ${index} gefunden`);
}
hideButton.addEventListener('click', async () => {
try {
const menuButton = video.querySelector('yt-icon-button#button.dropdown-trigger, button.yt-icon-button');
if (!menuButton) {
console.log(formatTranslation('hideNoMenuButton', { index }));
console.log(`[Hide Debug] Kein Menü-Button gefunden für Video ${index}`);
return;
}
// Simuliere Klick auf Menü-Button
const menuButtonElement = menuButton.querySelector('button') || menuButton;
simulateClick(menuButtonElement);
console.log(formatTranslation('hideMenuOpened', { index }));
// Warte auf das Menü-Popup
const menu = await waitForElement('ytd-menu-popup-renderer, tp-yt-paper-listbox', 2000, 3);
if (!menu) {
console.log(formatTranslation('hideOptionNotFound', { index }) + ' - Kein Menü-Popup gefunden nach 3 Versuchen');
return;
}
// Warte kurz, um sicherzustellen, dass das Menü vollständig gerendert ist
await new Promise(resolve => setTimeout(resolve, 750));
console.log(`[Hide Debug] Menü-Popup nach Verzögerung: ${menu.outerHTML}`);
// Suche nach der Ausblenden-Option
const menuItems = menu.querySelectorAll('ytd-menu-service-item-renderer, ytd-menu-navigation-item-renderer, tp-yt-paper-item');
const hideOption = Array.from(menuItems).find(item => {
const textElement = item.querySelector('yt-formatted-string, span, div');
const text = textElement?.textContent?.trim().toLowerCase();
const hasHideIcon = item.querySelector('yt-icon path[d*="M12 2c5.52 0 10 4.48 10 10s-4.48 10-10 10"]') !== null;
return hasHideIcon ||
text?.includes('ausblenden') ||
text?.includes('hide') ||
text?.includes('ocultar') ||
text?.includes('masquer') ||
text?.includes('nascondi');
});
if (hideOption) {
console.log(`[Hide Debug] Gefundene Ausblende-Option: ${hideOption.outerHTML}`);
const clickableItem = hideOption.querySelector('tp-yt-paper-item') || hideOption;
if (clickableItem.getAttribute('aria-disabled') !== 'true') {
simulateClick(clickableItem);
console.log(formatTranslation('hideOptionClicked', { index }));
} else {
console.log('[Hide Debug] Ausblende-Option nicht klickbar:', {
tagName: clickableItem.tagName,
ariaDisabled: clickableItem.getAttribute('aria-disabled'),
tabindex: clickableItem.getAttribute('tabindex'),
outerHTML: clickableItem.outerHTML
});
return;
}
} else {
console.log(formatTranslation('hideOptionNotFound', { index }));
console.log('[Hide Debug] Verfügbare Menü-Elemente:', Array.from(menuItems).map(item => item.outerHTML));
return;
}
// Warte auf den Bestätigungs-Dialog
const confirmDialog = await waitForElement('tp-yt-paper-dialog, ytd-popup-container, ytd-engagement-panel-section-list-renderer', 2000, 3);
if (!confirmDialog) {
console.log('[Hide Debug] Kein Bestätigungs-Dialog gefunden nach 3 Versuchen');
return;
}
// Warte auf den Bestätigungs-Button
const confirmButton = await waitForElement(
'yt-button-renderer#confirm-button, ' +
'yt-button-renderer:not([disabled]) a, ' +
'tp-yt-paper-button:not([disabled]), ' +
'tp-yt-paper-button[aria-label*="bestätigen" i], ' +
'tp-yt-paper-button[aria-label*="confirm" i], ' +
'yt-button-renderer a.yt-simple-endpoint[aria-label*="bestätigen" i], ' +
'yt-button-renderer a.yt-simple-endpoint[aria-label*="confirm" i]',
2000, 3
);
if (confirmButton) {
console.log(`[Hide Debug] Gefundener Bestätigungs-Button: ${confirmButton.outerHTML}`);
const clickableConfirm = confirmButton.querySelector('a.yt-simple-endpoint, tp-yt-paper-button') || confirmButton;
const buttonText = clickableConfirm.textContent?.trim().toLowerCase();
if (buttonText.includes('bestätigen') || buttonText.includes('confirm')) {
simulateClick(clickableConfirm);
console.log(formatTranslation('hideConfirmClicked', { index }));
} else {
console.log('[Hide Debug] Bestätigungs-Button hat unerwarteten Text:', buttonText);
}
} else {
console.log(formatTranslation('hideConfirmNotFound', { index }));
console.log('[Hide Debug] Kein Bestätigungs-Button gefunden nach 3 Versuchen');
console.log('[Hide Debug] Dialog-Inhalt:', confirmDialog.outerHTML);
}
} catch (err) {
console.error(formatTranslation('hideError', { index, error: err.message }));
}
});
});
}, 100); // Debounce mit 100 ms
// CSS hinzufügen
const style = document.createElement('style');
style.textContent = `
.hide-video-btn {
color: white !important;
background-color: rgba(0, 0, 0, 0.7) !important;
border-radius: 50% !important;
font-size: 18px !important;
width: 32px !important;
height: 32px !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
position: absolute !important;
top: 8px !important;
left: 8px !important;
cursor: pointer !important;
pointer-events: auto !important;
z-index: 10002 !important;
visibility: visible !important;
opacity: 1 !important;
}
.hide-video-btn:hover {
background-color: rgba(0, 0, 0, 0.9) !important;
box-shadow: 0 0 10px 2px rgba(255, 215, 0, 0.8) !important;
}
ytd-thumbnail, ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer {
position: relative !important;
}
ytd-thumbnail[overlay-style="DEFAULT"] > .yt-img-shadow,
ytd-thumbnail[overlay-style="DEFAULT"] > .thumbnail-overlay {
z-index: 0 !important;
}
.shorts-toggle-btn {
transition: background-color 0.2s !important;
}
.shorts-toggle-btn:not(:disabled):hover {
background-color: #cc0000 !important;
}
.shorts-toggle-wrapper {
display: inline-flex !important;
align-items: center !important;
margin-left: 8px !important;
z-index: 10001 !important;
}
`;
document.head.appendChild(style);
// Initiale Ausführung mit Verzögerung
function initialize() {
try {
addHideButton();
addShortsToggleButton();
console.log(formatTranslation('initStarted'));
// Wiederholte Prüfung für Shorts-Abschnitt
let attempts = 0;
const maxAttempts = 5;
const interval = setInterval(() => {
console.log(formatTranslation('initAttempt', { current: attempts + 1, max: maxAttempts }));
const shortsSection = document.querySelector('ytd-rich-shelf-renderer[is-shorts]');
if (shortsSection || document.querySelector('span[id="title"][textContent*="Shorts" i]')) {
addShortsToggleButton();
clearInterval(interval);
} else if (attempts >= maxAttempts) {
console.log(formatTranslation('initMaxAttempts'));
clearInterval(interval);
}
attempts++;
}, 1000);
} catch (err) {
console.error(formatTranslation('initError', { error: err.message }));
}
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(initialize, 1000);
} else {
document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 1000));
}
// Begrenzter MutationObserver
const observerTarget = document.querySelector('ytd-app #contents') || document.body;
const observer = new MutationObserver((mutations, obs) => {
try {
const hasRelevantChanges = mutations.some(mutation =>
mutation.addedNodes.length > 0 &&
mutation.addedNodes[0]?.nodeType === Node.ELEMENT_NODE &&
(mutation.target.matches('ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-rich-shelf-renderer, ytd-rich-section-renderer, ytd-masthead, ytd-rich-shelf-renderer[is-shorts]') ||
mutation.target.querySelector('ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-rich-shelf-renderer[is-shorts]')) &&
!mutation.target.classList?.contains('shorts-toggle-wrapper')
);
if (hasRelevantChanges) {
addHideButton();
addShortsToggleButton();
}
} catch (err) {
console.error(formatTranslation('observerError', { error: err.message }));
}
});
observer.observe(observerTarget, {
childList: true,
subtree: true,
attributes: false
});
})();