Skip to content

Instantly share code, notes, and snippets.

@IceBotYT
Created September 7, 2025 04:39
Show Gist options
  • Select an option

  • Save IceBotYT/6bbda6f52f5d18f11450ab6764326e92 to your computer and use it in GitHub Desktop.

Select an option

Save IceBotYT/6bbda6f52f5d18f11450ab6764326e92 to your computer and use it in GitHub Desktop.
Hides all auto-dubbed YouTube videos from recommended feeds
// ==UserScript==
// @name YouTube: Hide "Auto-dubbed" video cards
// @namespace https://github.com/IceBotYT
// @version 1.5
// @description Hides auto-dubbed YouTube videos
// @author IceBotYT
// @match https://www.youtube.com/*
// @match https://youtube.com/*
// @match https://youtu.be/*
// @run-at document-end
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
// --- DEBUG CONFIG ---
const DEBUG = false;
const PREFIX = '[AutoDub]';
function ts() { return (new Date()).toISOString(); }
function info(...args) { if (DEBUG) console.info(PREFIX, ts(), ...args); }
function debug(...args) { if (DEBUG) console.debug(PREFIX, ts(), ...args); }
function warn(...args) { if (DEBUG) console.warn(PREFIX, ts(), ...args); }
function error(...args) { if (DEBUG) console.error(PREFIX, ts(), ...args); }
// Toggle behavior:
// If MAKE_RED is true => mark matching cards with a red overlay (non-destructive)
// If MAKE_RED is false => hide matching cards entirely (display: none)
// Change this constant to switch modes.
const MAKE_RED = false;
// Expose basic stats for inspection in the console
window.AutoDubDebug = window.AutoDubDebug || {
scans: 0,
badgesSeen: 0,
badgesMatched: 0,
cardsMarked: 0,
cardsHidden: 0,
cardsRed: 0,
lastScanAt: null
};
const BADGE_SELECTOR = '.yt-content-metadata-view-model__badge';
const BADGE_PROCESSED_ATTR = 'data-autodub-badge-checked';
const CARD_MARKED_ATTR = 'data-autodub-card';
const STYLE_ID = 'autodub-card-styles-v1';
const OVERLAY_CLASS = 'autodub-card-overlay-v1';
const REL_CLASS = 'autodub-card-relative-v1';
const HIDE_CLASS = 'autodub-card-hidden-v1';
const CARD_SELECTORS = [
'.ytd-item-section-renderer',
'ytd-video-renderer',
'ytd-rich-item-renderer',
'ytd-grid-video-renderer',
'ytd-compact-video-renderer',
'ytd-rich-grid-media',
'ytd-playlist-video-renderer',
'ytd-reel-video-renderer',
'ytd-compact-movie-renderer',
'ytd-channel-video-renderer',
'[role="listitem"]'
];
function ensureStyles() {
if (document.getElementById(STYLE_ID)) return;
const css = `
.${REL_CLASS} { position: relative !important; }
.${OVERLAY_CLASS} {
position: absolute;
inset: 0;
background: rgba(255, 0, 0, 0.85);
pointer-events: none;
z-index: 11;
border-radius: 6px;
}
.${HIDE_CLASS} { display: none !important; }
`;
const s = document.createElement('style');
s.id = STYLE_ID;
s.textContent = css;
document.head.appendChild(s);
debug('Inserted stylesheet', STYLE_ID);
}
function findCardAncestor(el) {
if (!el) return null;
for (const sel of CARD_SELECTORS) {
const anc = el.closest(sel);
if (anc) {
debug('findCardAncestor: matched selector', sel, '->', describeNode(anc));
return anc;
}
}
const maybe = el.closest('a#thumbnail, a[href*="/watch"], ytd-thumbnail, ytd-rich-item-renderer, ytd-video-renderer');
if (maybe) debug('findCardAncestor: fallback matched', describeNode(maybe));
else debug('findCardAncestor: no ancestor found for', describeNode(el));
return maybe;
}
function describeNode(node) {
if (!node) return 'null';
try {
const tag = node.tagName || node.nodeName;
const id = node.id ? `#${node.id}` : '';
const classes = node.classList && node.classList.length ? `.${Array.from(node.classList).slice(0,5).join('.')}` : '';
const short = `${tag}${id}${classes}`;
return short;
} catch (e) {
return String(node);
}
}
function markOrHideCard(card) {
if (!card) {
warn('markOrHideCard called with falsy card');
return false;
}
if (card.hasAttribute && card.hasAttribute(CARD_MARKED_ATTR)) {
debug('markOrHideCard: card already processed, skipping', describeNode(card));
return false;
}
try {
card.setAttribute(CARD_MARKED_ATTR, MAKE_RED ? 'red' : 'hidden');
ensureStyles();
if (MAKE_RED) {
// RED MODE: add relative class and insert overlay (non-destructive)
card.classList.add(REL_CLASS);
// don't insert multiple overlays
if (card.querySelector && card.querySelector(`.${OVERLAY_CLASS}`)) {
debug('markOrHideCard: overlay already present on', describeNode(card));
window.AutoDubDebug.cardsMarked += 1;
window.AutoDubDebug.cardsRed += 1;
return true;
}
const overlay = document.createElement('div');
overlay.className = OVERLAY_CLASS;
overlay.setAttribute('aria-hidden', 'true');
overlay.dataset.autodubOverlay = '1';
card.insertBefore(overlay, card.firstChild);
// slight dim underlying images/videos to make overlay clear (non-destructive)
try {
const thumbs = card.querySelectorAll('img, video, yt-img-shadow');
for (const t of thumbs) {
t.style.filter = (t.style.filter ? t.style.filter + ' ' : '') + 'brightness(0.65)';
}
} catch (e) {
// ignore
}
window.AutoDubDebug.cardsMarked += 1;
window.AutoDubDebug.cardsRed += 1;
info('Marked card red:', describeNode(card));
return true;
} else {
// HIDE MODE: add hide class to collapse the card from the layout
card.classList.add(HIDE_CLASS);
// Also set aria-hidden and inert-like safety
try {
card.setAttribute('aria-hidden', 'true');
// try to make non-focusable
const anchors = card.querySelectorAll('a, button, input, [tabindex]');
for (const a of anchors) {
try {
if (a.hasAttribute('tabindex')) a.dataset._autodub_old_tabindex = a.getAttribute('tabindex');
a.setAttribute('tabindex', '-1');
} catch (e) { /* ignore */ }
}
} catch (e) { /* ignore */ }
window.AutoDubDebug.cardsMarked += 1;
window.AutoDubDebug.cardsHidden += 1;
info('Hidden card:', describeNode(card));
return true;
}
} catch (err) {
error('markOrHideCard error', err);
return false;
}
}
function processBadge(badgeEl, index) {
if (!badgeEl) return;
if (badgeEl.hasAttribute && badgeEl.hasAttribute(BADGE_PROCESSED_ATTR)) {
debug(`processBadge[${index}]: already processed`, describeNode(badgeEl));
return;
}
badgeEl.setAttribute(BADGE_PROCESSED_ATTR, '1');
window.AutoDubDebug.badgesSeen += 1;
const text = (badgeEl.innerText || badgeEl.textContent || '').trim();
debug(`processBadge[${index}]: text="${text}"`, describeNode(badgeEl));
if (!text) return;
if (text.includes("Auto-dubbed")) {
window.AutoDubDebug.badgesMatched += 1;
info(`processBadge[${index}]: MATCH -> "${text}"`);
const card = findCardAncestor(badgeEl);
if (card) {
const ok = markOrHideCard(card);
if (!ok) warn('processBadge: failed to process card', describeNode(card));
} else {
warn('processBadge: matched badge but no card ancestor found for', describeNode(badgeEl));
}
} else {
debug(`processBadge[${index}]: not an auto-dubbed badge`);
}
}
function scanBadges() {
try {
window.AutoDubDebug.scans += 1;
window.AutoDubDebug.lastScanAt = new Date().toISOString();
const badges = document.querySelectorAll(BADGE_SELECTOR);
debug('scanBadges: found badge elements count=', badges.length);
let i = 0;
for (const b of badges) {
processBadge(b, i++);
}
info(`scanBadges: badgesSeen=${window.AutoDubDebug.badgesSeen} badgesMatched=${window.AutoDubDebug.badgesMatched} cardsMarked=${window.AutoDubDebug.cardsMarked} cardsHidden=${window.AutoDubDebug.cardsHidden} cardsRed=${window.AutoDubDebug.cardsRed}`);
} catch (err) {
error('scanBadges error', err);
}
}
let scanTimeout = null;
function scheduleScan(delay = 150) {
if (scanTimeout) return;
debug('scheduleScan: scheduling in', delay, 'ms');
scanTimeout = setTimeout(() => {
scanTimeout = null;
scanBadges();
}, delay);
}
const observer = new MutationObserver((mutations) => {
try {
debug('observer: mutation batch', mutations.length);
let added = false;
for (const m of mutations) {
if (m.addedNodes && m.addedNodes.length) { added = true; break; }
if (m.type === 'characterData' || m.type === 'attributes') { added = true; break; }
}
if (added) scheduleScan(100);
} catch (err) {
error('observer callback error', err);
}
});
let pollHandle = null;
function startPolling() {
if (pollHandle) return;
pollHandle = setInterval(() => {
debug('polling: running scanBadges');
scanBadges();
}, 3000);
debug('startPolling: started with interval 3000ms');
}
function init() {
info('Initializing AutoDub debug script (MAKE_RED=' + MAKE_RED + ')');
setTimeout(scanBadges, 300);
try {
observer.observe(document.body, { childList: true, subtree: true, characterData: true, attributes: true });
debug('observer attached to document.body');
} catch (e) {
try {
observer.observe(document.documentElement, { childList: true, subtree: true, characterData: true, attributes: true });
debug('observer attached to document.documentElement');
} catch (err) {
error('Failed to attach observer', err);
}
}
// detect SPA navigation
const origPush = history.pushState;
history.pushState = function () {
const res = origPush.apply(this, arguments);
debug('history.pushState called -> scheduling scan');
scheduleScan(300);
return res;
};
window.addEventListener('yt-navigate-finish', () => {
debug('yt-navigate-finish event -> scheduling scan');
scheduleScan(200);
}, { passive: true });
startPolling();
}
// Start after short delay so initial DOM can settle
setTimeout(init, 500);
// Helpful note printed once
info('AutoDub debug script loaded. To inspect runtime stats: window.AutoDubDebug. Filter console for', PREFIX);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment