Greasy Fork is available in English.
自动在YouTube视频中开启字幕并选择中文翻译
// ==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();
}
})();