// ==UserScript==
// @name Telegram 输入框翻译并发送 (v2.9.3 - 默认开启自动发送 + 持久化设置)
// @namespace http://tampermonkey.net/
// @version 2.9.3
// @description v2.9.2基础: 将自动发送默认设置为开启,并将用户的开关选择持久化存储 (刷新页面后保留设置).
// @author Your Name / AI Assistant
// @match https://web.telegram.org/k/*
// @match https://web.telegram.org/a/*
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @connect api.ohmygpt.com
// @icon https://upload.wikimedia.org/wikipedia/commons/thumb/8/82/Telegram_logo.svg/48px-Telegram_logo.svg.png
// ==/UserScript==
(function() {
'use strict';
// --- 配置 ---
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 = 'telegramTranslateAutoSendPref'; // 用于 localStorage 的键名
// --- 翻译提示 (无变化) ---
const TRANSLATION_PROMPT = `
Role: You are a professional translator executing a specific task.
Task: Translate the user's Chinese or Burmese text, OR existing English text, into US-style English, adhering strictly to the output requirements below. Apply the abbreviation rules even if the input is already English.
Strict Output Requirements:
1. **Style:** Use US-style English with common letter-based abbreviations (e.g., u, ur, r, thx, &, bfr, frst, tmrw, nxt).
2. **Sophistication:** Maintain a high English level with polished, articulate, and sophisticated word choices despite abbreviations.
3. **Meaning:** Preserve the full original meaning. Include question marks if the original is a question.
4. **Punctuation:** Do NOT end the translation with a period (.).
5. **Abbreviations:** ONLY use letter-based abbreviations. ABSOLUTELY NO number-based abbreviations (NO "2" for "to", NO "4" for "for"). Use "to", "for". Double-check your output for numbers in abbreviations.
6. **Format:** Output ONLY the translated text. NO explanations, NO notes, NO apologies, NO preliminary remarks. If translation/abbreviation is unnecessary (e.g., proper nouns, codes), return the original text unmodified.
Input Text to Translate/Abbreviate:
{text_to_translate}
`;
// --- 选择器 ---
const INPUT_SELECTOR = 'div.input-message-input[contenteditable="true"]'; // 输入框
const SEND_BUTTON_SELECTOR = 'button.btn-send'; // 发送按钮
const INPUT_AREA_CONTAINER_SELECTOR = '.chat-input-main'; // 输入区域容器
// --- UI 元素 ID ---
const STATUS_BAR_ID = 'custom-input-status-bar'; // 状态栏 ID
const AUTO_SEND_TOGGLE_ID = 'custom-auto-send-toggle'; // 自动发送开关 ID
const RETRY_BUTTON_ID = 'custom-translate-retry-button'; // 重试原文按钮 ID
const RETRY_ABBREVIATION_BUTTON_ID = 'custom-abbreviation-retry-button'; // 重试缩写按钮 ID
// --- 语言检测正则 ---
const CHINESE_REGEX = /[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]/; // 中文正则
const BURMESE_REGEX = /[\u1000-\u109F]/; // 缅甸文正则
// --- 状态变量 ---
let statusBarElement = null; // 状态栏 DOM 元素
let autoSendToggleElement = null; // 自动发送开关 DOM 元素
let currentInputApiXhr = null; // 当前的 API 请求对象
let isTranslatingAndSending = false; // 是否正在翻译和发送中
let sendButtonClickListenerAttached = false; // 发送按钮监听器是否已附加
let lastOriginalText = null; // 上次翻译的原文(用于重试)
const translationCache = new Map(); // 翻译缓存
// --- << MODIFIED v2.9.3 >> 自动发送状态变量 (默认开启 + 从 localStorage 加载) ---
let autoSendEnabled = true; // 默认设置为开启
// 尝试从 localStorage 加载保存的设置
const savedAutoSendState = localStorage.getItem(STORAGE_KEY_AUTOSEND);
if (savedAutoSendState !== null) { // 检查是否存在已保存的值
// localStorage 存储的是字符串 "true" 或 "false",需要转换回布尔值
autoSendEnabled = savedAutoSendState === 'true';
console.log(`[InputTranslate] 已加载自动发送偏好设置: ${autoSendEnabled}`);
} else {
console.log(`[InputTranslate] 未找到已保存的自动发送偏好设置,使用默认值: ${autoSendEnabled}`);
}
// --- 自动发送状态变量修改结束 ---
// --- CSS 样式 (无变化) ---
GM_addStyle(`
/* 容器 */
${INPUT_AREA_CONTAINER_SELECTOR} { position: relative !important; overflow: visible !important; }
/* 状态栏 */
#${STATUS_BAR_ID} {
position: absolute; bottom: 2px; left: 8px; right: 8px; display: none; /* 默认隐藏 */
padding: 4px 8px; font-size: 12px; color: #ccc; /* 浅灰色文字 */
background-color: rgba(20, 20, 20, 0.85); backdrop-filter: blur(2px); /* 深灰半透明背景 + 毛玻璃效果 */
border-top: 1px solid rgba(255, 255, 255, 0.1); border-radius: 4px; /* 顶部细线 + 圆角 */
z-index: 149; line-height: 1.3; text-align: left; /* 层级, 行高, 对齐 */
transition: opacity 0.2s ease-in-out, bottom 0.2s ease-in-out; /* 过渡效果 */
opacity: 0; pointer-events: none; /* 默认透明且不响应鼠标事件 */
}
#${STATUS_BAR_ID}.visible {
display: flex; justify-content: space-between; align-items: center; /* Flex 布局, 两端对齐, 垂直居中 */
opacity: 1; pointer-events: auto; /* 可见且响应鼠标 */
}
#${STATUS_BAR_ID} .status-text { /* 状态文本容器 */
flex-grow: 1; margin-right: 8px; /* 占据剩余空间, 右边距 */
}
#${STATUS_BAR_ID} .status-buttons { /* 按钮容器 */
display: flex; gap: 5px; /* Flex 布局, 按钮间距 */
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} {
position: absolute; bottom: 100%; margin-bottom: 1px; right: 10px; /* 定位在输入框顶部靠右 */
z-index: 151; padding: 4px 10px; font-size: 12px; font-weight: bold; /* 层级, 内边距, 字号, 粗体 */
background-color: rgba(80, 80, 80, 0.9); color: #ccc; /* 默认背景和文字颜色 */
border: 1px solid rgba(255, 255, 255, 0.2); border-bottom: none; /* 边框, 无下边框 */
border-radius: 6px 6px 0 0; cursor: pointer; user-select: none; /* 顶部圆角, 手型光标, 禁止选中文本 */
transition: background-color 0.2s ease, color 0.2s ease; /* 过渡 */
}
#${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) { try { const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(element); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); element.focus(); } catch (e) { console.warn("[InputTranslate] 设置光标时出错:", e); } }
function ensureControlsExist() { // 确保控制按钮和状态栏存在
const inputMainContainer = document.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
if (!inputMainContainer) return;
if (window.getComputedStyle(inputMainContainer).position !== 'relative') { inputMainContainer.style.position = 'relative'; }
// 检查并创建自动发送按钮
if (!autoSendToggleElement || !inputMainContainer.contains(autoSendToggleElement)) {
autoSendToggleElement = document.createElement('button');
autoSendToggleElement.id = AUTO_SEND_TOGGLE_ID;
updateAutoSendButtonVisual(); // 使用加载后的 autoSendEnabled 值更新视觉
autoSendToggleElement.addEventListener('click', toggleAutoSend);
inputMainContainer.appendChild(autoSendToggleElement);
console.log("[InputTranslate] 自动发送开关按钮已创建。");
}
// 检查并创建状态栏
if (!statusBarElement || !inputMainContainer.contains(statusBarElement)) {
statusBarElement = document.createElement('div');
statusBarElement.id = STATUS_BAR_ID;
inputMainContainer.appendChild(statusBarElement);
console.log("[InputTranslate] 状态栏元素已创建。");
}
}
function updateStatusDisplay(content, type = 'status', duration = 0, showRetryButton = false, showRetryAbbreviationButton = false) { // 更新状态栏显示
ensureControlsExist(); // 确保元素存在
if (!statusBarElement) { console.error("[InputTranslate] 更新时未找到状态栏元素。"); 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'); // 使状态栏可见
// 附加监听器
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;
}
function hideStatusDisplay() { // 隐藏状态栏
if (statusBarElement) {
if (statusBarElement.hideTimeout) clearTimeout(statusBarElement.hideTimeout);
statusBarElement.hideTimeout = null;
statusBarElement.classList.remove('visible');
setTimeout(() => { if (statusBarElement && !statusBarElement.classList.contains('visible')) { statusBarElement.innerHTML = ''; } }, 250);
}
}
function fixNumberAbbreviations(text) { // 数字缩写修正 (无变化)
if (!text) return text;
let originalText = text;
text = text.replace(/\b2\b/gi, "to"); text = text.replace(/\b4\b/gi, "for");
text = text.replace(/\b(be?|b)4\b/gi, "before"); text = text.replace(/\b2day\b/gi, "today");
text = text.replace(/\b2nite\b/gi, "tonight"); text = text.replace(/\b2night\b/gi, "tonight");
text = text.replace(/\b2mrw\b/gi, "tomorrow"); text = text.replace(/\b2moro\b/gi, "tomorrow");
text = text.replace(/\bgr8\b/gi, "great"); text = text.replace(/\bl8r\b/gi, "later");
text = text.replace(/\bw8\b/gi, "wait"); text = text.replace(/\bh8\b/gi, "hate");
text = text.replace(/\bsk8\b/gi, "skate"); text = text.replace(/\bm8\b/gi, "mate");
if (text !== originalText) { console.log("[InputTranslate] 应用了数字/组合缩写修正:", originalText, "->", text); }
return text;
}
// --- 重试按钮处理程序 (无变化) ---
function handleRetryOriginalClick(event) { // 处理“重试原文”点击
event.preventDefault(); event.stopPropagation();
console.log("[InputTranslate] “重试原文”按钮被点击。");
if (isTranslatingAndSending) { console.warn("[InputTranslate] 正在翻译中,忽略“重试原文”点击。"); return; }
if (!lastOriginalText) { console.warn("[InputTranslate] 没有存储原文可供重试。"); hideStatusDisplay(); return; }
const inputElement = document.querySelector(INPUT_SELECTOR);
const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (!inputElement || !sendButton) { updateStatusDisplay("重试失败: 界面元素丢失", 'error', 4000, true, false); return; }
if (sendButton.disabled) { updateStatusDisplay("重试失败: 发送按钮不可用", 'error', 4000, true, false); return; }
console.log("[InputTranslate] 正在重试原文翻译:", lastOriginalText);
translateAndSend(lastOriginalText, inputElement, sendButton, true);
}
function handleRetryAbbreviationClick(event) { // 处理“重试缩写”点击
event.preventDefault(); event.stopPropagation();
console.log("[InputTranslate] “重试缩写”按钮被点击。");
if (isTranslatingAndSending) { console.warn("[InputTranslate] 正在翻译中,忽略“重试缩写”点击。"); return; }
const inputElement = document.querySelector(INPUT_SELECTOR);
const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (!inputElement || !sendButton) { updateStatusDisplay("重试失败: 界面元素丢失", 'error', 4000, true, false); return; }
const currentText = inputElement.textContent?.trim();
if (!currentText) { console.warn("[InputTranslate] 输入框为空,无法重试缩写。"); hideStatusDisplay(); return; }
if (sendButton.disabled) { updateStatusDisplay("重试失败: 发送按钮不可用", 'error', 4000, true, true); return; }
console.log("[InputTranslate] 正在对当前文本重试缩写/翻译:", currentText);
translateAndSend(currentText, inputElement, sendButton, true);
}
// --- << MODIFIED v2.9.3 >> 自动发送切换逻辑 (增加保存到 localStorage) ---
function updateAutoSendButtonVisual() { // 更新自动发送按钮的文本和样式
if (!autoSendToggleElement) return;
autoSendToggleElement.textContent = autoSendEnabled ? "自动发送: 开" : "自动发送: 关";
autoSendToggleElement.className = autoSendEnabled ? 'autosend-on' : '';
autoSendToggleElement.id = AUTO_SEND_TOGGLE_ID;
}
function toggleAutoSend() { // 切换自动发送状态并保存
autoSendEnabled = !autoSendEnabled; // 切换状态
console.log(`[InputTranslate] 自动发送切换: ${autoSendEnabled ? '开启' : '关闭'}`);
updateAutoSendButtonVisual(); // 更新按钮外观
updateStatusDisplay(`自动发送已${autoSendEnabled ? '开启' : '关闭'}`, 'status', 2000); // 显示状态提示
// --- << NEW v2.9.3 >> 将新的状态保存到 localStorage ---
try {
localStorage.setItem(STORAGE_KEY_AUTOSEND, autoSendEnabled.toString());
console.log(`[InputTranslate] 已将自动发送偏好设置 (${autoSendEnabled}) 保存到 localStorage。`);
} catch (e) {
console.error("[InputTranslate] 保存自动发送偏好设置到 localStorage 时出错:", e);
updateStatusDisplay("无法保存设置", 'error', 3000);
}
// --- 保存设置结束 ---
}
// --- 自动发送切换逻辑修改结束 ---
// --- 主要翻译逻辑 (代码本身无变化,但依赖的 autoSendEnabled 值已修改) ---
function translateAndSend(textToProcess, inputElement, sendButton, forceApi = false) {
if (isTranslatingAndSending) { console.warn("[InputTranslate] 已在处理中,忽略 translateAndSend 调用。"); return; }
if (!inputElement || !sendButton) { updateStatusDisplay("错误: 无法找到输入或发送按钮", 'error', 4000, true, false); return; }
isTranslatingAndSending = true;
const detectedLang = detectLanguage(textToProcess);
if (detectedLang === 'Chinese' || detectedLang === 'Burmese') { lastOriginalText = textToProcess; }
hideStatusDisplay();
// --- 缓存检查 ---
const useCache = !forceApi && (detectedLang === 'Chinese' || detectedLang === 'Burmese');
if (useCache && translationCache.has(textToProcess)) {
const cachedTranslation = translationCache.get(textToProcess);
console.log("[InputTranslate] 缓存命中:", textToProcess, "->", cachedTranslation);
updateStatusDisplay("已从缓存加载 ✓", 'info', 3000, false, !autoSendEnabled);
inputElement.textContent = cachedTranslation;
setCursorToEnd(inputElement);
inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
// 根据 autoSendEnabled (已加载或默认值) 决定是否发送
if (autoSendEnabled) {
const sendDelay = 50;
console.log(`[InputTranslate][Cache] 自动发送开启。设置超时 (${sendDelay}ms) 以模拟点击。`);
setTimeout(() => {
if (!isTranslatingAndSending) { console.log("[InputTranslate][CacheTimeout] 发送已中止。"); isTranslatingAndSending = false; return; }
if (sendButton && sendButton.isConnected && !sendButton.disabled) { sendButton.click(); hideStatusDisplay(); }
else { updateStatusDisplay("发送失败 (按钮不可用?)", 'error', 4000, true, true); }
isTranslatingAndSending = false;
}, sendDelay);
} else {
console.log("[InputTranslate][Cache] 自动发送关闭。");
isTranslatingAndSending = false;
}
return;
}
// --- 缓存检查结束 ---
// --- API 调用 ---
console.log(`[InputTranslate] ${forceApi ? '强制 API 调用' : '缓存未命中'}。正在调用 API (${INPUT_TRANSLATE_MODEL}) 处理: "${textToProcess.substring(0, 30)}..."`);
updateStatusDisplay("翻译/缩写中...", 'status');
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') { 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;
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] API 成功 (原始):", rawTranslation);
const checkedTranslation = fixNumberAbbreviations(rawTranslation);
if (!forceApi && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
if (translationCache.size >= MAX_CACHE_SIZE) { const oldestKey = translationCache.keys().next().value; translationCache.delete(oldestKey); }
translationCache.set(textToProcess, checkedTranslation);
console.log("[InputTranslate] 已缓存 (修正后):", textToProcess);
}
inputElement.textContent = checkedTranslation;
setCursorToEnd(inputElement);
inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));
// 根据 autoSendEnabled (已加载或默认值) 决定是否发送
if (autoSendEnabled) {
const sendDelay = 150;
console.log(`[InputTranslate][API] 自动发送开启。设置超时 (${sendDelay}ms) 以模拟点击。`);
setTimeout(() => {
if (!isTranslatingAndSending) { console.log("[InputTranslate][APITimeout] 发送已中止。"); isTranslatingAndSending = false; return; }
if (sendButton && sendButton.isConnected && !sendButton.disabled) { sendButton.click(); hideStatusDisplay(); }
else { updateStatusDisplay("发送失败 (按钮不可用?)", 'error', 4000, true, true); }
isTranslatingAndSending = false;
}, sendDelay);
} else {
console.log("[InputTranslate][API] 自动发送关闭。");
updateStatusDisplay("完成 ✓ (请手动发送或重试)", 'success', 5000, true, true);
isTranslatingAndSending = false;
}
} else { throw new Error(`API 错误: 无翻译内容 (结束原因: ${data.choices?.[0]?.finish_reason || 'N/A'})`); }
} else { let errorDetail = `HTTP ${response.status}: ${response.statusText}`; try { const errData = JSON.parse(response.responseText); errorDetail = errData.error?.message || errorDetail; } catch (e) { /* 忽略解析错误 */ } throw new Error(errorDetail); }
} catch (e) { console.error("[InputTranslate] API/解析/修正错误:", e); updateStatusDisplay(`处理失败: ${e.message.substring(0, 60)}`, 'error', 5000, true, false); isTranslatingAndSending = false; }
},
onerror: function(response) { currentInputApiXhr = null; console.error("[InputTranslate] 请求错误:", response); updateStatusDisplay(`处理失败: 网络错误 (${response.status || 'N/A'})`, 'error', 5000, true, false); isTranslatingAndSending = false; },
ontimeout: function() { currentInputApiXhr = null; console.error("[InputTranslate] 超时"); updateStatusDisplay("处理失败: 请求超时", 'error', 5000, true, false); isTranslatingAndSending = false; },
onabort: function() { currentInputApiXhr = null; console.log("[InputTranslate] API 请求已中止。"); hideStatusDisplay(); isTranslatingAndSending = false; },
timeout: 30000
});
}
// --- 事件监听器 (无变化) ---
function handleInputKeyDown(event) { // 处理输入框按键按下事件
const inputElement = event.target;
if (!inputElement || !inputElement.matches(INPUT_SELECTOR)) return;
if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey) {
if (statusBarElement && statusBarElement.classList.contains('visible') && !isTranslatingAndSending && !autoSendEnabled) {
const nonBlockingStatus = statusBarElement.querySelector('span.success, span.info');
if (nonBlockingStatus) { console.log("[InputTranslate][Enter] 检测到非阻塞状态,允许手动发送。"); hideStatusDisplay(); return; }
}
if (isTranslatingAndSending) { event.preventDefault(); event.stopPropagation(); return; }
const text = inputElement.textContent?.trim() || "";
const detectedLang = detectLanguage(text);
if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
console.log(`[InputTranslate][Enter] 检测到 ${detectedLang}。正在处理...`);
event.preventDefault(); event.stopPropagation();
const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if (!sendButton) { updateStatusDisplay("错误: 未找到发送按钮!", 'error', 5000, true, false); return; }
if (sendButton.disabled) { updateStatusDisplay("错误: 发送按钮不可用!", 'error', 5000, true, false); return; }
translateAndSend(text, inputElement, sendButton);
} else { hideStatusDisplay(); }
}
else if (isTranslatingAndSending && !['Shift', 'Control', 'Alt', 'Meta', 'Enter'].includes(event.key)) {
hideStatusDisplay();
if (currentInputApiXhr && typeof currentInputApiXhr.abort === 'function') { currentInputApiXhr.abort(); }
else { isTranslatingAndSending = false; }
} else if (!isTranslatingAndSending) {
if (statusBarElement && statusBarElement.classList.contains('visible')) {
const statusSpan = statusBarElement.querySelector('span.status');
if (!statusSpan) { hideStatusDisplay(); }
}
}
}
function handleSendButtonClick(event) { // 处理发送按钮点击事件
const sendButton = event.target.closest(SEND_BUTTON_SELECTOR);
if (!sendButton) return;
if (statusBarElement && statusBarElement.classList.contains('visible') && !isTranslatingAndSending && !autoSendEnabled) {
const nonBlockingStatus = statusBarElement.querySelector('span.success, span.info');
if (nonBlockingStatus) { console.log("[InputTranslate][Click] 检测到非阻塞状态,允许手动发送。"); hideStatusDisplay(); return; }
}
const inputElement = document.querySelector(INPUT_SELECTOR);
if (!inputElement) return;
const text = inputElement.textContent?.trim() || "";
const detectedLang = detectLanguage(text);
if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
if (isTranslatingAndSending) { event.preventDefault(); event.stopPropagation(); return; }
console.log(`[InputTranslate][Click] 检测到 ${detectedLang}。正在处理...`);
event.preventDefault(); event.stopPropagation();
if (sendButton.disabled) { updateStatusDisplay("错误: 发送按钮不可用!", 'error', 5000, true, false); return; }
translateAndSend(text, inputElement, sendButton);
} else {
if (!isTranslatingAndSending) { hideStatusDisplay(); }
}
}
// --- 初始化与附加监听器 (代码本身无变化,但依赖的 ensureControlsExist 已被间接影响) ---
function initialize() { // 初始化脚本
console.log("[Telegram Input Translator v2.9.3 - 默认开启自动发送 + 持久化设置] 初始化...");
const observer = new MutationObserver(mutations => {
let controlsNeedCheck = false;
mutations.forEach(mutation => {
if (mutation.addedNodes) {
mutation.addedNodes.forEach(node => {
if (node.nodeType !== 1) return;
const containerNode = node.matches(INPUT_AREA_CONTAINER_SELECTOR) ? node : node.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
if(containerNode) controlsNeedCheck = true;
const inputElementNode = node.matches(INPUT_SELECTOR) ? node : node.querySelector(INPUT_SELECTOR);
if (inputElementNode && !inputElementNode.dataset.customInputTranslateListener) {
attachInputListeners(inputElementNode);
controlsNeedCheck = true;
}
});
}
if (mutation.target && mutation.target.matches && mutation.target.matches(INPUT_AREA_CONTAINER_SELECTOR)) {
controlsNeedCheck = true;
}
});
if (controlsNeedCheck) { ensureControlsExist(); } // 确保控件存在并反映正确的自动发送状态
if (!sendButtonClickListenerAttached) { const sendButton = document.querySelector(SEND_BUTTON_SELECTOR); if (sendButton && !sendButton.dataset.customSendClickListener) { attachSendButtonListener(sendButton); } }
});
observer.observe(document.body, { childList: true, subtree: true });
// 初始检查
const initialContainer = document.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
if (initialContainer) { ensureControlsExist(); } // 调用 ensureControlsExist 来创建/更新按钮
const initialInputElement = document.querySelector(INPUT_SELECTOR);
if (initialInputElement && !initialInputElement.dataset.customInputTranslateListener) { attachInputListeners(initialInputElement); }
const initialSendButton = document.querySelector(SEND_BUTTON_SELECTOR);
if(initialSendButton && !initialSendButton.dataset.customSendClickListener) { attachSendButtonListener(initialSendButton); }
console.log("[Telegram Input Translator v2.9.3] 观察者已激活。");
}
function attachInputListeners(inputElement) { // 附加监听器到输入框
if (inputElement.dataset.customInputTranslateListener) return;
console.log("[InputTranslate] 正在附加 Keydown 监听器到输入框:", inputElement);
inputElement.addEventListener('keydown', handleInputKeyDown, true);
inputElement.dataset.customInputTranslateListener = 'true';
ensureControlsExist(); // 确保相关控件存在
}
function attachSendButtonListener(sendButton) { // 附加监听器到发送按钮
if (sendButton.dataset.customSendClickListener) return;
console.log("[InputTranslate] 正在附加 Click 监听器到发送按钮:", sendButton);
sendButton.addEventListener('click', handleSendButtonClick, true);
sendButton.dataset.customSendClickListener = 'true';
sendButtonClickListenerAttached = true;
const buttonObserver = new MutationObserver(() => {
if (!sendButton.isConnected) {
console.log("[InputTranslate] 发送按钮已移除。重置监听器标志。");
buttonObserver.disconnect();
if (sendButton.dataset.customSendClickListener) { delete sendButton.dataset.customSendClickListener; }
sendButtonClickListenerAttached = false;
}
});
if (sendButton.parentNode) { buttonObserver.observe(sendButton.parentNode, { childList: true, subtree: false }); }
else { console.warn("[InputTranslate] 未找到发送按钮的父节点用于观察器。"); }
}
// --- 启动初始化 ---
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 1800));
} else {
setTimeout(initialize, 1800);
}
})();