Prioritize "Soft Skip" (Mute+Seek/Button), fallback to "Reload" with cooldown; uses MutationObserver; reduces detection risk. / 优先“软跳过”(按钮/静音+跳尾),失败时执行带冷却的“重载”;使用 MutationObserver + 兜底定时器;降低被风控概率。
当前为
// ==UserScript==
// @name YouTube Ad Auto-Skipper (VoidMuser)
// @version 2025.11.23
// @match https://www.youtube.com/*
// @match https://m.youtube.com/*
// @match https://music.youtube.com/*
// @exclude https://studio.youtube.com/*
// @grant none
// @license MIT
// @noframes
// @run-at document-idle
// @namespace
// @description Prioritize "Soft Skip" (Mute+Seek/Button), fallback to "Reload" with cooldown; uses MutationObserver; reduces detection risk. / 优先“软跳过”(按钮/静音+跳尾),失败时执行带冷却的“重载”;使用 MutationObserver + 兜底定时器;降低被风控概率。
// ==/UserScript==
(function () {
'use strict';
const DEBUG = false;
const CSS_HIDE_SELECTORS = [
'#player-ads',
'#masthead-ad',
'#panels > ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-ads"]',
'.ytp-featured-product',
'.ytp-ad-overlay-container',
'.ytp-ad-text-overlay',
'.ytp-ad-image-overlay',
'.ytp-paid-content-overlay',
'.yt-mealbar-promo-renderer',
'ytd-merch-shelf-renderer',
'ytmusic-mealbar-promo-renderer',
'ytmusic-statement-banner-renderer'
];
// 【修改】缩短轮询时间,避免两个广告之间发呆太久
const CHECK_DEBOUNCE_MS = 100;
const INTERVAL_CHECK_MS = 800;
const RELOAD_BASE_COOLDOWN_MS = 5000;
const RELOAD_MAX_BACKOFF_MS = 60000;
const SEEK_EPSILON = 0.1;
const state = {
skipping: false,
lastReloadAt: 0,
reloadAttempts: 0,
scheduled: false,
checkTimer: null
};
const log = (...args) => { if (DEBUG) console.log('[ASYA]', ...args); };
const isMobile = location.hostname === 'm.youtube.com';
const isMusic = location.hostname === 'music.youtube.com';
function addCss() {
const sel = CSS_HIDE_SELECTORS.join(',');
if (!sel) return;
const style = document.createElement('style');
// 关键:只用 opacity:0,不移除 DOM,减少被检测风险
style.textContent = `${sel}{opacity:0 !important; pointer-events:none !important; z-index:-1 !important;}`;
document.head ? document.head.appendChild(style) : document.documentElement.appendChild(style);
}
function querySkipButton() {
// 增加了一些常见的跳过按钮 class
const byClass = document.querySelector('.ytp-ad-skip-button, .ytp-ad-skip-button-modern, .ytp-ad-skip-button-slot, .ytp-ad-skip-button-container button');
if (byClass) return byClass;
const btn = [...document.querySelectorAll('button')].find(b => {
// 获取按钮文本或 aria-label
const t = (b.getAttribute('aria-label') || b.textContent || '').trim();
return /skip ad|skip ads|跳过|跳過|繼續|Skip/i.test(t);
});
return btn || null;
}
function detectAdContext() {
const adShowing = !!document.querySelector('.ad-showing');
const pieCountdown = !!document.querySelector('.ytp-ad-timed-pie-countdown-container');
const survey = !!document.querySelector('.ytp-ad-survey-questions');
const skipBtn = querySkipButton();
// 只要有跳过按钮,或者处于广告倒计时,都认为是广告中
const adLikely = adShowing || pieCountdown || survey || !!skipBtn;
return { adShowing, pieCountdown, survey, skipBtn, adLikely };
}
function getPlayers() {
const moviePlayerEl = document.querySelector('#movie_player') || null;
let playerEl = null;
let player = null;
if (isMobile || isMusic) {
playerEl = moviePlayerEl;
player = moviePlayerEl;
} else {
const ytd = document.querySelector('#ytd-player');
playerEl = ytd || moviePlayerEl || null;
if (ytd && typeof ytd.getPlayer === 'function') {
try { player = ytd.getPlayer(); } catch (_) {}
}
if (!player && moviePlayerEl) player = moviePlayerEl;
}
return { moviePlayerEl, playerEl, player };
}
function trySoftSkip(players, ctx) {
// 1. 人性化点跳过按钮
if (ctx.skipBtn) {
// 随机延迟 300~800ms,模拟人类反应
const delay = 300 + Math.random() * 500;
setTimeout(() => {
try {
if (ctx.skipBtn && ctx.skipBtn.click) {
ctx.skipBtn.click(); // 简单直接的 click 通常最有效
log('Human-like clicked skip button after', delay, 'ms');
// 【核心修复】点击完按钮后,立即安排一次检查
// 解决连续广告时,脚本还在等下一次轮询的问题
setTimeout(() => {
scheduleCheck(0);
}, 50);
}
} catch (e) {
log('Click failed', e);
}
}, delay);
return true; // 只要安排了点击,就返回 true
}
// 2. 视频加速 + 静音 + 进度条拖到最后 (针对不能跳过的强制广告)
if (ctx.adLikely) {
const adVideo = document.querySelector('video.html5-main-video');
if (adVideo && !Number.isNaN(adVideo.duration) && adVideo.duration > 0) {
// 只有在确实是广告时才操作(防止误伤正片)
// 通常广告时播放器会有 .ad-showing 类,或者我们在 detectAdContext 判定了 adLikely
try {
// 稍微保守一点,不要设为 currentTime = duration,留一点点尾巴让它自然结束触发 ended 事件
const targetTime = Math.max(0, adVideo.duration - SEEK_EPSILON);
// 如果当前已经在末尾了,就不重复操作
if (adVideo.currentTime < targetTime) {
adVideo.muted = true;
adVideo.playbackRate = 16; // 16倍速
adVideo.currentTime = targetTime;
log('Muted + 16x + seek to end');
return true;
}
} catch (_) {}
}
}
return false;
}
// 完整的主循环逻辑
function skipAd() {
// 防止重入,虽然单线程JS不用太担心,但逻辑上清晰点
state.skipping = true;
const ctx = detectAdContext();
if (ctx.adLikely) {
log('Ad detected. Context:', ctx);
const players = getPlayers();
const handled = trySoftSkip(players, ctx);
if (handled) {
// 如果处理了,过一会再查一下是否还有残余
scheduleCheck(1000);
}
} else {
// 没广告,重置一些状态(可选)
state.skipping = false;
}
}
// 调度器:防抖 + 节流
function scheduleCheck(delayMs = CHECK_DEBOUNCE_MS) {
if (state.checkTimer) clearTimeout(state.checkTimer);
state.checkTimer = setTimeout(() => {
skipAd();
state.checkTimer = null;
}, delayMs);
}
// 启动观察者,监控页面变动(解决页面不刷新切视频的问题)
function setupObserver() {
const observer = new MutationObserver((mutations) => {
// 简单粗暴:只要有 DOM 变化,且最近没在查,就查一次
// 但为了性能,我们只在特定节点变化时触发
for (const m of mutations) {
if (m.target.classList &&
(m.target.classList.contains('ad-showing') ||
m.target.classList.contains('ytp-ad-skip-button-container'))) {
scheduleCheck(0);
return;
}
}
// 兜底:如果有变动,最慢 800ms 也会轮询一次,这里可以不频繁触发
});
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style'] // 只关注 class 和 style 变化
});
}
// === 入口 ===
addCss();
setupObserver();
scheduleCheck(0);
// 即使观察者失效,也有定时的心跳检测作为保底
setInterval(() => scheduleCheck(), INTERVAL_CHECK_MS);
log('Script loaded (Fixed Version)');
})();