Telegram 输入框翻译并发送 (v3.0.1 - 优化提示+本地缩写+持久化)

v2.9.3基础: 集成优化版提示词(v2.9.7 token优化版),添加本地applyLetterAbbreviations函数强制应用指定字母缩写,并保持自动发送默认开启及持久化设置。

当前为 2025-04-29 提交的版本,查看 最新版本

// ==UserScript==
// @name         Telegram 输入框翻译并发送 (v3.0.1 - 优化提示+本地缩写+持久化)
// @namespace    http://tampermonkey.net/
// @version      3.0.1
// @description  v2.9.3基础: 集成优化版提示词(v2.9.7 token优化版),添加本地applyLetterAbbreviations函数强制应用指定字母缩写,并保持自动发送默认开启及持久化设置。
// @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 SCRIPT_VERSION = '3.0.1'; // << NEW v3.0.1 >> Script version for logging
    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 的键名

    // --- << MODIFIED v3.0.1 >> 翻译提示 (来自 v3.0.1 - 优化Token v2.9.7) ---
    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:
1. Style: Sophisticated US English.
2. Abbreviations (ONLY these letters): u, ur, r, thx, &, bfr, frst, tmrw, nxt.
3. Capitalization: Correct sentence start (e.g., "U r here").
4. NO Number Abbreviations: Use "to", "for". Absolutely no "2", "4". Double-check.
5. Punctuation: NO period (.) at the end. Keep original question marks (?).
6. Output: ONLY the final processed text. No explanations or extra words.
7. Untranslatable Input: If input is *entirely* code, numbers, etc., return original unmodified.

