Greasy Fork is available in English.
Same as the name
当前为
// ==UserScript==
// @name YouTube 直播聊天实时翻译
// @version 1.0
// @author lslqtz
// @license GPL
// @grant none
// @inject-into page
// @run-at document-end
// @match *://*.youtube.com/live_chat*
// @namespace http://greasyfork.icu/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();
}
// 监听聊天更新
observeChatUpdates(chatContainer);
}
}
// 监听聊天消息更新
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();