Greasy Fork

Greasy Fork is available in English.

YouTube Video Hider with 🚫 Icon and Shorts Toggle

Adds a 🚫 symbol to video metadata for hiding videos, excludes Shorts thumbnails, with persistent Shorts toggle state

当前为 2026-04-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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 2026.4.8
// @description Adds a 🚫 symbol to video metadata for hiding videos, excludes Shorts thumbnails, with persistent Shorts toggle state
// @description:de Fügt ein 🚫 Symbol zu Video-Metadaten hinzu, exklusive Shorts, und einen kompakten Button zum Ein-/Ausblenden von Shorts mit persistentem Zustand
// @description:es Agrega un símbolo 🚫 a los metadatos de video, excluyendo Shorts, y un botón compacto para alternar Shorts con estado persistente
// @description:fr Ajoute un symbole 🚫 aux métadonnées des vidéos, sauf pour les Shorts, et un bouton compact pour activer/désactiver les Shorts avec état persistant
// @description:it Aggiunge un simbolo 🚫 ai metadati dei video, esclusi i Shorts, e un pulsante compatto per attivare/disattivare i Shorts con stato persistente
// @icon https://youtube.com/favicon.ico
// @author Copiis
// @license MIT
// @match https://www.youtube.com/*
// @grant GM_setValue
// @grant GM_getValue
// 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
// ==/UserScript==

(function () {
    'use strict';

    // Konfigurationsobjekt
    const config = {
        hideButtonSize: '24px',
        hideButtonOpacity: '0.7',
        shortsCheckInterval: 2000, // Erhöht auf 2000ms
        maxShortsAttempts: 30, // Erhöht auf 30 Versuche
        debugMode: true,
        reapplyInterval: 1000,
        menuLoadDelay: 150,
        maxMenuAttempts: 15
    };

    // Spracherkennung
    const userLang = (navigator.language || navigator.languages[0] || 'en').substring(0, 2);
    if (config.debugMode) console.log(`[Initializer] Erkannte Sprache: ${userLang}`);

    // Übersetzungen
    const translations = {
        en: {
            hideVideosFound: 'Found videos: ${count}',
            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',
            shortsButtonText: 'Shorts',
            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}',
            noMetadataFound: 'Video ${index}: No metadata container found'
        },
        de: {
            hideVideosFound: 'Gefundene Videos: ${count}',
            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',
            shortsButtonText: 'Shorts',
            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}',
            noMetadataFound: 'Video ${index}: Kein Metadaten-Container gefunden'
        }
    };

    const t = translations[userLang] || translations.en;

    // Funktion zum Formatieren von Übersetzungen
    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
    async function waitForElement(selector, timeout = 3000, maxAttempts = 5) {
        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));
            }
            if (config.debugMode) console.log(`[Wait Debug] Versuch ${attempt}/${maxAttempts}: Element ${selector} nicht gefunden`);
        }
        return null;
    }

    // Funktion zum Simulieren eines Klicks
    function simulateClick(element) {
        const events = [
            new MouseEvent('mousedown', { bubbles: true, cancelable: true }),
            new MouseEvent('click', { bubbles: true, cancelable: true }),
            new MouseEvent('mouseup', { bubbles: true, cancelable: true })
        ];
        element.focus();
        events.forEach(event => element.dispatchEvent(event));
    }

    // Debounce-Funktion
    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 + automatisches Klicken auf "Ausblenden"
