🪄 NoShorts YouTube UI Enhancer | Remove Shorts from Feed & Customize Layout

A modified version of Michaelsoft's "YouTube UI Enhancer". Enhances YouTube by showing full video titles on hover, removing ad overlays, and adding quick-access "Not Interested" icons, with all features configurable from a settings menu. All credit for the original script goes to Michaelsoft.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         🪄 NoShorts YouTube UI Enhancer | Remove Shorts from Feed & Customize Layout
// @namespace    https://greasyfork.org/en/users/15128-reaverxai
// @version      2.2.2
// @description  A modified version of Michaelsoft's "YouTube UI Enhancer". Enhances YouTube by showing full video titles on hover, removing ad overlays, and adding quick-access "Not Interested" icons, with all features configurable from a settings menu. All credit for the original script goes to Michaelsoft.
// @author       reaverxai (based on the work of Michaelsoft)
// @match        *://www.youtube.com/*
// @exclude      *://www.youtube.com/feed/you
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-end
// @license      MIT
// ==/UserScript==

/*
 * This script is a modification of the original "YouTube UI Enhancer" script by Michaelsoft.
 * The original script, under the MIT License, can be found at:
 * https://greasyfork.org/en/scripts/533654-youtube-ui-enhancer-resize-thumbnails-modify-layout-more
 */

