您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Remove irrelevant/extraneous items from YouTube search results with a toggleable menu.
// ==UserScript== // @name Decluttered YouTube Search // @namespace http://github.com/dv-001 // @version 0.1.0 // @description Remove irrelevant/extraneous items from YouTube search results with a toggleable menu. // @author dv-001 // @match https://www.youtube.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @run-at document-idle // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @supportURL https://github.com/dv-001/userscripts/issues // @license MIT // ==/UserScript== (function () { 'use strict'; /* --- Configuration & State --- */ // Easy-to-update selectors for various elements (since YouTube changes them often) const SELECTORS = { // 'People also search for', 'For you', etc. shelves: [ 'ytd-shelf-renderer', 'ytd-horizontal-card-list-renderer[card-list-style=HORIZONTAL_CARD_LIST_STYLE_TYPE_NARROW_SHELF]' ], // Rows of shorts shortsGrid: 'grid-shelf-view-model', // Individual shorts shorts: 'ytd-video-renderer:has(ytd-thumbnail-overlay-time-status-renderer[overlay-style="SHORTS"])', // Watched videos watched: 'ytd-thumbnail-overlay-resume-playback-renderer #progress', // YouTube search bar searchbox: 'yt-searchbox' }; const CONFIG = { hideShelves: GM_getValue('hideShelves', true), hideShortsGrid: GM_getValue('hideShortsGrid', true), hideShorts: GM_getValue('hideShorts', false), hideWatchedVideos: GM_getValue('hideWatchedVideos', false), watchedPercentage: GM_getValue('watchedPercentage', 90) }; function updateConfig(key, value) { CONFIG[key] = value; GM_setValue(key, value); updateStyles(CONFIG); } /* --- Main Logic --- */ // Our `<style>` element that will be injected into the page head const styleElement = document.createElement('style'); styleElement.id = 'dyts-style'; document.head.appendChild(styleElement); // Function to generate CSS rules based on current settings function updateStyles(config) { let css = ''; if (config.hideShelves) { css += `${SELECTORS.shelves.join(', ')} { display: none !important; }\n`; } if (config.hideShortsGrid) { css += `${SELECTORS.shortsGrid} { display: none !important; }\n`; } if (config.hideShorts) { css += `${SELECTORS.shorts} { display: none !important; }\n`; } if (config.hideWatchedVideos) { let percentage = config.watchedPercentage; if (percentage && (percentage >= 1 && percentage <= 100)) { for (let i = percentage; i <= 100; i++) { css += `ytd-video-renderer:has(${SELECTORS.watched}[style*='width: ${i}%;']) ` + `{ display: none !important; }\n`; } } } styleElement.textContent = css; } function createSettingsUI() { if (document.getElementById('dyts-settings-container')) { return; } const container = document.createElement('div'); container.id = 'dyts-settings-container'; container.style.position = 'relative'; // Button to open settings UI const gearButton = document.createElement('button'); gearButton.id = 'dyts-settings-button'; // Gear icon selector (so we don't have to store the path data ourselves) const gearSelector = 'iron-iconset-svg #settings path'; const gearSvgPath = document.querySelector(gearSelector).getAttribute('d'); const svgNS = 'http://www.w3.org/2000/svg'; const svg = document.createElementNS(svgNS, 'svg'); svg.setAttribute('viewBox', '0 0 24 24'); svg.setAttribute('preserveAspectRatio', 'xMidYMid meet'); svg.setAttribute('focusable', 'false'); svg.style.pointerEvents = 'none'; svg.style.display = 'block'; svg.style.width = '100%'; svg.style.height = '100%'; const path = document.createElementNS(svgNS, 'path'); path.setAttribute('clip-rule', 'evenodd'); path.setAttribute('fill-rule', 'evenodd'); path.setAttribute('d', gearSvgPath); svg.appendChild(path); gearButton.appendChild(svg); // Settings panel const panel = document.createElement('div'); panel.id = 'dyts-settings-panel'; // FIX: Explicitly set initial display state panel.style.display = 'none'; const optionLabels = { hideShelves: 'Hide shelves (For You, People Also Watched, etc.)', hideShortsGrid: 'Hide Shorts grids', hideShorts: 'Hide individual Shorts', hideWatchedVideos: 'Hide videos more than' }; for (const key in CONFIG) { if (typeof CONFIG[key] != 'boolean') { continue; } const row = document.createElement('div'); row.className = 'dyts-setting-row'; if (CONFIG[key]) { row.classList.add('dyts-setting-enabled'); } else { row.classList.add('dyts-setting-disabled'); } const label = document.createElement('label'); const checkbox = document.createElement('input'); checkbox.name = `dyts-checkbox-${key}` checkbox.type = 'checkbox'; checkbox.checked = CONFIG[key]; checkbox.dataset.key = key; checkbox.style.marginRight = '8px'; checkbox.addEventListener('change', (e) => { updateConfig(e.target.dataset.key, e.target.checked); updateStyles(CONFIG); }); label.appendChild(checkbox); label.append(optionLabels[key] || key); // Special logic for watched video percentage input if (key === 'hideWatchedVideos') { const percentageInput = document.createElement('input'); percentageInput.disabled = !CONFIG.hideWatchedVideos; percentageInput.name = 'dyts-input-watched-percentage'; percentageInput.type = 'number'; percentageInput.min = '1'; percentageInput.max = '100'; percentageInput.value = CONFIG.watchedPercentage.toString(); percentageInput.addEventListener('input', (e) => { const value = parseInt(e.target.value, 10); if (!isNaN(value) && value >= 1 && value <= 100) { updateConfig('watchedPercentage', value); } }); const afterPercentageText = document.createElement('span'); afterPercentageText.textContent = 'percent watched'; label.appendChild(percentageInput); label.appendChild(afterPercentageText); // Update the input's disabled state when checkbox changes checkbox.addEventListener('change', (e) => { percentageInput.disabled = !e.target.checked; }); } row.appendChild(label); panel.appendChild(row); } let panelVisible = false; gearButton.addEventListener('click', (e) => { e.stopPropagation(); panelVisible = !panelVisible; panel.style.display = panelVisible ? 'flex' : 'none'; }); // Don't hide when clicking inside the panel panel.addEventListener('click', (e) => e.stopPropagation()); document.addEventListener('click', () => { // Hide when clicking anywhere else panelVisible = false; panel.style.display = 'none'; }); // Insert the settings button next to the search bar container.appendChild(gearButton); container.appendChild(panel); const searchbox = document.querySelector(SELECTORS.searchbox); if (searchbox) { searchbox.appendChild(container); } } function removeSettingsUI() { const ui = document.getElementById('dyts-settings-container'); if (ui) { ui.remove(); } } // This function checks the URL and decides whether to add the gear/UI. // The stylesheet is always active, regardless of the page. function onPageChange() { const isSearchPage = window.location.pathname === '/results'; if (isSearchPage) { // Use a small delay or an interval to wait for the search box to be ready const interval = setInterval(() => { if (document.querySelector(SELECTORS.searchbox)) { clearInterval(interval); createSettingsUI(); } }, 100); } else { removeSettingsUI(); } } /* --- Styling --- */ function addGlobalStyles() { GM_addStyle(` #dyts-settings-button { background: none; border: none; cursor: pointer; width: 40px; height: 40px; padding: 8px; margin-left: 8px; border-radius: 50%; fill: var(--yt-spec-icon-inactive); } #dyts-settings-button:hover { background-color: var(--yt-spec-badge-chip-background); } #dyts-settings-panel { display: none; width: max-content; position: absolute; top: 5rem; right: 0; z-index: 9999; flex-direction: column; flex-wrap: nowrap; row-gap: 1rem; background-color: var(--yt-spec-static-overlay-additive-background); border: 2px solid var(--yt-spec-grey-4); border-radius: 1.5rem; padding: 1rem; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.6); backdrop-filter: blur(4rem); } #dyts-settings-panel h3 { margin: 0 0 10px 0; font-size: 1.75rem; color: var(--yt-spec-text-primary); } .dyts-setting-row label { display: flex; align-items: center; cursor: pointer; // padding: 6px 0; font-size: 1.6rem; color: var(--yt-spec-text-primary); } .dyts-setting-row .dyts-setting-enabled label { display: inherit; } dyts-setting-row .dyts-setting-disabled label { color: #f1f1f1b8 } .dyts-setting-row label > span:first-of-type { flex-grow: 1; /* Allow the label text to push the input to the right */ } .dyts-setting-row input[type="checkbox"] { margin-right: 12px; width: 18px; height: 18px; accent-color: var(--yt-spec-text-primary); } .dyts-setting-row input[type="number"] { width: 2.25rem; margin-left: 0.5rem; margin-right: 0.5rem; background-color: var(--yt-spec-badge-chip-background); border: 1px solid var(--yt-spec-10-percent-layer); color: var(--yt-spec-text-primary); border-radius: 0.5rem; padding: 0.25rem; text-align: center; } `); } /* --- Invoke --- */ // Run on script load updateStyles(CONFIG); onPageChange(); addGlobalStyles(); window.addEventListener('yt-navigate-finish', onPageChange); })();