// ==UserScript==
// @name YouTube Mute and Skip Ads
// @namespace https://github.com/ion1/userscripts
// @version 0.0.2
// @author ion
// @description Mutes, blurs and skips ads on YouTube. Reloads the page if unskippable ads are too long. The consistent skipping (rather than ad blocking) on the desktop seems to make more ads skippable on mobile as well.
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @homepage https://github.com/ion1/userscripts/tree/master/packages/youtube-mute-skip-ads
// @homepageURL https://github.com/ion1/userscripts/tree/master/packages/youtube-mute-skip-ads
// @match *://www.youtube.com/*
// @match *://music.youtube.com/*
// ==/UserScript==
(t=>{const e=document.createElement("style");e.dataset.source="vite-plugin-monkey",e.innerText=t,document.head.appendChild(e)})(" #movie_player.ad-showing video{filter:blur(100px) opacity(.25) grayscale(.5)}#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-ad-visit-advertiser-button{filter:blur(4px) opacity(.5) grayscale(.5);transition:.05s filter ease}:is(#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-ad-visit-advertiser-button):hover,:is(#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-ad-visit-advertiser-button):focus-within{filter:none}#movie_player.ad-showing .caption-window,.ytp-ad-player-overlay-flyout-cta,ytd-action-companion-ad-renderer,ytd-display-ad-renderer,ytd-ad-slot-renderer,ytd-promoted-sparkles-web-renderer{filter:blur(10px) opacity(.25) grayscale(.5);transition:.05s filter ease}:is(#movie_player.ad-showing .caption-window,.ytp-ad-player-overlay-flyout-cta,ytd-action-companion-ad-renderer,ytd-display-ad-renderer,ytd-ad-slot-renderer,ytd-promoted-sparkles-web-renderer):hover,:is(#movie_player.ad-showing .caption-window,.ytp-ad-player-overlay-flyout-cta,ytd-action-companion-ad-renderer,ytd-display-ad-renderer,ytd-ad-slot-renderer,ytd-promoted-sparkles-web-renderer):focus-within{filter:none}.youtube-mute-skip-ads-reload-notification{pointer-events:none;position:fixed;left:0;top:0;width:100%;height:100%;z-index:9999;display:flex;align-items:center;justify-content:center}.youtube-mute-skip-ads-reload-notification>div{font-size:6rem;line-height:1.3;text-align:center;background-color:#000000b3;color:#fff;padding:2rem;border-radius:1rem}.youtube-mute-skip-ads-reload-notification>div>small{font-size:3rem} ");
(function() {
"use strict";
const main = "";
const adMaxCount = 1;
const adMaxTime = 7;
const playerId = "movie_player";
const videoSelector = "#movie_player video";
const muteButtonClass = "ytp-mute-button";
const adUIClass = "ytp-ad-player-overlay-skip-or-preview";
const adBadgeClass = "ytp-ad-simple-ad-badge";
const preskipClass = "ytp-ad-preview-text";
const skipContainerClass = "ytp-ad-skip-button-slot";
const skipButtonClass = "ytp-ad-skip-button";
const overlayCloseButtonClass = "ytp-ad-overlay-close-button";
function getSelfOrChildrenByClassName(node, className) {
if (!(node instanceof Element)) {
return [];
} else if (node.classList.contains(className)) {
return [node];
} else {
return node.getElementsByClassName(className);
}
}
function setVideoProperties(props) {
const video = document.querySelector(videoSelector);
if (!(video instanceof HTMLVideoElement)) {
return;
}
if (props.muted != null) {
video.muted = props.muted;
}
}
function toggleMuteTwice() {
for (const elem of document.getElementsByClassName(muteButtonClass)) {
if (!(elem instanceof HTMLElement)) {
continue;
}
elem.click();
elem.click();
}
}
function adUIAdded(_elem) {
console.info("youtube-mute-skip-ads: An ad is playing, muting");
setVideoProperties({ muted: true });
}
function adUIRemoved(_elem) {
console.info("youtube-mute-skip-ads: An ad is no longer playing, unmuting");
toggleMuteTwice();
}
function reloadPage() {
const playerElem = document.getElementById(playerId);
if (playerElem == null || !("getCurrentTime" in playerElem)) {
return;
}
const notifDiv = document.createElement("div");
notifDiv.setAttribute("class", "youtube-mute-skip-ads-reload-notification");
notifDiv.innerHTML = `<div aria-live="assertive" aria-atomic="true">Reloading<br/><small>(Youtube Mute and Skip Ads)</small></div>`;
document.body.append(notifDiv);
const currentTime = playerElem.getCurrentTime();
var searchParams = new URLSearchParams(window.location.search);
searchParams.set("t", "" + Math.floor(currentTime) + "s");
console.info(
"youtube-mute-skip-ads: Reloading with t =",
searchParams.get("t")
);
window.location.search = searchParams.toString();
}
function adBadgeAdded(elem) {
var _a, _b;
const adCounter = (_b = (_a = elem.textContent) == null ? void 0 : _a.match(
/^[^0-9]*([0-9]+)\/([0-9]+)[^0-9]*$/
)) == null ? void 0 : _b[1];
console.debug(
"youtube-mute-skip-ads: Ad badge added with counter =",
adCounter
);
if (adCounter != null && Number(adCounter) > adMaxCount) {
console.info(
"youtube-mute-skip-ads: Ad counter exceeds maximum, reloading page:",
adCounter,
">",
adMaxCount
);
reloadPage();
}
}
function preskipAdded(elem) {
var _a, _b;
const adTime = (_b = (_a = elem.textContent) == null ? void 0 : _a.match(/^[^0-9]*([0-9]+)[^0-9]*$/)) == null ? void 0 : _b[1];
console.debug(
"youtube-mute-skip-ads: Ad preskip added with countdown =",
adTime
);
if (adTime == null) {
console.info("youtube-mute-skip-ads: No ad countdown, reloading page");
reloadPage();
}
if (Number(adTime) > adMaxTime) {
console.info(
"youtube-mute-skip-ads: Ad countdown exceeds maximum, reloading page:",
adTime,
">",
adMaxTime
);
reloadPage();
}
}
function clickSkipIfVisible(button) {
const isVisible = button.offsetParent !== null;
if (isVisible) {
console.info("youtube-mute-skip-ads: Skipping");
button.click();
}
return isVisible;
}
function skipAdded(elem) {
var _a;
console.debug("youtube-mute-skip-ads: Skip added");
const button = (_a = elem.getElementsByClassName(skipButtonClass)) == null ? void 0 : _a[0];
if (!(button instanceof HTMLElement)) {
console.error(
"youtube-mute-skip-ads: Failed to find skip button:",
elem.outerHTML
);
return;
}
if (!clickSkipIfVisible(button)) {
console.info("youtube-mute-skip-ads: Skip button is invisible, waiting");
const skipObserver = new MutationObserver(() => {
clickSkipIfVisible(button);
});
skipObserver.observe(elem, {
attributes: true,
attributeFilter: ["style"],
subtree: true
});
}
}
function overlayCloseAdded(elem) {
if (!(elem instanceof HTMLElement)) {
console.error(
"youtube-mute-skip-ads: Overlay close added, not an HTMLElement?",
elem.outerHTML
);
return;
}
console.info("youtube-mute-skip-ads: Overlay close added, clicking");
elem.click();
}
const addedMap = [
{ className: adUIClass, func: adUIAdded },
{ className: adBadgeClass, func: adBadgeAdded },
{ className: preskipClass, func: preskipAdded },
{ className: skipContainerClass, func: skipAdded },
{ className: overlayCloseButtonClass, func: overlayCloseAdded }
];
const removedMap = [{ className: adUIClass, func: adUIRemoved }];
for (const { className, func } of addedMap) {
for (const elem of document.getElementsByClassName(className)) {
func(elem);
}
}
const observer = new MutationObserver((mutations) => {
for (const mut of mutations) {
if (mut.type === "childList") {
for (const parentNode of mut.addedNodes) {
for (const { className, func } of addedMap) {
for (const node of getSelfOrChildrenByClassName(
parentNode,
className
)) {
func(node);
}
}
}
for (const parentNode of mut.removedNodes) {
for (const { className, func } of removedMap) {
for (const node of getSelfOrChildrenByClassName(
parentNode,
className
)) {
func(node);
}
}
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();