(function () {
    'use strict';

    // WARNING: Users should update their preferences through UI - values below will be overwritten every time the script is updated.
    const userSettings = {
        videosPerRow: 6,
        shortsPerRow: 12,
        disableShorts: true,
        enableShowMoreFix: true,
        hideUIButton: false,
        hideUIButtonShortcut: true,
        enableCustomTooltips: false,
        enableAdBlocker: true,
        enableInterestIcons: true,
        enableRefreshButton: true,
    };

    const settings = {};
    for (const key in userSettings) {
        settings[key] = GM_getValue(key, userSettings[key]);
    }

    function saveSetting(key, value) {
        GM_setValue(key, value);
        settings[key] = value;
        applyCustomizations();
    }

    function resetSettings() {
        for (const key in userSettings) {
            saveSetting(key, userSettings[key]);
        }
    }

    let customStyle = null;
    let featureObservers = [];

    function applyCustomizations() {
        if (customStyle) customStyle.remove();
        customStyle = GM_addStyle(`
            ytd-rich-grid-renderer {
                --ytd-rich-grid-items-per-row: ${settings.videosPerRow} !important;
                --ytd-rich-grid-posts-per-row: ${settings.videosPerRow} !important;
                --ytd-rich-grid-slim-items-per-row: ${settings.shortsPerRow} !important;
                --ytd-rich-grid-game-cards-per-row: 7 !important;
                --ytd-rich-grid-gutter-margin: 0px !important;
            }
            ytd-rich-shelf-renderer {
                --ytd-rich-grid-items-per-row: ${settings.shortsPerRow} !important;
            }
            ${settings.disableShorts ? `
                ytd-rich-section-renderer.style-scope.ytd-rich-grid-renderer {
                    display: none !important;
                }
            ` : ''}
        `);
    }

    function enableShowMoreFix() {
        const observer = new MutationObserver(() => {
            document.querySelectorAll('ytd-rich-item-renderer[hidden]').forEach(el => el.removeAttribute('hidden'));
            document.querySelectorAll('ytd-rich-shelf-renderer').forEach(el => el.setAttribute('is-show-more-hidden', ''));
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
        featureObservers.push(observer);
    }

    // FEATURE: Show full video titles on hover
    let titleTooltip = null;

    function createTitleTooltip() {
        if (titleTooltip) return;
        titleTooltip = document.createElement('div');
        Object.assign(titleTooltip.style, {
            position: 'fixed', zIndex: '10000', padding: '5px 10px', fontSize: '14px',
            backgroundColor: isDarkTheme() ? 'rgba(40, 40, 40, 0.95)' : 'rgba(240, 240, 240, 0.95)',
            color: isDarkTheme() ? '#eee' : '#000', border: `1px solid ${isDarkTheme() ? '#555' : '#ccc'}`,
            borderRadius: '4px', display: 'none', pointerEvents: 'none', maxWidth: '450px',
            textAlign: 'left', lineHeight: '1.4'
        });
        document.body.appendChild(titleTooltip);
    }

    function showFullTitle(event) {
        if (!titleTooltip) createTitleTooltip();
        const target = event.currentTarget;
        const visibleTitleSpan = target.querySelector('span.yt-core-attributed-string');
        if (!visibleTitleSpan || visibleTitleSpan.scrollWidth <= visibleTitleSpan.clientWidth) return;
        const fullTitle = target.closest('h3')?.getAttribute('title') || visibleTitleSpan.textContent.trim();
        if (!fullTitle) return;
        titleTooltip.textContent = fullTitle;
        titleTooltip.style.display = 'block';
        updateTooltipPosition(event);
    }

    function hideFullTitle() {
        if (titleTooltip) titleTooltip.style.display = 'none';
    }

    function updateTooltipPosition(event) {
        if (titleTooltip && titleTooltip.style.display === 'block') {
            let x = event.clientX + 15;
            let y = event.clientY + 15;
            if (y + titleTooltip.offsetHeight > window.innerHeight) y = event.clientY - titleTooltip.offsetHeight - 15;
            if (x + titleTooltip.offsetWidth > window.innerWidth) x = event.clientX - titleTooltip.offsetWidth - 15;
            titleTooltip.style.left = `${x}px`;
            titleTooltip.style.top = `${y}px`;
        }
    }

    function initTitleHoverFeature() {
        const setupObserver = new MutationObserver(() => {
            document.querySelectorAll('a#video-title:not([data-title-hover-init]), .yt-lockup-metadata-view-model__title:not([data-title-hover-init])').forEach(el => {
                el.setAttribute('data-title-hover-init', 'true');
                el.addEventListener('mouseenter', showFullTitle);
                el.addEventListener('mouseleave', hideFullTitle);
                el.addEventListener('mousemove', updateTooltipPosition);
            });
        });
        setupObserver.observe(document.documentElement, { childList: true, subtree: true });
        featureObservers.push(setupObserver);

        const attrObserver = new MutationObserver(mutations => {
            mutations.forEach(m => {
                if (m.type === 'attributes' && m.attributeName === 'title') m.target.removeAttribute('title');
                if (m.type === 'childList') m.target.querySelectorAll('a#video-title[title], .yt-lockup-metadata-view-model__title[title]').forEach(el => el.removeAttribute('title'));
            });
        });
        attrObserver.observe(document.documentElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['title'] });
        featureObservers.push(attrObserver);
    }

    // FEATURE: Ad Blocker
    function initAdBlocker() {
        const adObserver = new MutationObserver(mutations => {
            mutations.forEach(m => {
                if (m.type === 'childList') {
                    m.addedNodes.forEach(node => {
                        if (node.nodeType === 1 && node.classList?.contains('ytmPaidContentOverlayHost')) node.remove();
                    });
                }
            });
        });
        adObserver.observe(document.body, { childList: true, subtree: true });
        featureObservers.push(adObserver);
    }

    // FEATURE: "Not Interested" Icons
    function initInterestIcons() {
        GM_addStyle(`
            .yt-lockup-metadata-view-model__menu-button, #menu { position: relative !important; overflow: visible !important; }
            .interest-icons-container { position: absolute; top: 40%; right: 7%; z-index: 5; display: flex; flex-direction: column; gap: 4px; opacity: 0; transition: opacity 0.1s linear; pointer-events: none; }
            ytd-rich-item-renderer:hover .interest-icons-container, ytd-video-renderer:hover .interest-icons-container, ytd-grid-video-renderer:hover .interest-icons-container { opacity: 1; pointer-events: auto; }
            .interest-icon-button { width: 32px; height: 32px; border: none; background: none; cursor: pointer; padding: 6px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background-color: var(--yt-spec-background-solid, ${isDarkTheme() ? '#212121' : '#fff'}); }
            .interest-icon-button:hover { background-color: ${isDarkTheme() ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.1)'}; }
            .interest-icon-button svg { width: 20px; height: 20px; fill: var(--yt-spec-text-primary, ${isDarkTheme() ? '#fff' : '#030303'}); }
        `);
        const interestObserver = new MutationObserver(mutations => {
            mutations.forEach(m => m.addedNodes.forEach(node => {
                if (node.nodeType === 1) {
                    const renderers = node.matches('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer') ? [node] : node.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer');
                    renderers.forEach(addInterestIcons);
                }
            }));
        });
        interestObserver.observe(document.documentElement, { childList: true, subtree: true });
        featureObservers.push(interestObserver);
    }

    function addInterestIcons(videoRenderer) {
        const menuContainer = videoRenderer.querySelector('.yt-lockup-metadata-view-model__menu-button, #menu');
        if (!menuContainer || menuContainer.querySelector('.interest-icons-container')) return;
        const container = document.createElement('div');
        container.className = 'interest-icons-container';
        const notInterestedBtn = createIconButton('Not interested', '<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 0 24 24" width="24" focusable="false" aria-hidden="true"><path d="M12 2c5.52 0 10 4.48 10 10s-4.48 10-10 10S2 17.52 2 12 6.48 2 12 2zM3 12c0 2.31.87 4.41 2.29 6L18 5.29C16.41 3.87 14.31 3 12 3c-4.97 0-9 4.03-9 9zm15.71-6L6 18.71C7.59 20.13 9.69 21 12 21c4.97 0 9-4.03 9-9 0-2.31-.87-4.41-2.29-6z" fill-rule="evenodd"></path></svg>');
        const dontRecommendBtn = createIconButton("Don't recommend channel", '<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24" viewBox="0 0 24 24" width="24" focusable="false" aria-hidden="true"><g><path d="M12 3c-4.96 0-9 4.04-9 9s4.04 9 9 9 9-4.04 9-9-4.04-9-9-9m0-1c5.52 0 10 4.48 10 10s-4.48 10-10 10S2 17.52 2 12 6.48 2 12 2zm7 11H5v-2h14v2z"></path></g></svg>');
        notInterestedBtn.onclick = e => { e.stopPropagation(); handleInterestClick(videoRenderer, 'Not interested'); };
        dontRecommendBtn.onclick = e => { e.stopPropagation(); handleInterestClick(videoRenderer, "Don't recommend channel"); };
        container.append(notInterestedBtn, dontRecommendBtn);
        menuContainer.prepend(container);
    }

    function scanAndApplyInterestIcons() {
        setTimeout(() => document.querySelectorAll('ytd-rich-item-renderer, ytd-video-renderer, ytd-grid-video-renderer').forEach(addInterestIcons), 500);
    }

    function createIconButton(title, svg) {
        const btn = document.createElement('button');
        btn.className = 'interest-icon-button';
        btn.title = title;
        btn.innerHTML = svg;
        return btn;
    }

    function handleInterestClick(videoRenderer, optionText) {
        const menuButton = videoRenderer.querySelector('button[aria-label="More actions"], button[aria-label="Action menu"]');
        if (menuButton) {
            menuButton.click();
            setTimeout(() => {
                document.querySelectorAll('yt-list-item-view-model .yt-core-attributed-string').forEach(item => {
                    if (item.textContent.trim() === optionText) item.closest('yt-list-item-view-model').click();
                });
            }, 100);
        }
    }

    function isDarkTheme() { return document.querySelector('html')?.getAttribute('dark') !== null; }

    let settingsButton = null, refreshButton = null;

    function refreshFeed() {
        const path = window.location.pathname;
        let targetText = (path === '/') ? 'Home' : (path === '/feed/subscriptions') ? 'Subscriptions' : null;
        if (targetText) {
            for (const entry of document.querySelectorAll('ytd-guide-entry-renderer')) {
                const titleEl = entry.querySelector('yt-formatted-string#title, .title.ytd-guide-entry-renderer');
                if (titleEl?.textContent.trim() === targetText) {
                    entry.querySelector('a#endpoint')?.click();
                    return;
                }
            }
        }
        document.querySelector('ytd-topbar-logo-renderer a, a#logo')?.click();
    }

    function createUiButtons() {
        if (settings.enableRefreshButton) {
            refreshButton = document.createElement('div');
            refreshButton.innerHTML = '⟳';
            refreshButton.title = 'Refresh Feed';
            Object.assign(refreshButton.style, { position: 'fixed', bottom: '70px', left: '20px', zIndex: '9999', width: '40px', height: '40px', fontSize: '24px', lineHeight: '40px', textAlign: 'center', backgroundColor: '#065fd4', color: '#fff', borderRadius: '50%', cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.4)', display: 'flex', alignItems: 'center', justifyContent: 'center' });
            refreshButton.onclick = refreshFeed;
            document.body.appendChild(refreshButton);
        }
        settingsButton = document.createElement('div');
        settingsButton.id = 'yt-ui-enhancer-settings-button';
        settingsButton.innerHTML = '🪄';
        settingsButton.title = 'Customize Layout';
        Object.assign(settingsButton.style, { position: 'fixed', bottom: '20px', left: '20px', zIndex: '9999', width: '40px', height: '40px', fontSize: '20px', backgroundColor: '#065fd4', color: '#fff', borderRadius: '50%', cursor: 'pointer', boxShadow: '0 4px 12px rgba(0,0,0,0.4)', display: settings.hideUIButton ? 'none' : 'flex', alignItems: 'center', justifyContent: 'center' });
        settingsButton.onclick = openSettingsMenu;
        document.body.appendChild(settingsButton);
        if (settings.hideUIButtonShortcut) window.addEventListener('keydown', e => { if (e.altKey && e.shiftKey && e.key === 'U') toggleUIButtonVisibility(); });
    }

    function toggleUIButtonVisibility() {
        if (settingsButton) {
            const isHidden = settingsButton.style.display === 'none';
            settingsButton.style.display = isHidden ? 'flex' : 'none';
            saveSetting('hideUIButton', !isHidden);
        }
    }

    function openSettingsMenu() {
        const darkMode = isDarkTheme();
        if (settingsButton) {
            settingsButton.style.pointerEvents = 'none';
            settingsButton.style.opacity = '0.5';
        }
        const overlay = document.createElement('div');
        Object.assign(overlay.style, { position: 'fixed', top: '0', left: '0', width: '100vw', height: '100vh', backgroundColor: 'rgba(0,0,0,0.7)', zIndex: '9998' });
        overlay.onclick = () => {
            document.body.removeChild(overlay);
            if (settingsButton) {
                settingsButton.style.pointerEvents = 'auto';
                settingsButton.style.opacity = '1';
            }
        };
        const menu = document.createElement('div');
        Object.assign(menu.style, { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', backgroundColor: darkMode ? '#222' : '#fff', color: darkMode ? '#eee' : '#000', padding: '20px', borderRadius: '12px', boxShadow: '0 8px 24px rgba(0,0,0,0.6)', width: '360px', zIndex: '9999', fontSize: '14px', lineHeight: '1.5', textAlign: 'left' });
        menu.onclick = e => e.stopPropagation();
        menu.innerHTML = `
            <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;">
                <a href="https://greasyfork.org/en/scripts/533654-youtube-ui-enhancer-resize-thumbnails-modify-layout-more" target="_blank" style="text-decoration:none;color:inherit;font-size:20px;font-weight:bold;">🪄 NoShorts UI Enhancer</a>
                <button id="closeOverlay" style="background:none;border:none;color:${darkMode ? 'white' : 'black'};font-size:26px;line-height:1;cursor:pointer;padding:0;margin-left:10px;padding-bottom:5px;">×</button>
            </div>
            <label style="display:block; margin-bottom:10px;">Videos Per Row: <input id="videosPerRow" type="number" min="1" value="${settings.videosPerRow}" style="width:60px;"/></label>
            <label style="display:block; margin-bottom:20px;">Shorts Per Row: <input id="shortsPerRow" type="number" min="1" value="${settings.shortsPerRow}" style="width:60px;"/></label>
            <label style="display:block; margin-bottom:10px;"><input id="disableShorts" type="checkbox" ${settings.disableShorts ? 'checked' : ''} /> Hide Shorts Section</label>
            <label style="display:block; margin-bottom:10px;"><input id="enableShowMoreFix" type="checkbox" ${settings.enableShowMoreFix ? 'checked' : ''} /> Expand Shorts Automatically</label>
            <div style="margin-top:20px; margin-bottom:20px; border-top:1px solid ${darkMode ? 'white' : 'lightgrey'};"></div>
            <label style="display:block; margin-bottom:10px;"><input id="enableCustomTooltips" type="checkbox" ${settings.enableCustomTooltips ? 'checked' : ''} /> Show Full Titles on Hover</label>
            <label style="display:block; margin-bottom:10px;"><input id="enableAdBlocker" type="checkbox" ${settings.enableAdBlocker ? 'checked' : ''} /> Hide "Paid Promotion" Overlay (on Thumbnails)</label>
            <label style="display:block; margin-bottom:20px;"><input id="enableInterestIcons" type="checkbox" ${settings.enableInterestIcons ? 'checked' : ''} /> Not Interested/ Don't Recommend on Hover</label>
            <div style="margin-top:20px; margin-bottom:20px; border-top:1px solid ${darkMode ? 'white' : 'lightgrey'};"></div>
            <label style="display:block; margin-bottom:10px;"><input id="enableRefreshButton" type="checkbox" ${settings.enableRefreshButton ? 'checked' : ''} /> Feed Refresh Button</label>
            <label style="display:block; margin-bottom:20px;"><input id="hideUIButton" type="checkbox" ${settings.hideUIButton ? 'checked' : ''} /> Hide Script Button (Alt+Shift+U to Show)</label>
            <button id="saveSettingsBtn" style="padding:8px 14px;background:#065fd4;color:white;border:none;border-radius:8px;font-size:15px;font-weight:500;cursor:pointer;">Save Changes</button>
            <button id="resetSettingsBtn" style="margin-left:10px;padding:8px 14px;background:none;color:${darkMode ? 'white' : 'black'};border:1px solid lightgrey;border-radius:8px;font-size:15px;font-weight:500;cursor:pointer;">Reset to Default</button>
        `;
        overlay.appendChild(menu);
        document.body.appendChild(overlay);
        document.getElementById('closeOverlay').onclick = () => overlay.onclick();
        document.getElementById('saveSettingsBtn').onclick = () => {
            const needsReload = ['enableShowMoreFix', 'enableCustomTooltips', 'enableAdBlocker', 'enableInterestIcons', 'enableRefreshButton'].some(key => settings[key] !== document.getElementById(key).checked);
            saveSetting('videosPerRow', parseInt(document.getElementById('videosPerRow').value, 10));
            saveSetting('shortsPerRow', parseInt(document.getElementById('shortsPerRow').value, 10));
            ['disableShorts', 'enableShowMoreFix', 'hideUIButton', 'enableCustomTooltips', 'enableAdBlocker', 'enableInterestIcons', 'enableRefreshButton'].forEach(key => saveSetting(key, document.getElementById(key).checked));
            if (settingsButton) settingsButton.style.display = settings.hideUIButton ? 'none' : 'flex';
            if (needsReload) location.reload(); else overlay.onclick();
        };
        document.getElementById('resetSettingsBtn').onclick = () => { resetSettings(); location.reload(); };
    }

    function checkPageAndToggleUiButtons() {
        const isFeed = ['/', '/feed/subscriptions'].includes(window.location.pathname);
        if (settingsButton) settingsButton.style.display = (isFeed && !settings.hideUIButton) ? 'flex' : 'none';
        if (refreshButton) refreshButton.style.display = isFeed ? 'flex' : 'none';
    }

    // --- MAIN CONTROL LOGIC ---
    function handlePageChange() {
        featureObservers.forEach(obs => obs.disconnect());
        featureObservers = [];
        if (customStyle) {
            customStyle.remove();
            customStyle = null;
        }
        if (window.location.pathname !== '/feed/you') {
            applyCustomizations();
            if (settings.enableShowMoreFix) enableShowMoreFix();
            if (settings.enableCustomTooltips) initTitleHoverFeature();
            if (settings.enableAdBlocker) initAdBlocker();
            if (settings.enableInterestIcons) {
                initInterestIcons();
                scanAndApplyInterestIcons();
            }
        }
        checkPageAndToggleUiButtons();
    }

    function initializeScript() {
        if (document.getElementById('yt-ui-enhancer-settings-button')) return;
        createUiButtons();
        handlePageChange();
        window.addEventListener('yt-navigate-finish', handlePageChange);
    }

    const initObserver = new MutationObserver((_, obs) => {
        if (document.querySelector('ytd-app')) {
            initializeScript();
            obs.disconnect();
        }
    });
    initObserver.observe(document.documentElement, { childList: true, subtree: true });
})();