const addHideButton = debounce(async () => {
    const videoContainers = document.querySelectorAll('ytd-rich-item-renderer:not([data-hide-button-added])');

    for (const [index, video] of Array.from(videoContainers).entries()) {
        video.setAttribute('data-hide-button-added', 'true');

        const menuContainer = video.querySelector('.ytLockupMetadataViewModelMenuButton');
        if (!menuContainer) continue;
        if (menuContainer.querySelector('.hide-video-btn')) continue;

        const hideButton = document.createElement('div');
        hideButton.className = 'hide-video-btn';
        hideButton.textContent = '🚫';
        hideButton.style.cssText = `
            display: inline-flex;
            align-items: center;
            justify-content: center;
            width: ${config.hideButtonSize};
            height: ${config.hideButtonSize};
            border-radius: 50%;
            font-size: 18px;
            background-color: rgba(0, 0, 0, ${config.hideButtonOpacity});
            color: #fff;
            cursor: pointer;
            margin-left: 6px;
            z-index: 10;
            flex-shrink: 0;
        `;

        menuContainer.parentNode.insertBefore(hideButton, menuContainer);

        hideButton.addEventListener('click', async (e) => {
            e.stopImmediatePropagation();
            e.preventDefault();

            const menuButton = menuContainer.querySelector('button.yt-spec-button-shape-next');
            if (!menuButton) return;

            simulateClick(menuButton);

            await new Promise(r => setTimeout(r, 420));

            const hideOption = Array.from(document.querySelectorAll('yt-list-item-view-model'))
                .find(item => {
                    const span = item.querySelector('.yt-core-attributed-string');
                    return span && span.textContent.trim() === 'Ausblenden';
                });

            if (hideOption) {
                const clickTarget = hideOption.querySelector('button') || hideOption;
                simulateClick(clickTarget);
            }
        });
    }
}, 150);

    // Funktion zum Hinzufügen des Shorts-Toggle-Buttons
    let shortsButton = null;
    let shortsSection = null;
    let isShortsHidden = GM_getValue('isShortsHidden', false); // Lade gespeicherten Zustand

    function addShortsToggleButton() {
    const topbar = document.querySelector('ytd-masthead #masthead-container') || document.querySelector('ytd-masthead');
    if (!topbar) {
        console.log(formatTranslation('shortsNoTopbar'));
        return;
    }
    if (document.querySelector('.shorts-toggle-wrapper')) {
        console.log(formatTranslation('shortsButtonExists'));
        return;
    }
    const toggleWrapper = document.createElement('div');
    toggleWrapper.className = 'shorts-toggle-wrapper';
    shortsButton = document.createElement('button');
    shortsButton.className = 'shorts-toggle-btn';
    const textSpan = document.createElement('span');
    textSpan.textContent = formatTranslation('shortsButtonText');
    const iconSpan = document.createElement('span');
    iconSpan.className = 'shorts-toggle-icon';
    iconSpan.textContent = '🚫';
    shortsButton.appendChild(textSpan);
    shortsButton.appendChild(iconSpan);
    Object.assign(shortsButton.style, {
        padding: '2px 8px',
        border: 'none',
        borderRadius: '4px',
        backgroundColor: 'transparent',
        color: 'white',
        cursor: 'pointer',
        fontSize: '12px',
        display: 'flex',
        alignItems: 'center',
        gap: '4px'
    });
    toggleWrapper.appendChild(shortsButton);
    const logoContainer = topbar.querySelector('#logo') || topbar.querySelector('#container');
    if (logoContainer) {
        logoContainer.appendChild(toggleWrapper);
        console.log(formatTranslation('shortsButtonAdded'));
    }
    const checkShortsSection = () => {
        const shortsSections = document.querySelectorAll(
            'ytd-rich-shelf-renderer[is-shorts], ' +
            'ytd-rich-section-renderer ytd-rich-shelf-renderer[is-shorts], ' +
            'ytd-reel-shelf-renderer, ' +
            'ytm-shorts-lockup-view-model, ' +
            'a[href*="/shorts/"], ' +
            'ytd-rich-item-renderer[is-shelf-item], ' +
            'div[id*="contents"] ytd-rich-shelf-renderer'
        );
        if (shortsSections.length > 0) {
            console.log(formatTranslation('shortsFound', { details: shortsSections[0].outerHTML.slice(0, 100) }));
            shortsButton.disabled = false;
            iconSpan.style.display = isShortsHidden ? 'none' : 'inline';
            shortsSections.forEach(section => {
                const parentSection = section.closest('ytd-rich-section-renderer');
                if (parentSection) {
                    parentSection.style.display = isShortsHidden ? 'none' : '';
                } else if (section.tagName === 'YTM-SHORTS-LOCKUP-VIEW-MODEL' || section.tagName === 'A' || section.tagName === 'YTD-RICH-ITEM-RENDERER') {
                    const parent = section.closest('ytd-rich-item-renderer, ytd-grid-video-renderer, ytd-compact-video-renderer');
                    if (parent) parent.style.display = isShortsHidden ? 'none' : '';
                } else {
                    section.style.display = isShortsHidden ? 'none' : '';
                }
            });
        } else {
            console.log(formatTranslation('shortsNotFound'));
            if (config.debugMode) {
                console.log('[Debug] Verfügbare Sektionen:', Array.from(document.querySelectorAll('ytd-rich-shelf-renderer, ytd-rich-section-renderer, ytm-shorts-lockup-view-model, ytd-reel-shelf-renderer, a[href*="/shorts/"], ytd-rich-item-renderer[is-shelf-item], div[id*="contents"] ytd-rich-shelf-renderer')).map(el => ({
                    tag: el.tagName,
                    outerHTML: el.outerHTML.slice(0, 100)
                })));
            }
            shortsButton.disabled = true;
            iconSpan.style.display = 'none';
        }
    };
    checkShortsSection();
    shortsButton.addEventListener('click', () => {
        isShortsHidden = !isShortsHidden;
        GM_setValue('isShortsHidden', isShortsHidden);
        checkShortsSection(); // Neu anwenden nach Toggle
        console.log(isShortsHidden ? formatTranslation('shortsSectionHidden') : formatTranslation('shortsSectionShown'));
    });
    if (isShortsHidden) {
        checkShortsSection();
        iconSpan.style.display = 'none';
    }
    // Kontinuierlicher Check für Shorts
    setInterval(checkShortsSection, config.shortsCheckInterval);
}

    // CSS hinzufügen
    const style = document.createElement('style');
    style.textContent = `
        .hide-video-btn {
            color: white !important;
            background-color: rgba(0, 0, 0, ${config.hideButtonOpacity}) !important;
            border-radius: 50% !important;
            font-size: 16px !important;
            width: ${config.hideButtonSize} !important;
            height: ${config.hideButtonSize} !important;
            display: inline-flex !important;
            align-items: center !important;
            justify-content: center !important;
            cursor: pointer !important;
            pointerEvents: auto !important;
            z-index: 10003 !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;
        }
        .yt-lockup-metadata-view-model__avatar {
            display: flex !important;
            align-items: center !important;
        }
        .shorts-toggle-btn {
            transition: color 0.2s !important;
        }
        .shorts-toggle-btn:not(:disabled):hover {
            color: #cc0000 !important;
        }
        .shorts-toggle-wrapper {
            display: inline-flex !important;
            align-items: center !important;
            margin-left: 8px !important;
            z-index: 10001 !important;
        }
        .shorts-toggle-icon {
            display: none;
            font-size: 12px;
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background-color: rgba(0, 0, 0, 0.7);
            text-align: center;
            line-height: 16px;
            color: white;
        }
    `;
    document.head.appendChild(style);

    // Initiale Ausführung
    function initialize() {
        try {
            addHideButton();
            addShortsToggleButton();
            console.log(formatTranslation('initStarted'));
            let attempts = 0;
            const interval = setInterval(() => {
                console.log(formatTranslation('initAttempt', { current: attempts + 1, max: config.maxShortsAttempts }));
                const shortsSection = document.querySelector(
                    'ytd-rich-shelf-renderer[is-shorts], ' +
                    'ytd-rich-section-renderer ytd-rich-shelf-renderer[is-shorts], ' +
                    'ytd-rich-shelf-renderer span#title-container span#title[textContent*="Shorts" i], ' +
                    'ytd-reel-shelf-renderer, ' +
                    'ytm-shorts-lockup-view-model, ' +
                    'a[href*="/shorts/"], ' +
                    'ytd-rich-item-renderer[is-shelf-item], ' +
                    'div[id*="contents"] ytd-rich-shelf-renderer'
                );
                if (shortsSection) {
                    addShortsToggleButton();
                    clearInterval(interval);
                } else if (attempts >= config.maxShortsAttempts) {
                    console.log(formatTranslation('initMaxAttempts'));
                    clearInterval(interval);
                }
                attempts++;
            }, config.shortsCheckInterval);
            setInterval(addHideButton, config.reapplyInterval);
        } 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));
    }

    // MutationObserver
    const observerTarget = document.querySelector('ytd-app #contents') || document.body;
    const observer = new MutationObserver((mutations) => {
        try {
            const hasRelevantChanges = mutations.some(mutation =>
                mutation.addedNodes.length > 0 &&
                mutation.addedNodes[0]?.nodeType === Node.ELEMENT_NODE &&
                (mutation.target.matches('yt-lockup-view-model, ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-rich-shelf-renderer, ytd-rich-section-renderer, ytm-shorts-lockup-view-model, ytd-reel-shelf-renderer, a[href*="/shorts/"], ytd-rich-item-renderer[is-shelf-item], div[id*="contents"] ytd-rich-shelf-renderer') ||
                 mutation.target.querySelector('yt-lockup-view-model, ytd-rich-grid-media, ytd-grid-video-renderer, ytd-compact-video-renderer, ytd-rich-shelf-renderer[is-shorts], ytd-rich-section-renderer, ytm-shorts-lockup-view-model, ytd-reel-shelf-renderer, a[href*="/shorts/"], ytd-rich-item-renderer[is-shelf-item], div[id*="contents"] ytd-rich-shelf-renderer, .yt-lockup-metadata-view-model')))
            if (hasRelevantChanges) {
                addHideButton();
                addShortsToggleButton();
            }
        } catch (err) {
            console.error(formatTranslation('observerError', { error: err.message }));
        }
    });
    observer.observe(observerTarget, { childList: true, subtree: true });
})();