DeTrend – Hide "CHECK THE SOUND" bait in YT Shorts

This is for people who hate these stupid trends

// ==UserScript==
// @name         DeTrend – Hide "CHECK THE SOUND" bait in YT Shorts
// @namespace    https://winverse.detrend
// @version      1.0.0
// @description  This is for people who hate these stupid trends
// @author       Winverse
// @match        *://*.youtube.com/shorts/*
// @run-at       document-idle
// @icon         https://i.ibb.co/VYG22nmP/Projekt-bez-nazwy.png
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  // --- Config ---
  const TARGET_CLASSES = [
    'yt-core-attributed-string',
    'yt-core-attributed-string--white-space-pre-wrap',
    'yt-core-attributed-string--link-inherit-color'
  ];

  // If you prefer to hide instead of remove, set to true.
  const HIDE_INSTEAD_OF_REMOVE = true;

  // --- Fuzzy matcher for "CHECK THE SOUND" with optional "DON'T"/"DO NOT" and symbol junk ---
  // Allows symbols/spaces between letters to catch bypasses like: D[o}! N{{O}}T C#H(E)C[K] T--H[E] S*O^U!N?D
  const esc = s => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const fuzzyWord = (w) => w.split('').map(ch => esc(ch) + '[\\W_]*').join('');
  const OPT_DONT  = `(?:${fuzzyWord("don't")}|${fuzzyWord("dont")}|${fuzzyWord("do not")})[\\W_]*`;
  const CHECK     = fuzzyWord('check') + '[\\W_]*';
  const THE       = fuzzyWord('the')   + '[\\W_]*';
  const SOUND     = fuzzyWord('sound');

  // Build a regex that matches with or without the "DON'T/DO NOT" prefix, anywhere in the text.
  const baitRegex = new RegExp(
    `(?:${OPT_DONT})?${CHECK}${THE}${SOUND}`,
    'i'
  );

  // Old quick filter
  // const cheapContains = (t) => /check[\W_]*the[\W_]*sound/i.test(t);

  // New quick filter — allows any characters (including emoji) in between
  const cheapContains = (t) => /check[\s\S]*the[\s\S]*sound/i.test(t);

  function looksLikeShortsPage() {
    // Only act on Shorts contexts (URL contains /shorts/) or when Shorts are embedded in feeds
    // We still scan anywhere on YT because Shorts cards can appear on home/search.
    return location.pathname.includes('/shorts/') || true;
  }

  function isTargetTitleElement(el) {
    if (!(el instanceof HTMLElement)) return false;
    // Require at least the base class, others are commonly present on Shorts titles
    if (!el.classList.contains('yt-core-attributed-string')) return false;

    // If all three classes exist, even more certain
    const hasAll = TARGET_CLASSES.every(c => el.classList.contains(c));
    return hasAll || el.closest('ytd-reel-video-renderer,ytd-reel-item-renderer,ytm-reel-video-renderer,ytm-reel-item-renderer,ytm-shorts-lockup-view-model') !== null;
  }

  function nuke(el) {
    if (!el || !el.parentElement) return;
    if (HIDE_INSTEAD_OF_REMOVE) {
      el.style.display = 'none';
      el.style.visibility = 'hidden';
      el.setAttribute('data-detrend-hidden', '1');
    } else {
      el.remove();
    }
  }

  let removedCount = 0;
  function scan(root = document) {
    if (!looksLikeShortsPage()) return;

    // Candidate text containers
    const nodes = root.querySelectorAll('.yt-core-attributed-string, yt-formatted-string, span, h1, h2, h3');

    for (const el of nodes) {
      if (!isTargetTitleElement(el)) continue;

      const t = (el.textContent || '').trim();
      if (!t) continue;
      if (!cheapContains(t)) continue;

      if (baitRegex.test(t)) {
        nuke(el);
        removedCount++;
      }
    }
  }

  // Debounced scanner for mutations
  let pending = false;
  const scheduleScan = () => {
    if (pending) return;
    pending = true;
    requestAnimationFrame(() => {
      try { scan(); } finally { pending = false; }
    });
  };

  // Initial scan
  scan();

  // MutationObserver to catch dynamically loaded Shorts/cards
  const mo = new MutationObserver((mutations) => {
    for (const m of mutations) {
      if (m.addedNodes) {
        for (const n of m.addedNodes) {
          if (n.nodeType === 1) { // ELEMENT_NODE
            scheduleScan();
          }
        }
      }
      if (m.type === 'characterData') scheduleScan();
    }
  });

  mo.observe(document.documentElement || document.body, {
    childList: true,
    subtree: true,
    characterData: true
  });

  // Also rescan on key YT navigation events (SPA)
  window.addEventListener('yt-navigate-finish', scheduleScan, true);
  window.addEventListener('yt-page-data-updated', scheduleScan, true);
  window.addEventListener('yt-action', scheduleScan, true);

  // A tiny console status so you can verify it works
  const logOnce = () => {
    console.debug('[DeTrend] active. Removed so far:', removedCount);
  };
  setTimeout(logOnce, 1500);
  setInterval(() => {
    console.debug('[DeTrend] removed:', removedCount, ' current URL:', location.href);
  }, 15000);
})();