您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Discord 自动翻译脚本 - Ctrl+Enter 翻译成英文后发送
// ==UserScript== // @name Discord Auto Translator (Ctrl+Enter) // @namespace http://tampermonkey.net/ // @version 1.2 // @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 // ========== 工具函数 ========== function parseSlateNode(node) { // 1. 如果是文本节点,直接返回其内容 if (node.nodeType === Node.TEXT_NODE) { return node.textContent; } // 2. 如果不是元素节点,则忽略 if (node.nodeType !== Node.ELEMENT_NODE) { return ''; } const element = node; // 3. 明确忽略 Slate.js 内部使用的 "噪音" 节点 if (element.hasAttribute('data-slate-zero-width') || element.hasAttribute('data-slate-spacer')) { return ''; } // 4. 特殊处理表情符号 (标准和自定义) if (element.tagName === 'IMG' && element.classList.contains('emoji') && element.alt) { // alt 属性包含了我们需要的文本代码,例如 ":rage:" 或 ":Sydney:" return element.alt; } // 5. 特殊处理剧透 (Spoiler) // Discord 使用一个带有 'spoilerText' 类名的 span 来包裹剧透内容 if (element.matches('[class*="spoilerText"]')) { let spoilerContent = ''; for (const child of element.childNodes) { spoilerContent += parseSlateNode(child); } return `||${spoilerContent}||`; } // 6. 默认行为:递归处理所有子节点 // 这会正确处理普通文本、加粗、斜体、以及提及(@user)等容器元素 let content = ''; for (const child of element.childNodes) { content += parseSlateNode(child); } return content; } function extractTextFromDiscordInput() { console.log('[Discord Translator] 开始提取输入框内容 (Slate.js 精确版)...'); // 定位到 Discord 的 Slate 编辑器 const editor = document.querySelector('.textArea__74017.textAreaSlate__74017 [role="textbox"][data-slate-editor="true"]'); if (!editor) { console.error('[Discord Translator] 找不到 Slate 编辑器'); return ''; } let result = ''; // 编辑器中的每一行都是一个直接子元素 const lines = Array.from(editor.children); lines.forEach((lineElement, index) => { // 对每一行应用我们精确的解析函数 result += parseSlateNode(lineElement); // 在行与行之间添加换行符,但最后一行后面不加 if (index < lines.length - 1) { result += '\n'; } }); console.log('[Discord Translator] 提取的内容:', result); return result; } // 调用 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或关注我们的公众号极客氢云获取最新地址