// ==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 // @downloadURL https://update.greasyfork.icu/scripts/567338/YouTube%E8%87%AA%E5%8A%A8%E4%B8%AD%E6%96%87%E5%AD%97%E5%B9%95%E5%88%87%E6%8D%A2.user.js // @updateURL https://update.greasyfork.icu/scripts/567338/YouTube%E8%87%AA%E5%8A%A8%E4%B8%AD%E6%96%87%E5%AD%97%E5%B9%95%E5%88%87%E6%8D%A2.meta.js // ==/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 = `

YouTube自动中文字幕

`; 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(); } })();