YouTube 4K 视频过滤器 - 高级版

通过检查视频设置菜单识别并筛选YouTube上的4K视频

// ==UserScript==
// @license MIT
// @name         YouTube 4K 视频过滤器 - 高级版
// @namespace    http://tampermonkey.net/
// @version      0.4
// @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');
            
            // 检查是否在播放广告,如果是,等待广告结束
            await waitForAdToFinish();
            
            // 点击设置按钮
            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);
            
            // 如果是扫描模式,仍然标记为已扫描,避免卡住
            if (isScanningMode()) {
                markVideoAsScanned(videoId);
                setTimeout(() => {
                    window.close();
                }, 1000);
            }
        }
    }
    
    // 检查并等待广告结束
    async function waitForAdToFinish() {
        // 检查是否存在广告标识
        const isAdPlaying = () => {
            return Boolean(
                document.querySelector('.ytp-ad-text') || 
                document.querySelector('.ytp-ad-preview-container') ||
                document.querySelector('.ytp-ad-skip-button') ||
                document.querySelector('[class*="ytp-ad"]')?.textContent?.includes('广告')
            );
        };
        
        let adWaitTime = 0;
        const maxWaitTime = 90000; // 最多等待90秒
        const checkInterval = 1000; // 每秒检查一次
        
        if (isAdPlaying()) {
            console.log('检测到广告播放,等待广告结束...');
            
            // 等待广告结束
            while (isAdPlaying() && adWaitTime < maxWaitTime) {
                await sleep(checkInterval);
                adWaitTime += checkInterval;
                
                // 如果找到跳过广告按钮,尝试点击
                const skipButton = document.querySelector('.ytp-ad-skip-button');
                if (skipButton && skipButton.offsetParent !== null) {
                    console.log('点击跳过广告按钮');
                    skipButton.click();
                    await sleep(500);
                }
            }
            
            if (adWaitTime >= maxWaitTime) {
                console.log('广告等待超时,继续执行');
            } else {
                console.log('广告播放结束');
                // 广告结束后额外等待一小段时间,确保界面恢复正常
                await sleep(1000);
            }
        }
    }
    
    // 等待元素出现
    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.8)';
        statusDisplay.style.color = 'white';
        statusDisplay.style.padding = '10px';
        statusDisplay.style.borderRadius = '4px';
        statusDisplay.style.fontSize = '14px';
        statusDisplay.style.fontWeight = 'bold';
        statusDisplay.style.minWidth = '200px';
        statusDisplay.style.boxShadow = '0 2px 10px rgba(0,0,0,0.3)';
        statusDisplay.style.display = 'none';
        statusDisplay.style.transition = 'opacity 0.1s';
        
        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';
            
            // 强制重新渲染状态框,确保内容更新
            statusEl.style.opacity = '0.99';
            setTimeout(() => {
                statusEl.style.opacity = '1';
            }, 50);
            
            // 将状态消息记录到控制台,便于调试
            console.log(`状态更新: ${message}`);
        }
    }
    
    // 切换过滤状态
    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) {
        // 检查是否已存在标签
        if (videoElement.querySelector(`.${badgeClass}`)) return;
        
        // 直接在视频元素上添加标记,而不是在缩略图链接上
        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 = '100';
        badge.style.pointerEvents = 'none'; // 防止点击干扰
        
        // 查找更合适的容器
        const thumbnailContainer = videoElement.querySelector('#thumbnail') || 
                                 videoElement.querySelector('.yt-simple-endpoint');
        
        if (thumbnailContainer) {
            // 使用relative定位,这样不会破坏布局
            const currentPosition = window.getComputedStyle(thumbnailContainer).position;
            if (currentPosition === 'static') {
                thumbnailContainer.style.position = 'relative';
            }
            
            // 确保缩略图容器已有相对或绝对定位
            thumbnailContainer.appendChild(badge);
        } else {
            // 备用方法:如果找不到合适的容器,创建一个覆盖层
            const overlay = document.createElement('div');
            overlay.style.position = 'absolute';
            overlay.style.top = '0';
            overlay.style.left = '0';
            overlay.style.width = '100%';
            overlay.style.height = '100%';
            overlay.style.pointerEvents = 'none';
            overlay.style.zIndex = '10';
            overlay.appendChild(badge);
            
            // 找到卡片的第一个子元素并插入
            const firstChild = videoElement.firstElementChild;
            if (firstChild) {
                const currentPosition = window.getComputedStyle(firstChild).position;
                if (currentPosition === 'static') {
                    firstChild.style.position = 'relative';
                }
                firstChild.appendChild(overlay);
            }
        }
    }
    
    // 扫描功能
    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 scrollToLoadAllVideos() {
        updateStatus('正在加载所有视频...', true);
        
        // 记录初始视频数量
        let previousCount = document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer').length;
        let sameCountTimes = 0;
        let maxScrollAttempts = 100; // 最大滚动尝试次数
        let scrollAttempts = 0;
        
        // 循环滚动直到无法加载更多视频
        while (scrollAttempts < maxScrollAttempts) {
            // 滚动到页面底部
            window.scrollTo(0, document.body.scrollHeight);
            await sleep(1500); // 等待内容加载
            
            // 获取当前视频数量
            const currentCount = document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer').length;
            
            // 更新状态
            updateStatus(`正在加载所有视频... (已加载 ${currentCount} 个)`, true);
            
            // 如果视频数量没有增加
            if (currentCount === previousCount) {
                sameCountTimes++;
                // 如果连续5次滚动都没有新视频,认为已加载完毕
                if (sameCountTimes >= 5) {
                    break;
                }
            } else {
                // 重置计数器
                sameCountTimes = 0;
                previousCount = currentCount;
            }
            
            scrollAttempts++;
        }
        
        // 滚动回页面顶部
        window.scrollTo(0, 0);
        
        // 返回加载的视频数量
        return document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer').length;
    }
    
    // 修改扫描函数,先加载所有视频再扫描
    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';
        
        try {
            // 首先滚动加载所有视频
            const totalVideos = await scrollToLoadAllVideos();
            updateStatus(`加载完成,共发现 ${totalVideos} 个视频`, true);
            await sleep(1000);
            
            // 获取页面上的所有视频
            const videoElements = document.querySelectorAll('ytd-grid-video-renderer, ytd-rich-item-renderer, ytd-video-renderer');
            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},已知视频跳过`, true);
                    continue;
                }
                
                // 在新标签页中打开视频
                updateStatus(`扫描进度: ${scannedCount}/${totalVideos},打开视频 ${videoId}`, true);
                
                // 限制并发标签页数量
                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);
        } catch (error) {
            console.error('扫描过程中出错:', error);
            setScanningMode(false);
            scanButton.textContent = '扫描4K视频';
            scanButton.style.backgroundColor = 'blue';
            updateStatus('扫描过程中出错', true);
        }
    }
    
    // 等待任意视频被扫描
    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变化
            createDOMObserver();
        }
    }
    
    // 启动程序
    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 });
    }
    
    // 更安全的观察者实现,避免频繁DOM更新
    function createDOMObserver() {
        // 移除任何现有的观察器
        if (window.ytObserver) {
            window.ytObserver.disconnect();
        }
        
        // 创建新的观察器
        window.ytObserver = new MutationObserver(mutations => {
            // 节流:避免太频繁地更新
            clearTimeout(window.observerTimeout);
            window.observerTimeout = setTimeout(() => {
                if (!document.getElementById(buttonId)) {
                    createFilterButton();
                }
                
                // 仅在必要时更新视频标记
                if (!filterActive) {
                    markKnown4KVideos();
                }
            }, 1000);
        });
        
        // 开始观察DOM变化
        window.ytObserver.observe(document.body, {
            childList: true,
            subtree: true
        });
    }
    
    start();
})(); 

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址