// ==UserScript==
// @license MIT
// @name YouTube 4K 视频过滤器 - 高级版
// @namespace http://tampermonkey.net/
// @version 0.3
// @description 通过检查视频设置菜单识别并筛选YouTube上的4K视频
// @author You
// @match https://www.youtube.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_openInTab
// ==/UserScript==
(function() {
'use strict';
// 存储空间键名
const STORAGE_KEY = 'youtube_4k_videos';
const SCANNING_KEY = 'youtube_4k_scanning';
const buttonId = 'yt-4k-filter-button';
const scanButtonId = 'yt-4k-scan-button';
const badgeClass = 'yt-4k-badge';
// 存储和获取4K视频记录
function saveVideoQuality(videoId, is4K) {
const storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
storage[videoId] = {
is4K: is4K,
timestamp: Date.now()
};
localStorage.setItem(STORAGE_KEY, JSON.stringify(storage));
console.log(`保存视频 ${videoId} 4K状态: ${is4K}`);
}
function getVideoQuality(videoId) {
const storage = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
return storage[videoId] || null;
}
function getAllVideoQualities() {
return JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}');
}
// 检测是否在视频详情页
function isVideoPage() {
return window.location.pathname === '/watch';
}
// 检测是否在频道或浏览页面
function isListingPage() {
const path = window.location.pathname;
return path.includes('/channel/') ||
path.includes('/c/') ||
path.includes('/user/') ||
path.includes('/@') ||
path === '/' ||
path.includes('/results') ||
path.includes('/feed/');
}
// 从URL中提取视频ID
function extractVideoId(url) {
const regExp = /(?:\/|v=)([a-zA-Z0-9_-]{11})(?:\?|&|\/|$)/;
const match = url.match(regExp);
return match ? match[1] : null;
}
// 获取当前视频ID
function getCurrentVideoId() {
if (isVideoPage()) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get('v');
}
return null;
}
// === 视频页面功能 ===
// 检查当前视频是否有4K选项
async function checkCurrentVideoFor4K() {
const videoId = getCurrentVideoId();
if (!videoId) return;
// 如果已经检查过此视频,则跳过
const existingData = getVideoQuality(videoId);
if (existingData) {
console.log(`视频 ${videoId} 已检查过,跳过`);
return;
}
console.log(`检查视频 ${videoId} 是否有4K选项`);
try {
// 等待视频播放器加载
await waitForElement('button.ytp-settings-button');
// 点击设置按钮
const settingsButton = document.querySelector('button.ytp-settings-button');
settingsButton.click();
await sleep(300);
// 点击质量选项
const qualityButton = Array.from(document.querySelectorAll('.ytp-panel-menu .ytp-menuitem'))
.find(el => el.textContent.includes('画质') || el.textContent.includes('Quality'));
if (qualityButton) {
qualityButton.click();
await sleep(300);
// 检查是否有4K选项
const qualityOptions = document.querySelectorAll('.ytp-quality-menu .ytp-menuitem');
let has4K = false;
qualityOptions.forEach(option => {
const text = option.textContent.trim();
if (text.includes('2160p') || text.includes('4K')) {
has4K = true;
console.log(`发现4K选项: ${text}`);
}
});
// 保存结果
saveVideoQuality(videoId, has4K);
// 关闭菜单
const closeButton = document.querySelector('.ytp-popup .ytp-panel-back-button');
if (closeButton) closeButton.click();
await sleep(100);
settingsButton.click();
// 如果是由扫描模式打开的,向列表页面发送消息并关闭此页面
if (isScanningMode()) {
// 将此视频标记为已扫描
markVideoAsScanned(videoId);
// 1秒后关闭页面
setTimeout(() => {
window.close();
}, 1000);
}
}
} catch (error) {
console.error('检查视频质量时出错:', error);
}
}
// 等待元素出现
function waitForElement(selector, timeout = 5000) {
return new Promise((resolve, reject) => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
observer.disconnect();
resolve(document.querySelector(selector));
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// 设置超时
setTimeout(() => {
observer.disconnect();
reject(new Error(`等待元素 ${selector} 超时`));
}, timeout);
});
}
// 睡眠函数
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// === 列表页面功能 ===
// 创建4K过滤按钮
function createFilterButton() {
if (document.getElementById(buttonId)) return;
console.log('创建4K过滤按钮');
const container = document.createElement('div');
container.id = 'yt-4k-filter-container';
container.style.position = 'fixed';
container.style.top = '120px';
container.style.right = '20px';
container.style.zIndex = '9999';
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '10px';
// 过滤按钮
const filterButton = document.createElement('button');
filterButton.id = buttonId;
filterButton.textContent = '显示4K视频';
filterButton.style.padding = '10px 16px';
filterButton.style.backgroundColor = 'red';
filterButton.style.color = 'white';
filterButton.style.border = '2px solid white';
filterButton.style.borderRadius = '4px';
filterButton.style.cursor = 'pointer';
filterButton.style.fontWeight = 'bold';
filterButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.5)';
filterButton.addEventListener('click', toggleFilter);
// 扫描按钮
const scanButton = document.createElement('button');
scanButton.id = scanButtonId;
scanButton.textContent = '扫描4K视频';
scanButton.style.padding = '10px 16px';
scanButton.style.backgroundColor = 'blue';
scanButton.style.color = 'white';
scanButton.style.border = '2px solid white';
scanButton.style.borderRadius = '4px';
scanButton.style.cursor = 'pointer';
scanButton.style.fontWeight = 'bold';
scanButton.style.boxShadow = '0 2px 5px rgba(0,0,0,0.5)';
scanButton.addEventListener('click', startScanningVideos);
// 状态显示
const statusDisplay = document.createElement('div');
statusDisplay.id = 'yt-4k-status';
statusDisplay.style.backgroundColor = 'rgba(0,0,0,0.7)';
statusDisplay.style.color = 'white';
statusDisplay.style.padding = '8px';
statusDisplay.style.borderRadius = '4px';
statusDisplay.style.fontSize = '12px';
statusDisplay.style.display = 'none';
container.appendChild(filterButton);
container.appendChild(scanButton);
container.appendChild(statusDisplay);
document.body.appendChild(container);
}
// 更新状态显示
function updateStatus(message, show = true) {
const statusEl = document.getElementById('yt-4k-status');
if (statusEl) {
statusEl.textContent = message;
statusEl.style.display = show ? 'block' : 'none';
}
}
// 切换过滤状态
let filterActive = false;
function toggleFilter() {
filterActive = !filterActive;
const button = document.getElementById(buttonId);
if (filterActive) {
button.textContent = '显示所有视频';
button.style.backgroundColor = 'green';
const storage = getAllVideoQualities();
let count4K = 0;
// 筛选视频
const videoElements = document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer');
videoElements.forEach(video => {
const linkElement = video.querySelector('a#thumbnail') ||
video.querySelector('a.yt-simple-endpoint');
if (!linkElement || !linkElement.href) return;
const videoId = extractVideoId(linkElement.href);
if (!videoId) return;
const qualityData = storage[videoId];
const is4K = qualityData && qualityData.is4K;
if (is4K) {
count4K++;
// 添加4K标记
if (!video.querySelector(`.${badgeClass}`)) {
addBadgeToVideo(video, linkElement);
}
video.style.display = '';
} else {
video.style.display = 'none';
}
});
updateStatus(`找到 ${count4K} 个4K视频`);
} else {
button.textContent = '显示4K视频';
button.style.backgroundColor = 'red';
// 恢复所有视频显示
document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer').forEach(video => {
video.style.display = '';
});
updateStatus('', false);
}
}
// 为视频添加4K标记
function addBadgeToVideo(videoElement, linkElement) {
const badge = document.createElement('div');
badge.className = badgeClass;
badge.textContent = '4K';
badge.style.position = 'absolute';
badge.style.top = '5px';
badge.style.right = '5px';
badge.style.backgroundColor = 'red';
badge.style.color = 'white';
badge.style.padding = '3px 6px';
badge.style.borderRadius = '3px';
badge.style.fontWeight = 'bold';
badge.style.fontSize = '12px';
badge.style.zIndex = '2';
if (linkElement) {
linkElement.style.position = 'relative';
linkElement.appendChild(badge);
}
}
// 扫描功能
function isScanningMode() {
return localStorage.getItem(SCANNING_KEY) === 'true';
}
function setScanningMode(isScanning) {
localStorage.setItem(SCANNING_KEY, isScanning ? 'true' : 'false');
}
function markVideoAsScanned(videoId) {
const scannedVideos = JSON.parse(localStorage.getItem('scanned_videos') || '[]');
if (!scannedVideos.includes(videoId)) {
scannedVideos.push(videoId);
localStorage.setItem('scanned_videos', JSON.stringify(scannedVideos));
}
}
function isVideoScanned(videoId) {
const scannedVideos = JSON.parse(localStorage.getItem('scanned_videos') || '[]');
return scannedVideos.includes(videoId);
}
async function startScanningVideos() {
const scanButton = document.getElementById(scanButtonId);
if (isScanningMode()) {
// 停止扫描
setScanningMode(false);
scanButton.textContent = '扫描4K视频';
scanButton.style.backgroundColor = 'blue';
updateStatus('扫描已停止', true);
setTimeout(() => updateStatus('', false), 3000);
return;
}
// 开始扫描
setScanningMode(true);
scanButton.textContent = '停止扫描';
scanButton.style.backgroundColor = 'orange';
// 获取页面上的所有视频
const videoElements = document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer');
const totalVideos = videoElements.length;
let scannedCount = 0;
let pendingCount = 0;
// 清除已扫描记录
localStorage.setItem('scanned_videos', '[]');
// 遍历视频元素
for (let i = 0; i < videoElements.length; i++) {
if (!isScanningMode()) break; // 如果扫描被中止则退出
const video = videoElements[i];
const linkElement = video.querySelector('a#thumbnail') ||
video.querySelector('a.yt-simple-endpoint');
if (!linkElement || !linkElement.href) continue;
const videoId = extractVideoId(linkElement.href);
if (!videoId) continue;
// 检查是否已经知道此视频的4K状态
const qualityData = getVideoQuality(videoId);
if (qualityData) {
scannedCount++;
updateStatus(`扫描进度: ${scannedCount}/${totalVideos},已知视频跳过`);
continue;
}
// 在新标签页中打开视频
updateStatus(`扫描进度: ${scannedCount}/${totalVideos},打开视频 ${videoId}`);
// 限制并发标签页数量
pendingCount++;
if (pendingCount >= 3) {
// 等待至少一个视频被扫描
await waitForAnyVideoScanned();
pendingCount--;
}
// 打开新标签页
window.open(`https://www.youtube.com/watch?v=${videoId}`, '_blank');
// 等待一段时间再继续
await sleep(1500);
}
// 扫描完成
setScanningMode(false);
scanButton.textContent = '扫描4K视频';
scanButton.style.backgroundColor = 'blue';
updateStatus('扫描完成!', true);
setTimeout(() => updateStatus('', false), 5000);
}
// 等待任意视频被扫描
function waitForAnyVideoScanned() {
return new Promise(resolve => {
const initialCount = JSON.parse(localStorage.getItem('scanned_videos') || '[]').length;
const checkInterval = setInterval(() => {
const currentCount = JSON.parse(localStorage.getItem('scanned_videos') || '[]').length;
if (currentCount > initialCount || !isScanningMode()) {
clearInterval(checkInterval);
resolve();
}
}, 500);
// 设置超时,避免永久等待
setTimeout(() => {
clearInterval(checkInterval);
resolve();
}, 10000);
});
}
// 标记已知的4K视频
function markKnown4KVideos() {
if (filterActive) return; // 如果正在过滤则跳过
const storage = getAllVideoQualities();
const videoElements = document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer');
videoElements.forEach(video => {
const linkElement = video.querySelector('a#thumbnail') ||
video.querySelector('a.yt-simple-endpoint');
if (!linkElement || !linkElement.href) return;
const videoId = extractVideoId(linkElement.href);
if (!videoId) return;
const qualityData = storage[videoId];
if (qualityData && qualityData.is4K && !video.querySelector(`.${badgeClass}`)) {
addBadgeToVideo(video, linkElement);
}
});
}
// === 初始化 ===
// 根据页面类型初始化功能
function init() {
if (isVideoPage()) {
// 视频详情页
checkCurrentVideoFor4K();
} else if (isListingPage()) {
// 视频列表页
createFilterButton();
markKnown4KVideos();
// 监听DOM变化
const observer = new MutationObserver(mutations => {
if (!document.getElementById(buttonId)) {
createFilterButton();
}
// 延迟执行,避免频繁调用
clearTimeout(window.markTimeout);
window.markTimeout = setTimeout(() => {
markKnown4KVideos();
if (filterActive) toggleFilter();
}, 1000);
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
// 启动程序
function start() {
console.log('YouTube 4K 过滤器启动');
// 尝试多次初始化,确保在页面加载后运行
setTimeout(init, 5000);
// 监听URL变化
let lastUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== lastUrl) {
lastUrl = location.href;
// URL变化时重新初始化
setTimeout(init, 1500);
}
});
urlObserver.observe(document.body, { subtree: true, childList: true });
}
start();
})();