您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Discord 自动翻译脚本 - Ctrl+Enter 翻译成英文后发送
当前为
// ==UserScript== // @name Discord Auto Translator (Ctrl+Enter) // @namespace http://tampermonkey.net/ // @version 1.1 // @license MIT // @description Discord 自动翻译脚本 - Ctrl+Enter 翻译成英文后发送 // @author You // @match https://discord.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=discord.com // @grant GM_getValue // @grant GM_setValue // @grant GM_xmlhttpRequest // @connect openrouter.ai // ==/UserScript== (function() { 'use strict'; console.log('[Discord Translator] 脚本已加载'); // ========== 配置部分 ========== // 翻译提示词 - 可以根据需要修改 const TRANSLATION_PROMPT = `You are a professional translator. Translate the following text to English. Keep the original format, including markdown syntax, mentions (@username), and special Discord formatting. Only return the translated text without any explanation or additional content. If the text is already in English, return it as is.`; // OpenRouter 配置 const OPENROUTER_BASE_URL = 'https://openrouter.ai/api/v1/chat/completions'; const MODEL = 'google/gemini-2.5-flash-preview-05-20'; // ========== API Key 管理 ========== let apiKey = GM_getValue('openrouter_api_key', ''); if (!apiKey) { apiKey = prompt('[Discord Translator] 请输入您的 OpenRouter API Key:'); if (apiKey) { GM_setValue('openrouter_api_key', apiKey); console.log('[Discord Translator] API Key 已保存'); } else { console.error('[Discord Translator] 未提供 API Key,脚本将无法工作'); alert('未提供 API Key,翻译功能将无法使用!'); } } // ========== 状态管理 ========== let isTranslating = false; let translatedContent = null; let shouldTranslate = false; let isProgrammaticEnter = false; // 新增:标记是否是程序触发的 Enter // ========== 工具函数 ========== // 从 Discord 输入框提取纯文本内容 function extractTextFromDiscordInput() { console.log('[Discord Translator] 开始提取输入框内容...'); const textArea = document.querySelector('.textArea__74017.textAreaSlate__74017 [role="textbox"]'); if (!textArea) { console.error('[Discord Translator] 找不到输入框'); return ''; } // 获取所有文本内容,保留格式 let content = ''; const walker = document.createTreeWalker( textArea, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null, false ); let node; while (node = walker.nextNode()) { if (node.nodeType === Node.TEXT_NODE) { // 文本节点 const text = node.textContent; if (text && !text.match(/^[\u200B\uFEFF\u00A0]+$/)) { content += text; } } else if (node.nodeType === Node.ELEMENT_NODE) { // 元素节点 if (node.tagName === 'DIV' && node.parentElement === textArea) { // 新行 if (content && !content.endsWith('\n')) { content += '\n'; } } else if (node.classList.contains('wrapper_f61d60') && node.getAttribute('role') === 'button') { // @提及 content += node.textContent; } } } // 清理末尾的换行 content = content.replace(/\n+$/, ''); console.log('[Discord Translator] 提取的内容:', content); return content; } // 调用 OpenRouter API 进行翻译 async function translateText(text) { console.log('[Discord Translator] 开始翻译文本:', text); return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: OPENROUTER_BASE_URL, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'HTTP-Referer': 'https://discord.com', 'X-Title': 'Discord Auto Translator' }, data: JSON.stringify({ model: MODEL, messages: [ { role: 'system', content: TRANSLATION_PROMPT }, { role: 'user', content: text } ], temperature: 0.3, max_tokens: 4096 }), onload: function(response) { console.log('[Discord Translator] API 响应状态:', response.status); console.log('[Discord Translator] API 响应内容:', response.responseText); if (response.status === 200) { try { const data = JSON.parse(response.responseText); const translatedText = data.choices[0].message.content; console.log('[Discord Translator] 翻译结果:', translatedText); resolve(translatedText); } catch (error) { console.error('[Discord Translator] 解析响应失败:', error); reject(error); } } else { console.error('[Discord Translator] API 请求失败:', response.status, response.responseText); reject(new Error(`API request failed: ${response.status}`)); } }, onerror: function(error) { console.error('[Discord Translator] 网络请求失败:', error); reject(error); } }); }); } // ========== 请求拦截 ========== // 保存原始的 XMLHttpRequest const originalXHR = window.XMLHttpRequest; const originalOpen = originalXHR.prototype.open; const originalSend = originalXHR.prototype.send; // 拦截 open 方法 originalXHR.prototype.open = function(method, url, ...args) { this._method = method; this._url = url; console.log('[Discord Translator] XHR Open:', method, url); return originalOpen.apply(this, [method, url, ...args]); }; // 拦截 send 方法 originalXHR.prototype.send = function(data) { console.log('[Discord Translator] XHR Send 被调用, URL:', this._url); // 检查是否是发送消息的请求 if (this._method === 'POST' && this._url && this._url.includes('/api/v9/channels/') && this._url.includes('/messages')) { console.log('[Discord Translator] 检测到发送消息请求'); console.log('[Discord Translator] 原始请求数据:', data); console.log('[Discord Translator] shouldTranslate:', shouldTranslate); console.log('[Discord Translator] translatedContent:', translatedContent); if (shouldTranslate && translatedContent) { try { // 解析原始请求数据 const requestData = JSON.parse(data); console.log('[Discord Translator] 解析的请求数据:', requestData); // 替换内容为翻译后的内容 requestData.content = translatedContent; data = JSON.stringify(requestData); console.log('[Discord Translator] 修改后的请求数据:', data); console.log('[Discord Translator] 成功替换为翻译内容!'); // 重置状态 - 在成功发送后才重置 shouldTranslate = false; translatedContent = null; } catch (error) { console.error('[Discord Translator] 修改请求数据失败:', error); } } } return originalSend.apply(this, [data]); }; // ========== 键盘事件处理 ========== document.addEventListener('keydown', async function(event) { // 检查是否在输入框中 const target = event.target; const isInTextbox = target && target.getAttribute('role') === 'textbox' && target.classList.contains('editor__1b31f'); if (!isInTextbox) { return; } console.log('[Discord Translator] 键盘事件:', event.key, 'Ctrl:', event.ctrlKey, 'isProgrammatic:', isProgrammaticEnter); // Enter 键被按下 if (event.key === 'Enter') { if (event.ctrlKey) { // Ctrl+Enter: 翻译后发送 console.log('[Discord Translator] 检测到 Ctrl+Enter'); if (!apiKey) { alert('请先设置 API Key!'); event.preventDefault(); event.stopPropagation(); return; } if (isTranslating) { console.log('[Discord Translator] 正在翻译中,请稍候...'); event.preventDefault(); event.stopPropagation(); return; } // 提取输入框内容 const content = extractTextFromDiscordInput(); if (!content.trim()) { console.log('[Discord Translator] 输入框为空,不进行翻译'); return; } // 阻止默认发送 event.preventDefault(); event.stopPropagation(); // 开始翻译 isTranslating = true; shouldTranslate = true; try { console.log('[Discord Translator] 开始翻译过程...'); translatedContent = await translateText(content); console.log('[Discord Translator] 翻译完成,准备发送'); // 标记下一个 Enter 是程序触发的 isProgrammaticEnter = true; // 模拟正常的 Enter 按键来触发发送 setTimeout(() => { const newEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true }); target.dispatchEvent(newEvent); // 重置标记 setTimeout(() => { isProgrammaticEnter = false; }, 100); }, 100); } catch (error) { console.error('[Discord Translator] 翻译失败:', error); alert('翻译失败: ' + error.message); shouldTranslate = false; translatedContent = null; } finally { isTranslating = false; } } else { // 仅 Enter: 检查是否是程序触发的 if (isProgrammaticEnter) { console.log('[Discord Translator] 检测到程序触发的 Enter,保持翻译状态'); // 不要重置状态 } else { console.log('[Discord Translator] 检测到用户按下 Enter(无 Ctrl),正常发送'); shouldTranslate = false; translatedContent = null; } } } }, true); // 使用捕获阶段 // ========== 清理 API Key 功能 ========== window.clearTranslatorAPIKey = function() { GM_setValue('openrouter_api_key', ''); console.log('[Discord Translator] API Key 已清除'); alert('API Key 已清除,刷新页面后需要重新输入'); }; console.log('[Discord Translator] 脚本初始化完成'); console.log('[Discord Translator] 提示:在控制台输入 clearTranslatorAPIKey() 可以清除保存的 API Key'); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址