// ==UserScript==
// @name WhatsApp 输入框翻译并发送 (v3.1.9-WA - Detailed Chinese Logging)
// @namespace http://tampermonkey.net/
// @version 3.1.9
// @description [WhatsApp Adapt Fix] v3.1.8 基础上,将所有控制台日志(console.log/warn/error)改为中文并提供更详细的执行信息,特别是文本替换细节。
// @author Your Name / AI Assistant (Adapted for WhatsApp)
// @match https://web.whatsapp.com/
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect api.ohmygpt.com
// @icon https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/WhatsApp.svg/48px-WhatsApp.svg.png
// ==/UserScript==
(function() {
'use strict';
// --- 配置 ---
const SCRIPT_VERSION = '3.1.9-WA'; // 脚本版本日志
const OHMYGPT_API_KEY = "sk-RK1MU6Cg6a48fBecBBADT3BlbKFJ4C209a954d3b4428b54b"; // 请替换为你自己的API Key
const OHMYGPT_API_ENDPOINT = "https://api.ohmygpt.com/v1/chat/completions";
const INPUT_TRANSLATE_MODEL = "gpt-4o-mini";
const MAX_CACHE_SIZE = 100;
const STORAGE_KEY_AUTOSEND = 'whatsAppTranslateAutoSendPref_v312';
// --- 翻译提示 (Unchanged) ---
const TRANSLATION_PROMPT = `
Role: Translator to US English.
Task: Translate/Abbreviate the input following strict rules. Prioritize translating any Chinese/Burmese text, even if short or mixed with numbers/English (e.g., "输入 123" -> "Input 123"). Also abbreviate existing English.
Strict Rules:
Style: Sophisticated US English.
Abbreviations (ONLY these letters): u, ur, r, thx, &, bfr, frst, tmrw, nxt.
Capitalization: Correct sentence start (e.g., "U r here").
NO Number Abbreviations: Use "to", "for". Absolutely no "2", "4". Double-check.
Punctuation: NO period (.) at the end. Keep original question marks (?).
Output: ONLY the final processed text. No explanations or extra words.
Untranslatable Input: If input is entirely code, numbers, etc., return original unmodified.
Input Text:
{text_to_translate}
`;
// --- 选择器 (Unchanged) ---
const INPUT_SELECTOR = 'footer div[contenteditable="true"][role="textbox"][data-lexical-editor="true"]';
const SEND_BUTTON_SELECTOR = 'button[data-testid="send"], button[aria-label="发送"], button[aria-label="Send"]';
const FOOTER_SELECTOR = 'footer._ak1i';
const UI_PARENT_SELECTOR = `${FOOTER_SELECTOR} div.copyable-area`;
const INPUT_AREA_SELECTOR = `${UI_PARENT_SELECTOR} > div.xh8yej3`;
// --- UI 元素 ID (Unchanged) ---
const CONTROLS_WRAPPER_ID = 'custom-controls-wrapper';
const STATUS_BAR_ID = 'custom-input-status-bar';
const AUTO_SEND_TOGGLE_ID = 'custom-auto-send-toggle';
const RETRY_BUTTON_ID = 'custom-translate-retry-button';
const RETRY_ABBREVIATION_BUTTON_ID = 'custom-abbreviation-retry-button';
// --- 语言检测正则 (Unchanged) ---
const CHINESE_REGEX = /[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]/;
const BURMESE_REGEX = /[\u1000-\u109F]/;
// --- 状态变量 (Unchanged) ---
let controlsWrapperElement = null;
let statusBarElement = null;
let autoSendToggleElement = null;
let currentInputApiXhr = null;
let isTranslatingAndSending = false;
let sendButtonClickListenerAttached = false;
let lastOriginalText = null;
const translationCache = new Map();
// --- 自动发送状态变量 (Unchanged, Log Modified) ---
let autoSendEnabled = true;
const savedAutoSendState = localStorage.getItem(STORAGE_KEY_AUTOSEND);
if (savedAutoSendState !== null) {
autoSendEnabled = savedAutoSendState === 'true';
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [配置] 已加载自动发送偏好设置: ${autoSendEnabled}`);
} else {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [配置] 未找到已保存的自动发送偏好设置,使用默认值: ${autoSendEnabled}`);
}
// --- CSS (Unchanged from v3.1.6) ---
GM_addStyle(`
${UI_PARENT_SELECTOR} { display: flex !important; flex-direction: column !important; }
#${CONTROLS_WRAPPER_ID} { order: 0 !important; display: flex; flex-direction: column; width: 100%; padding: 0 8px; box-sizing: border-box; margin-top: 0; margin-bottom: 4px; position: relative; z-index: 148; }
${INPUT_AREA_SELECTOR} { order: 1 !important; }
#${STATUS_BAR_ID} { width: 100%; padding: 4px 8px; font-size: 12px; color: #ccc; background-color: rgba(20, 20, 20, 0.85); backdrop-filter: blur(2px); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 4px; line-height: 1.3; text-align: left; transition: opacity 0.2s ease-in-out, max-height 0.2s ease-in-out; opacity: 0; max-height: 0; overflow: hidden; box-sizing: border-box; display: flex; justify-content: space-between; align-items: center; }
#${STATUS_BAR_ID}.visible { opacity: 1; max-height: 50px; margin-top: 2px; }
#${STATUS_BAR_ID} .status-text { flex-grow: 1; margin-right: 8px; }
#${STATUS_BAR_ID} .status-buttons { display: flex; gap: 5px; flex-shrink: 0; }
#${STATUS_BAR_ID} .status { font-style: italic; color: #a0a0a0; }
#${STATUS_BAR_ID} .info { font-style: italic; color: #87cefa; }
#${STATUS_BAR_ID} .error { font-weight: bold; color: #ff8a8a; }
#${STATUS_BAR_ID} .success { font-weight: bold; color: #8ade8a; }
#${RETRY_BUTTON_ID}, #${RETRY_ABBREVIATION_BUTTON_ID} { padding: 2px 6px; font-size: 11px; font-weight: bold; color: #d0d0d0; background-color: rgba(80, 80, 80, 0.9); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 3px; cursor: pointer; flex-shrink: 0; transition: background-color 0.2s ease, color 0.2s ease; white-space: nowrap; }
#${RETRY_BUTTON_ID}:hover, #${RETRY_ABBREVIATION_BUTTON_ID}:hover { background-color: rgba(100, 100, 100, 0.9); color: #fff; }
#${RETRY_BUTTON_ID}:active, #${RETRY_ABBREVIATION_BUTTON_ID}:active { background-color: rgba(60, 60, 60, 0.9); }
#${AUTO_SEND_TOGGLE_ID} { align-self: flex-end; padding: 3px 8px; font-size: 11px; font-weight: bold; background-color: rgba(80, 80, 80, 0.9); color: #ccc; border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 5px; cursor: pointer; user-select: none; transition: background-color 0.2s ease, color 0.2s ease; opacity: 1; max-height: 30px; }
#${AUTO_SEND_TOGGLE_ID}.autosend-on { background-color: rgba(70, 130, 180, 0.95); color: #fff; }
#${AUTO_SEND_TOGGLE_ID}:hover { filter: brightness(1.1); }
`);
// --- 辅助函数 ---
function detectLanguage(text) { /* ... (代码不变) ... */ if (!text) return null; if (CHINESE_REGEX.test(text)) return 'Chinese'; if (BURMESE_REGEX.test(text)) return 'Burmese'; return 'Other'; }
function setCursorToEnd(element) { /* ... (代码不变 v3.1.7) ... */ try { const sel = window.getSelection(); if (!sel) return; const focusElement = element; let targetNode = element; const textSpan = element.querySelector('p span[data-lexical-text="true"]'); if (textSpan) { if (textSpan.lastChild && textSpan.lastChild.nodeType === Node.TEXT_NODE) { targetNode = textSpan.lastChild; } else { targetNode = textSpan; } } else { if (element.lastChild && element.lastChild.nodeType === Node.TEXT_NODE) { targetNode = element.lastChild; } else { targetNode = element; } } const range = document.createRange(); if (targetNode.nodeType === Node.TEXT_NODE) { range.setStart(targetNode, targetNode.length); range.collapse(true); } else { range.selectNodeContents(targetNode); range.collapse(false); } sel.removeAllRanges(); sel.addRange(range); focusElement.focus(); } catch (e) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [错误] 设置光标时出错:`, e); try { element.focus(); } catch (fe) {} } }
// --- <<< MODIFIED ensureControlsExist (Log Han Hua) >>> ---
function ensureControlsExist() {
const footerElement = document.querySelector(FOOTER_SELECTOR);
if (!footerElement) { /* console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI检查] 未找到 Footer 元素 ('${FOOTER_SELECTOR}')。`); */ return false; }
const inputElement = footerElement.querySelector(INPUT_SELECTOR);
if (!inputElement) { /* console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI检查] 在 Footer 内未找到 Input 元素 ('${INPUT_SELECTOR}')。`); */ return false; }
const targetContainer = footerElement.querySelector('div.copyable-area');
if (!targetContainer) {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI检查] [错误] 在 Footer 内未找到目标容器 ('div.copyable-area')。`);
return false;
}
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI检查] 找到目标容器:`, targetContainer);
controlsWrapperElement = document.getElementById(CONTROLS_WRAPPER_ID);
if (!controlsWrapperElement) {
controlsWrapperElement = document.createElement('div');
controlsWrapperElement.id = CONTROLS_WRAPPER_ID;
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 准备将 UI Wrapper 附加到:`, targetContainer);
targetContainer.appendChild(controlsWrapperElement);
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] UI Wrapper 已创建并附加到目标容器。`);
} else if (controlsWrapperElement.parentNode !== targetContainer) {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 准备将 UI Wrapper 移动 (通过附加) 到:`, targetContainer);
targetContainer.appendChild(controlsWrapperElement);
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] UI Wrapper 已移动到目标容器。`);
} else {
if (controlsWrapperElement !== targetContainer.lastChild) {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 确保 UI Wrapper 是目标容器的最后一个子元素。`);
targetContainer.appendChild(controlsWrapperElement);
}
}
autoSendToggleElement = document.getElementById(AUTO_SEND_TOGGLE_ID);
if (!autoSendToggleElement) {
autoSendToggleElement = document.createElement('button');
autoSendToggleElement.id = AUTO_SEND_TOGGLE_ID;
autoSendToggleElement.addEventListener('click', toggleAutoSend);
controlsWrapperElement.appendChild(autoSendToggleElement);
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 自动发送切换按钮已创建。`);
} else if (!controlsWrapperElement.contains(autoSendToggleElement)){
controlsWrapperElement.appendChild(autoSendToggleElement);
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 自动发送切换按钮已附加到 Wrapper。`);
}
updateAutoSendButtonVisual();
statusBarElement = document.getElementById(STATUS_BAR_ID);
if (!statusBarElement) {
statusBarElement = document.createElement('div');
statusBarElement.id = STATUS_BAR_ID;
controlsWrapperElement.appendChild(statusBarElement);
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 状态栏元素已创建。`);
} else if (!controlsWrapperElement.contains(statusBarElement)){
controlsWrapperElement.appendChild(statusBarElement);
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [UI操作] 状态栏元素已附加到 Wrapper。`);
}
return true;
}
// --- <<< MODIFIED updateStatusDisplay (Log Han Hua) >>> ---
function updateStatusDisplay(content, type = 'status', duration = 0, showRetryButton = false, showRetryAbbreviationButton = false) {
if (!ensureControlsExist()) { console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [状态栏] [错误] 更新状态失败: 无法创建或找到 UI 控件。`); return; }
if (!statusBarElement) { console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [状态栏] [错误] 更新状态失败: 状态栏元素无效。`); return; }
let buttonsHtml = '';
if (showRetryButton && lastOriginalText) { buttonsHtml += `<button id="${RETRY_BUTTON_ID}" type="button">重试原文</button>`; }
if (showRetryAbbreviationButton) { buttonsHtml += `<button id="${RETRY_ABBREVIATION_BUTTON_ID}" type="button">重试缩写</button>`; }
statusBarElement.innerHTML = `<span class="status-text ${type}">${content}</span>${buttonsHtml ? `<div class="status-buttons">${buttonsHtml}</div>` : ''}`;
statusBarElement.classList.add('visible');
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [状态栏] 显示: [${type}] ${content}`);
if (showRetryButton && lastOriginalText) { const retryBtn = statusBarElement.querySelector(`#${RETRY_BUTTON_ID}`); if (retryBtn) retryBtn.addEventListener('click', handleRetryOriginalClick); }
if (showRetryAbbreviationButton) { const retryAbbrBtn = statusBarElement.querySelector(`#${RETRY_ABBREVIATION_BUTTON_ID}`); if (retryAbbrBtn) retryAbbrBtn.addEventListener('click', handleRetryAbbreviationClick); }
if (statusBarElement.hideTimeout) clearTimeout(statusBarElement.hideTimeout);
statusBarElement.hideTimeout = duration > 0 ? setTimeout(hideStatusDisplay, duration) : null;
}
// --- <<< MODIFIED hideStatusDisplay (Log Han Hua) >>> ---
function hideStatusDisplay() {
if (!statusBarElement) statusBarElement = document.getElementById(STATUS_BAR_ID);
if (!statusBarElement) return;
if (statusBarElement.hideTimeout) clearTimeout(statusBarElement.hideTimeout);
statusBarElement.hideTimeout = null;
if (statusBarElement.classList.contains('visible')) {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [状态栏] 隐藏状态栏。`);
statusBarElement.classList.remove('visible');
}
}
// --- <<< MODIFIED Helper Functions (Log Han Hua & Detail) >>> ---
function fixNumberAbbreviations(text) {
if (!text) return text;
let originalText = text;
text = text.replace(/\b2\b/gi,"to")
.replace(/\b4\b/gi,"for")
.replace(/\b(be?|b)4\b/gi,"before") // More specific before "4"
.replace(/\b2day\b/gi,"today")
.replace(/\b2nite\b/gi,"tonight")
.replace(/\b2night\b/gi,"tonight")
.replace(/\b2mrw\b/gi,"tomorrow")
.replace(/\b2moro\b/gi,"tomorrow")
.replace(/\bgr8\b/gi,"great")
.replace(/\bl8r\b/gi,"later")
.replace(/\bw8\b/gi,"wait")
.replace(/\bh8\b/gi,"hate")
.replace(/\bsk8\b/gi,"skate")
.replace(/\bm8\b/gi,"mate");
if (text !== originalText) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [文本处理] [数字修正] 是: 原文="${originalText}" -> 改为="${text}"`);
} else {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [文本处理] [数字修正] 否: "${originalText}" (无需修正)`);
}
return text;
}
function applyLetterAbbreviations(text) {
if (!text) return text;
let originalText = text;
let modifiedText = text;
let firstWordAbbr = false;
let otherAbbr = false;
const abbreviations = {"you":"u","your":"ur","are":"r","thanks":"thx","and":"&","before":"bfr","first":"frst","tomorrow":"tmrw","next":"nxt"};
const capitalizeList = ["u","ur","r","thx","bfr","frst","tmrw","nxt"]; // Abbreviations that need capitalization if at start
// Handle first word specially for capitalization
let firstWordOffset = -1;
let firstWord = "";
let prefix = "";
const firstWordMatch = modifiedText.match(/^(\s*[^a-zA-Z\s]*)?([a-zA-Z]+)/);
if (firstWordMatch) {
prefix = firstWordMatch[1] || "";
firstWord = firstWordMatch[2];
firstWordOffset = prefix.length;
const lowerFirstWord = firstWord.toLowerCase();
if (abbreviations.hasOwnProperty(lowerFirstWord)) {
const abbr = abbreviations[lowerFirstWord];
let replacement = abbr;
if (capitalizeList.includes(abbr)) {
replacement = abbr.charAt(0).toUpperCase() + abbr.slice(1);
}
modifiedText = prefix + replacement + modifiedText.substring(firstWordOffset + firstWord.length);
firstWordAbbr = true;
}
}
// Handle remaining words (skip the first word if it was already abbreviated)
const processStartIndex = firstWordAbbr ? (firstWordOffset + abbreviations[firstWord.toLowerCase()].length) : 0;
let textToProcess = modifiedText.substring(processStartIndex);
const originalTextToProcess = textToProcess; // Keep original part for comparison
for (const word in abbreviations) {
const abbr = abbreviations[word];
const regexWord = new RegExp(`\\b${word}\\b`, 'gi'); // Case-insensitive word boundary replace
textToProcess = textToProcess.replace(regexWord, abbr);
}
if (textToProcess !== originalTextToProcess) {
otherAbbr = true;
modifiedText = modifiedText.substring(0, processStartIndex) + textToProcess;
}
// Log results
if (firstWordAbbr || otherAbbr) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [文本处理] [字母缩写] 是: 原文="${originalText}" -> 改为="${modifiedText}"`);
} else {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [文本处理] [字母缩写] 否: "${originalText}" (无需缩写)`);
}
return modifiedText;
}
// --- <<< MODIFIED 重试按钮处理程序 (Log Han Hua) >>> ---
function handleRetryOriginalClick(event) {
event.preventDefault(); event.stopPropagation();
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] “重试原文”按钮被点击。`);
if (isTranslatingAndSending) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] [警告] 正在翻译中,忽略“重试原文”请求。`); return; }
if (!lastOriginalText) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] [警告] 没有可重试的原文文本。`); hideStatusDisplay(); return; }
const currentInputElement = document.querySelector(INPUT_SELECTOR);
const currentSendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (!currentInputElement || !currentSendButton) { updateStatusDisplay("重试失败:UI 元素丢失", 'error', 4000, true, false); return; }
if (!currentSendButton.offsetParent) { updateStatusDisplay("重试失败:发送按钮隐藏?", 'error', 4000, true, false); return; }
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] 准备使用原文重试翻译:`, lastOriginalText);
translateAndSend(lastOriginalText, currentInputElement, currentSendButton, true); // Force API call
}
function handleRetryAbbreviationClick(event) {
event.preventDefault(); event.stopPropagation();
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] “重试缩写”按钮被点击。`);
if (isTranslatingAndSending) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] [警告] 正在翻译中,忽略“重试缩写”请求。`); return; }
const currentInputElement = document.querySelector(INPUT_SELECTOR);
const currentSendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (!currentInputElement || !currentSendButton) { updateStatusDisplay("重试失败:UI 元素丢失", 'error', 4000, true, true); return; }
const text = currentInputElement.textContent?.trim(); // Get current text, might be already translated
if (!text) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] [警告] 输入框为空,无法重试缩写。`); hideStatusDisplay(); return; }
if (!currentSendButton.offsetParent) { updateStatusDisplay("重试失败:发送按钮隐藏?", 'error', 4000, true, true); return; }
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [按钮点击] 准备使用当前文本重试翻译/缩写:`, text);
translateAndSend(text, currentInputElement, currentSendButton, true); // Force API call
}
// --- <<< MODIFIED 自动发送切换逻辑 (Log Han Hua) >>> ---
function updateAutoSendButtonVisual() {
if (!autoSendToggleElement) autoSendToggleElement = document.getElementById(AUTO_SEND_TOGGLE_ID);
if(!autoSendToggleElement) return;
autoSendToggleElement.textContent = autoSendEnabled ? "自动发送: 开" : "自动发送: 关";
autoSendToggleElement.className = autoSendEnabled ? 'autosend-on' : '';
autoSendToggleElement.id = AUTO_SEND_TOGGLE_ID;
}
function toggleAutoSend() {
autoSendEnabled = !autoSendEnabled;
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [设置] 自动发送切换为: ${autoSendEnabled ? '开启' : '关闭'}`);
updateAutoSendButtonVisual();
updateStatusDisplay(`自动发送已${autoSendEnabled ? '开启' : '关闭'}`, 'status', 2000);
try {
localStorage.setItem(STORAGE_KEY_AUTOSEND, autoSendEnabled.toString());
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [设置] 自动发送偏好已保存到 localStorage。`);
} catch (e) {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [设置] [错误] 保存自动发送偏好到 localStorage 时出错:`, e);
updateStatusDisplay("保存设置失败", 'error', 3000);
}
}
// --- <<< MODIFIED translateAndSend (Log Han Hua) >>> ---
function translateAndSend(textToProcess, originalInputElementRef, originalSendButtonRef, forceApi = false) {
if (isTranslatingAndSending) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [警告] 正在进行翻译,忽略新的请求: "${textToProcess.substring(0, 30)}..."`); return; }
if (!originalInputElementRef || !originalSendButtonRef) { updateStatusDisplay("错误:初始输入/发送按钮丢失", 'error', 4000, true, false); return; }
isTranslatingAndSending = true; // 设置繁忙状态
const detectedLang = detectLanguage(textToProcess);
if (detectedLang === 'Chinese' || detectedLang === 'Burmese') {
lastOriginalText = textToProcess; // 记录原文以备重试
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] 检测到 ${detectedLang},原文已记录。`);
}
hideStatusDisplay(); // 清除旧状态
updateStatusDisplay("正在翻译/缩写...", 'status'); // 显示当前状态
// --- 缓存检查 ---
const useCache = !forceApi && (detectedLang === 'Chinese' || detectedLang === 'Burmese');
if (useCache && translationCache.has(textToProcess)) {
const cachedTranslationRaw = translationCache.get(textToProcess);
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存命中] 原文: "${textToProcess}", 缓存结果 (待处理): "${cachedTranslationRaw}"`);
const finalCachedText = applyLetterAbbreviations(cachedTranslationRaw); // 对缓存结果应用缩写
updateStatusDisplay("已从缓存加载 ✓", 'info', autoSendEnabled ? 3000 : 5000, !autoSendEnabled, !autoSendEnabled);
const currentInputElement = document.querySelector(INPUT_SELECTOR);
if (!currentInputElement) {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] [错误] 输入元素在更新前消失!`);
updateStatusDisplay("错误:输入元素丢失!", 'error', 4000);
isTranslatingAndSending = false; return;
}
// 更新输入框 (Lexical 兼容)
const textSpanCache = currentInputElement.querySelector('p span[data-lexical-text="true"]');
if (textSpanCache) { textSpanCache.textContent = finalCachedText; } else { currentInputElement.textContent = finalCachedText; }
setCursorToEnd(currentInputElement);
currentInputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] 输入框内容已更新 (通过 span/fallback)。`);
// 自动发送逻辑
if (autoSendEnabled) {
const sendDelay = 100;
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] 自动发送已开启。延迟 ${sendDelay}ms 后发送。`);
setTimeout(() => {
if (!isTranslatingAndSending) { console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] 发送超时回调时,翻译状态已重置,取消发送。`); isTranslatingAndSending = false; return; } // 检查状态是否被中断
const currentSendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (currentSendButton && currentSendButton.isConnected && currentSendButton.offsetParent) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] 正在点击发送按钮...`);
currentSendButton.click();
// 尝试清空输入框
setTimeout(() => {
const inputAfterSend = document.querySelector(INPUT_SELECTOR);
const spanAfterSend = inputAfterSend?.querySelector('p span[data-lexical-text="true"]');
if (inputAfterSend && (inputAfterSend.textContent === finalCachedText || spanAfterSend?.textContent === finalCachedText)) {
if (spanAfterSend) { spanAfterSend.textContent = ''; } else { inputAfterSend.textContent = ''; }
inputAfterSend.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] 发送后尝试清空输入框。`);
}
}, 50);
} else {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] [错误] 发送失败,发送按钮不可用或已消失。`);
updateStatusDisplay("发送失败(按钮不可用?)", 'error', 4000, true, true);
}
isTranslatingAndSending = false; // 重置繁忙状态
}, sendDelay);
} else {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [缓存] 自动发送已关闭。`);
isTranslatingAndSending = false; // 重置繁忙状态
}
return; // 缓存处理完毕,退出函数
}
// --- 结束缓存检查 ---
// --- API 调用 ---
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] ${forceApi ? '[强制API]' : '[缓存未命中]'} 调用 API (${INPUT_TRANSLATE_MODEL}) 处理: "${textToProcess.substring(0, 30)}..."`);
const finalPrompt = TRANSLATION_PROMPT.replace('{text_to_translate}', textToProcess);
const requestBody = { model: INPUT_TRANSLATE_MODEL, messages: [{"role": "user", "content": finalPrompt }], temperature: 0.6 };
if (currentInputApiXhr && typeof currentInputApiXhr.abort === 'function') {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 检测到正在进行的 API 请求,正在中止旧请求...`);
currentInputApiXhr.abort();
}
currentInputApiXhr = GM_xmlhttpRequest({
method: "POST", url: OHMYGPT_API_ENDPOINT,
headers: { "Content-Type": "application/json", "Authorization": `Bearer ${OHMYGPT_API_KEY}` },
data: JSON.stringify(requestBody),
onload: function(response) {
currentInputApiXhr = null; // 清除当前请求引用
let updateSuccessful = false;
try {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 收到响应,状态码: ${response.status}`);
const currentInputElement = document.querySelector(INPUT_SELECTOR);
if (!currentInputElement) {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] 输入元素在处理响应时消失!`);
updateStatusDisplay("错误:翻译后输入元素丢失!", 'error', 5000, true, false);
return; // 退出 try
}
if (response.status >= 200 && response.status < 300) {
const data = JSON.parse(response.responseText);
const rawTranslation = data.choices?.[0]?.message?.content?.trim();
if (rawTranslation) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 成功获取原始翻译结果: "${rawTranslation}"`);
const checkedTranslation = fixNumberAbbreviations(rawTranslation); // 先修正数字
const finalApiText = applyLetterAbbreviations(checkedTranslation); // 再应用缩写
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 处理后最终文本: "${finalApiText}"`);
// 缓存处理后的结果 (数字修正后,缩写前)
if (!forceApi && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
if (translationCache.size >= MAX_CACHE_SIZE) {
const oldestKey = translationCache.keys().next().value;
translationCache.delete(oldestKey);
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 缓存已满,移除最旧条目: "${oldestKey}"`);
}
translationCache.set(textToProcess, checkedTranslation); // 缓存修正数字后的文本
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 翻译结果 (数字修正后) 已缓存: "${textToProcess}" -> "${checkedTranslation}"`);
}
// 更新输入框 (Lexical 兼容)
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 准备更新输入元素 (通过 span/p/fallback):`, currentInputElement);
const textSpanApi = currentInputElement.querySelector('p span[data-lexical-text="true"]');
if (textSpanApi) {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 找到文本 span,正在更新其内容。`);
textSpanApi.textContent = finalApiText;
updateSuccessful = true;
} else {
const paragraph = currentInputElement.querySelector('p.selectable-text');
if(paragraph){
console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [警告] 未找到文本 span,但段落存在。正在重建结构。`);
paragraph.innerHTML = `<span data-lexical-text="true">${finalApiText}</span>`;
updateSuccessful = true;
} else {
console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [警告] 未找到文本 span 和段落。回退到设置 textContent。`);
currentInputElement.textContent = finalApiText;
updateSuccessful = true;
}
}
setCursorToEnd(currentInputElement);
currentInputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 输入更新事件已分派 (通过 span/p/fallback)。`);
// 自动发送逻辑
if (autoSendEnabled) {
const sendDelay = 200; // API 调用后延迟稍长
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 自动发送已开启。延迟 ${sendDelay}ms 后发送。`);
updateStatusDisplay("翻译完成 ✓", 'success', sendDelay + 1000); // 显示成功状态
setTimeout(() => {
if (!isTranslatingAndSending) { console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 发送超时回调时,翻译状态已重置,取消发送。`); isTranslatingAndSending = false; return; }
const currentSendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (currentSendButton && currentSendButton.isConnected && currentSendButton.offsetParent) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 正在点击发送按钮...`);
currentSendButton.click();
// 尝试清空输入框
setTimeout(() => {
const inputAfterSend = document.querySelector(INPUT_SELECTOR);
const spanAfterSend = inputAfterSend?.querySelector('p span[data-lexical-text="true"]');
if (inputAfterSend && (inputAfterSend.textContent === finalApiText || spanAfterSend?.textContent === finalApiText)) {
if(spanAfterSend){ spanAfterSend.textContent = ''; } else { inputAfterSend.textContent = '';}
inputAfterSend.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 发送后尝试清空输入框。`);
}
}, 50);
} else {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] 发送失败,发送按钮不可用或已消失。`);
updateStatusDisplay("发送失败(按钮不可用?)", 'error', 4000, true, true);
}
isTranslatingAndSending = false; // 重置繁忙状态
}, sendDelay);
} else {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 自动发送已关闭。`);
updateStatusDisplay("完成 ✓ (请手动发送或重试)", 'success', 5000, true, true);
}
} else {
// API 返回成功但内容为空
const reason = data.choices?.[0]?.finish_reason || 'N/A';
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] API 返回成功但 choices 内容为空或无效。结束原因: ${reason}`);
throw new Error(`API 错误:无有效翻译内容 (结束原因: ${reason === 'content_filter' ? '内容过滤' : reason})`);
}
} else {
// HTTP 状态码错误
let errorMsg = `HTTP ${response.status}: ${response.statusText}`;
try {
const errorData = JSON.parse(response.responseText);
errorMsg = errorData.error?.message || errorMsg;
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] API 错误详情:`, errorData);
} catch(_) {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] 非 JSON 错误响应:`, response.responseText);
}
throw new Error(errorMsg);
}
} catch (e) {
console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] 处理 API 响应或更新输入框时出错:`, e);
updateStatusDisplay(`处理失败: ${e.message.substring(0, 60)}`, 'error', 5000, true, false);
const currentInputElementOnError = document.querySelector(INPUT_SELECTOR); // 检查元素是否存在
if (!currentInputElementOnError) { console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [错误处理] 输入元素在错误处理期间也丢失了。`); }
} finally {
// 无论成功失败,如果不是自动发送模式,或更新未成功,都要在这里重置状态
if (!autoSendEnabled || !updateSuccessful) {
isTranslatingAndSending = false;
}
}
}, // End onload
onerror: function(r) { currentInputApiXhr = null; console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] 网络请求失败:`, r); updateStatusDisplay(`失败:网络错误 (${r.status||'N/A'})`, 'error', 5000, true, false); isTranslatingAndSending = false; },
ontimeout: function() { currentInputApiXhr = null; console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] [错误] 请求超时。`); updateStatusDisplay("失败:请求超时", 'error', 5000, true, false); isTranslatingAndSending = false; },
onabort: function() { currentInputApiXhr = null; console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [翻译流程] [API] 请求被中止。`); hideStatusDisplay(); isTranslatingAndSending = false; },
timeout: 30000
}); // End GM_xmlhttpRequest
}
// --- 事件监听器 (Log Han Hua) ---
function handleInputKeyDown(event) {
const targetElement = event.target;
if (!targetElement || !targetElement.matches(INPUT_SELECTOR)) return;
// --- Enter 键逻辑 ---
if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到 Enter 按键。`);
if (statusBarElement && statusBarElement.classList.contains('visible') && !isTranslatingAndSending && !autoSendEnabled) {
const nonBlockingStatus = statusBarElement.querySelector('span.success, span.info');
if (nonBlockingStatus) { console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到非阻塞状态,允许手动发送。`); hideStatusDisplay(); return; }
}
if (isTranslatingAndSending) { event.preventDefault(); event.stopPropagation(); console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] [警告] 正在翻译中,已阻止 Enter 发送。`); return; }
const text = targetElement.textContent?.trim() || "";
const detectedLang = detectLanguage(text);
if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到 ${detectedLang} 文本,准备触发翻译...`);
event.preventDefault(); event.stopPropagation(); // 阻止默认发送行为
const currentInputElement = document.querySelector(INPUT_SELECTOR);
const currentSendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (!currentInputElement) { console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] [错误] 无法找到输入元素!无法翻译。`); updateStatusDisplay("错误:输入元素丢失!", 'error', 4000); return; }
if (!currentSendButton || !currentSendButton.offsetParent) { console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] [错误] 发送按钮丢失或隐藏!无法翻译。`); updateStatusDisplay("错误:发送按钮不可用!", 'error', 5000, true, false); return; }
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 调用 translateAndSend 处理文本: "${text.substring(0,20)}..."`);
translateAndSend(text, currentInputElement, currentSendButton);
} else {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 无需翻译 (语言: ${detectedLang}, 文本空: ${!text})。允许 Enter 默认行为或隐藏状态栏。`);
if (!isTranslatingAndSending) { hideStatusDisplay(); } // 如果不繁忙,隐藏可能存在的旧状态
}
}
// --- 输入时中止翻译逻辑 ---
else if (isTranslatingAndSending && !['Shift', 'Control', 'Alt', 'Meta', 'Enter', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Escape', 'Tab'].includes(event.key)) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到在翻译过程中输入 (${event.key}),正在中止 API 请求...`);
if (currentInputApiXhr && typeof currentInputApiXhr.abort === 'function') {
currentInputApiXhr.abort(); // Abort 会触发 onabort 回调,在那里重置状态
} else {
// 如果没有活动的 XHR (可能 API 已完成但在等待发送超时),直接重置状态
isTranslatingAndSending = false;
hideStatusDisplay();
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 没有活动的 API 请求可中止,直接重置翻译状态。`);
}
}
// --- 不繁忙时输入隐藏状态栏 ---
else if (!isTranslatingAndSending) {
// 如果状态栏可见且显示的不是“正在进行”的状态,则隐藏它
if (statusBarElement && statusBarElement.classList.contains('visible') && !statusBarElement.querySelector('span.status')) {
hideStatusDisplay();
}
}
}
function handleSendButtonClick(event) {
const sendButton = event.target.closest(SEND_BUTTON_SELECTOR);
if (!sendButton) return;
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到发送按钮点击。`);
if (statusBarElement && statusBarElement.classList.contains('visible') && !isTranslatingAndSending && !autoSendEnabled) {
const nonBlockingStatus = statusBarElement.querySelector('span.success, span.info');
if (nonBlockingStatus) { console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到非阻塞状态,允许手动发送。`); hideStatusDisplay(); return; }
}
const currentInputElement = document.querySelector(INPUT_SELECTOR);
if (!currentInputElement) { console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] [错误] 无法找到输入元素!无法检查内容。`); return; } // 允许 WA 处理丢失输入框的情况
const text = currentInputElement.textContent?.trim() || "";
const detectedLang = detectLanguage(text);
if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
if (isTranslatingAndSending) { console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] [警告] 正在翻译中,已阻止发送按钮点击。`); event.preventDefault(); event.stopPropagation(); return; }
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 检测到 ${detectedLang} 文本,拦截点击以触发翻译...`);
event.preventDefault(); event.stopPropagation(); // 阻止默认发送行为
if (!sendButton.offsetParent) { console.error(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] [错误] 发送按钮在点击处理时变得不可见!无法翻译。`); updateStatusDisplay("错误:发送按钮不可用!", 'error', 5000, true, false); return; }
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION} [事件] 调用 translateAndSend 处理文本: "${text.substring(0,20)}..."`);
translateAndSend(text, currentInputElement, sendButton);
} else {
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [事件] 无需翻译 (语言: ${detectedLang}, 文本空: ${!text}, 翻译中: ${isTranslatingAndSending})。允许发送按钮默认行为。`);
if (!isTranslatingAndSending) { hideStatusDisplay(); } // 如果不繁忙,隐藏可能存在的旧状态
}
}
// --- 初始化与附加监听器 (Log Han Hua) ---
function initialize() {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] 脚本开始执行...`);
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] 5秒后执行首次元素检查...`);
setTimeout(() => {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] 正在执行首次元素检查...`);
if (ensureControlsExist()) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] 首次检查成功,尝试附加初始监听器。`);
attachInitialListeners();
} else {
console.warn("[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] [警告] 首次检查未能找到所需元素,将依赖 MutationObserver。");
}
}, 5000);
const observer = new MutationObserver(mutations => {
let needsRecheck = false;
for (const mutation of mutations) {
if (mutation.type === 'childList') {
let footerChanged = false;
const parentFooter = mutation.target.closest(FOOTER_SELECTOR); // 检查变更是否发生在 footer 内部或其子孙节点
if (parentFooter) {
footerChanged = true;
} else { // 如果变更发生在外部,检查是否添加/删除了关键元素
const checkNodes = (nodes) => {
for (const node of nodes) {
if (node.nodeType !== 1) continue;
if (node.matches && (node.matches(INPUT_SELECTOR) || node.matches(SEND_BUTTON_SELECTOR) || node.matches(FOOTER_SELECTOR) || node.id === CONTROLS_WRAPPER_ID)) return true;
if (node.querySelector && (node.querySelector(INPUT_SELECTOR) || node.querySelector(SEND_BUTTON_SELECTOR) || node.querySelector(FOOTER_SELECTOR) || node.querySelector(`#${CONTROLS_WRAPPER_ID}`))) return true;
if (node.matches && node.matches('div.copyable-area') && node.closest(FOOTER_SELECTOR)) return true;
if (node.querySelector && node.querySelector('div.copyable-area')) return true;
}
return false;
};
if (checkNodes(mutation.addedNodes) || checkNodes(mutation.removedNodes)) {
footerChanged = true;
}
}
if (footerChanged) {
needsRecheck = true;
// console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [Observer] 检测到相关 DOM 变动。`, mutation);
break; // 找到相关变动,无需检查此批次剩余 mutation
}
}
}
if (needsRecheck) {
// console.log("[InputTranslate-WA v${SCRIPT_VERSION}] [Observer] 触发元素重新检查...");
if (window.waTranslateRecheckTimeout) clearTimeout(window.waTranslateRecheckTimeout);
window.waTranslateRecheckTimeout = setTimeout(() => {
// console.log("[InputTranslate-WA v${SCRIPT_VERSION}] [Observer] 执行防抖后的元素重新检查和监听器附加。");
if(ensureControlsExist()) {
attachInitialListeners();
} else {
// console.log("[InputTranslate-WA v${SCRIPT_VERSION}] [Observer] 重新检查失败,未找到所需元素。");
}
}, 150); // 防抖处理
}
});
observer.observe(document.body, { childList: true, subtree: true });
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] MutationObserver 已激活,监视 document.body。`);
}
function attachInitialListeners() {
const inputElement = document.querySelector(INPUT_SELECTOR);
if (inputElement && !inputElement.dataset.customInputTranslateListener) {
attachInputListeners(inputElement);
} else if (!inputElement) {
// console.log("[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] attachInitialListeners: 未找到输入元素。");
}
const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if(sendButton && !sendButtonClickListenerAttached) { // 使用 flag 检查
attachSendButtonListener(sendButton);
} else if (!sendButton) {
// console.log("[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] attachInitialListeners: 未找到发送按钮。");
}
}
function attachInputListeners(inputElement) {
if (inputElement.dataset.customInputTranslateListener) return;
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] 正在向输入元素附加 Keydown 监听器:`, inputElement);
inputElement.addEventListener('keydown', handleInputKeyDown, { capture: true });
inputElement.dataset.customInputTranslateListener = 'true'; // 标记已附加
// 监视输入元素自身是否被移除
const inputObserver = new MutationObserver(() => {
if (!inputElement.isConnected) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] 检测到输入元素被移除。监听器自动失效。`);
inputObserver.disconnect(); // 停止监视
}
});
if (inputElement.parentNode) {
inputObserver.observe(inputElement.parentNode, { childList: true, subtree: false }); // 监视父节点的变化
} else {
console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] [警告] 输入元素没有父节点,无法启动移除监视。`);
}
}
function attachSendButtonListener(sendButton) {
if (sendButtonClickListenerAttached) return; // 再次检查 flag
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] 正在向发送按钮附加 Click 监听器:`, sendButton);
sendButton.addEventListener('click', handleSendButtonClick, { capture: true });
sendButtonClickListenerAttached = true; // 设置 flag
// 监视发送按钮是否被移除或替换
const buttonObserver = new MutationObserver(() => {
// 需要重新查询按钮,因为原始 sendButton 变量可能指向已移除的元素
const currentSendButton = sendButton.parentNode?.querySelector(SEND_BUTTON_SELECTOR);
if (!currentSendButton || !currentSendButton.isConnected) {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] 检测到发送按钮被移除或替换。重置监听器附加标志。`);
buttonObserver.disconnect();
sendButtonClickListenerAttached = false; // 重置 flag 是关键!
}
});
if (sendButton.parentNode) {
buttonObserver.observe(sendButton.parentNode, { childList: true, subtree: true }); // 监视父节点及其子树
} else {
console.warn(`[InputTranslate-WA v${SCRIPT_VERSION}] [监听器] [警告] 发送按钮没有父节点,无法启动移除监视。`);
}
}
// --- 启动初始化 ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] DOMContentLoaded 事件触发,准备执行 initialize()。`);
initialize();
});
} else {
console.log(`[InputTranslate-WA v${SCRIPT_VERSION}] [初始化] DOM 已加载,直接执行 initialize()。`);
initialize();
}
})();