// ==UserScript==
// @name YouTube AI广告助手 (调试版)
// @namespace http://tampermonkey.net/
// @version 2.0
// @description AI动态更新选择器 + 自动静音加速广告 + 自动跳过广告按钮 + 首页广告隐藏 + 调试面板实时状态显示
// @author Little Fool
// @match *://www.youtube.com/*
// @match *://m.youtube.com/*
// @match *://music.youtube.com/*
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_xmlhttpRequest
// @run-at document-end
// ==/UserScript==
(function () {
'use strict';
// =============================
// 1. API 管理
// =============================
const API_URL = 'https://openkey.cloud/v1/chat/completions';
function getApiKey() {
return GM_getValue("YT_AI_API_KEY", null);
}
// =============================
// 2. 日志
// =============================
const log = (...args) => console.log("[YT广告助手]", ...args);
// =============================
// 3. 广告规则
// =============================
const LOCAL_RULES = [
'.ytp-ad-module', '.ytp-ad-overlay-container', '.ytp-ad-player-overlay',
'#player-ads', '.ad-showing .video-ads', 'ytd-ad-slot-renderer',
'ytd-display-ad-renderer', 'ytd-in-feed-ad-layout-renderer',
'ytd-action-companion-ad-renderer', 'ytd-promoted-video-renderer',
'#masthead-ad', 'ytd-banner-promo-renderer', 'ytd-rich-item-renderer[is-ad]',
];
let selectors = new Set(LOCAL_RULES);
// =============================
// 4. AI 更新选择器
// =============================
async function fetchAdSelectorsFromAI() {
const API_KEY = getApiKey();
if (!API_KEY) {
log("⚠️ 未设置 API KEY,请运行 GM_setValue('YT_AI_API_KEY','你的key')");
return;
}
try {
const res = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: 'POST',
url: API_URL,
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
data: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{ role: 'user', content: "请提供最新的YouTube广告CSS选择器数组,仅返回JSON数组" }
],
temperature: 0.2
}),
onload: resolve,
onerror: reject
});
});
const result = JSON.parse(res.responseText);
const selectorsText = result?.choices?.[0]?.message?.content?.trim();
const newSelectors = JSON.parse(selectorsText);
if (Array.isArray(newSelectors) && newSelectors.length > 0) {
selectors = new Set([...LOCAL_RULES, ...newSelectors]);
GM_setValue("yt_ad_selectors", Array.from(selectors));
log("✅ AI 更新选择器成功", selectors.size);
}
} catch (err) {
log("⚠️ AI 更新失败,使用缓存/本地规则", err);
loadLocalSelectors();
}
}
function loadLocalSelectors() {
const cached = GM_getValue("yt_ad_selectors", []);
if (Array.isArray(cached) && cached.length > 0) {
selectors = new Set(cached);
log("📥 使用缓存选择器", selectors.size);
} else {
selectors = new Set(LOCAL_RULES);
GM_setValue("yt_ad_selectors", LOCAL_RULES);
log("📥 使用本地默认规则");
}
}
// =============================
// 5. 调试面板
// =============================
function createDebugPanel() {
const panel = document.createElement('div');
panel.id = 'yt-ad-debug-panel';
panel.style.cssText = `
position: fixed;
bottom: 80px;
right: 20px;
width: 280px;
max-height: 300px;
overflow-y: auto;
background: rgba(0,0,0,0.8);
color: #0f0;
font-size: 12px;
line-height: 1.4;
padding: 10px;
border-radius: 8px;
z-index: 999999;
display: none;
`;
document.body.appendChild(panel);
// 浮动按钮
const toggleBtn = document.createElement('button');
toggleBtn.innerText = '调试';
toggleBtn.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
width: 60px;
height: 40px;
border-radius: 8px;
border: none;
background: #ff5722;
color: #fff;
font-size: 14px;
cursor: pointer;
z-index: 999999;
`;
document.body.appendChild(toggleBtn);
toggleBtn.addEventListener('click', () => {
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
});
return panel;
}
const debugPanel = createDebugPanel();
function updateDebugPanel(info) {
if (!debugPanel) return;
debugPanel.innerHTML = `
<div><b>🎯 YouTube 广告调试面板</b></div>
<div>检测到广告元素: ${info.ads.length}</div>
<div>最新广告选择器: ${info.lastSelector || '无'}</div>
<div>是否加速: ${info.accelerated ? '✅ 已加速' : '❌ 未加速'}</div>
<div>是否点击跳过: ${info.skipped ? '✅ 已跳过' : '❌ 无按钮'}</div>
`;
}
// =============================
// 6. 广告处理逻辑
// =============================
function getVideo() {
return document.querySelector("video");
}
function hideAds() {
let detected = [];
selectors.forEach(sel => {
document.querySelectorAll(sel).forEach(el => {
el.style.display = 'none';
el.style.opacity = '0.01';
el.style.pointerEvents = 'none';
detected.push(sel);
});
});
updateDebugPanel({
ads: detected,
lastSelector: detected[0] || null,
accelerated: false,
skipped: false
});
}
function clickSkip() {
const btn = document.querySelector('.ytp-ad-skip-button');
let skipped = false;
if (btn && btn.offsetParent !== null) {
btn.click();
skipped = true;
log("⏭️ 自动跳过广告");
}
updateDebugPanel({
ads: [],
lastSelector: '.ytp-ad-skip-button',
accelerated: false,
skipped
});
}
function accelerateAd() {
const video = getVideo();
if (!video) return;
const adElement = document.querySelector('.ad-showing');
let accelerated = false;
if (adElement && video.currentTime > 1.5) {
video.playbackRate = 16;
video.muted = true;
accelerated = true;
log('🎬 广告中:静音 + 加速');
} else {
video.playbackRate = 1;
video.muted = false;
}
updateDebugPanel({
ads: [],
lastSelector: '.ad-showing',
accelerated,
skipped: false
});
}
function observePage() {
const observer = new MutationObserver(() => {
hideAds();
clickSkip();
accelerateAd();
});
observer.observe(document.body, { childList: true, subtree: true });
}
// =============================
// 7. 初始化
// =============================
function init() {
loadLocalSelectors();
fetchAdSelectorsFromAI();
setInterval(fetchAdSelectorsFromAI, 60 * 60 * 1000); // 每小时更新一次
observePage();
log("🚀 YouTube AI 广告助手启动完成");
}
init();
})();