您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
通用型选中文本翻译/解释工具,支持复杂动态网页
当前为
// ==UserScript== // @name Universal DeepSeek Text Selection // @namespace http://tampermonkey.net/ // @version 3.1 // @description 通用型选中文本翻译/解释工具,支持复杂动态网页 // @author tangwang // @license MIT // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_addStyle // @connect api.deepseek.com // @connect api.deepseek.ai // @connect * // @run-at document-start // ==/UserScript== (function() { 'use strict'; const CONFIG = { API_KEY: '', API_URL: '', MAX_RETRIES: 3, RETRY_DELAY: 1000, DEBOUNCE_DELAY: 200, SHORTCUTS: { translate: 'Alt+T', explain: 'Alt+E', summarize: 'Alt+S' } }; // 样式注入 GM_addStyle(` #ai-floating-menu { all: initial; position: fixed; z-index: 2147483647; background: white; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); padding: 5px; display: none; font-family: system-ui, -apple-system, sans-serif; animation: fadeIn 0.2s ease-in-out; } #ai-floating-menu button { all: initial; display: block; width: 120px; margin: 3px; padding: 8px 12px; background: #2c3e50; color: white; border: none; border-radius: 4px; cursor: pointer; font-family: inherit; font-size: 14px; text-align: center; transition: all 0.2s; } #ai-floating-menu button:hover { background: #34495e; transform: translateY(-1px); } #ai-floating-menu button:active { transform: translateY(1px); } #ai-floating-menu .shortcut { float: right; font-size: 12px; opacity: 0.7; } #ai-result-box { all: initial; position: fixed; z-index: 2147483647; background: white; border-radius: 8px; box-shadow: 0 3px 15px rgba(0,0,0,0.2); padding: 15px; min-width: 200px; max-width: 500px; max-height: 400px; display: none; font-family: system-ui, -apple-system, sans-serif; font-size: 14px; line-height: 1.6; color: #333; overflow: auto; animation: fadeIn 0.2s ease-in-out; } #ai-result-box .close-btn { all: initial; position: absolute; top: 8px; right: 8px; width: 20px; height: 20px; line-height: 20px; text-align: center; background: #f0f0f0; border: none; border-radius: 50%; cursor: pointer; font-family: inherit; font-size: 14px; color: #666; transition: all 0.2s; } #ai-result-box .close-btn:hover { background: #e0e0e0; transform: rotate(90deg); } #ai-result-box .content { margin-top: 5px; white-space: pre-wrap; word-break: break-word; } .loading-spinner { display: inline-block; width: 20px; height: 20px; border: 2px solid #f3f3f3; border-top: 2px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } `); // 创建UI元素 const menu = document.createElement('div'); menu.id = 'ai-floating-menu'; menu.innerHTML = ` <button data-action="translate">翻译为中文 <span class="shortcut">Alt+T</span></button> <button data-action="explain">解释内容 <span class="shortcut">Alt+E</span></button> <button data-action="summarize">总结要点 <span class="shortcut">Alt+S</span></button> `; const resultBox = document.createElement('div'); resultBox.id = 'ai-result-box'; resultBox.innerHTML = ` <button class="close-btn">×</button> <div class="content"></div> `; // 工具函数 const utils = { debounce(func, wait) { let timeout; return function(...args) { clearTimeout(timeout); timeout = setTimeout(() => func.apply(this, args), wait); }; }, async retry(fn, retries = CONFIG.MAX_RETRIES, delay = CONFIG.RETRY_DELAY) { try { return await fn(); } catch (error) { if (retries === 0) throw error; await new Promise(resolve => setTimeout(resolve, delay)); return this.retry(fn, retries - 1, delay * 2); } }, createLoadingSpinner() { return '<div class="loading-spinner"></div> 处理中...'; } }; // API调用类 class APIClient { static async call(text, action) { const prompts = { translate: '将以下内容翻译成中文:', explain: '请解释以下内容:', summarize: '请总结以下内容的要点:' }; return utils.retry(async () => { const response = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'POST', url: CONFIG.API_URL, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CONFIG.API_KEY}` }, data: JSON.stringify({ model: 'deepseek-chat', messages: [{ role: 'user', content: `${prompts[action]}${text}` }], temperature: 0.7, max_tokens: 1000 }), onload: res => { if (res.status === 200) { try { const data = JSON.parse(res.responseText); if (data.choices?.[0]?.message?.content) { resolve(data.choices[0].message.content); } else { reject(new Error('API返回格式错误')); } } catch (e) { reject(new Error('解析响应失败')); } } else { reject(new Error(`API错误: ${res.status}`)); } }, onerror: () => reject(new Error('网络请求失败')), ontimeout: () => reject(new Error('请求超时')) }); }); return response; }); } } // UI管理类 class UIManager { static ensureElementsExist() { if (!document.getElementById('ai-floating-menu')) { document.body.appendChild(menu); } if (!document.getElementById('ai-result-box')) { document.body.appendChild(resultBox); } } static showMenu(x, y) { this.ensureElementsExist(); menu.style.left = `${Math.max(0, Math.min(x, window.innerWidth - menu.offsetWidth))}px`; menu.style.top = `${Math.max(0, Math.min(y, window.innerHeight - menu.offsetHeight))}px`; menu.style.display = 'block'; } static showResult(content, x, y) { this.ensureElementsExist(); const contentDiv = resultBox.querySelector('.content'); contentDiv.innerHTML = content; const maxWidth = Math.min(500, window.innerWidth - 40); resultBox.style.maxWidth = `${maxWidth}px`; let left = Math.max(10, Math.min(x + 10, window.innerWidth - maxWidth - 20)); let top = Math.max(10, Math.min(y, window.innerHeight - resultBox.offsetHeight - 20)); resultBox.style.left = `${left}px`; resultBox.style.top = `${top}px`; resultBox.style.display = 'block'; } static hideAll() { menu.style.display = 'none'; resultBox.style.display = 'none'; } } // 文本选择管理类 class SelectionManager { static getSelectedText() { let text = ''; let range = null; // 检查常规选择 const selection = window.getSelection(); text = selection.toString().trim(); if (text && selection.rangeCount > 0) { range = selection.getRangeAt(0); return { text, range }; } // 检查iframe try { const iframes = document.getElementsByTagName('iframe'); for (const iframe of iframes) { try { const iframeSelection = iframe.contentWindow.getSelection(); const iframeText = iframeSelection.toString().trim(); if (iframeText) { return { text: iframeText, range: iframeSelection.rangeCount > 0 ? iframeSelection.getRangeAt(0) : null }; } } catch (e) {} } } catch (e) {} // 检查输入框 const activeElement = document.activeElement; if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) { const start = activeElement.selectionStart; const end = activeElement.selectionEnd; if (start !== end) { text = activeElement.value.substring(start, end).trim(); return { text, range: null }; } } return { text: '', range: null }; } } // 事件处理类 class EventHandler { static init() { UIManager.ensureElementsExist(); // 菜单按钮点击 menu.addEventListener('click', async (e) => { const button = e.target.closest('button'); if (!button) return; const action = button.dataset.action; const { text } = SelectionManager.getSelectedText(); if (!text) return; await this.handleAction(action, text, e.clientX, e.clientY); }); // 关闭按钮 resultBox.querySelector('.close-btn').addEventListener('click', () => { UIManager.hideAll(); }); // 点击外部关闭 document.addEventListener('mousedown', (e) => { if (!menu.contains(e.target) && !resultBox.contains(e.target)) { UIManager.hideAll(); } }, true); // 快捷键 document.addEventListener('keydown', (e) => { for (const [action, shortcut] of Object.entries(CONFIG.SHORTCUTS)) { const [modifier, key] = shortcut.split('+'); if (e[`${modifier.toLowerCase()}Key`] && e.key.toUpperCase() === key) { e.preventDefault(); const { text } = SelectionManager.getSelectedText(); if (text) { this.handleAction(action, text, e.clientX, e.clientY); } } } }); // 选择文本 this.addSelectionListeners(); this.observeDynamicContent(); } static async handleAction(action, text, x, y) { UIManager.hideAll(); UIManager.showResult(utils.createLoadingSpinner(), x, y); try { const response = await APIClient.call(text, action); UIManager.showResult(response, x, y); } catch (error) { UIManager.showResult(`错误: ${error.message}`, x, y); } } static addSelectionListeners(target = document) { const handleSelection = utils.debounce((e) => { const { text, range } = SelectionManager.getSelectedText(); if (!text) { UIManager.hideAll(); return; } let x = e?.clientX || 0; let y = e?.clientY || 0; if (range) { try { const rect = range.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { x = rect.right; y = rect.bottom + 5; } } catch (e) {} } UIManager.showMenu(x, y); }, CONFIG.DEBOUNCE_DELAY); target.addEventListener('mouseup', handleSelection, true); target.addEventListener('keyup', handleSelection, true); target.addEventListener('selectionchange', handleSelection, true); } static observeDynamicContent() { const observer = new MutationObserver(utils.debounce(() => { document.querySelectorAll('iframe').forEach(iframe => { try { if (iframe.contentDocument) { this.addSelectionListeners(iframe.contentDocument); } } catch (e) {} }); }, CONFIG.DEBOUNCE_DELAY)); observer.observe(document.body, { childList: true, subtree: true }); } } // 初始化 if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => EventHandler.init()); } else { EventHandler.init(); } })();