// ==UserScript==
// @name YouTube Mute and Skip Ads
// @namespace https://github.com/ion1/userscripts
// @version 0.0.16
// @author ion
// @description Mutes, blurs and skips ads on YouTube. Reloads the page (maintaining the position in the video) if unskippable ads are too long. Clicks "yes" on "are you there?" on YouTube Music.
// @license MIT
// @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/*
// @run-at document-body
// ==/UserScript==
(n=>{const e=document.createElement("style");e.dataset.source="vite-plugin-monkey",e.textContent=n,document.head.append(e)})(` #movie_player.ad-showing video {
filter: blur(100px) opacity(0.25) grayscale(0.5);
}
#movie_player.ad-showing .ytp-title,
#movie_player.ad-showing .ytp-title-channel,
.ytp-ad-visit-advertiser-button,
ytmusic-app:has(#movie_player.ad-showing)
ytmusic-player-bar
:is(.title, .subtitle) {
filter: blur(4px) opacity(0.5) grayscale(0.5);
}
@media (prefers-reduced-motion: no-preference) {
#movie_player.ad-showing .ytp-title,
#movie_player.ad-showing .ytp-title-channel,
.ytp-ad-visit-advertiser-button,
ytmusic-app:has(#movie_player.ad-showing)
ytmusic-player-bar
:is(.title, .subtitle) {
transition: 0.05s filter linear;
}
}
:is(#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-ad-visit-advertiser-button,ytmusic-app:has(#movie_player.ad-showing)
ytmusic-player-bar
:is(.title, .subtitle)):hover,
:is(#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-ad-visit-advertiser-button,ytmusic-app:has(#movie_player.ad-showing)
ytmusic-player-bar
:is(.title, .subtitle)):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,
ytd-player-legacy-desktop-watch-ads-renderer {
filter: blur(10px) opacity(0.25) grayscale(0.5);
}
@media (prefers-reduced-motion: no-preference) {
#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,
ytd-player-legacy-desktop-watch-ads-renderer {
transition: 0.05s filter linear;
}
}
: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,ytd-player-legacy-desktop-watch-ads-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,ytd-player-legacy-desktop-watch-ads-renderer):focus-within {
filter: none;
}
.youtube-mute-skip-ads-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;
animation: none;
}
@media (prefers-reduced-motion: no-preference) {
.youtube-mute-skip-ads-notification.fade-out {
animation: youtube-mute-skip-ads-fade-out 3s;
animation-fill-mode: forwards;
}
}
.youtube-mute-skip-ads-notification > div {
font-size: 3rem;
line-height: 1.3;
text-align: center;
background-color: rgb(0 0 0 / 0.7);
color: white;
padding: 2rem;
border-radius: 1rem;
}
.youtube-mute-skip-ads-notification > div > :first-child {
font-size: 6rem;
}
@keyframes youtube-mute-skip-ads-fade-out {
0% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
`);
(function () {
'use strict';
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __publicField = (obj, key, value) => {
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
return value;
};
const logPrefix = "youtube-mute-skip-ads:";
class Watcher {
constructor(name, elem) {
__publicField(this, "name");
__publicField(this, "element");
__publicField(this, "onCreated");
__publicField(this, "onRemoved");
__publicField(this, "nodeObserver");
__publicField(this, "nodeWatchers");
__publicField(this, "textObserver");
__publicField(this, "onTextChanged");
__publicField(this, "onAttrChanged");
__publicField(this, "visibilityAncestor");
__publicField(this, "visibilityObserver");
__publicField(this, "isVisible");
__publicField(this, "visibilityWatchers");
this.name = name;
this.element = null;
this.onCreated = [];
this.onRemoved = [];
this.nodeObserver = null;
this.nodeWatchers = [];
this.textObserver = null;
this.onTextChanged = [];
this.onAttrChanged = [];
this.visibilityAncestor = null;
this.visibilityObserver = null;
this.isVisible = null;
this.visibilityWatchers = [];
if (elem != null) {
this.connect(elem);
}
}
assertElement() {
if (this.element == null) {
throw new Error(`Watcher not connected to an element`);
}
return this.element;
}
assertVisibilityAncestor() {
if (this.visibilityAncestor == null) {
throw new Error(`Watcher is missing a visibilityAncestor`);
}
return this.visibilityAncestor;
}
isConnected() {
return this.element != null;
}
connect(element, visibilityAncestor) {
if (this.element != null) {
if (this.element !== element) {
console.error(
logPrefix,
`Watcher already connected to`,
this.element,
`while trying to connect to`,
element
);
}
return;
}
this.element = element;
this.visibilityAncestor = visibilityAncestor ?? null;
for (const callback of this.onCreated) {
callback(this.element);
}
for (const { selector, name, watcher: watcher2 } of this.nodeWatchers) {
for (const descElem of getDescendantsBy(this.element, selector, name)) {
watcher2.connect(descElem, this.element);
}
}
for (const callback of this.onTextChanged) {
callback(this.element.textContent);
}
for (const { name, callback } of this.onAttrChanged) {
callback(this.element.getAttribute(name));
}
this.registerNodeObserver();
this.registerTextObserver();
this.registerAttrObservers();
this.registerVisibilityObserver();
}
disconnect() {
if (this.element == null) {
return;
}
for (const child of this.nodeWatchers) {
child.watcher.disconnect();
}
for (const callback of this.onTextChanged) {
callback(null);
}
for (const { callback } of this.onAttrChanged) {
callback(null);
}
for (const child of this.visibilityWatchers) {
child.disconnect();
}
this.deregisterNodeObserver();
this.deregisterTextObserver();
this.deregisterAttrObservers();
this.deregisterVisibilityObserver();
for (const callback of this.onRemoved) {
callback(this.element);
}
this.element = null;
}
registerNodeObserver() {
if (this.nodeObserver != null) {
return;
}
if (this.nodeWatchers.length === 0) {
return;
}
const elem = this.assertElement();
this.nodeObserver = new MutationObserver((mutations) => {
for (const mut of mutations) {
for (const node of mut.addedNodes) {
for (const { selector, name, watcher: watcher2 } of this.nodeWatchers) {
for (const descElem of getSelfOrDescendantsBy(
node,
selector,
name
)) {
watcher2.connect(descElem, elem);
}
}
}
for (const node of mut.removedNodes) {
for (const { selector, name, watcher: watcher2 } of this.nodeWatchers) {
for (const _descElem of getSelfOrDescendantsBy(
node,
selector,
name
)) {
watcher2.disconnect();
}
}
}
}
});
this.nodeObserver.observe(elem, {
subtree: true,
childList: true
});
}
registerTextObserver() {
if (this.textObserver != null) {
return;
}
if (this.onTextChanged.length === 0) {
return;
}
const elem = this.assertElement();
this.textObserver = new MutationObserver((_mutations) => {
for (const callback of this.onTextChanged) {
callback(elem.textContent);
}
});
this.textObserver.observe(elem, {
subtree: true,
// This is needed when elements are replaced to update their text.
childList: true,
characterData: true
});
}
registerAttrObservers() {
const elem = this.assertElement();
for (const handler of this.onAttrChanged) {
if (handler.observer != null) {
continue;
}
const { name, callback } = handler;
handler.observer = new MutationObserver((_mutations) => {
callback(elem.getAttribute(name));
});
handler.observer.observe(elem, {
attributes: true,
attributeFilter: [name]
});
}
}
registerVisibilityObserver() {
if (this.visibilityObserver != null) {
return;
}
if (this.visibilityWatchers.length === 0) {
return;
}
this.isVisible = false;
const elem = this.assertElement();
const visibilityAncestor = this.assertVisibilityAncestor();
this.visibilityObserver = new IntersectionObserver(
(entries) => {
const oldVisible = this.isVisible;
for (const entry of entries) {
this.isVisible = entry.isIntersecting;
}
if (this.isVisible !== oldVisible) {
if (this.isVisible) {
for (const watcher2 of this.visibilityWatchers) {
watcher2.connect(elem, visibilityAncestor);
}
} else {
for (const watcher2 of this.visibilityWatchers) {
watcher2.disconnect();
}
}
}
},
{
root: visibilityAncestor
}
);
this.visibilityObserver.observe(elem);
}
deregisterNodeObserver() {
if (this.nodeObserver == null) {
return;
}
this.nodeObserver.disconnect();
this.nodeObserver = null;
}
deregisterTextObserver() {
if (this.textObserver == null) {
return;
}
this.textObserver.disconnect();
this.textObserver = null;
}
deregisterAttrObservers() {
for (const handler of this.onAttrChanged) {
if (handler.observer == null) {
continue;
}
handler.observer.disconnect();
handler.observer = null;
}
}
deregisterVisibilityObserver() {
if (this.visibilityObserver == null) {
return;
}
this.visibilityObserver.disconnect();
this.visibilityObserver = null;
this.isVisible = null;
}
lifecycle(onCreated, onRemoved) {
this.onCreated.push(onCreated);
if (onRemoved != null) {
this.onRemoved.push(onRemoved);
}
if (this.element != null) {
onCreated(this.element);
}
return this;
}
descendant(selector, name) {
const watcher2 = new Watcher(`${this.name} → ${name}`);
this.nodeWatchers.push({ selector, name, watcher: watcher2 });
if (this.element != null) {
for (const descElem of getDescendantsBy(this.element, selector, name)) {
watcher2.connect(descElem, this.element);
}
this.registerNodeObserver();
}
return watcher2;
}
id(idName) {
return this.descendant("id", idName);
}
klass(className) {
return this.descendant("class", className);
}
tag(tagName) {
return this.descendant("tag", tagName);
}
visible() {
const watcher2 = new Watcher(`${this.name} (visible)`);
this.visibilityWatchers.push(watcher2);
if (this.element != null) {
const visibilityAncestor = this.assertVisibilityAncestor();
if (this.isVisible) {
watcher2.connect(this.element, visibilityAncestor);
}
this.registerVisibilityObserver();
}
return watcher2;
}
text(callback) {
this.onTextChanged.push(callback);
if (this.element != null) {
callback(this.element.textContent);
this.registerTextObserver();
}
return this;
}
attr(name, callback) {
this.onAttrChanged.push({ name, callback, observer: null });
if (this.element != null) {
callback(this.element.getAttribute(name));
this.registerAttrObservers();
}
return this;
}
}
function getSelfOrDescendantsBy(node, selector, name) {
if (!(node instanceof HTMLElement)) {
return [];
}
if (selector === "id" || selector === "class" || selector === "tag") {
if (selector === "id" && node.id === name || selector === "class" && node.classList.contains(name) || selector === "tag" && node.tagName.toLowerCase() === name.toLowerCase()) {
return [node];
} else {
return getDescendantsBy(node, selector, name);
}
} else {
const impossible = selector;
throw new Error(`Impossible selector type: ${JSON.stringify(impossible)}`);
}
}
function getDescendantsBy(node, selector, name) {
if (!(node instanceof HTMLElement)) {
return [];
}
let cssSelector = "";
if (selector === "id") {
cssSelector += "#";
} else if (selector === "class") {
cssSelector += ".";
} else if (selector === "tag")
;
else {
const impossible = selector;
throw new Error(`Impossible selector type: ${JSON.stringify(impossible)}`);
}
cssSelector += CSS.escape(name);
return Array.from(node.querySelectorAll(cssSelector));
}
let visibleElem = null;
let hideTimerId = null;
function showNotification(params) {
const containerElem = document.createElement("div");
containerElem.setAttribute("class", "youtube-mute-skip-ads-notification");
const notifElem = document.createElement("div");
notifElem.setAttribute("aria-live", "assertive");
notifElem.setAttribute("aria-atomic", "true");
containerElem.appendChild(notifElem);
const headerElem = document.createElement("div");
headerElem.textContent = params.heading;
notifElem.appendChild(headerElem);
if (params.description != null) {
const descrElem = document.createElement("div");
descrElem.textContent = params.description;
notifElem.appendChild(descrElem);
}
const footerElem = document.createElement("div");
footerElem.textContent = "(Youtube Mute and Skip Ads)";
notifElem.appendChild(footerElem);
if (hideTimerId != null) {
clearTimeout(hideTimerId);
hideTimerId = null;
}
if (visibleElem != null) {
document.body.removeChild(visibleElem);
visibleElem = null;
}
document.body.append(containerElem);
visibleElem = containerElem;
if (params.fadeOut) {
containerElem.classList.add("fade-out");
hideTimerId = setTimeout(() => {
document.body.removeChild(containerElem);
visibleElem = null;
hideTimerId = null;
}, 3e3);
}
}
function parse(value) {
return new Parser([], value);
}
function optional(func) {
try {
return func();
} catch (e) {
if (!(e instanceof ParserError)) {
throw e;
}
return null;
}
}
class ParserError extends Error {
constructor(msg) {
super(msg);
Object.setPrototypeOf(this, new.target.prototype);
}
}
class Parser {
constructor(path, value) {
__publicField(this, "path");
__publicField(this, "value");
this.path = [...path];
this.value = value;
}
pathString() {
return this.path.join(" → ");
}
boolean() {
if (!(this.value != null && typeof this.value === "boolean")) {
throw new ParserError(
`${this.pathString()}: Expected boolean: ${this.value}`
);
}
return new Parser([...this.path, "boolean"], this.value);
}
number() {
if (!(this.value != null && typeof this.value === "number")) {
throw new ParserError(
`${this.pathString()}: Expected number: ${this.value}`
);
}
return new Parser([...this.path, "number"], this.value);
}
object() {
if (!(this.value != null && typeof this.value === "object")) {
throw new ParserError(
`${this.pathString()}: Expected object: ${this.value}`
);
}
return new Parser([...this.path, "object"], this.value);
}
function() {
if (!(this.value != null && typeof this.value === "function")) {
throw new ParserError(
`${this.pathString()}: Expected function: ${this.value}`
);
}
const func = this.value;
return new Parser([...this.path, "function"], func);
}
property(name) {
if (!(name in this.value)) {
throw new ParserError(
`${this.pathString()}: Expected property ${JSON.stringify(name)}: ${this.value}`
);
}
const prop = this.value[name];
return new Parser([...this.path, `property ${JSON.stringify(name)}`], prop);
}
method(name) {
const func = this.property(name).function().value;
const boundFunc = func.bind(this.value);
return new Parser(
[...this.path, `method ${JSON.stringify(name)}`],
boundFunc
);
}
call(...args) {
const result = this.value(...args);
return new Parser([...this.path, "call"], result);
}
}
const playerId = "movie_player";
const videoSelector = "#movie_player video";
const muteButtonSelector = ":is(.ytp-mute-button, ytmusic-player-bar tp-yt-paper-icon-button.volume)";
function getPlayerState() {
try {
const playerElemP = parse(document.getElementById(playerId)).object();
const currentTime = playerElemP.method("getCurrentTime").call().number().value;
const duration = playerElemP.method("getDuration").call().number().value;
const isLive = optional(
() => playerElemP.method("getPlayerResponse").call().object().property("videoDetails").object().property("isLive").boolean().value
) ?? false;
return { currentTime, duration, isLive };
} catch (e) {
if (!(e instanceof ParserError)) {
throw e;
}
console.error(logPrefix, e.message);
return null;
}
}
function getVideoElement() {
const videoElem = document.querySelector(videoSelector);
if (!(videoElem instanceof HTMLVideoElement)) {
console.error(
logPrefix,
"Expected",
JSON.stringify(videoSelector),
"to be a video element, got:",
videoElem == null ? void 0 : videoElem.cloneNode(true)
);
return null;
}
return videoElem;
}
function getMuteButton() {
for (const elem of document.querySelectorAll(muteButtonSelector)) {
if (!(elem instanceof HTMLElement)) {
console.error(
logPrefix,
"Expected",
JSON.stringify(muteButtonSelector),
"to be an HTML element, got:",
elem.cloneNode(true)
);
continue;
}
return elem;
}
console.error(
logPrefix,
"Failed to find",
JSON.stringify(muteButtonSelector)
);
return null;
}
const adMaxTime = 7;
const notificationKey = "youtube-mute-skip-ads-notification";
const restoreFocusKey = "youtube-mute-skip-ads-restore-focus";
function reloadNotification(description) {
showNotification({ heading: "⟳ Reloading", description });
sessionStorage.setItem(notificationKey, description);
}
function reloadCanceledNotification() {
showNotification({ heading: "✗ Reload canceled", fadeOut: true });
sessionStorage.removeItem(notificationKey);
}
function reloadedNotification() {
const description = sessionStorage.getItem(notificationKey);
sessionStorage.removeItem(notificationKey);
if (description != null) {
showNotification({ heading: "✓ Reloaded", description, fadeOut: true });
}
}
function storeFocusState() {
var _a;
const id = (_a = document.activeElement) == null ? void 0 : _a.id;
if (id != null && id !== "") {
sessionStorage.setItem(restoreFocusKey, id);
}
}
function removeFocusState() {
sessionStorage.removeItem(restoreFocusKey);
}
let focusElementId = sessionStorage.getItem(restoreFocusKey);
removeFocusState();
function restoreFocusState(elem) {
if (focusElementId == null) {
return;
}
console.info(logPrefix, "Restoring focus to", JSON.stringify(elem.id));
elem.focus();
focusElementId = null;
}
class Reloader {
constructor() {
__publicField(this, "state");
__publicField(this, "adCounter");
// In the bottom left of the video.
__publicField(this, "adDurationRemaining");
// The preskip might exist but not have a countdown.
__publicField(this, "hasPreskip");
// In the preskip box preceding the skip button.
__publicField(this, "preskipRemaining");
__publicField(this, "inYouTubeMusic");
this.state = { id: "idle" };
this.adCounter = null;
this.adDurationRemaining = null;
this.hasPreskip = false;
this.preskipRemaining = null;
this.inYouTubeMusic = false;
}
updateAdCounter(value) {
this.adCounter = value;
this.dispatch();
}
updateAdDurationRemaining(value) {
this.adDurationRemaining = value;
this.dispatch();
}
updatePreskip(info) {
this.hasPreskip = info.hasPreskip;
this.preskipRemaining = info.remaining;
this.dispatch();
}
updateInYouTubeMusic(inYouTubeMusic) {
this.inYouTubeMusic = inYouTubeMusic;
this.dispatch();
}
setState(state) {
this.state = state;
}
dispatch() {
switch (this.state.id) {
case "idle":
return this.dispatchWhileIdle();
case "pausing":
return this.dispatchWhilePausing();
case "reloading":
return this.dispatchWhileReloading();
case "reload-canceled":
return this.dispatchWhileReloadCanceled();
case "youtube-music":
return this.dispatchWhileYouTubeMusic();
case "end-of-video-ad":
return this.dispatchWhileEndOfVideoAd();
default: {
const impossible = this.state;
throw new Error(`Impossible state: ${JSON.stringify(impossible)}`);
}
}
}
enterIdle() {
this.setState({ id: "idle" });
}
dispatchWhileIdle() {
if (this.inYouTubeMusic) {
return this.enterYouTubeMusic();
}
if (!(this.adDurationRemaining != null && this.hasPreskip)) {
return;
}
const playerState = getPlayerState();
if (playerState == null) {
return;
}
const { currentTime, duration, isLive } = playerState;
const endOfVideo = duration >= 1 && Math.floor(currentTime) === Math.floor(duration);
if (endOfVideo && !isLive) {
return this.enterEndOfVideoAd();
}
if (this.adCounter != null && !this.adCounter.parsed.includes(1)) {
console.info(logPrefix, "Ad counter exceeds 1, reloading page");
return this.doReload({
description: `Reason: ad counter: ${this.adCounter.text}`,
currentTime
});
}
let time = this.adDurationRemaining.parsed;
if (this.preskipRemaining != null) {
time = Math.min(time, this.preskipRemaining.parsed);
}
if (time > adMaxTime) {
console.info(
logPrefix,
"Ad duration remaining exceeds maximum, reloading page:",
time,
">",
adMaxTime
);
return this.doReload({
description: `Reason: ad duration: ${time} s`,
currentTime
});
}
}
doReload(info) {
const { description, currentTime } = info;
const videoElem = getVideoElement();
if (videoElem == null) {
return;
}
if (videoElem.paused) {
return this.enterReloading({ description, currentTime });
} else {
videoElem.addEventListener(
"pause",
() => {
this.paused();
},
{ once: true }
);
videoElem.pause();
return this.enterPausing({ description, currentTime });
}
}
enterPausing(info) {
this.setState({ id: "pausing", ...info });
}
dispatchWhilePausing() {
return;
}
paused() {
if (this.state.id !== "pausing") {
return;
}
const { description, currentTime } = this.state;
setTimeout(() => {
this.enterReloading({ description, currentTime });
});
}
enterReloading(info) {
const { description, currentTime } = info;
reloadNotification(description);
storeFocusState();
const searchParams = new URLSearchParams(window.location.search);
searchParams.set("t", `${Math.floor(currentTime)}s`);
console.info(logPrefix, "Reloading with t =", searchParams.get("t"));
window.location.search = searchParams.toString();
this.setState({ id: "reloading" });
}
dispatchWhileReloading() {
if (this.adDurationRemaining == null && !this.hasPreskip) {
return this.enterReloadCanceled();
}
}
enterReloadCanceled() {
removeFocusState();
this.setState({ id: "reload-canceled" });
setTimeout(() => {
reloadCanceledNotification();
this.enterIdle();
}, 500);
}
dispatchWhileReloadCanceled() {
return;
}
enterYouTubeMusic() {
console.info(
logPrefix,
"Not reloading on YouTube Music; it messes up random playlists"
);
this.setState({ id: "youtube-music" });
}
dispatchWhileYouTubeMusic() {
if (!this.inYouTubeMusic) {
return this.enterIdle();
}
}
enterEndOfVideoAd() {
showNotification({ heading: "End of video", fadeOut: true });
this.setState({ id: "end-of-video-ad" });
}
dispatchWhileEndOfVideoAd() {
if (this.adDurationRemaining == null && !this.hasPreskip) {
this.enterIdle();
}
}
}
function disableVisibilityChecks() {
for (const eventName of ["visibilitychange", "blur", "focus"]) {
document.addEventListener(
eventName,
(ev) => {
ev.stopImmediatePropagation();
},
{ capture: true }
);
}
document.hasFocus = () => true;
Object.defineProperties(document, {
visibilityState: { value: "visible" },
hidden: { value: false }
});
}
reloadedNotification();
const reloader = new Reloader();
function adUIAdded(_elem) {
console.info(logPrefix, "An ad is playing, muting");
const video = getVideoElement();
if (video == null) {
return;
}
video.muted = true;
}
function adUIRemoved(_elem) {
console.info(logPrefix, "An ad is no longer playing, unmuting");
const elem = getMuteButton();
if (elem == null) {
return;
}
elem.click();
elem.click();
}
function adCounterUpdated(fullText) {
var _a;
const text = ((_a = fullText == null ? void 0 : fullText.match(/[0-9](?:.*[0-9])?/)) == null ? void 0 : _a[0]) ?? null;
if (text == null) {
reloader.updateAdCounter(null);
return;
}
const counter = (text.match(/[0-9]+/g) ?? []).map(Number);
if (counter.length === 0) {
reloader.updateAdCounter(null);
return;
}
reloader.updateAdCounter({ text, parsed: counter });
}
function durationRemainingUpdated(fullText) {
const match = fullText == null ? void 0 : fullText.match(/^(?:(?:([0-9]+):)?([0-9]+):)?([0-9]+)$/);
if (match == null) {
reloader.updateAdDurationRemaining(null);
return;
}
const text = match[0];
const h = Number(match[1] ?? 0);
const m = Number(match[2] ?? 0);
const s = Number(match[3] ?? 0);
const time = (h * 60 + m) * 60 + s + 1;
reloader.updateAdDurationRemaining({ text, parsed: time });
}
function preskipUpdated(fullText) {
var _a;
const hasPreskip = fullText != null;
const text = (_a = fullText == null ? void 0 : fullText.match(/^[^0-9]*([0-9]+)[^0-9]*$/)) == null ? void 0 : _a[1];
if (text == null) {
reloader.updatePreskip({ hasPreskip, remaining: null });
} else {
reloader.updatePreskip({
hasPreskip,
remaining: { text, parsed: Number(text) }
});
}
}
function click(description) {
return (elem) => {
console.info(logPrefix, "Clicking:", description);
elem.click();
};
}
disableVisibilityChecks();
const watcher = new Watcher("body", document.body);
if (focusElementId != null) {
watcher.id(focusElementId).lifecycle(restoreFocusState);
}
const playerOverlay = watcher.klass("ytp-ad-player-overlay").lifecycle(adUIAdded, adUIRemoved);
playerOverlay.klass("ytp-ad-simple-ad-badge").text(adCounterUpdated);
playerOverlay.klass("ytp-ad-duration-remaining").text(durationRemainingUpdated);
playerOverlay.klass("ytp-ad-preview-text").text(preskipUpdated);
playerOverlay.klass("ytp-ad-skip-button").visible().lifecycle(click("skip"));
watcher.klass("ytp-ad-overlay-close-button").lifecycle(click("overlay close"));
watcher.tag("ytmusic-app").lifecycle(
() => {
reloader.updateInYouTubeMusic(true);
},
() => {
reloader.updateInYouTubeMusic(false);
}
);
watcher.tag("ytmusic-you-there-renderer").tag("button").lifecycle(click("are-you-there"));
})();