您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
将微博内容翻译成更易理解的中文 Github:https://github.com/SomiaWhiteRing/chinese2chinese4weibo
// ==UserScript== // @name 申请微博中译中 // @namespace https://github.com/SomiaWhiteRing/chinese2chinese4weibo // @version 0.2 // @description 将微博内容翻译成更易理解的中文 Github:https://github.com/SomiaWhiteRing/chinese2chinese4weibo // @author WhiteRing // @match https://*.weibo.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function () { 'use strict'; // 存储设置的key const STORAGE_KEYS = { API_URL: 'openai_api_url', API_KEY: 'openai_api_key', PROMPT: 'default_prompt', MODEL: 'openai_model' }; // 默认设置 const DEFAULT_SETTINGS = { apiUrl: 'https://api.deepseek.com', apiKey: '', prompt: '总结并以第一人称复述这篇微博,复述要生动简洁且前后逻辑完整并突出重点细节:', model: 'deepseek-chat' }; // 创建设置弹窗 function createSettingsDialog() { const dialog = document.createElement('div'); dialog.innerHTML = ` <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.3);"> <h3>设置</h3> <div style="margin: 10px 0;"> <label>API地址:</label><br> <input id="apiUrl" style="width: 300px" type="text" value="${GM_getValue(STORAGE_KEYS.API_URL, DEFAULT_SETTINGS.apiUrl)}"> </div> <div style="margin: 10px 0;"> <label>API Key:</label><br> <input id="apiKey" style="width: 300px" type="password" value="${GM_getValue(STORAGE_KEYS.API_KEY, '')}"> </div> <div style="margin: 10px 0;"> <label>模型:</label><br> <input id="model" style="width: 300px" type="text" value="${GM_getValue(STORAGE_KEYS.MODEL, DEFAULT_SETTINGS.model)}"> </div> <div style="margin: 10px 0;"> <label>默认提示词:</label><br> <textarea id="prompt" style="width: 300px; height: 100px">${GM_getValue(STORAGE_KEYS.PROMPT, DEFAULT_SETTINGS.prompt)}</textarea> </div> <div style="text-align: right; margin-top: 10px;"> <button id="saveSettings">保存</button> <button id="cancelSettings">取消</button> </div> </div> <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9998;"></div> `; return dialog; } // 保存设置 function saveSettings(dialog) { const apiUrl = document.getElementById('apiUrl').value; const apiKey = document.getElementById('apiKey').value; const prompt = document.getElementById('prompt').value; const model = document.getElementById('model').value; GM_setValue(STORAGE_KEYS.API_URL, apiUrl); GM_setValue(STORAGE_KEYS.API_KEY, apiKey); GM_setValue(STORAGE_KEYS.PROMPT, prompt); GM_setValue(STORAGE_KEYS.MODEL, model); document.body.removeChild(dialog); } // 调用API并处理流式响应 async function callOpenAI(text) { const apiUrl = GM_getValue(STORAGE_KEYS.API_URL); const apiKey = GM_getValue(STORAGE_KEYS.API_KEY); if (!apiUrl || !apiKey) { // 创建引导对话框 const dialog = createTranslationDialog(); document.body.appendChild(dialog); const contentDiv = dialog.querySelector('#translationContent'); contentDiv.innerHTML = ` <div style="text-align: center; padding: 20px;"> <p>您还未配置 API 密钥,请先获取密钥:</p> <p><a href="https://platform.deepseek.com/api_keys" target="_blank" style="color: #1DA1F2; text-decoration: none;"> 点击这里获取 DeepSeek API 密钥 </a></p> <p>获取后请点击"翻译设置"按钮进行配置</p> </div> `; return; } const prompt = GM_getValue(STORAGE_KEYS.PROMPT); const model = GM_getValue(STORAGE_KEYS.MODEL, DEFAULT_SETTINGS.model); console.log('开始API调用...'); // 创建对话框 const dialog = createTranslationDialog(); document.body.appendChild(dialog); const contentDiv = dialog.querySelector('#translationContent'); let fullContent = ''; try { const response = await fetch(`${apiUrl}/chat/completions`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, 'Accept': 'text/event-stream' }, body: JSON.stringify({ model: model, messages: [ { role: 'user', content: prompt + '\n\n' + text } ], temperature: 0.7, stream: true }) }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const reader = response.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { value, done } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; // 保留不完整的行 for (const line of lines) { if (line.startsWith('data: ') && line !== 'data: [DONE]') { try { const jsonStr = line.slice(6); const json = JSON.parse(jsonStr); const delta = json.choices[0]?.delta?.content || ''; if (delta) { fullContent += delta; if (contentDiv) { contentDiv.innerHTML = fullContent.split('\n').map(line => `<p style="margin: 0.5em 0;">${line}</p>` ).join(''); contentDiv.scrollTop = contentDiv.scrollHeight; } } } catch (e) { console.log('解析数据出错:', e, '原始数据:', line); } } } } // 处理最后可能剩余的数据 if (buffer) { console.log('处理剩余数据:', buffer); } return fullContent; } catch (error) { console.error('API调用失败:', error); throw error; } } // 获取微博正文内容 function getWeiboContent() { // 尝试获取微博正文内容 const contentSelectors = [ '[class*="detail_wbtext"]', // 模糊匹配detail_wbtext '[class*="Feed_body"] [class*="wbtext"]', // Feed_body下的wbtext '.wbpro-feed-content' // 旧版微博正文class ]; let content = ''; for (const selector of contentSelectors) { const element = document.querySelector(selector); if (element) { content = element.innerText.trim(); if (content) { console.log('找到微博正文:', selector); break; } } } if (!content) { throw new Error('未找到微博正文内容'); } return content; } // 创建翻译结果对话框 function createTranslationDialog() { const dialog = document.createElement('div'); dialog.innerHTML = ` <div style="position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border-radius: 8px; z-index: 9999; box-shadow: 0 0 10px rgba(0,0,0,0.3); max-width: 80%; min-width: 300px;"> <!-- 标题栏 --> <div style="padding: 16px 20px; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #eee;"> <h3 style="margin: 0; font-size: 16px; color: #333;">申请中译中!</h3> <button id="closeTranslation" style="border: none; background: none; cursor: pointer; font-size: 18px; color: #666; padding: 4px 8px;">✕</button> </div> <!-- 内容区域 --> <div style="padding: 20px;"> <div id="translationContent" style="margin: 0; line-height: 1.6; font-size: 14px; max-height: 60vh; overflow-y: auto; padding-right: 10px;"> <div class="loading" style="text-align: center;"> <span>加载中...</span> </div> </div> </div> </div> <div style="position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); z-index: 9998;"></div> `; // 添加关闭事件 dialog.querySelector('#closeTranslation').onclick = () => { document.body.removeChild(dialog); }; // 添加自定义滚动条样式 const style = document.createElement('style'); style.textContent = ` #translationContent::-webkit-scrollbar { width: 8px; } #translationContent::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; } #translationContent::-webkit-scrollbar-thumb { background: #888; border-radius: 4px; } #translationContent::-webkit-scrollbar-thumb:hover { background: #555; } `; document.head.appendChild(style); return dialog; } // 替换关注按钮 function replaceFollowButton() { const observer = new MutationObserver((mutations, obs) => { const ariaButtons = Array.from(document.querySelectorAll('button')) .filter(btn => btn.textContent.includes('无障碍')); ariaButtons.forEach(ariaBtn => { if (!ariaBtn.nextElementSibling?.hasAttribute('data-translate-btn')) { // 创建翻译按钮 const translateBtn = document.createElement('button'); translateBtn.innerHTML = '中译中'; translateBtn.className = ariaBtn.className; translateBtn.setAttribute('data-translate-btn', 'true'); translateBtn.onclick = async function () { try { console.log('点击翻译按钮'); translateBtn.disabled = true; translateBtn.innerHTML = '翻译中...'; const weiboText = getWeiboContent(); console.log('获取到微博文本:', weiboText); if (!weiboText) { throw new Error('未获取到微博内容'); } console.log('开始调用API'); await callOpenAI(weiboText); } catch (error) { console.error('翻译失败:', error); const contentDiv = document.querySelector('#translationContent'); if (contentDiv) { contentDiv.innerHTML = `<div style="color: red;">翻译失败: ${error.message || '请检查设置和网络连接'}</div>`; } } finally { translateBtn.disabled = false; translateBtn.innerHTML = '中译中'; } }; // 创建设置按钮 const settingsBtn = document.createElement('button'); settingsBtn.innerHTML = '翻译设置'; settingsBtn.className = ariaBtn.className; settingsBtn.setAttribute('data-settings-btn', 'true'); settingsBtn.onclick = function () { const dialog = createSettingsDialog(); document.body.appendChild(dialog); document.getElementById('saveSettings').onclick = () => saveSettings(dialog); document.getElementById('cancelSettings').onclick = () => document.body.removeChild(dialog); }; // 插入按钮 ariaBtn.parentNode.insertBefore(translateBtn, ariaBtn.nextSibling); translateBtn.parentNode.insertBefore(settingsBtn, translateBtn.nextSibling); } }); }); observer.observe(document.body, { childList: true, subtree: true }); } // 初始化 function init() { replaceFollowButton(); } init(); })();