您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Mutes, blurs and skips ads on YouTube. Speeds up ad playback. Clicks "yes" on "are you there?" on YouTube Music.
当前为
// ==UserScript== // @name YouTube Mute and Skip Ads // @namespace https://github.com/ion1/userscripts // @version 0.0.25 // @author ion // @description Mutes, blurs and skips ads on YouTube. Speeds up ad playback. 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/* // @grant GM_addStyle // @run-at document-body // ==/UserScript== (e=>{if(typeof GM_addStyle=="function"){GM_addStyle(e);return}const n=document.createElement("style");n.textContent=e,document.head.append(n)})(` /* Keep these in sync with the watchers. */ #movie_player :is(.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-skip-ad-button) { anchor-name: --youtube-mute-skip-ads-unclickable-button; } body:has( #movie_player :is( .ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-skip-ad-button ):not([style*="display: none"], [aria-hidden="true"]) )::after { content: "\u{1D606}\u{1D5FC}\u{1D602}\u{1D601}\u{1D602}\u{1D5EF}\u{1D5F2}-\u{1D5FA}\u{1D602}\u{1D601}\u{1D5F2}-\u{1D600}\u{1D5F8}\u{1D5F6}\u{1D5FD}-\u{1D5EE}\u{1D5F1}\u{1D600}\\A\\A" "Unfortunately, YouTube has started to block automated clicks based on isTrusted being false.\\A\\A" "Please click on the skip button manually."; white-space: pre-line; pointer-events: none; z-index: 9999; position: fixed; position-anchor: --youtube-mute-skip-ads-unclickable-button; padding: 1.5em; border-radius: 1.5em; margin-bottom: 1em; bottom: anchor(--youtube-mute-skip-ads-unclickable-button top); right: anchor(--youtube-mute-skip-ads-unclickable-button right); max-width: 25em; font-size: 1.4rem; line-height: 2rem; font-weight: 400; color: rgb(240 240 240); background-color: rgb(0 0 0 / 0.7); backdrop-filter: blur(10px); animation: fade-in 3s linear; } @keyframes fade-in { 0% { opacity: 0; } 67% { opacity: 0; } 100% { opacity: 1; } } #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-visit-advertiser-link, .ytp-ad-visit-advertiser-button, .ytp-suggested-action-badge, ytmusic-app:has(#movie_player.ad-showing) ytmusic-player-bar :is(.title, .subtitle) { filter: blur(4px) opacity(0.5) grayscale(0.5); transition: 0.05s filter linear; } :is(#movie_player.ad-showing .ytp-title,#movie_player.ad-showing .ytp-title-channel,.ytp-visit-advertiser-link,.ytp-ad-visit-advertiser-button,.ytp-suggested-action-badge,ytmusic-app:has(#movie_player.ad-showing) ytmusic-player-bar :is(.title,.subtitle)):is(:hover,:focus-within) { filter: none; } #movie_player.ad-showing .caption-window, .ytp-ad-player-overlay-flyout-cta, .ytp-ad-player-overlay-layout__player-card-container, /* Seen since 2024-04-06. */ .ytp-ad-action-interstitial-slot, /* Added on 2024-08-25. */ 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, ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"], ytd-merch-shelf-renderer { filter: blur(10px) opacity(0.25) grayscale(0.5); transition: 0.05s filter linear; } :is(#movie_player.ad-showing .caption-window,.ytp-ad-player-overlay-flyout-cta,.ytp-ad-player-overlay-layout__player-card-container,.ytp-ad-action-interstitial-slot,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,ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"],ytd-merch-shelf-renderer):is(:hover,:focus-within) { filter: none; } .ytp-ad-action-interstitial-background-container /* Added on 2024-08-25. */ { /* An image ad in place of the video. */ filter: blur(10px) opacity(0.25) grayscale(0.5); } `); (function () { 'use strict'; var __defProp = Object.defineProperty; var __typeError = (msg) => { throw TypeError(msg); }; 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); var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var _onCreatedCallbacks, _onRemovedCallbacks, _nodeObserver, _nodeWatchers, _textObserver, _onTextChangedCallbacks, _onAttrChangedCallbacks, _visibilityObserver, _isVisible, _visibilityWatchers, _Watcher_instances, assertElement_fn, assertVisibilityAncestor_fn, connect_fn, disconnect_fn, registerNodeObserver_fn, registerTextObserver_fn, registerAttrObservers_fn, registerVisibilityObserver_fn, deregisterNodeObserver_fn, deregisterTextObserver_fn, deregisterAttrObservers_fn, deregisterVisibilityObserver_fn; const logPrefix = "youtube-mute-skip-ads:"; const _Watcher = class _Watcher { constructor(name, elem) { __privateAdd(this, _Watcher_instances); __publicField(this, "name"); __publicField(this, "element"); __privateAdd(this, _onCreatedCallbacks); __privateAdd(this, _onRemovedCallbacks); __privateAdd(this, _nodeObserver); __privateAdd(this, _nodeWatchers); __privateAdd(this, _textObserver); __privateAdd(this, _onTextChangedCallbacks); __privateAdd(this, _onAttrChangedCallbacks); __publicField(this, "visibilityAncestor"); __privateAdd(this, _visibilityObserver); __privateAdd(this, _isVisible); __privateAdd(this, _visibilityWatchers); this.name = name; this.element = null; __privateSet(this, _onCreatedCallbacks, []); __privateSet(this, _onRemovedCallbacks, []); __privateSet(this, _nodeObserver, null); __privateSet(this, _nodeWatchers, []); __privateSet(this, _textObserver, null); __privateSet(this, _onTextChangedCallbacks, []); __privateSet(this, _onAttrChangedCallbacks, []); this.visibilityAncestor = null; __privateSet(this, _visibilityObserver, null); __privateSet(this, _isVisible, null); __privateSet(this, _visibilityWatchers, []); if (elem != null) { __privateMethod(this, _Watcher_instances, connect_fn).call(this, elem); } } onCreated(onCreatedCb) { __privateGet(this, _onCreatedCallbacks).push(onCreatedCb); if (this.element != null) { const onRemovedCb = onCreatedCb(this.element); if (onRemovedCb) { __privateGet(this, _onRemovedCallbacks).push(onRemovedCb); } } return this; } descendant(selector, name) { var _a; const watcher2 = new _Watcher(`${this.name} → ${name}`); __privateGet(this, _nodeWatchers).push({ selector, name, watcher: watcher2 }); if (this.element != null) { for (const descElem of getDescendantsBy(this.element, selector, name)) { __privateMethod(_a = watcher2, _Watcher_instances, connect_fn).call(_a, descElem, this.element); } __privateMethod(this, _Watcher_instances, registerNodeObserver_fn).call(this); } return watcher2; } id(idName) { return this.descendant("id", idName); } klass(className) { return this.descendant("class", className); } tag(tagName) { return this.descendant("tag", tagName); } visible() { var _a; const watcher2 = new _Watcher(`${this.name} (visible)`); __privateGet(this, _visibilityWatchers).push(watcher2); if (this.element != null) { const visibilityAncestor = __privateMethod(this, _Watcher_instances, assertVisibilityAncestor_fn).call(this); if (__privateGet(this, _isVisible)) { __privateMethod(_a = watcher2, _Watcher_instances, connect_fn).call(_a, this.element, visibilityAncestor); } __privateMethod(this, _Watcher_instances, registerVisibilityObserver_fn).call(this); } return watcher2; } /// `null` implies null textContent. `undefined` implies that the watcher is /// being disconnected. text(callback) { __privateGet(this, _onTextChangedCallbacks).push(callback); if (this.element != null) { callback(this.element, this.element.textContent); __privateMethod(this, _Watcher_instances, registerTextObserver_fn).call(this); } return this; } /// `null` implies no such attribute. `undefined` implies that the watcher is /// being disconnected. attr(name, callback) { __privateGet(this, _onAttrChangedCallbacks).push({ name, callback, observer: null }); if (this.element != null) { callback(this.element, this.element.getAttribute(name)); __privateMethod(this, _Watcher_instances, registerAttrObservers_fn).call(this); } return this; } }; _onCreatedCallbacks = new WeakMap(); _onRemovedCallbacks = new WeakMap(); _nodeObserver = new WeakMap(); _nodeWatchers = new WeakMap(); _textObserver = new WeakMap(); _onTextChangedCallbacks = new WeakMap(); _onAttrChangedCallbacks = new WeakMap(); _visibilityObserver = new WeakMap(); _isVisible = new WeakMap(); _visibilityWatchers = new WeakMap(); _Watcher_instances = new WeakSet(); assertElement_fn = function() { if (this.element == null) { throw new Error(`Watcher not connected to an element`); } return this.element; }; assertVisibilityAncestor_fn = function() { if (this.visibilityAncestor == null) { throw new Error(`Watcher is missing a visibilityAncestor`); } return this.visibilityAncestor; }; connect_fn = function(element, visibilityAncestor) { var _a; 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 onCreatedCb of __privateGet(this, _onCreatedCallbacks)) { const onRemovedCb = onCreatedCb(this.element); if (onRemovedCb) { __privateGet(this, _onRemovedCallbacks).push(onRemovedCb); } } for (const { selector, name, watcher: watcher2 } of __privateGet(this, _nodeWatchers)) { for (const descElem of getDescendantsBy(this.element, selector, name)) { __privateMethod(_a = watcher2, _Watcher_instances, connect_fn).call(_a, descElem, this.element); } } for (const callback of __privateGet(this, _onTextChangedCallbacks)) { callback(this.element, this.element.textContent); } for (const { name, callback } of __privateGet(this, _onAttrChangedCallbacks)) { callback(this.element, this.element.getAttribute(name)); } __privateMethod(this, _Watcher_instances, registerNodeObserver_fn).call(this); __privateMethod(this, _Watcher_instances, registerTextObserver_fn).call(this); __privateMethod(this, _Watcher_instances, registerAttrObservers_fn).call(this); __privateMethod(this, _Watcher_instances, registerVisibilityObserver_fn).call(this); }; disconnect_fn = function() { var _a, _b; if (this.element == null) { return; } for (const child of __privateGet(this, _nodeWatchers)) { __privateMethod(_a = child.watcher, _Watcher_instances, disconnect_fn).call(_a); } for (const callback of __privateGet(this, _onTextChangedCallbacks)) { callback(this.element, void 0); } for (const { callback } of __privateGet(this, _onAttrChangedCallbacks)) { callback(this.element, void 0); } for (const child of __privateGet(this, _visibilityWatchers)) { __privateMethod(_b = child, _Watcher_instances, disconnect_fn).call(_b); } __privateMethod(this, _Watcher_instances, deregisterNodeObserver_fn).call(this); __privateMethod(this, _Watcher_instances, deregisterTextObserver_fn).call(this); __privateMethod(this, _Watcher_instances, deregisterAttrObservers_fn).call(this); __privateMethod(this, _Watcher_instances, deregisterVisibilityObserver_fn).call(this); while (__privateGet(this, _onRemovedCallbacks).length > 0) { const onRemovedCb = __privateGet(this, _onRemovedCallbacks).shift(); onRemovedCb(); } this.element = null; }; registerNodeObserver_fn = function() { if (__privateGet(this, _nodeObserver) != null) { return; } if (__privateGet(this, _nodeWatchers).length === 0) { return; } const elem = __privateMethod(this, _Watcher_instances, assertElement_fn).call(this); __privateSet(this, _nodeObserver, new MutationObserver((mutations) => { var _a, _b; for (const mut of mutations) { for (const node of mut.addedNodes) { for (const { selector, name, watcher: watcher2 } of __privateGet(this, _nodeWatchers)) { for (const descElem of getSelfOrDescendantsBy( node, selector, name )) { __privateMethod(_a = watcher2, _Watcher_instances, connect_fn).call(_a, descElem, elem); } } } for (const node of mut.removedNodes) { for (const { selector, name, watcher: watcher2 } of __privateGet(this, _nodeWatchers)) { for (const _descElem of getSelfOrDescendantsBy( node, selector, name )) { __privateMethod(_b = watcher2, _Watcher_instances, disconnect_fn).call(_b); } } } } })); __privateGet(this, _nodeObserver).observe(elem, { subtree: true, childList: true }); }; registerTextObserver_fn = function() { if (__privateGet(this, _textObserver) != null) { return; } if (__privateGet(this, _onTextChangedCallbacks).length === 0) { return; } const elem = __privateMethod(this, _Watcher_instances, assertElement_fn).call(this); __privateSet(this, _textObserver, new MutationObserver((_mutations) => { for (const callback of __privateGet(this, _onTextChangedCallbacks)) { callback(elem, elem.textContent); } })); __privateGet(this, _textObserver).observe(elem, { subtree: true, // This is needed when elements are replaced to update their text. childList: true, characterData: true }); }; registerAttrObservers_fn = function() { const elem = __privateMethod(this, _Watcher_instances, assertElement_fn).call(this); for (const handler of __privateGet(this, _onAttrChangedCallbacks)) { if (handler.observer != null) { continue; } const { name, callback } = handler; handler.observer = new MutationObserver((_mutations) => { callback(elem, elem.getAttribute(name)); }); handler.observer.observe(elem, { attributes: true, attributeFilter: [name] }); } }; registerVisibilityObserver_fn = function() { if (__privateGet(this, _visibilityObserver) != null) { return; } if (__privateGet(this, _visibilityWatchers).length === 0) { return; } __privateSet(this, _isVisible, false); const elem = __privateMethod(this, _Watcher_instances, assertElement_fn).call(this); const visibilityAncestor = __privateMethod(this, _Watcher_instances, assertVisibilityAncestor_fn).call(this); __privateSet(this, _visibilityObserver, new IntersectionObserver( (entries) => { var _a, _b; const oldVisible = __privateGet(this, _isVisible); for (const entry of entries) { __privateSet(this, _isVisible, entry.isIntersecting); } if (__privateGet(this, _isVisible) !== oldVisible) { if (__privateGet(this, _isVisible)) { for (const watcher2 of __privateGet(this, _visibilityWatchers)) { __privateMethod(_a = watcher2, _Watcher_instances, connect_fn).call(_a, elem, visibilityAncestor); } } else { for (const watcher2 of __privateGet(this, _visibilityWatchers)) { __privateMethod(_b = watcher2, _Watcher_instances, disconnect_fn).call(_b); } } } }, { root: visibilityAncestor } )); __privateGet(this, _visibilityObserver).observe(elem); }; deregisterNodeObserver_fn = function() { if (__privateGet(this, _nodeObserver) == null) { return; } __privateGet(this, _nodeObserver).disconnect(); __privateSet(this, _nodeObserver, null); }; deregisterTextObserver_fn = function() { if (__privateGet(this, _textObserver) == null) { return; } __privateGet(this, _textObserver).disconnect(); __privateSet(this, _textObserver, null); }; deregisterAttrObservers_fn = function() { for (const handler of __privateGet(this, _onAttrChangedCallbacks)) { if (handler.observer == null) { continue; } handler.observer.disconnect(); handler.observer = null; } }; deregisterVisibilityObserver_fn = function() { if (__privateGet(this, _visibilityObserver) == null) { return; } __privateGet(this, _visibilityObserver).disconnect(); __privateSet(this, _visibilityObserver, null); __privateSet(this, _isVisible, null); }; let Watcher = _Watcher; 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)); } const videoSelector = "#movie_player video"; 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 callMoviePlayerMethod(name, onSuccess, args) { var _a; try { const movieElem = document.getElementById("movie_player"); if (movieElem == null) { console.warn(logPrefix, "movie_player element not found"); return; } const method = (_a = Object.getOwnPropertyDescriptor( movieElem, name )) == null ? void 0 : _a.value; if (method == null) { console.warn( logPrefix, `movie_player element has no ${JSON.stringify(name)} property` ); return; } if (!(typeof method === "function")) { console.warn( logPrefix, `movie_player element property ${JSON.stringify(name)} is not a function` ); return; } const result = method.apply(movieElem, args); if (onSuccess != null) { onSuccess(result); } return result; } catch (e) { console.warn( logPrefix, `movie_player method ${JSON.stringify(name)} failed:`, e ); return; } } 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 } }); } function adIsPlaying(_elem) { console.info(logPrefix, "An ad is playing, muting and speeding up"); const video = getVideoElement(); if (video == null) { return; } mute(video); speedup(video); const cancelRemovedCallback = cancelPlayback(video); return function onRemoved() { cancelRemovedCallback(); }; } function mute(video) { console.debug(logPrefix, "Muting"); video.muted = true; return unmute; } function unmute() { { return; } } function speedup(video) { for (let rate = 16; rate >= 2; rate /= 2) { try { video.playbackRate = rate; break; } catch (e) { console.debug(logPrefix, `Setting playback rate to`, rate, `failed:`, e); } } } function cancelPlayback(video) { let shouldResume = false; function doCancelPlayback() { console.info(logPrefix, "Attempting to cancel playback"); callMoviePlayerMethod("cancelPlayback", () => { shouldResume = true; }); } if (video.paused) { console.debug( logPrefix, "Ad paused, waiting for it to play before canceling playback" ); video.addEventListener("play", doCancelPlayback); } else { doCancelPlayback(); } return function onRemoved() { video.removeEventListener("play", doCancelPlayback); if (shouldResume) { resumePlaybackIfNotAtEnd(); } }; } function resumePlaybackIfNotAtEnd() { const currentTime = callMoviePlayerMethod("getCurrentTime"); const duration = callMoviePlayerMethod("getDuration"); const isAtLiveHead = callMoviePlayerMethod("isAtLiveHead"); if (currentTime == null || duration == null || typeof currentTime !== "number" || typeof duration !== "number" || isNaN(currentTime) || isNaN(duration)) { console.warn( logPrefix, `movie_player methods getCurrentTime/getDuration failed, got time: ${JSON.stringify(currentTime)}, duration: ${JSON.stringify(duration)}` ); return; } if (isAtLiveHead == null || typeof isAtLiveHead !== "boolean") { console.warn( logPrefix, `movie_player method isAtLiveHead failed, got: ${JSON.stringify(isAtLiveHead)}` ); return; } const atEnd = duration - currentTime < 1; if (atEnd && !isAtLiveHead) { console.info( logPrefix, `Video is at the end (${currentTime}/${duration}), not attempting to resume playback` ); return; } console.info(logPrefix, "Attempting to resume playback"); callMoviePlayerMethod("playVideo"); } function click(description) { return (elem) => { if (elem.getAttribute("aria-hidden")) { console.info(logPrefix, "Not clicking (aria-hidden):", description); } else { console.info(logPrefix, "Clicking:", description); elem.click(); } }; } disableVisibilityChecks(); const watcher = new Watcher("body", document.body); const adPlayerOverlayClasses = [ "ytp-ad-player-overlay", "ytp-ad-player-overlay-layout" // Seen since 2024-04-06. ]; for (const adPlayerOverlayClass of adPlayerOverlayClasses) { watcher.klass(adPlayerOverlayClass).onCreated(adIsPlaying); } const adSkipButtonClasses = [ "ytp-ad-skip-button", "ytp-ad-skip-button-modern", // Seen since 2023-11-10. "ytp-skip-ad-button" // Seen since 2024-04-06. ]; for (const adSkipButtonClass of adSkipButtonClasses) { watcher.id("movie_player").klass(adSkipButtonClass).visible().attr("aria-hidden", (elem, value) => { if (value === null) { click(`skip (${adSkipButtonClass})`)(elem); } }); } watcher.klass("ytp-ad-overlay-close-button").visible().onCreated(click("overlay close")); watcher.klass("ytp-featured-product").klass("ytp-suggested-action-badge-dismiss-button-icon").visible().onCreated(click("suggested action close")); watcher.tag("ytmusic-you-there-renderer").tag("button").visible().onCreated(click("are-you-there")); })();