Skip to content

Instantly share code, notes, and snippets.

@entrptaher
Created September 21, 2025 05:54
Show Gist options
  • Select an option

  • Save entrptaher/51afae3cb6005520a652e3d4378f5e57 to your computer and use it in GitHub Desktop.

Select an option

Save entrptaher/51afae3cb6005520a652e3d4378f5e57 to your computer and use it in GitHub Desktop.
// ==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