Input Text:
{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(); // 翻译缓存

    // --- 自动发送状态变量 (默认开启 + 从 localStorage 加载) ---
    let autoSendEnabled = true; // 默认设置为开启
    const savedAutoSendState = localStorage.getItem(STORAGE_KEY_AUTOSEND);
    if (savedAutoSendState !== null) {
        autoSendEnabled = savedAutoSendState === 'true';
        console.log(`[InputTranslate v${SCRIPT_VERSION}] 已加载自动发送偏好设置: ${autoSendEnabled}`);
    } else {
        console.log(`[InputTranslate v${SCRIPT_VERSION}] 未找到已保存的自动发送偏好设置,使用默认值: ${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 v${SCRIPT_VERSION}] 设置光标时出错:`, 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();
            autoSendToggleElement.addEventListener('click', toggleAutoSend);
            inputMainContainer.appendChild(autoSendToggleElement);
            console.log(`[InputTranslate v${SCRIPT_VERSION}] 自动发送开关按钮已创建。`);
        }
        if (!statusBarElement || !inputMainContainer.contains(statusBarElement)) {
            statusBarElement = document.createElement('div');
            statusBarElement.id = STATUS_BAR_ID;
            inputMainContainer.appendChild(statusBarElement);
            console.log(`[InputTranslate v${SCRIPT_VERSION}] 状态栏元素已创建。`);
        }
    }
    function updateStatusDisplay(content, type = 'status', duration = 0, showRetryButton = false, showRetryAbbreviationButton = false) {
        ensureControlsExist();
        if (!statusBarElement) { console.error(`[InputTranslate 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');
        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 v${SCRIPT_VERSION}] 应用了数字/组合缩写修正:`, originalText, "->", text); }
        return text;
    }

    // --- << NEW from v3.0.1 >> 应用指定的字母缩写 (完整中文日志) ---
    function applyLetterAbbreviations(text) {
        if (!text) return text;
        let originalText = text;
        let modifiedText = text;
        let initialCapitalizationApplied = false;
        let changesMade = false;

        // 映射: 完整单词 -> 缩写
        const abbrMap = { "you": "u", "your": "ur", "are": "r", "thanks": "thx", "and": "&", "before": "bfr", "first": "frst", "tomorrow": "tmrw", "next": "nxt" };
        // 这些缩写在句首时需要大写首字母
        const capitalizeAtStart = ["u", "ur", "r", "thx", "bfr", "frst", "tmrw", "nxt"];

        // 1. 处理句子开头的第一个单词
        let firstWordIndex = -1;
        let firstWord = "";
        let leadingChars = ""; // 开头的空格或非字母字符
        const match = modifiedText.match(/^(\s*[^a-zA-Z\s]*)?([a-zA-Z]+)/); // 匹配开头的非字母+字母单词
        if (match) {
            leadingChars = match[1] || ""; // 获取开头的非字母部分 (可能为空)
            firstWord = match[2];        // 获取第一个字母单词
            firstWordIndex = leadingChars.length; // 第一个单词的起始索引
            const lowerFirstWord = firstWord.toLowerCase();

            if (abbrMap.hasOwnProperty(lowerFirstWord)) { // 如果第一个单词在映射表中
                const abbreviation = abbrMap[lowerFirstWord];
                let replacementMade = false;
                // 检查是否需要大写首字母
                if (capitalizeAtStart.includes(abbreviation)) {
                    const capitalizedAbbr = abbreviation.charAt(0).toUpperCase() + abbreviation.slice(1);
                    modifiedText = leadingChars + capitalizedAbbr + modifiedText.substring(firstWordIndex + firstWord.length);
                    initialCapitalizationApplied = true; // 标记已处理句首大写
                    replacementMade = true;
                } else if (abbreviation === '&') { // '&' 在句首保持原样
                     modifiedText = leadingChars + abbreviation + modifiedText.substring(firstWordIndex + firstWord.length);
                     initialCapitalizationApplied = true; // '&' 也算处理过句首
                     replacementMade = true;
                }
                // else: 如果是其他不需要大写的缩写(目前没有),则不做替换,让后续步骤处理小写

                if (replacementMade) changesMade = true; // 标记发生了更改
            }
        }

        // 2. 替换剩余文本中的单词 (使用函数避免闭包问题)
        const replaceRemaining = (fullWord, abbr) => {
            // 构造正则表达式,匹配单词边界 (\b)
            const regexLower = new RegExp(`\\b${fullWord}\\b`, 'g'); // 匹配小写形式,全局替换
            const regexUpper = new RegExp(`\\b${fullWord.charAt(0).toUpperCase() + fullWord.slice(1)}\\b`, 'g'); // 匹配首字母大写形式

             // 确定从哪里开始替换 (如果句首单词已被处理,则跳过它)
             let startIndex = 0;
             if (initialCapitalizationApplied && firstWord.toLowerCase() === fullWord) {
                 startIndex = firstWordIndex + abbrMap[fullWord].length; // 从已替换的缩写之后开始
             }

             let targetStringPart = modifiedText.substring(startIndex); // 需要处理的部分
             let prefix = modifiedText.substring(0, startIndex);      // 不需要处理的前缀
             let replacedPart = targetStringPart;                      // 复制待处理部分
             let currentChangesMade = false;
             const originalLength = replacedPart.length;

             // 执行替换
             if (abbr === '&') {
                 // 特殊处理 'and' -> '&', 忽略大小写
                 replacedPart = replacedPart.replace(/\b[Aa]nd\b/g, '&');
             } else {
                 // 替换小写和首字母大写形式
                 replacedPart = replacedPart.replace(regexLower, abbr);
                 replacedPart = replacedPart.replace(regexUpper, abbr); // 如果首字母大写了,也替换成小写缩写
             }

             if(replacedPart.length !== originalLength || replacedPart !== targetStringPart) currentChangesMade = true; // 检查是否有实际变化

             modifiedText = prefix + replacedPart; // 拼接回完整字符串

             if (currentChangesMade) changesMade = true; // 更新全局更改标记
        };

        // 遍历映射表,对每个单词执行替换
        for (const word in abbrMap) {
             replaceRemaining(word, abbrMap[word]);
        }


        // 中文日志记录结果
        if (changesMade) {
            console.log(`[InputTranslate v${SCRIPT_VERSION}] ApplyLetterAbbreviations: 是,已应用更改。\n    输入: "${originalText}"\n    输出: "${modifiedText}"`);
        } else {
            // 如果没有进行任何替换,也打印日志说明
            console.log(`[InputTranslate v${SCRIPT_VERSION}] ApplyLetterAbbreviations: 否,未应用任何更改。\n    输入: "${originalText}"`);
        }
        return modifiedText; // 返回处理后的文本
    }

    // --- 重试按钮处理程序 ---
    function handleRetryOriginalClick(event) {
         event.preventDefault(); event.stopPropagation();
         console.log(`[InputTranslate v${SCRIPT_VERSION}] “重试原文”按钮被点击。`);
         if (isTranslatingAndSending) { console.warn(`[InputTranslate v${SCRIPT_VERSION}] 正在翻译中,忽略“重试原文”点击。`); return; }
         if (!lastOriginalText) { console.warn(`[InputTranslate v${SCRIPT_VERSION}] 没有存储原文可供重试。`); 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 v${SCRIPT_VERSION}] 正在重试原文翻译:`, lastOriginalText);
         translateAndSend(lastOriginalText, inputElement, sendButton, true); // forceApi = true
    }
    function handleRetryAbbreviationClick(event) {
        event.preventDefault(); event.stopPropagation();
        console.log(`[InputTranslate v${SCRIPT_VERSION}] “重试缩写”按钮被点击。`);
        if (isTranslatingAndSending) { console.warn(`[InputTranslate v${SCRIPT_VERSION}] 正在翻译中,忽略“重试缩写”点击。`); 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 v${SCRIPT_VERSION}] 输入框为空,无法重试缩写。`); hideStatusDisplay(); return; }
        if (sendButton.disabled) { updateStatusDisplay("重试失败: 发送按钮不可用", 'error', 4000, true, true); return; }
        // 重试缩写时,我们假设用户可能想重新触发翻译+缩写流程,特别是如果API没做好
        // 或者,如果用户只是想应用本地缩写,那么应该只调用 applyLetterAbbreviations
        // 为了与“重试原文”一致,这里也强制调用 API
        console.log(`[InputTranslate v${SCRIPT_VERSION}] 正在对当前文本重试翻译/缩写:`, currentText);
        translateAndSend(currentText, inputElement, sendButton, true); // forceApi = true
    }

    // --- 自动发送切换逻辑 (带 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 v${SCRIPT_VERSION}] 自动发送切换: ${autoSendEnabled ? '开启' : '关闭'}`);
        updateAutoSendButtonVisual();
        updateStatusDisplay(`自动发送已${autoSendEnabled ? '开启' : '关闭'}`, 'status', 2000);
        try {
            localStorage.setItem(STORAGE_KEY_AUTOSEND, autoSendEnabled.toString());
            console.log(`[InputTranslate v${SCRIPT_VERSION}] 已将自动发送偏好设置 (${autoSendEnabled}) 保存到 localStorage。`);
        } catch (e) {
            console.error(`[InputTranslate v${SCRIPT_VERSION}] 保存自动发送偏好设置到 localStorage 时出错:`, e);
            updateStatusDisplay("无法保存设置", 'error', 3000);
        }
    }

    // --- << MODIFIED v3.0.1 >> 主要翻译逻辑 (调用 applyLetterAbbreviations) ---
    function translateAndSend(textToProcess, inputElement, sendButton, forceApi = false) {
        if (isTranslatingAndSending) { console.warn(`[InputTranslate v${SCRIPT_VERSION}] 已在处理中,忽略 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 cachedTranslationRaw = translationCache.get(textToProcess); // 获取缓存的 (数字修正后) 的结果
            console.log(`[InputTranslate v${SCRIPT_VERSION}] 缓存命中 (原始缓存):`, textToProcess, "->", cachedTranslationRaw);
            // << MODIFIED v3.0.1 >> 应用字母缩写
            const finalCachedText = applyLetterAbbreviations(cachedTranslationRaw);
            updateStatusDisplay("已从缓存加载 ✓", 'info', 3000, false, !autoSendEnabled);
            inputElement.textContent = finalCachedText; // 显示最终处理过的文本
            setCursorToEnd(inputElement);
            inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

            if (autoSendEnabled) {
                const sendDelay = 50;
                console.log(`[InputTranslate v${SCRIPT_VERSION}][Cache] 自动发送开启。设置超时 (${sendDelay}ms) 以模拟点击。`);
                setTimeout(() => {
                    if (!isTranslatingAndSending) { console.log(`[InputTranslate v${SCRIPT_VERSION}][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 v${SCRIPT_VERSION}][Cache] 自动发送关闭。`);
                isTranslatingAndSending = false;
            }
            return; // 缓存命中,结束函数
        }
        // --- 缓存检查结束 ---

        // --- API 调用 ---
        console.log(`[InputTranslate v${SCRIPT_VERSION}] ${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 v${SCRIPT_VERSION}] API 成功 (原始):`, rawTranslation);
                            // Step 1: 修正数字缩写
                            const checkedTranslation = fixNumberAbbreviations(rawTranslation);
                            // << MODIFIED v3.0.1 >> Step 2: 应用/强制字母缩写
                            const finalApiText = applyLetterAbbreviations(checkedTranslation);
                            console.log(`[InputTranslate v${SCRIPT_VERSION}] API 文本处理后 (数字修正+字母缩写):`, finalApiText);

                            // 缓存数字修正后的结果 (applyLetterAbbreviations 会在每次读取时应用)
                            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); // Cache the number-fixed version
                                console.log(`[InputTranslate v${SCRIPT_VERSION}] 已缓存 (修正数字后):`, textToProcess);
                            }

                            inputElement.textContent = finalApiText; // 显示最终处理过的文本
                            setCursorToEnd(inputElement);
                            inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true }));

                            if (autoSendEnabled) {
                                const sendDelay = 150;
                                console.log(`[InputTranslate v${SCRIPT_VERSION}][API] 自动发送开启。设置超时 (${sendDelay}ms) 以模拟点击。`);
                                setTimeout(() => {
                                    if (!isTranslatingAndSending) { console.log(`[InputTranslate v${SCRIPT_VERSION}][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 v${SCRIPT_VERSION}][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 v${SCRIPT_VERSION}] API/解析/处理错误:`, e); updateStatusDisplay(`处理失败: ${e.message.substring(0, 60)}`, 'error', 5000, true, false); isTranslatingAndSending = false; }
            },
            onerror: function(response) { currentInputApiXhr = null; console.error(`[InputTranslate v${SCRIPT_VERSION}] 请求错误:`, response); updateStatusDisplay(`处理失败: 网络错误 (${response.status || 'N/A'})`, 'error', 5000, true, false); isTranslatingAndSending = false; },
            ontimeout: function() { currentInputApiXhr = null; console.error(`[InputTranslate v${SCRIPT_VERSION}] 超时`); updateStatusDisplay("处理失败: 请求超时", 'error', 5000, true, false); isTranslatingAndSending = false; },
            onabort: function() { currentInputApiXhr = null; console.log(`[InputTranslate v${SCRIPT_VERSION}] API 请求已中止。`); hideStatusDisplay(); isTranslatingAndSending = false; },
            timeout: 30000 // 30 seconds timeout
        });
    }

    // --- 事件监听器 ---
    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 v${SCRIPT_VERSION}][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 v${SCRIPT_VERSION}][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(); } // Hide if only error/success/info was shown
             }
        }
    }
    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 v${SCRIPT_VERSION}][Click] 检测到非阻塞状态,允许手动发送。`); hideStatusDisplay(); return; }
         }
         const inputElement = document.querySelector(INPUT_SELECTOR);
         if (!inputElement) return;
         const text = inputElement.textContent?.trim() || "";
         const detectedLang = detectLanguage(text);
         // Only intercept click for translation if it's Chinese or Burmese
         if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) {
             if (isTranslatingAndSending) { event.preventDefault(); event.stopPropagation(); return; }
             console.log(`[InputTranslate v${SCRIPT_VERSION}][Click] 检测到 ${detectedLang}。正在处理...`);
             event.preventDefault(); event.stopPropagation();
             if (sendButton.disabled) { updateStatusDisplay("错误: 发送按钮不可用!", 'error', 5000, true, false); return; }
             translateAndSend(text, inputElement, sendButton);
         } else {
             // If not CJK/Burmese or already translated (no longer detected), allow normal send
             if (!isTranslatingAndSending) { hideStatusDisplay(); }
             // Do NOT preventDefault here for normal English messages etc.
         }
    }

    // --- 初始化与附加监听器 ---
    function initialize() {
        console.log(`[Telegram Input Translator v${SCRIPT_VERSION}] 初始化...`);
        const observer = new MutationObserver(mutations => {
             let controlsNeedCheck = false;
             let sendButtonMaybeAppeared = false;

             mutations.forEach(mutation => {
                if (mutation.addedNodes) {
                    mutation.addedNodes.forEach(node => {
                        if (node.nodeType !== 1) return; // Only check element nodes
                        // Check if container appeared or input box inside appeared
                        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; // Input appeared, ensure controls are there
                        }
                        // Check if send button appeared
                        const sendButtonNode = node.matches(SEND_BUTTON_SELECTOR) ? node : node.querySelector(SEND_BUTTON_SELECTOR);
                        if(sendButtonNode) sendButtonMaybeAppeared = true;
                    });
                }
                // If the container itself changed attributes/children, check controls
                 if (mutation.target && mutation.target.matches && mutation.target.matches(INPUT_AREA_CONTAINER_SELECTOR)) {
                     controlsNeedCheck = true;
                 }
                 // If the send button itself changed (e.g., enabled/disabled), maybe re-check attachment logic?
                 // Let's handle send button separately below.
            });

             if (controlsNeedCheck) { ensureControlsExist(); }

             // Check for send button listener specifically if it might have appeared or if listener is not attached
             if (sendButtonMaybeAppeared || !sendButtonClickListenerAttached) {
                 const sendButton = document.querySelector(SEND_BUTTON_SELECTOR);
                 if (sendButton && !sendButton.dataset.customSendClickListener) {
                     attachSendButtonListener(sendButton);
                 }
             }
        });
        observer.observe(document.body, { childList: true, subtree: true });

        // Initial check after delay
        const initialContainer = document.querySelector(INPUT_AREA_CONTAINER_SELECTOR);
        if (initialContainer) { 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 v${SCRIPT_VERSION}] 观察者已激活。`);
    }
    function attachInputListeners(inputElement) {
         if (inputElement.dataset.customInputTranslateListener) return;
         console.log(`[InputTranslate v${SCRIPT_VERSION}] 正在附加 Keydown 监听器到输入框:`, inputElement);
         // Use capture phase for keydown to intercept Enter before Telegram does
         inputElement.addEventListener('keydown', handleInputKeyDown, true);
         inputElement.dataset.customInputTranslateListener = 'true';
         ensureControlsExist(); // Make sure controls are present when input is ready
    }
    function attachSendButtonListener(sendButton) {
        if (sendButton.dataset.customSendClickListener) return;
         console.log(`[InputTranslate v${SCRIPT_VERSION}] 正在附加 Click 监听器到发送按钮:`, sendButton);
         // Use capture phase for click to intercept before Telegram sends
         sendButton.addEventListener('click', handleSendButtonClick, true);
         sendButton.dataset.customSendClickListener = 'true';
         sendButtonClickListenerAttached = true; // Mark as attached

         // Add observer to detect when the button is removed from DOM
         const buttonObserver = new MutationObserver(() => {
             if (!sendButton.isConnected) {
                 console.log(`[InputTranslate v${SCRIPT_VERSION}] 发送按钮已移除。重置监听器标志。`);
                 buttonObserver.disconnect();
                 if (sendButton.dataset.customSendClickListener) {
                     delete sendButton.dataset.customSendClickListener; // Clean up attribute
                 }
                 sendButtonClickListenerAttached = false; // Reset flag so it can be re-attached later
             }
         });
         // Observe the parent node for changes affecting the button
         if (sendButton.parentNode) {
             buttonObserver.observe(sendButton.parentNode, { childList: true, subtree: false });
         } else {
             console.warn(`[InputTranslate v${SCRIPT_VERSION}] 未找到发送按钮的父节点用于观察器。`);
         }
    }

    // --- 启动初始化 ---
    if (document.readyState === 'loading') {
        // Wait for DOMContentLoaded, then add a small delay for Telegram's UI to likely stabilize
        document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 1800));
    } else {
        // If already loaded, just apply the delay
        setTimeout(initialize, 1800);
    }

})();