您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Removes sections from Twitch.tv homepage based on user-defined strings in their rendered text. Gemini 2.5 Pro wrote the whole thing. Oneshot. No modifications. I'm unlikely to fix any issues.
// ==UserScript== // @name Twitch Custom Section Remover // @namespace http://tampermonkey.net/ // @version 1.0 // @description Removes sections from Twitch.tv homepage based on user-defined strings in their rendered text. Gemini 2.5 Pro wrote the whole thing. Oneshot. No modifications. I'm unlikely to fix any issues. // @author SumOfAllN00bs // @license MIT // @match https://www.twitch.tv/* // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_registerMenuCommand // @run-at document-idle // ==/UserScript== (function() { 'use strict'; const SCRIPT_PREFIX = 'TwitchSectionRemover'; // --- Configuration --- // Load blocked strings from storage, with a default example. let blockedStrings = GM_getValue('twitchBlockedStrings', ['Streamer University']); // --- Styling for Hidden Sections --- // This class will be added to sections that should be hidden. GM_addStyle(` .gm-hidden-section { display: none !important; visibility: hidden !important; height: 0 !important; width: 0 !important; overflow: hidden !important; margin: 0 !important; padding: 0 !important; border: none !important; } `); // --- Functions to Manage Blocked Strings (via Tampermonkey Menu) --- function listBlockedStrings() { const currentList = GM_getValue('twitchBlockedStrings', []); console.log(`${SCRIPT_PREFIX}: Currently blocked strings:`, currentList); alert(`${SCRIPT_PREFIX}: Currently blocked strings:\n${currentList.join('\n') || '(No strings blocked)'}`); } function addBlockedString() { const newString = prompt(`${SCRIPT_PREFIX}: Enter a string to block (case-sensitive):`); if (newString && newString.trim() !== '') { let currentBlockedStrings = GM_getValue('twitchBlockedStrings', []); if (!currentBlockedStrings.includes(newString.trim())) { currentBlockedStrings.push(newString.trim()); GM_setValue('twitchBlockedStrings', currentBlockedStrings); blockedStrings = currentBlockedStrings; // Update local cache console.log(`${SCRIPT_PREFIX}: Added '${newString.trim()}'. New list:`, currentBlockedStrings); alert(`${SCRIPT_PREFIX}: Added '${newString.trim()}'.`); checkAndRemoveSections(); // Re-run to apply the new string immediately } else { alert(`${SCRIPT_PREFIX}: String '${newString.trim()}' is already in the list.`); } } } function removeBlockedString() { let currentBlockedStrings = GM_getValue('twitchBlockedStrings', []); if (currentBlockedStrings.length === 0) { alert(`${SCRIPT_PREFIX}: Blocklist is currently empty.`); return; } const stringToRemove = prompt(`${SCRIPT_PREFIX}: Enter the exact string to remove from the blocklist:\n\nCurrently blocked:\n${currentBlockedStrings.join('\n')}`); if (stringToRemove && stringToRemove.trim() !== '') { const index = currentBlockedStrings.indexOf(stringToRemove.trim()); if (index > -1) { const removed = currentBlockedStrings.splice(index, 1); GM_setValue('twitchBlockedStrings', currentBlockedStrings); blockedStrings = currentBlockedStrings; // Update local cache console.log(`${SCRIPT_PREFIX}: Removed '${removed[0]}'. New list:`, currentBlockedStrings); alert(`${SCRIPT_PREFIX}: Removed '${removed[0]}'.`); // Re-evaluate all sections: those that were hidden ONLY by the removed string will now be unhidden. checkAndRemoveSections(); } else { alert(`${SCRIPT_PREFIX}: String '${stringToRemove.trim()}' not found in the list.`); } } } // Register menu commands for Tampermonkey GM_registerMenuCommand("List Blocked Strings", listBlockedStrings); GM_registerMenuCommand("Add String to Blocklist", addBlockedString); GM_registerMenuCommand("Remove String from Blocklist", removeBlockedString); // --- Core Logic to Find and Remove/Hide Elements --- function checkAndRemoveSections() { // Refresh blockedStrings from storage in case it was modified blockedStrings = GM_getValue('twitchBlockedStrings', []); // If no strings are blocked, ensure all sections previously hidden by this script are unhidden. if (blockedStrings.length === 0) { document.querySelectorAll('.gm-hidden-section').forEach(el => { el.classList.remove('gm-hidden-section'); }); return; } // Selector for the "great-grandparent" div. // Based on your input: "The parent of the parent of this div is this element: <div class="Layout-sc-1xcs6mc-0 cwJXDZ">" // This means `div.Layout-sc-1xcs6mc-0.cwJXDZ` is the great-grandparent of the text holder, // and the grandparent of the div-to-be-removed. const greatGrandParentSelector = 'div.Layout-sc-1xcs6mc-0.cwJXDZ'; const greatGrandParents = document.querySelectorAll(greatGrandParentSelector); greatGrandParents.forEach(ggpElement => { // Iterate through children of ggpElement (these are potential GrandParents of the target sections) Array.from(ggpElement.children).forEach(gpElement => { if (gpElement.nodeType !== Node.ELEMENT_NODE) return; // Iterate through children of gpElement (these are the "grand-child" divs, candidates for removal) Array.from(gpElement.children).forEach(gcDiv => { if (gcDiv.nodeType !== Node.ELEMENT_NODE) return; let sectionShouldBeHidden = false; // Check this gcDiv and its descendants for any of the blocked strings. // Query for elements that are likely to contain primary text/titles. const elementsToCheckForText = [gcDiv, ...Array.from(gcDiv.querySelectorAll('h1, h2, h3, h4, h5, h6, p, a, span, div[class*="title"], div[data-a-target*="title"]'))]; for (const textHoldingElement of elementsToCheckForText) { // Ensure the element is visible (not display:none via other means) unless we hid its parent (gcDiv). // If gcDiv itself is hidden, its children won't have an offsetParent. if (textHoldingElement.offsetParent === null && !gcDiv.classList.contains('gm-hidden-section')) { continue; } // Get text from direct child text nodes of the current textHoldingElement. // This adheres to "rendered text of the same element". let currentElementOwnText = ""; for (const childNode of textHoldingElement.childNodes) { if (childNode.nodeType === Node.TEXT_NODE) { currentElementOwnText += childNode.textContent; } } currentElementOwnText = currentElementOwnText.replace(/\s+/g, ' ').trim(); // Normalize whitespace if (currentElementOwnText) { for (const blockedStr of blockedStrings) { if (currentElementOwnText.includes(blockedStr)) { sectionShouldBeHidden = true; // console.log(`${SCRIPT_PREFIX}: Matched "${blockedStr}" in text "${currentElementOwnText}" of element:`, textHoldingElement); break; // Found a match for this textHoldingElement } } } if (sectionShouldBeHidden) break; // No need to check more text elements if one match already decided to hide gcDiv } // Apply or remove the hidden class based on the check if (sectionShouldBeHidden) { if (!gcDiv.classList.contains('gm-hidden-section')) { // console.log(`${SCRIPT_PREFIX}: Hiding section (gcDiv):`, gcDiv); gcDiv.classList.add('gm-hidden-section'); } } else { // If it was previously hidden by this script but no longer matches any current blocked string if (gcDiv.classList.contains('gm-hidden-section')) { // console.log(`${SCRIPT_PREFIX}: Unhiding section (gcDiv) as it no longer matches:`, gcDiv); gcDiv.classList.remove('gm-hidden-section'); } } }); }); }); } // --- Utility: Debounce Function --- // This prevents the checkAndRemoveSections function from running too frequently during rapid DOM changes. function debounce(func, wait) { let timeout; return function executedFunction(...args) { const context = this; const later = () => { timeout = null; func.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // --- Mutation Observer --- // Observes changes in the DOM and triggers the removal logic. const debouncedSectionCheck = debounce(checkAndRemoveSections, 500); // 500ms debounce window const observer = new MutationObserver((mutationsList) => { for (const mutation of mutationsList) { // We are interested in changes to the child list (elements added/removed) // and subtree modifications. if (mutation.type === 'childList' && (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) { debouncedSectionCheck(); return; // Debounced call will handle it } // if (mutation.type === 'subtree' ) { // Too frequent, childList is usually enough // debouncedSectionCheck(); // return; // } } }); // Start observing the entire document for changes. observer.observe(document.documentElement, { childList: true, // Observe direct children additions/removals subtree: true // Observe all descendants as well }); // --- Initial Execution --- // Run the check function a couple of times after the page initially loads, // as content might still be populating. setTimeout(checkAndRemoveSections, 1000); // After 1 second setTimeout(checkAndRemoveSections, 3000); // After 3 seconds for more dynamic content })();