您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
按回车或点击发送按钮时,翻译输入框内容(中/缅->指定风格英文)并自动替换和发送。拦截中文/缅甸语原文发送。
当前为
// ==UserScript== // @name Telegram 输入框翻译并发送 (v2.1 - 支持回车和按钮) // @namespace http://tampermonkey.net/ // @version 2.1 // @description 按回车或点击发送按钮时,翻译输入框内容(中/缅->指定风格英文)并自动替换和发送。拦截中文/缅甸语原文发送。 // @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'; // --- Configuration --- const OHMYGPT_API_KEY = "sk-RK1MU6Cg6a48fBecBBADT3BlbKFJ4C209a954d3b4428b54b"; // 你的 OhMyGPT API Key const OHMYGPT_API_ENDPOINT = "https://api.ohmygpt.com/v1/chat/completions"; const INPUT_TRANSLATE_MODEL = "gpt-4o-mini"; // 输入框翻译模型 const TRANSLATION_PROMPT = `Ur a pro translator, & u must strictly follow these rules, understood? I’ll send Chinese, & u will translate to US-style English with abbreviations, no period at the end. Keep the full meaning & question marks for questions. Use fluent and sophisticated English to ensure the expression is polished, articulate, and of a high linguistic standard.Only use letter-based abbreviations like "u" for "you," "ur" for "your," "r" for "are," "thx" for "thanks," & but ensure the language still reflects a high English level with polished & sophisticated word choices, got it? Use & for And. U r absolutely forbidden from using numbers in abbreviations—no "2" for "to," no "4" for "for," no "b4" for "before," or any number ever! Only use letters: "to," "for," "bfr" for "before," "frst" for "first," "tmrw" for "tomorrow," "nxt" for "next." U must double-check ur output to ensure no numbers like "2" or "4" appear in abbreviations, replacing them with "to" or "for," u can't use number abbreviations in English words, clear? Chinese text to translate: {text_to_translate}`; // Selectors const INPUT_SELECTOR = 'div.input-message-input[contenteditable="true"]'; const SEND_BUTTON_SELECTOR = 'button.btn-send'; // 请根据需要检查并调整此选择器 // Input Translation Overlay (For status/error feedback) const INPUT_OVERLAY_ID = 'custom-input-translate-overlay'; // Language Detection Regex const CHINESE_REGEX = /[\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]/; const BURMESE_REGEX = /[\u1000-\u109F]/; // State Variables let inputTranslationOverlayElement = null; let currentInputApiXhr = null; let isTranslatingAndSending = false; // Flag to prevent conflicts/loops let sendButtonClickListenerAttached = false; // Track if click listener is attached // --- CSS Styles (Only for Overlay) --- GM_addStyle(` #${INPUT_OVERLAY_ID} { /* ... Overlay styles unchanged ... */ position: absolute; bottom: 100%; left: 10px; right: 10px; background-color: rgba(30, 30, 30, 0.9); backdrop-filter: blur(3px); border: 1px solid rgba(255, 255, 255, 0.2); border-bottom: none; padding: 4px 8px; font-size: 13px; color: #e0e0e0; border-radius: 6px 6px 0 0; box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.2); z-index: 150; display: none; max-height: 60px; overflow-y: auto; line-height: 1.3; text-align: left; } #${INPUT_OVERLAY_ID}.visible { display: block; } #${INPUT_OVERLAY_ID} .status { font-style: italic; color: #aaa; } #${INPUT_OVERLAY_ID} .error { font-weight: bold; color: #ff8a8a; } `); // --- Helper Functions --- 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) { const range = document.createRange(); const sel = window.getSelection(); range.selectNodeContents(element); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); element.focus(); } function ensureInputOverlayExists(inputMainContainer) { if (!inputMainContainer) return; if (!inputTranslationOverlayElement || !document.body.contains(inputTranslationOverlayElement)) { inputTranslationOverlayElement = document.createElement('div'); inputTranslationOverlayElement.id = INPUT_OVERLAY_ID; inputMainContainer.style.position = 'relative'; inputMainContainer.appendChild(inputTranslationOverlayElement); console.log("[InputTranslate] Overlay element created."); } } function updateInputOverlay(content, type = 'status', duration = 0) { if (!inputTranslationOverlayElement) { const inputContainer = document.querySelector(INPUT_SELECTOR)?.closest('.chat-input-main'); ensureInputOverlayExists(inputContainer); if(!inputTranslationOverlayElement) return; } inputTranslationOverlayElement.innerHTML = `<span class="${type}">${content}</span>`; inputTranslationOverlayElement.classList.add('visible'); inputTranslationOverlayElement.scrollTop = inputTranslationOverlayElement.scrollHeight; if (duration > 0) { setTimeout(hideInputOverlay, duration); } } function hideInputOverlay() { if (inputTranslationOverlayElement) { inputTranslationOverlayElement.classList.remove('visible'); inputTranslationOverlayElement.textContent = ''; } } // --- Shared Translate -> Replace -> Send Logic --- function translateAndSend(originalText, inputElement, sendButton) { if (isTranslatingAndSending) { console.warn("[InputTranslate] Already processing, ignoring translateAndSend call."); return; } if (!inputElement || !sendButton) { console.error("[InputTranslate] Input element or send button missing in translateAndSend."); return; } isTranslatingAndSending = true; hideInputOverlay(); // Clear previous status updateInputOverlay("翻译中...", 'status'); const finalPrompt = TRANSLATION_PROMPT.replace('{text_to_translate}', originalText); const requestBody = { model: INPUT_TRANSLATE_MODEL, messages: [{"role": "user", "content": finalPrompt }], temperature: 0.6 }; console.log(`[InputTranslate] Calling API (${INPUT_TRANSLATE_MODEL}) for translateAndSend`); 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 { const data = JSON.parse(response.responseText); const translation = data.choices?.[0]?.message?.content?.trim(); if (translation) { console.log("[InputTranslate] Success:", translation); inputElement.textContent = translation; // Replace content setCursorToEnd(inputElement); // Move cursor inputElement.dispatchEvent(new Event('input', { bubbles: true, cancelable: true })); // Trigger input event // Use a small delay before clicking send setTimeout(() => { console.log("[InputTranslate] Programmatically clicking send button."); sendButton.click(); hideInputOverlay(); // Clear "翻译中..." isTranslatingAndSending = false; // Reset flag *after* initiating send }, 150); // Increased delay slightly } else { let errorMsg = data.error?.message || "API返回空内容"; throw new Error(errorMsg); // Treat as error } } catch (e) { console.error("[InputTranslate] API/Parse Error:", e); updateInputOverlay(`翻译失败: ${e.message.substring(0, 50)}`, 'error', 4000); isTranslatingAndSending = false; // Reset flag on error } }, onerror: function(response) { /* ... error handling ... */ currentInputApiXhr = null; console.error("[InputTranslate] Request Error:", response); updateInputOverlay(`翻译失败: 网络错误 (${response.status})`, 'error', 4000); isTranslatingAndSending = false; }, ontimeout: function() { /* ... error handling ... */ currentInputApiXhr = null; console.error("[InputTranslate] Timeout"); updateInputOverlay("翻译失败: 请求超时", 'error', 4000); isTranslatingAndSending = false; }, onabort: function() { /* ... error handling ... */ currentInputApiXhr = null; console.log("[InputTranslate] API request aborted."); /* Don't reset flag here */ }, timeout: 30000 }); } // --- Event Listeners --- function handleInputKeyDown(event) { if (event.key === 'Enter' && !event.shiftKey && !event.altKey && !event.ctrlKey) { if (isTranslatingAndSending) { console.log("[InputTranslate][Enter] Ignored, already processing."); event.preventDefault(); event.stopPropagation(); return; } const inputElement = event.target; const text = inputElement.textContent?.trim() || ""; const detectedLang = detectLanguage(text); if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) { console.log(`[InputTranslate][Enter] Detected ${detectedLang}. Translating & sending...`); event.preventDefault(); // <<< PREVENT default Enter action (sending original) event.stopPropagation(); const sendButton = document.querySelector(SEND_BUTTON_SELECTOR); if (!sendButton) { updateInputOverlay("错误: 未找到发送按钮!", 'error', 5000); return; } translateAndSend(text, inputElement, sendButton); // Use the shared function } else { console.log(`[InputTranslate][Enter] Allowing normal send for ${detectedLang || 'empty'}.`); hideInputOverlay(); // Allow default action (send original text or do nothing if empty) } } else { if (!['Shift', 'Control', 'Alt', 'Meta', 'Enter'].includes(event.key)) { hideInputOverlay(); if (currentInputApiXhr && typeof currentInputApiXhr.abort === 'function') { currentInputApiXhr.abort(); isTranslatingAndSending = false; // Reset if user types other keys during processing } } } } function handleSendButtonClick(event) { // Check if the click target *is* the send button we are tracking const sendButton = event.target.closest(SEND_BUTTON_SELECTOR); if (!sendButton) { return; // Click was not on the send button or its child } if (isTranslatingAndSending) { console.log("[InputTranslate][Click] Ignored, already processing."); // Important: Don't prevent default here, allow the *second*, programmatic click to go through return; } const inputElement = document.querySelector(INPUT_SELECTOR); if (!inputElement) { console.error("[InputTranslate][Click] Input element not found."); return; // Allow default click action? Or prevent? Better allow. } const text = inputElement.textContent?.trim() || ""; const detectedLang = detectLanguage(text); if (text && (detectedLang === 'Chinese' || detectedLang === 'Burmese')) { console.log(`[InputTranslate][Click] Detected ${detectedLang}. Translating & sending...`); event.preventDefault(); // <<< PREVENT the *first* default click action (sending original) event.stopPropagation(); translateAndSend(text, inputElement, sendButton); // Use the shared function } else { console.log(`[InputTranslate][Click] Allowing normal send for ${detectedLang || 'empty'}.`); hideInputOverlay(); // Allow default click action (send original text or do nothing if empty) } } function handleInputBlur(event) { // Optional: More advanced blur handling if needed // setTimeout(() => { /* ... Check activeElement ... */ }, 200); } // --- Initialization & Attaching Listeners --- function initialize() { console.log("[Telegram Input Translator v2.1] Initializing..."); const inputElement = document.querySelector(INPUT_SELECTOR); // Attach listener to input field for Enter key if (inputElement && !inputElement.dataset.customInputTranslateListener) { console.log("[Telegram Input Translator] Attaching Keydown listener to input field."); inputElement.addEventListener('keydown', handleInputKeyDown, true); // Use capture // inputElement.addEventListener('blur', handleInputBlur); // Blur listener might be less critical now inputElement.dataset.customInputTranslateListener = 'true'; const inputContainer = inputElement.closest('.chat-input-main'); ensureInputOverlayExists(inputContainer); } else if (!inputElement) { console.log("[Telegram Input Translator] Input field not found yet, retrying init..."); setTimeout(initialize, 1500); // Retry initialization return; } // Attach listener to Send Button (using periodic check for simplicity) // A MutationObserver would be more efficient but adds complexity if (!sendButtonClickListenerAttached) { setInterval(() => { const sendButton = document.querySelector(SEND_BUTTON_SELECTOR); // Check if button exists and listener is not already attached (using a data attribute) if (sendButton && !sendButton.dataset.customSendClickListener) { console.log("[Telegram Input Translator] Attaching Click listener to Send button."); // Use capture phase for the click listener too, to intercept early sendButton.addEventListener('click', handleSendButtonClick, true); sendButton.dataset.customSendClickListener = 'true'; sendButtonClickListenerAttached = true; // Mark as attached globally (though interval continues) } else if (!sendButton && sendButtonClickListenerAttached) { // If button disappears, reset flag so we re-attach if it reappears console.log("[Telegram Input Translator] Send button lost, listener flag reset."); sendButtonClickListenerAttached = false; } }, 1000); // Check every second } console.log("[Telegram Input Translator v2.1] Initialization complete. Ready."); } // Wait for the UI if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => setTimeout(initialize, 2000)); } else { setTimeout(initialize, 2000); } })();