YouTube 直播聊天实时翻译

Same as the name

目前为 2024-12-12 提交的版本。查看 最新版本

// ==UserScript==
// @name        YouTube 直播聊天实时翻译
// @version     1.1
// @author      lslqtz
// @license     GPL
// @grant       none
// @inject-into page
// @run-at      document-end
// @match       *://*.youtube.com/live_chat*
// @namespace https://gf.qytechs.cn/users/155581
// @description Same as the name
// ==/UserScript==

// 启动翻译脚本.
function startYouTubeLiveChatTranslator() {
	console.log("启动 YouTube 直播聊天翻译脚本");

	if (window.ytLiveChatInterval) {
		clearInterval(window.ytLiveChatInterval);
		console.log("已清除之前的计时器");
	}
	if (window.ytObserver) {
		window.ytObserver.disconnect();
	}

	// 启动定时器.
	window.ytLiveChatInterval = setInterval(checkAndObserveChatContainer, 1000);
}

// Google Translate API 调用.
async function translateText(text, targetLang = 'zh-CN') {
	try {
		console.log("翻译消息: " + text);
		var response = await fetch('https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=' + targetLang + '&dt=t&q=' + encodeURIComponent(text));
		if (!response.ok) {
			console.error("翻译 API 请求失败", response.statusText);
			return "[翻译失败]";
		}
		var result = await response.json();
		return result[0][0][0];
	} catch (error) {
		console.error("翻译出错", error);
		return "[翻译错误]";
	}
}

// 解析消息内容, 过滤表情和图片, 并将它们替换为占位符.
function extractTextContent(element) {
	var text = '';
	var elements = element.childNodes;
	var placeholders = []; // 存储占位符与原始内容的对应关系.

	elements.forEach(function (node, index) {
		if (node.nodeType === Node.TEXT_NODE) {
			text += node.nodeValue.trim();
		} else if (node.nodeType === Node.ELEMENT_NODE) {
			if (node.tagName.toLowerCase() === 'img' || (node.tagName.toLowerCase() === 'span' && node.classList.contains('emoji'))) {
				var placeholder = `{{emoji_placeholder_${index}}}`; // 使用占位符.
				placeholders.push({ placeholder: placeholder, html: node.outerHTML });
				text += placeholder; // 将表情或图片替换为占位符.
			}
		}
	});

	return { text: text.trim(), placeholders: placeholders };
}

// 检查并观察聊天容器.
function checkAndObserveChatContainer() {
	var chatContainer = document.querySelector('#live-chat-item-list-panel');
	if (chatContainer) {
		console.log("聊天容器找到: ", chatContainer);

		if (window.ytObserver) {
		    window.ytObserver.disconnect();
                    console.log("解除之前监听聊天更新");
	        }

		observeChatUpdates(chatContainer);

                translateInitialMessages(chatContainer);
	}
}

// 翻译已有的最新 20 条消息.
function translateInitialMessages(chatContainer) {
    console.log("开始翻译已有消息...");

    // 获取所有聊天消息节点.
    var messages = chatContainer.querySelectorAll('yt-live-chat-text-message-renderer, yt-live-chat-paid-message-renderer');
    var totalMessages = messages.length;

    // 只处理最后的 20 条消息.
    for (var i = Math.max(0, totalMessages - 20); i < totalMessages; i++) {
        var messageNode = messages[i];
        var messageElement = messageNode.querySelector('#message');
        if (messageElement) {
            var { text, placeholders } = extractTextContent(messageElement);
            if (!text) {
                console.log("已有消息内容为空,跳过翻译");
                continue;
            }
            console.log("已有消息: " + text);
            var translatedMessage = translateText(text);
            var finalMessage = insertPlaceholdersIntoTranslation(translatedMessage, placeholders);
            insertTranslatedMessage(messageElement, finalMessage);
        }
    }
}

// 监听聊天消息更新.
function observeChatUpdates(chatContainer) {
	window.ytObserver = new MutationObserver(async function (mutations) {
		for (var i = 0; i < mutations.length; i++) {
			var mutation = mutations[i];
			mutation.addedNodes.forEach(async function (node) {
				// 检查是否为聊天消息.
				if (node.nodeType === 1 && (node.tagName.toLowerCase() === 'yt-live-chat-text-message-renderer' || node.tagName.toLowerCase() === 'yt-live-chat-paid-message-renderer')) {
					var messageElement = node.querySelector('#message');
					if (messageElement) {
						// 提取文本并替换表情或图片为占位符.
						var { text, placeholders } = extractTextContent(messageElement);
						if (!text) {
							console.log("消息内容为空, 跳过翻译");
							return;
						}
						console.log("检测到新消息: " + text);
						var translatedMessage = await translateText(text);

						// 将翻译后的文本和表情或图片组合.
						var finalMessage = insertPlaceholdersIntoTranslation(translatedMessage, placeholders);
						insertTranslatedMessage(messageElement, finalMessage);
					} else {
						console.warn("未找到消息内容元素");
					}
				}
			});
		}
	});

	console.log("开始监听聊天更新...");
	window.ytObserver.observe(chatContainer, { childList: true, subtree: true });
}

// 重新将占位符替换为表情和图片.
function insertPlaceholdersIntoTranslation(translatedMessage, placeholders) {
	placeholders.forEach(function (placeholder) {
		translatedMessage = translatedMessage.replace(placeholder.placeholder, placeholder.html);
	});
	return translatedMessage;
}

// 插入翻译后的消息.
function insertTranslatedMessage(messageElement, translatedMessage) {
	var translationElement = document.createElement('div');
	translationElement.style.color = 'gray';
	translationElement.style.fontSize = 'small';
	translationElement.innerHTML = "[翻译]: " + translatedMessage;

	// 在原消息下方插入翻译内容.
	messageElement.appendChild(translationElement);
}

startYouTubeLiveChatTranslator();

QingJ © 2025

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