Created
September 21, 2025 05:54
-
-
Save entrptaher/51afae3cb6005520a652e3d4378f5e57 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Block by URL OR by text (shadow DOM safe, no overlay) | |
| // @namespace taher-block-url-or-text | |
| // @description If URL has a keyword: blank the page. Else: hide elements whose text matches (scans nested shadow DOMs). No overlay. | |
| // @version 1.0 | |
| // @run-at document-start | |
| // @match *://*/* | |
| // @grant none | |
| // ==/UserScript== | |
| (function () { | |
| 'use strict'; | |
| /*** CONFIG ***/ | |
| // Keywords (case-insensitive) used for BOTH url and text checks | |
| const KEYWORDS = [ | |
| 'nerd', | |
| 'popular', | |
| 'video', | |
| // dress-up / makeover | |
| 'fashion', | |
| 'dressup', | |
| 'makeover', | |
| 'salon', | |
| 'party', | |
| 'bacha', | |
| 'ellie', | |
| 'barbie', | |
| 'beauty', | |
| 'spa', | |
| // gambling | |
| 'poker', | |
| 'casino', | |
| 'blackjack', | |
| 'roulette', | |
| 'slots', | |
| 'bingo', | |
| 'jackpot', | |
| 'bet', | |
| 'betting', | |
| 'wager', | |
| 'gamble', | |
| 'lottery', | |
| // adult / explicit | |
| 'porn', | |
| 'porno', | |
| 'xxx', | |
| 'sex', | |
| 'nude', | |
| 'adult', | |
| 'erotic', | |
| 'camgirl', | |
| 'camsex', | |
| 'onlyfans', | |
| 'escort', | |
| // violent / mature (optional) | |
| 'sniper', | |
| 'shooter', | |
| 'gun', | |
| 'bloody', | |
| 'murder', | |
| 'kill', | |
| 'gore' | |
| ]; | |
| // Scope to sites (empty = all). For MSN only: | |
| const SITES = [/\.msn\.com$/i]; | |
| // When matching by TEXT (not URL), hide the closest ancestor matching one of these: | |
| const CONTAINER_SELECTORS = [ | |
| 'game-card', | |
| 'game-wrapper', | |
| 'game-layout-v2', | |
| 'game-card-carousel', | |
| 'a[href]', | |
| '[role="link"]', | |
| 'article', | |
| 'li', | |
| '[data-card]', | |
| '[data-tile]', | |
| '.card', | |
| '.tile', | |
| '.item', | |
| '.module', | |
| 'div' // fallback | |
| ]; | |
| // Try to read text inside closed shadow roots by forcing them open | |
| const COERCE_CLOSED_SHADOW_TO_OPEN = true; | |
| // Limit scanning to common text nodes for performance | |
| const TEXT_TAGS = 'h1,h2,h3,h4,h5,h6,a,button,span,div,p,li'; | |
| /*** EARLY EXIT ***/ | |
| if (SITES.length && !SITES.some(r => r.test(location.hostname))) return; | |
| /*** MATCHERS ***/ | |
| const KW_RE = new RegExp(KEYWORDS.map(esc).join('|'), 'i'); | |
| function esc(s){ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } | |
| const urlMatches = () => KW_RE.test(location.href); | |
| /*** HARD PAGE BLANKING (for URL matches) ***/ | |
| function setRootHidden(hidden) { | |
| const val = hidden ? 'none' : ''; | |
| document.documentElement.style.setProperty('display', val, 'important'); | |
| document.documentElement.style.setProperty('visibility', hidden ? 'hidden' : '', 'important'); | |
| if (document.body) { | |
| document.body.style.setProperty('display', val, 'important'); | |
| document.body.style.setProperty('visibility', hidden ? 'hidden' : '', 'important'); | |
| } | |
| } | |
| let rootGuardMO; | |
| function applyUrlBlock() { | |
| try { window.stop?.(); } catch {} | |
| setRootHidden(true); | |
| if (!rootGuardMO) { | |
| rootGuardMO = new MutationObserver(() => setRootHidden(true)); | |
| rootGuardMO.observe(document.documentElement, { attributes: true, attributeFilter: ['style', 'class'] }); | |
| rootGuardMO.observe(document, { childList: true, subtree: true }); | |
| } | |
| } | |
| function clearUrlBlock() { | |
| if (rootGuardMO) { rootGuardMO.disconnect(); rootGuardMO = null; } | |
| setRootHidden(false); | |
| } | |
| /*** TEXT-BASED HIDING (deep into shadow DOMs) ***/ | |
| function getVisibleText(el){ | |
| try { return (typeof el.innerText === 'string' ? el.innerText : el.textContent) || ''; } | |
| catch { return ''; } | |
| } | |
| function isTextMatch(el){ | |
| if (!(el instanceof Element)) return false; | |
| if (KW_RE.test(getVisibleText(el))) return true; | |
| const a = el.getAttribute?.('title') || el.getAttribute?.('aria-label') || el.getAttribute?.('alt') || ''; | |
| return a && KW_RE.test(a); | |
| } | |
| function findContainer(el){ | |
| for (const sel of CONTAINER_SELECTORS) { | |
| const c = el.closest(sel); | |
| if (c) return c; | |
| } | |
| return el; | |
| } | |
| function hideContainer(el){ | |
| try { | |
| el.style.setProperty('display','none','important'); | |
| el.style.setProperty('visibility','hidden','important'); | |
| } catch {} | |
| } | |
| function checkElement(el){ | |
| if (!(el instanceof Element)) return; | |
| if (isTextMatch(el)) { hideContainer(findContainer(el)); return; } | |
| } | |
| function sweep(root){ | |
| try { | |
| root.querySelectorAll?.(TEXT_TAGS).forEach(checkElement); | |
| root.querySelectorAll?.('*').forEach(n => { if (n.shadowRoot) observeRoot(n.shadowRoot); }); | |
| } catch {} | |
| } | |
| function observeRoot(root){ | |
| if (!root || root.__kwObserved) return; | |
| root.__kwObserved = true; | |
| sweep(root); | |
| const mo = new MutationObserver(muts=>{ | |
| for (const m of muts){ | |
| if (m.type === 'childList'){ | |
| m.addedNodes.forEach(node=>{ | |
| if (!(node instanceof Element)) return; | |
| checkElement(node); | |
| node.querySelectorAll?.(TEXT_TAGS).forEach(checkElement); | |
| if (node.shadowRoot) observeRoot(node.shadowRoot); | |
| }); | |
| } else if (m.type === 'characterData'){ | |
| const host = m.target.parentElement || m.target.getRootNode()?.host || null; | |
| if (host) { checkElement(host); const near = host.closest?.(TEXT_TAGS) || host; checkElement(near); } | |
| } else if (m.type === 'attributes'){ | |
| if (m.target instanceof Element) checkElement(m.target); | |
| } | |
| } | |
| }); | |
| mo.observe(root, { | |
| childList:true, subtree:true, | |
| characterData:true, | |
| attributes:true, | |
| attributeFilter:['title','aria-label','alt'] | |
| }); | |
| } | |
| /*** SHADOW HOOKS ***/ | |
| (function hookAttachShadow(){ | |
| const orig = Element.prototype.attachShadow; | |
| if (!orig) return; | |
| Element.prototype.attachShadow = function(init){ | |
| let cfg = init || { mode: 'open' }; | |
| if (COERCE_CLOSED_SHADOW_TO_OPEN) { try { cfg = Object.assign({}, cfg, { mode:'open' }); } catch {} } | |
| const sr = orig.call(this, cfg); | |
| observeRoot(sr); | |
| return sr; | |
| }; | |
| })(); | |
| (function hookCreateElement(){ | |
| const orig = Document.prototype.createElement; | |
| Document.prototype.createElement = function(name, options){ | |
| const el = orig.call(this, name, options); | |
| queueMicrotask(()=>{ if (el.isConnected) checkElement(el); }); | |
| return el; | |
| }; | |
| })(); | |
| /*** FLOW ***/ | |
| function applyAccordingToURL(){ | |
| if (urlMatches()) { | |
| applyUrlBlock(); // hard blank if URL has a keyword | |
| } else { | |
| clearUrlBlock(); // unblank if navigating away | |
| observeRoot(document); // ensure text-mode hiding is active | |
| } | |
| } | |
| // run now | |
| applyAccordingToURL(); | |
| // SPA hooks | |
| (function hookHistory(){ | |
| const fire = () => setTimeout(applyAccordingToURL, 0); | |
| const _ps = history.pushState, _rs = history.replaceState; | |
| if (_ps) history.pushState = function(){ const r = _ps.apply(this, arguments); fire(); return r; }; | |
| if (_rs) history.replaceState = function(){ const r = _rs.apply(this, arguments); fire(); return r; }; | |
| window.addEventListener('popstate', fire); | |
| window.addEventListener('hashchange', fire); | |
| })(); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment