Greasy Fork

来自缓存

Greasy Fork is available in English.

YouTube自动中文字幕切换

自动在YouTube视频中开启字幕并选择中文翻译

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube自动中文字幕切换
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  自动在YouTube视频中开启字幕并选择中文翻译
// @author       麻广森
// @match        https://www.youtube.com/watch*
// @grant        none
// @run-at       document-idle
// @license       MIT
// ==/UserScript==

(function() {
    'use strict';

    // 配置选项
    const CONFIG = {
        // 等待时间(毫秒)
        waitTime: 1000,
        // 最大等待时间(毫秒)
        maxWaitTime: 10000,
        // 是否显示日志
        showLogs: true,
        // 自动执行
        autoExecute: true
    };

    // 日志函数
    function log(message, type = 'info') {
        if (!CONFIG.showLogs) return;
        const prefix = '[YouTube自动中文字幕]';
        const styles = {
            info: 'color: #2196F3; font-weight: bold;',
            success: 'color: #4CAF50; font-weight: bold;',
            warning: 'color: #FF9800; font-weight: bold;',
            error: 'color: #F44336; font-weight: bold;'
        };
        console.log(`%c${prefix} ${message}`, styles[type] || styles.info);
    }

    // 等待元素出现的函数
    function waitForElement(selector, timeout = CONFIG.maxWaitTime) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();

            const checkElement = () => {
                const element = document.querySelector(selector);
                if (element) {
                    resolve(element);
                    return;
                }

                if (Date.now() - startTime > timeout) {
                    reject(new Error(`等待元素超时: ${selector}`));
                    return;
                }

                setTimeout(checkElement, 100);
            };

            checkElement();
        });
    }

    // 等待多个元素中的一个出现
    function waitForAnyElement(selectors, timeout = CONFIG.maxWaitTime) {
        return new Promise((resolve, reject) => {
            const startTime = Date.now();

            const checkElements = () => {
                for (const selector of selectors) {
                    const element = document.querySelector(selector);
                    if (element) {
                        resolve({ element, selector });
                        return;
                    }
                }

                if (Date.now() - startTime > timeout) {
                    reject(new Error(`等待任何元素超时: ${selectors.join(', ')}`));
                    return;
                }

                setTimeout(checkElements, 100);
            };

            checkElements();
        });
    }

    // 检查字幕按钮状态
    function checkSubtitleButtonState() {
        const subtitleButton = document.querySelector('button.ytp-subtitles-button');
        if (!subtitleButton) {
            log('❌ 未找到字幕按钮', 'error');
            return null;
        }

        const isOn = subtitleButton.classList.contains('ytp-subtitles-button-on');
        log(`字幕按钮状态: ${isOn ? '✅ 已开启' : '❌ 已关闭'}`);
        return { button: subtitleButton, isOn };
    }

    // 开启字幕
    async function enableSubtitles() {
        try {
            const state = checkSubtitleButtonState();
            if (!state) return false;

            if (!state.isOn) {
                log('字幕未开启,正在点击字幕按钮...');
                state.button.click();

                // 等待状态更新
                await new Promise(resolve => setTimeout(resolve, CONFIG.waitTime));

                const newState = checkSubtitleButtonState();
                if (newState && newState.isOn) {
                    log('✅ 字幕已成功开启', 'success');
                    return true;
                } else {
                    log('⚠️ 点击后字幕仍未开启', 'warning');
                    return false;
                }
            } else {
                log('✅ 字幕已开启', 'success');
                return true;
            }
        } catch (error) {
            log(`❌ 开启字幕失败: ${error.message}`, 'error');
            return false;
        }
    }

    // 打开设置菜单
    async function openSettingsMenu() {
        try {
            const settingsButton = await waitForElement('button.ytp-settings-button');
            log('找到设置按钮,正在打开菜单...');
            settingsButton.click();

            // 等待菜单出现
            await new Promise(resolve => setTimeout(resolve, CONFIG.waitTime));

            const settingsMenu = document.querySelector('.ytp-settings-menu');
            if (settingsMenu) {
                log('✅ 设置菜单已打开', 'success');
                return { button: settingsButton, menu: settingsMenu };
            } else {
                log('❌ 设置菜单未出现', 'error');
                return null;
            }
        } catch (error) {
            log(`❌ 打开设置菜单失败: ${error.message}`, 'error');
            return null;
        }
    }

    // 在设置菜单中查找并点击字幕选项
    async function clickCaptionOption(settingsMenu) {
        try {
            const menuItems = settingsMenu.querySelectorAll('.ytp-menuitem');
            log(`设置菜单中有 ${menuItems.length} 个选项`);

            let captionMenuItem = null;
            menuItems.forEach((item, index) => {
                const label = item.querySelector('.ytp-menuitem-label');
                if (label) {
                    const text = label.textContent.trim();
                    log(`选项 ${index}: ${text}`);
                    if (text.includes('字幕') || text.includes('CC') || text.includes('Subtitles')) {
                        captionMenuItem = item;
                        log(`✅ 找到字幕选项: "${text}"`);
                    }
                }
            });

            if (captionMenuItem) {
                captionMenuItem.click();
                log('✅ 已点击字幕选项', 'success');

                // 等待子菜单出现
                await new Promise(resolve => setTimeout(resolve, CONFIG.waitTime));
                return true;
            } else {
                log('❌ 未找到字幕选项', 'error');
                return false;
            }
        } catch (error) {
            log(`❌ 查找字幕选项失败: ${error.message}`, 'error');
            return false;
        }
    }

    // 在字幕子菜单中查找并点击自动翻译选项
    async function clickTranslateOption() {
        try {
            // 重新获取菜单项(因为子菜单已经打开)
            const menuItems = document.querySelectorAll('.ytp-settings-menu .ytp-menuitem');
            log(`字幕子菜单中有 ${menuItems.length} 个选项`);

            let translateMenuItem = null;
            menuItems.forEach((item, index) => {
                const label = item.querySelector('.ytp-menuitem-label');
                if (label) {
                    const text = label.textContent.trim();
                    log(`子菜单项 ${index}: ${text}`);
                    if (text.includes('自动翻译') || text.includes('Auto-translate') || text.includes('Translate')) {
                        translateMenuItem = item;
                        log(`✅ 找到自动翻译选项: "${text}"`);
                    }
                }
            });

            if (translateMenuItem) {
                translateMenuItem.click();
                log('✅ 已点击自动翻译选项', 'success');

                // 等待语言列表出现
                await new Promise(resolve => setTimeout(resolve, CONFIG.waitTime));
                return true;
            } else {
                log('❌ 未找到自动翻译选项', 'error');
                return false;
            }
        } catch (error) {
            log(`❌ 查找自动翻译选项失败: ${error.message}`, 'error');
            return false;
        }
    }

    // 在翻译语言列表中选择中文
    async function selectChineseLanguage() {
        try {
            // 重新获取菜单项(因为翻译语言列表已经打开)
            const menuItems = document.querySelectorAll('.ytp-settings-menu .ytp-menuitem');
            log(`翻译语言列表中有 ${menuItems.length} 个选项`);

            let chineseItem = null;
            menuItems.forEach((item, index) => {
                const label = item.querySelector('.ytp-menuitem-label');
                if (label) {
                    const text = label.textContent.trim();
                    log(`语言选项 ${index}: ${text}`);
                    if (text.includes('中文') || text.includes('Chinese') || text.includes('简体') || text.includes('繁體')) {
                        chineseItem = item;
                        log(`✅ 找到中文选项: "${text}"`);
                    }
                }
            });

            if (chineseItem) {
                chineseItem.click();
                log('✅ 已选择中文翻译!', 'success');
                return true;
            } else {
                log('❌ 未找到中文选项', 'error');
                return false;
            }
        } catch (error) {
            log(`❌ 选择中文语言失败: ${error.message}`, 'error');
            return false;
        }
    }

    // 关闭设置菜单
    async function closeSettingsMenu() {
        try {
            const settingsButton = await waitForElement('button.ytp-settings-button');
            settingsButton.click();
            log('✅ 设置菜单已关闭', 'success');
            return true;
        } catch (error) {
            log(`❌ 关闭设置菜单失败: ${error.message}`, 'error');
            return false;
        }
    }

    // 验证最终状态
    async function verifyFinalState() {
        try {
            const player = await waitForElement('#movie_player');
            if (!player || typeof player.getOption !== 'function') {
                log('❌ 无法访问播放器API', 'error');
                return false;
            }

            const currentTrack = player.getOption('captions', 'track');
            if (currentTrack) {
                log(`当前字幕轨道: ${currentTrack.languageName || currentTrack.languageCode}`, 'info');

                // 检查是否是中文
                if (currentTrack.languageCode && currentTrack.languageCode.includes('zh')) {
                    log('✅ 当前显示的是中文翻译字幕!', 'success');
                    return true;
                } else {
                    log('⚠️ 当前显示的不是中文翻译字幕', 'warning');
                    return false;
                }
            } else {
                log('⚠️ 当前没有激活的字幕轨道', 'warning');
                return false;
            }
        } catch (error) {
            log(`❌ 验证状态失败: ${error.message}`, 'error');
            return false;
        }
    }

    // 主执行函数
    async function main() {
        log('开始执行自动中文字幕切换脚本...', 'info');

        try {
            // 步骤1: 等待播放器加载
            log('步骤1: 等待播放器加载...');
            await waitForElement('#movie_player');
            log('✅ 播放器已加载', 'success');

            // 步骤2: 开启字幕
            log('步骤2: 开启字幕...');
            const subtitleEnabled = await enableSubtitles();
            if (!subtitleEnabled) {
                log('❌ 无法开启字幕,脚本终止', 'error');
                return;
            }

            // 步骤3: 打开设置菜单
            log('步骤3: 打开设置菜单...');
            const settings = await openSettingsMenu();
            if (!settings) {
                log('❌ 无法打开设置菜单,脚本终止', 'error');
                return;
            }

            // 步骤4: 点击字幕选项
            log('步骤4: 点击字幕选项...');
            const captionClicked = await clickCaptionOption(settings.menu);
            if (!captionClicked) {
                log('❌ 无法点击字幕选项,脚本终止', 'error');
                await closeSettingsMenu();
                return;
            }

            // 步骤5: 点击自动翻译选项
            log('步骤5: 点击自动翻译选项...');
            const translateClicked = await clickTranslateOption();
            if (!translateClicked) {
                log('❌ 无法点击自动翻译选项,脚本终止', 'error');
                await closeSettingsMenu();
                return;
            }

            // 步骤6: 选择中文语言
            log('步骤6: 选择中文语言...');
            const chineseSelected = await selectChineseLanguage();
            if (!chineseSelected) {
                log('❌ 无法选择中文语言,脚本终止', 'error');
                await closeSettingsMenu();
                return;
            }

            // 步骤7: 关闭设置菜单
            log('步骤7: 关闭设置菜单...');
            await closeSettingsMenu();

            // 步骤8: 验证最终状态
            log('步骤8: 验证最终状态...');
            const success = await verifyFinalState();

            if (success) {
                log('🎉 脚本执行成功!已自动开启中文字幕', 'success');
            } else {
                log('⚠️ 脚本执行完成,但可能未成功开启中文字幕', 'warning');
            }

        } catch (error) {
            log(`❌ 脚本执行失败: ${error.message}`, 'error');
        }
    }

    // 创建UI控制面板
    function createUI() {
        const panel = document.createElement('div');
        panel.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 15px;
            border-radius: 8px;
            z-index: 9999;
            font-family: Arial, sans-serif;
            min-width: 200px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
        `;

        panel.innerHTML = `
            <h3 style="margin: 0 0 10px 0; color: #4CAF50;">YouTube自动中文字幕</h3>
            <div style="margin-bottom: 10px; font-size: 12px;">
                <label>
                    <input type="checkbox" id="autoExecute" ${CONFIG.autoExecute ? 'checked' : ''}> 自动执行
                </label>
            </div>
            <div style="margin-bottom: 10px; font-size: 12px;">
                <label>
                    <input type="checkbox" id="showLogs" ${CONFIG.showLogs ? 'checked' : ''}> 显示日志
                </label>
            </div>
            <button id="startScript" style="
                background: #4CAF50;
                color: white;
                border: none;
                padding: 8px 15px;
                border-radius: 4px;
                cursor: pointer;
                margin-right: 5px;
                font-size: 12px;
            ">立即执行</button>
            <button id="hidePanel" style="
                background: #666;
                color: white;
                border: none;
                padding: 8px 15px;
                border-radius: 4px;
                cursor: pointer;
                font-size: 12px;
            ">隐藏</button>
            <div id="status" style="margin-top: 10px; font-size: 11px; color: #ccc;"></div>
        `;

        document.body.appendChild(panel);

        // 绑定事件
        panel.querySelector('#autoExecute').addEventListener('change', (e) => {
            CONFIG.autoExecute = e.target.checked;
            log(`自动执行已${CONFIG.autoExecute ? '开启' : '关闭'}`);
        });

        panel.querySelector('#showLogs').addEventListener('change', (e) => {
            CONFIG.showLogs = e.target.checked;
            log(`日志显示已${CONFIG.showLogs ? '开启' : '关闭'}`);
        });

        panel.querySelector('#startScript').addEventListener('click', () => {
            main();
        });

        panel.querySelector('#hidePanel').addEventListener('click', () => {
            panel.style.display = 'none';
            // 显示一个浮动按钮来重新显示面板
            const showButton = document.createElement('button');
            showButton.textContent = '显示控制面板';
            showButton.style.cssText = `
                position: fixed;
                top: 10px;
                right: 10px;
                background: #4CAF50;
                color: white;
                border: none;
                padding: 8px 12px;
                border-radius: 4px;
                cursor: pointer;
                z-index: 9999;
                font-size: 12px;
            `;
            showButton.addEventListener('click', () => {
                panel.style.display = 'block';
                showButton.remove();
            });
            document.body.appendChild(showButton);
        });

        return panel;
    }

    // 初始化
    function init() {
        log('脚本初始化...', 'info');

        // 创建UI控制面板
        const uiPanel = createUI();

        // 如果自动执行开启,则等待页面加载完成后执行
        if (CONFIG.autoExecute) {
            // 等待页面加载完成
            if (document.readyState === 'complete') {
                // 页面已加载完成,直接执行
                setTimeout(main, 2000); // 等待2秒确保YouTube播放器完全加载
            } else {
                // 页面未加载完成,监听load事件
                window.addEventListener('load', () => {
                    setTimeout(main, 2000); // 等待2秒确保YouTube播放器完全加载
                });
            }
        }
    }

    // 启动脚本
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();