// ==UserScript==
// @name YouTube Content Warning Skipper
// @namespace http://tampermonkey.net/
// @version 1.9
// @description Skips those annoying age warnings on YouTube (This video may be inappropriate for some users. I understand and wish to proceed) and Shorts (This content may be inappropriate for some users. Do you wish to continue? Continue. Skip video.) by automatically clicking 'proceed' or 'continue' for you.
// @author Dormy
// @match *://www.youtube.com/*
// @icon https://i.dawn.com/primary/2014/09/54163b6024e73.jpg
// @license MIT
// @grant none
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
// !!! IMPORTANT: SET TO true TO ENABLE DETAILED LOGS IN BROWSER CONSOLE (F12) !!!
const DEBUG_MODE = false;
function log(message) {
if (DEBUG_MODE) {
console.log('[YT Skipper] ' + message);
}
}
function isVisible(elem) {
if (!(elem instanceof Element)) return false;
const style = getComputedStyle(elem);
if (style.display === 'none' || style.visibility === 'hidden' || parseFloat(style.opacity) < 0.1) {
return false;
}
const rect = elem.getBoundingClientRect();
return rect.width > 1 && rect.height > 1;
}
function findVisibleButtonByText(textToFind, parentEl = document) {
const buttons = parentEl.querySelectorAll(
'button, yt-button-renderer, tp-yt-paper-button, div[role="button"], a.yt-spec-button-shape-next'
);
for (const buttonCandidate of buttons) {
let actualButtonElement = buttonCandidate;
if (buttonCandidate.tagName.toLowerCase() === 'yt-button-renderer' ||
buttonCandidate.tagName.toLowerCase() === 'tp-yt-paper-button') {
const innerButton = buttonCandidate.querySelector('button');
if (innerButton) actualButtonElement = innerButton;
}
let combinedText = '';
const textSpans = actualButtonElement.querySelectorAll(
'yt-formatted-string, .yt-core-attributed-string, .yt-spec-button-shape-next__button-text-content span, .button-renderer-text span'
);
if (textSpans.length > 0) {
textSpans.forEach(span => combinedText += (span.textContent || '').trim() + ' ');
combinedText = combinedText.trim();
} else {
combinedText = (actualButtonElement.textContent || '').trim();
}
const ariaLabel = (buttonCandidate.getAttribute('aria-label') || actualButtonElement.getAttribute('aria-label') || '').trim();
if ((combinedText === textToFind || ariaLabel === textToFind) && isVisible(actualButtonElement)) {
log(`Found button: "${textToFind}" (Visible: true, Element: <${actualButtonElement.tagName.toLowerCase()}>, Text: "${combinedText}", Aria: "${ariaLabel}")`);
return actualButtonElement;
} else if (DEBUG_MODE && (combinedText === textToFind || ariaLabel === textToFind)) {
log(`Button candidate: "${textToFind}" (Visible: false, Element: <${actualButtonElement.tagName.toLowerCase()}>, Text: "${combinedText}", Aria: "${ariaLabel}")`);
}
}
if (DEBUG_MODE && parentEl) {
const contextTag = parentEl.tagName ? parentEl.tagName.toLowerCase() : 'document';
log(`Button "${textToFind}" not found/visible in context: <${contextTag} class="${parentEl.className || ''}" id="${parentEl.id || ''}">`);
}
return null;
}
function findVisibleTextElement(textToFind, parentEl = document) {
const selectors = 'yt-formatted-string, p, div, span, h1, h2';
const elements = parentEl.querySelectorAll(selectors);
for (const el of elements) {
const textContent = (el.textContent || '').replace(/\s+/g, ' ').trim();
if (textContent.includes(textToFind) && isVisible(el)) {
log(`Found text element containing: "${textToFind}" in <${el.tagName.toLowerCase()} class="${el.className || ''}" id="${el.id || ''}">`);
return el;
}
}
if (DEBUG_MODE && parentEl) {
const contextTag = parentEl.tagName ? parentEl.tagName.toLowerCase() : 'document';
log(`Text element containing "${textToFind}" not found/visible in context: <${contextTag} class="${parentEl.className || ''}" id="${parentEl.id || ''}">`);
}
return null;
}
const warningConfigurations = [
{
id: "regularVideoWarning",
warningTexts: ["This video may be inappropriate for some users."],
buttonText: "I understand and wish to proceed",
modalSelectors: 'ytd-modal-with-title-and-body-renderer, tp-yt-paper-dialog, div[role="dialog"], ytd-popup-container',
pageContextCheck: () => {
const onShorts = !!document.querySelector('ytd-reel-video-renderer, #shorts-container, ytd-shorts, [is-shorts=""]');
if (DEBUG_MODE) log(`pageContextCheck for 'regularVideoWarning': onShorts = ${onShorts}. Config will be attempted if onShorts is false: ${!onShorts}`);
return !onShorts;
}
},
{
id: "shortsWarning",
warningTexts: [
"This content may be inappropriate for some users",
"Do you wish to continue?"
],
buttonText: "Continue",
modalSelectors: 'yt-player-interstitial-renderer, yt-interstitial-view-model, ytd-reel-player-overlay-renderer, ytd-popup-container, div[role="dialog"], div[aria-modal="true"]',
pageContextCheck: () => {
const onShorts = !!document.querySelector('ytd-reel-video-renderer, #shorts-container, ytd-shorts, [is-shorts=""]');
if (DEBUG_MODE) log(`pageContextCheck for 'shortsWarning': onShorts = ${onShorts}. Config will be attempted if onShorts is true: ${onShorts}`);
return onShorts;
}
}
];
function processPage() {
log("processPage: Starting unified check across configurations.");
for (const config of warningConfigurations) {
log(`Processing config: ${config.id}`);
if (config.pageContextCheck && !config.pageContextCheck()) {
log(`Config ${config.id}: Page context check returned false. Skipping this configuration.`);
continue;
}
log(`Config ${config.id}: Page context check passed (or was not defined). Proceeding with text search.`);
if (!config.warningTexts || config.warningTexts.length === 0) {
log(`Config ${config.id}: No warningTexts defined. Skipping.`);
continue;
}
let allTextsFound = true;
let firstWarningElement = null;
let currentSearchContext = document;
for (let i = 0; i < config.warningTexts.length; i++) {
const textToFind = config.warningTexts[i];
const foundElement = findVisibleTextElement(textToFind, currentSearchContext);
if (!foundElement) {
allTextsFound = false;
log(`Config ${config.id}: Required warning text "${textToFind}" not found in current context.`);
break;
}
if (i === 0) {
firstWarningElement = foundElement;
const modalContext = firstWarningElement.closest(config.modalSelectors);
if (modalContext) {
currentSearchContext = modalContext;
log(`Config ${config.id}: Narrowed search context for subsequent texts to modal: <${modalContext.tagName.toLowerCase()} class="${modalContext.className || ''}" id="${modalContext.id || ''}">`);
} else {
log(`Config ${config.id}: First warning text found, but no specific modal context via "${config.modalSelectors}". Subsequent texts will be searched more broadly if not found in current parent.`);
}
}
}
if (allTextsFound && firstWarningElement) {
log(`Config ${config.id}: All ${config.warningTexts.length} warning text(s) found.`);
const modalForButton = firstWarningElement.closest(config.modalSelectors);
const searchContextForButton = modalForButton || document;
if (modalForButton) {
log(`Config ${config.id}: Modal context for button search: <${modalForButton.tagName.toLowerCase()} class="${modalForButton.className || ''}" id="${modalForButton.id || ''}">`);
} else {
log(`Config ${config.id}: Modal context for button not found using selectors "${config.modalSelectors}". Searching document.`);
}
const buttonToClick = findVisibleButtonByText(config.buttonText, searchContextForButton);
if (buttonToClick) {
log(`Config ${config.id}: Found button "${config.buttonText}". Clicking.`);
if (DEBUG_MODE && config.id === "shortsWarning") {
const shortsSkipButtonText = "Skip video";
if (findVisibleButtonByText(shortsSkipButtonText, searchContextForButton)) {
log(`Config ${config.id}: Context confirmation - "${shortsSkipButtonText}" button also found nearby for Shorts.`);
} else {
log(`Config ${config.id}: Context note - "${shortsSkipButtonText}" button NOT found nearby for Shorts.`);
}
}
buttonToClick.click();
return true; // Action taken
}
} else if (config.warningTexts.length > 0) {
log(`Config ${config.id}: Not all warning texts were found or first warning element was null.`);
}
log(`Config ${config.id}: Finished processing.`);
}
if (DEBUG_MODE) log("processPage: No actions taken in this cycle for any configuration.");
return false;
}
const observer = new MutationObserver((mutationsList, obs) => {
if (processPage()) {
log("Action successfully taken after DOM change.");
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
log("Script loaded. Performing initial check soon.");
setTimeout(processPage, 1200);
})();