(.*?)<\/div>/s;
const match2 = html.match(regex2);
if (match2 && match2[1]) {
translatedText = match2[1].trim();
}
}
// 模式3: 另一种可能的结果容器
if (!translatedText) {
const regex3 = /
(.*?)<\/div>/s;
const match3 = html.match(regex3);
if (match3 && match3[1]) {
translatedText = match3[1].trim();
}
}
// 如果所有模式都失败,尝试更通用的方法
if (!translatedText) {
// 查找任何可能包含翻译结果的div
const resultDivRegex = /
]*>(.*?)<\/div>/gs;
const allDivs = [...html.matchAll(resultDivRegex)];
// 查找包含原文长度相似的div(可能是翻译结果)
for (const divMatch of allDivs) {
const divContent = divMatch[1].trim();
// 排除太短或太长的内容
if (divContent && divContent.length > text.length * 0.5 && divContent.length < text.length * 2) {
translatedText = divContent;
break;
}
}
}
// 如果仍然没有找到翻译结果,返回原文
if (!translatedText) {
log('无法从HTML中提取翻译结果', 'WARNING');
return text;
}
// 清理HTML标签
translatedText = translatedText.replace(/<[^>]*>/g, '');
// 更新缓存
if (translationCache.size >= CONFIG.translationCacheSize) {
// 如果缓存已满,删除最早添加的项
const firstKey = translationCache.keys().next().value;
translationCache.delete(firstKey);
}
translationCache.set(text, translatedText);
log(`翻译完成: ${text.substring(0, 20)}... -> ${translatedText.substring(0, 20)}...`, 'INFO');
return translatedText;
} catch (error) {
log(`翻译失败: ${error.message}`, 'ERROR');
return text; // 翻译失败时返回原文
}
}
// 从聊天消息元素中提取用户名和消息内容
function extractMessageInfo(chatMessageElement) {
if (!chatMessageElement) return null;
try {
// 检查是否是系统消息
if (chatMessageElement.classList.contains('ChatMessage_systemMessage__3Jz9e')) {
// 系统消息通常只有一个span,直接包含在消息元素中
const systemSpan = chatMessageElement.querySelector('span:not(.ChatMessage_timestamp__1iRZO)');
if (systemSpan && systemSpan.textContent) {
return {
isSystemMessage: true,
username: 'System',
contentSpan: systemSpan,
originalContent: systemSpan.textContent
};
}
return null;
}
// 查找用户名元素
const nameSelector = getClassSelector(CONFIG.classNamePrefixes.name);
const nameElement = chatMessageElement.querySelector(nameSelector);
if (!nameElement) {
log('未找到用户名元素', 'DEBUG');
return null;
}
// 提取用户名 - 用户名不需要翻译,所以只是提取文本
const username = nameElement.textContent.trim();
// 查找最后一个span元素,通常是消息内容
// 这个方法更可靠,因为消息内容总是在最后
const allSpans = Array.from(chatMessageElement.querySelectorAll('span'));
// 过滤掉时间戳、用户名相关的span和已翻译的span
const contentSpans = allSpans.filter(span => {
// 跳过时间戳
if (span.classList.toString().includes(CONFIG.classNamePrefixes.timestamp)) {
return false;
}
// 跳过包含用户名的span或其父元素
if (span.contains(nameElement) || nameElement.contains(span)) {
return false;
}
// 跳过冒号span (通常紧跟用户名)
if (span.textContent.trim() === ':' || span.textContent.trim() === ':') {
return false;
}
// 跳过已经被标记为翻译过的span
if (span.hasAttribute('data-translated')) {
return false;
}
// 跳过包含在链接容器中的span
const linkContainer = span.closest('.ChatMessage_linkContainer__18Kv3');
if (linkContainer) {
return false;
}
// 跳过物品元素
const itemContainer = span.closest('.Item_itemContainer__x7kH1');
if (itemContainer) {
return false;
}
return span.textContent.trim() !== '';
});
// 如果找不到合适的内容span,返回null
if (contentSpans.length === 0) {
log('未找到合适的消息内容span', 'DEBUG');
return null;
}
// 使用最后一个符合条件的span作为消息内容
const contentSpan = contentSpans[contentSpans.length - 1];
return {
isSystemMessage: false,
username, // 用户名不翻译
contentSpan, // 只翻译消息内容span
originalContent: contentSpan.textContent
};
} catch (error) {
log(`提取消息信息时出错: ${error.message}`, 'ERROR');
return null;
}
}
// 处理单个聊天消息
async function processChatMessage(chatMessageElement) {
if (!chatMessageElement) return false;
try {
// 在DEBUG级别下显示消息结构
if (CONFIG.logLevel === 'DEBUG') {
debugMessageStructure(chatMessageElement);
}
const messageInfo = extractMessageInfo(chatMessageElement);
if (!messageInfo) {
log('无法提取消息信息', 'DEBUG');
return false;
}
const { isSystemMessage, username, contentSpan, originalContent } = messageInfo;
// 确保我们找到的是消息内容而不是用户名
if (!contentSpan || !originalContent) {
log('未找到有效的消息内容', 'WARNING');
return false;
}
// 检查是否已翻译(添加一个标记属性)
if (contentSpan.hasAttribute('data-translated')) {
log(`跳过已翻译的消息: ${originalContent.substring(0, 20)}...`, 'DEBUG');
return false;
}
// 跳过只包含表情符号、特殊字符或非英文内容的消息
if (!/[a-zA-Z]{2,}/.test(originalContent)) {
log(`跳过非英文内容: ${originalContent}`, 'DEBUG');
contentSpan.setAttribute('data-translated', 'non-english');
return false;
}
// 跳过系统消息(可选,根据需要配置)
if (isSystemMessage && !CONFIG.translateSystemMessages) {
log(`跳过系统消息: ${originalContent.substring(0, 20)}...`, 'DEBUG');
contentSpan.setAttribute('data-translated', 'system');
return false;
}
log(`准备翻译 ${username} 的消息内容: ${originalContent.substring(0, 30)}...`, 'DEBUG');
// 只翻译消息内容,不翻译用户名
const translatedContent = await translateText(originalContent);
// 如果翻译结果与原文相同,则不做更改
if (translatedContent === originalContent) {
contentSpan.setAttribute('data-translated', 'same');
log(`翻译结果与原文相同: ${originalContent.substring(0, 20)}...`, 'DEBUG');
return false;
}
// 更新span内容
contentSpan.textContent = translatedContent;
contentSpan.setAttribute('data-translated', 'true');
contentSpan.setAttribute('title', `原文: ${originalContent}`); // 添加原文作为提示
log(`已翻译 ${username} 的消息内容: ${originalContent.substring(0, 20)}... -> ${translatedContent.substring(0, 20)}...`, 'INFO');
return true;
} catch (error) {
log(`处理聊天消息时出错: ${error.message}`, 'ERROR');
return false;
}
}
// 扫描并翻译聊天消息
async function scanAndTranslate() {
log('开始扫描聊天消息...', 'INFO');
const startTime = performance.now();
try {
// 使用类名前缀查找所有聊天消息元素
const chatMessageSelector = getClassSelector(CONFIG.classNamePrefixes.chatMessage);
const chatMessages = document.querySelectorAll(chatMessageSelector);
log(`找到 ${chatMessages.length} 条聊天消息`, 'INFO');
let totalTranslations = 0;
for (const message of chatMessages) {
const translated = await processChatMessage(message);
if (translated) {
totalTranslations++;
}
}
const elapsedTime = performance.now() - startTime;
log(`扫描完成: ${totalTranslations} 处翻译,耗时 ${elapsedTime.toFixed(2)}ms`, 'INFO');
return totalTranslations;
} catch (error) {
log(`扫描过程中出错: ${error.message}`, 'ERROR');
return 0;
}
}
// 处理新添加的节点
async function processAddedNode(node) {
if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
try {
// 检查节点是否是聊天消息
const chatMessageSelector = getClassSelector(CONFIG.classNamePrefixes.chatMessage);
if (node.matches && node.matches(chatMessageSelector)) {
await processChatMessage(node);
return;
}
// 查找节点内的所有聊天消息
const chatMessages = node.querySelectorAll(chatMessageSelector);
for (const message of chatMessages) {
await processChatMessage(message);
}
} catch (error) {
log(`处理新添加节点时出错: ${error.message}`, 'ERROR');
}
}
// 检查元素是否在聊天历史区域内
function isInChatHistory(element) {
if (!element) return false;
// 向上查找聊天历史容器
let current = element;
const chatHistorySelector = getClassSelector(CONFIG.classNamePrefixes.chatHistory);
while (current && current !== document.body) {
if (current.matches && current.matches(chatHistorySelector)) {
return true;
}
current = current.parentElement;
}
return false;
}
// 防抖函数,避免频繁处理
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(context, args), wait);
};
}
// 处理DOM变化的防抖函数
const debouncedProcessMutations = debounce(async (mutations) => {
let hasNewMessages = false;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
// 处理新增节点
for (const node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && isInChatHistory(node)) {
await processAddedNode(node);
hasNewMessages = true;
}
}
} else if (mutation.type === 'characterData') {
// 处理文本内容变更
const textNode = mutation.target;
if (textNode && textNode.nodeType === Node.TEXT_NODE && isInChatHistory(textNode)) {
// 查找包含此文本节点的聊天消息元素
let current = textNode.parentNode;
const chatMessageSelector = getClassSelector(CONFIG.classNamePrefixes.chatMessage);
while (current && current !== document.body) {
if (current.matches && current.matches(chatMessageSelector)) {
await processChatMessage(current);
hasNewMessages = true;
break;
}
current = current.parentElement;
}
}
}
}
if (hasNewMessages) {
log('处理了新的聊天消息', 'DEBUG');
}
}, 300); // 300ms防抖延迟
// 初始化函数
function init() {
log('翻译器初始化中...', 'INFO');
try {
// 初始延迟扫描,等待页面完全加载
setTimeout(async () => {
log('执行初始聊天消息扫描...', 'INFO');
const initialTranslations = await scanAndTranslate();
log(`初始扫描完成,翻译了 ${initialTranslations} 条消息`, 'INFO');
// 动态监听DOM变化
const observer = new MutationObserver(mutations => {
debouncedProcessMutations(mutations);
});
// 查找聊天历史容器并监听其变化
const chatHistorySelector = getClassSelector(CONFIG.classNamePrefixes.chatHistory);
const chatHistoryElements = document.querySelectorAll(chatHistorySelector);
if (chatHistoryElements.length > 0) {
for (const element of chatHistoryElements) {
observer.observe(element, {
childList: true,
subtree: true,
characterData: true
});
log(`开始监听聊天历史容器: ${element.className}`, 'INFO');
}
} else {
// 如果找不到聊天历史容器,则监听整个body
observer.observe(document.body, {
childList: true,
subtree: true,
characterData: true
});
log('未找到聊天历史容器,监听整个页面', 'WARNING');
}
log('翻译器已启动并监听DOM变化', 'INFO');
}, CONFIG.initialScanDelay);
// 定期重新扫描整个DOM
setInterval(async () => {
await scanAndTranslate();
}, CONFIG.rescanInterval);
log(`翻译器配置: 源语言=${CONFIG.sourceLanguage}, 目标语言=${CONFIG.targetLanguage}, 扫描间隔=${CONFIG.rescanInterval/1000}s`, 'INFO');
} catch (error) {
log(`初始化失败: ${error.message}`, 'ERROR');
}
}
// 添加翻译样式
function addTranslationStyles() {
const styleElement = document.createElement('style');
styleElement.textContent = `
span[data-translated="true"] {
color: #4CAF50 !important;
text-decoration: underline dotted #4CAF50;
position: relative;
}
span[data-translated="true"]:hover::after {
content: attr(title);
position: absolute;
bottom: 100%;
left: 0;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
white-space: pre-wrap;
max-width: 300px;
z-index: 1000;
}
.translator-status {
position: fixed;
bottom: 10px;
right: 10px;
background: rgba(33, 150, 243, 0.8);
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
z-index: 10000;
display: none;
transition: opacity 0.3s;
}
`;
document.head.appendChild(styleElement);
}
// 添加状态指示器
function addStatusIndicator() {
const statusDiv = document.createElement('div');
statusDiv.className = 'translator-status';
statusDiv.textContent = '翻译器已启动';
document.body.appendChild(statusDiv);
// 显示状态指示器几秒钟,然后淡出
setTimeout(() => {
statusDiv.style.display = 'block';
setTimeout(() => {
statusDiv.style.opacity = '0';
setTimeout(() => {
statusDiv.style.display = 'none';
}, 1000);
}, 3000);
}, 1000);
return statusDiv;
}
// 更新状态指示器
function updateStatus(message, duration = 3000) {
const statusDiv = document.querySelector('.translator-status') || addStatusIndicator();
statusDiv.textContent = message;
statusDiv.style.display = 'block';
statusDiv.style.opacity = '1';
setTimeout(() => {
statusDiv.style.opacity = '0';
setTimeout(() => {
statusDiv.style.display = 'none';
}, 1000);
}, duration);
}
// 启动脚本
function startScript() {
// 添加样式
addTranslationStyles();
// 初始化翻译器
init();
// 添加状态指示器
addStatusIndicator();
// 打印初始状态信息
if (CONFIG.enableConsoleLog) {
console.log('%c[Translator] 翻译器已加载,控制台日志已开启', 'color: #2196F3');
} else {
console.log('%c[Translator] 翻译器已加载,控制台日志已关闭', 'color: #888');
}
}
// 根据页面加载状态启动脚本
if (document.readyState === 'loading') {
window.addEventListener('DOMContentLoaded', startScript);
} else {
startScript();
}
// 调试函数:显示消息结构
function debugMessageStructure(chatMessageElement) {
if (!CONFIG.enableConsoleLog || CONFIG.logLevel !== 'DEBUG') return;
try {
console.group('消息结构调试');
console.log('消息元素:', chatMessageElement);
// 查找用户名元素
const nameSelector = getClassSelector(CONFIG.classNamePrefixes.name);
const nameElement = chatMessageElement.querySelector(nameSelector);
console.log('用户名元素:', nameElement);
if (nameElement) {
console.log('用户名文本:', nameElement.textContent.trim());
}
// 查找所有span元素
const spans = chatMessageElement.querySelectorAll('span');
console.log('所有span元素:', spans);
// 查找可能的消息内容span
for (let i = 0; i < spans.length; i++) {
const span = spans[i];
console.log(`Span ${i}:`, {
element: span,
text: span.textContent.trim(),
classes: span.className,
containsUserName: nameElement && (span.contains(nameElement) || nameElement.contains(span))
});
}
console.groupEnd();
} catch (error) {
console.error('调试消息结构时出错:', error);
}
}
// 导出一些函数到全局作用域,方便调试
window.messageTranslator = {
translate: translateText,
scan: scanAndTranslate,
updateStatus: updateStatus,
config: CONFIG,
debug: {
messageStructure: debugMessageStructure,
extractMessageInfo: extractMessageInfo
}
};
})();