您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
使用自定义AI API总结NodeSeek帖子的内容,并提供设置面板。
// ==UserScript== // @name NodeSeek AI 内容总结 // @name:en NodeSeek AI Content Summarizer // @namespace http://tampermonkey.net/ // @version 1.1 // @description 使用自定义AI API总结NodeSeek帖子的内容,并提供设置面板。 // @description:en Use a custom AI API to summarize the content of NodeSeek posts, with a settings panel. // @author Gemini // @match https://www.nodeseek.com/post-* // @icon https://www.google.com/s2/favicons?sz=64&domain=nodeseek.com // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @connect * // @license MIT // ==/UserScript== (function() { 'use strict'; // --- 样式定义 --- // 使用 GM_addStyle 添加 CSS 样式,避免污染页面 GM_addStyle(` /* 控制面板容器 */ .ns-ai-container { margin: 15px 0; padding: 15px; border: 1px solid #e0e0e0; border-radius: 8px; background-color: #f9f9f9; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } /* 按钮通用样式 */ .ns-ai-btn { padding: 8px 15px; border: 1px solid #ccc; border-radius: 5px; background-color: #fff; color: #333; cursor: pointer; font-size: 14px; margin-right: 10px; transition: all 0.2s ease; } .ns-ai-btn:hover { background-color: #f0f0f0; border-color: #bbb; } .ns-ai-btn-primary { background-color: #007bff; color: white; border-color: #007bff; } .ns-ai-btn-primary:hover { background-color: #0056b3; border-color: #0056b3; } /* 总结内容显示区域 */ #ns-ai-summary-output { margin-top: 15px; padding: 15px; border: 1px dashed #ccc; border-radius: 5px; background-color: #fff; white-space: pre-wrap; /* 保持换行 */ line-height: 1.6; color: #333; } /* 加载动画 */ .ns-ai-loader { border: 4px solid #f3f3f3; border-radius: 50%; border-top: 4px solid #3498db; width: 20px; height: 20px; animation: spin 1s linear infinite; display: inline-block; vertical-align: middle; margin-left: 10px; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* 设置弹窗样式 */ .ns-ai-modal { display: none; position: fixed; z-index: 9999; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0,0,0,0.5); justify-content: center; align-items: center; } .ns-ai-modal-content { background-color: #fefefe; margin: auto; padding: 20px; border: 1px solid #888; width: 90%; max-width: 500px; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.2); } .ns-ai-modal-content h2 { margin-top: 0; border-bottom: 1px solid #eee; padding-bottom: 10px; } .ns-ai-modal-content label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: bold; } .ns-ai-modal-content input { width: calc(100% - 20px); padding: 8px 10px; border: 1px solid #ccc; border-radius: 4px; } .ns-ai-modal-buttons { margin-top: 20px; text-align: right; } `); // --- HTML 结构 --- // 创建UI元素并插入到页面中 function createUI() { // *** 修改点: 将目标元素从 .post-header 改为 .post-title *** const postTitle = document.querySelector('.post-title'); if (!postTitle) { console.error('[NodeSeek AI] 无法找到帖子标题元素 .post-title。'); return; } // 创建主容器 const container = document.createElement('div'); container.className = 'ns-ai-container'; // 创建按钮 const summarizeBtn = document.createElement('button'); summarizeBtn.textContent = '一键总结'; summarizeBtn.id = 'ns-ai-summarize-btn'; summarizeBtn.className = 'ns-ai-btn ns-ai-btn-primary'; const settingsBtn = document.createElement('button'); settingsBtn.textContent = '设置'; settingsBtn.id = 'ns-ai-settings-btn'; settingsBtn.className = 'ns-ai-btn'; // 创建总结输出区域 const summaryOutput = document.createElement('div'); summaryOutput.id = 'ns-ai-summary-output'; summaryOutput.style.display = 'none'; // 默认隐藏 // 组装UI container.appendChild(summarizeBtn); container.appendChild(settingsBtn); container.appendChild(summaryOutput); // *** 修改点: 插入到 .post-title 元素的后面 *** postTitle.parentNode.insertBefore(container, postTitle.nextSibling); // 创建设置弹窗 createSettingsModal(); // 绑定事件 summarizeBtn.addEventListener('click', handleSummarize); settingsBtn.addEventListener('click', openSettingsModal); } // 创建设置弹窗的HTML function createSettingsModal() { const modal = document.createElement('div'); modal.id = 'ns-ai-settings-modal'; modal.className = 'ns-ai-modal'; modal.innerHTML = ` <div class="ns-ai-modal-content"> <h2>AI API 设置</h2> <p>请填入兼容 OpenAI 格式的 API 信息。</p> <label for="ns-ai-api-url">API 地址 (URL):</label> <input type="text" id="ns-ai-api-url" placeholder="例如: https://api.openai.com/v1/chat/completions"> <label for="ns-ai-api-key">密钥 (API Key):</label> <input type="password" id="ns-ai-api-key" placeholder="请输入您的 API Key"> <label for="ns-ai-api-model">模型 (Model):</label> <input type="text" id="ns-ai-api-model" placeholder="例如: gpt-3.5-turbo"> <div class="ns-ai-modal-buttons"> <button id="ns-ai-save-settings" class="ns-ai-btn ns-ai-btn-primary">保存</button> <button id="ns-ai-cancel-settings" class="ns-ai-btn">取消</button> </div> </div> `; document.body.appendChild(modal); // 绑定弹窗内部事件 document.getElementById('ns-ai-save-settings').addEventListener('click', saveSettings); document.getElementById('ns-ai-cancel-settings').addEventListener('click', closeSettingsModal); modal.addEventListener('click', (e) => { if (e.target === modal) { closeSettingsModal(); } }); } // --- 功能函数 --- // 处理总结按钮点击事件 function handleSummarize() { const outputDiv = document.getElementById('ns-ai-summary-output'); const summarizeBtn = document.getElementById('ns-ai-summarize-btn'); outputDiv.style.display = 'block'; outputDiv.innerHTML = '正在分析内容,请稍候... <div class="ns-ai-loader"></div>'; summarizeBtn.disabled = true; // 1. 获取配置 const apiUrl = GM_getValue('apiUrl'); const apiKey = GM_getValue('apiKey'); const model = GM_getValue('apiModel'); if (!apiUrl || !apiKey || !model) { outputDiv.innerHTML = '⚠️ 配置不完整,请点击“设置”按钮填写 API 信息。'; summarizeBtn.disabled = false; return; } // 2. 提取帖子内容 const postContentElement = document.querySelector('article.post-content'); if (!postContentElement) { outputDiv.innerHTML = '❌ 错误:无法找到帖子内容元素 `article.post-content`。'; summarizeBtn.disabled = false; return; } // 使用 innerText 获取纯文本,去除HTML标签 const postText = postContentElement.innerText.trim(); if (postText.length < 50) { // 内容太短,不进行总结 outputDiv.innerHTML = 'ℹ️ 内容过短,无需总结。'; summarizeBtn.disabled = false; return; } // 3. 调用AI API callAiApi(apiUrl, apiKey, model, postText); } // 调用AI进行总结 function callAiApi(url, key, model, text) { const outputDiv = document.getElementById('ns-ai-summary-output'); const summarizeBtn = document.getElementById('ns-ai-summarize-btn'); const prompt = `你是一个内容总结助手。请你用中文、精炼、客观、分点的形式总结以下帖子的核心内容,不要添加任何自己的评论或补充信息。帖子内容如下:\n\n---\n\n${text}`; GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${key}` }, data: JSON.stringify({ model: model, messages: [{ role: 'user', content: prompt }], temperature: 0.5, // 较低的温度使输出更具确定性 }), timeout: 60000, // 60秒超时 onload: function(response) { try { const data = JSON.parse(response.responseText); const summary = data.choices[0]?.message?.content; if (summary) { outputDiv.innerHTML = `<strong>🤖 AI 总结:</strong><br>${summary}`; } else { console.error('API 响应解析失败:', data); outputDiv.innerText = `❌ API 响应格式不正确,请检查浏览器控制台获取详细信息。`; } } catch (e) { console.error('解析API响应时出错:', e); outputDiv.innerText = `❌ 解析API响应失败,可能是网络问题或API返回了非JSON格式的数据。`; } finally { summarizeBtn.disabled = false; } }, onerror: function(error) { console.error('GM_xmlhttpRequest error:', error); outputDiv.innerText = '❌ 请求API失败,请检查网络连接、API地址是否正确,或查看浏览器控制台。'; summarizeBtn.disabled = false; }, ontimeout: function() { outputDiv.innerText = '❌ 请求超时,请检查网络或API服务是否可用。'; summarizeBtn.disabled = false; } }); } // 打开设置弹窗 function openSettingsModal() { // 加载已保存的配置 document.getElementById('ns-ai-api-url').value = GM_getValue('apiUrl', ''); document.getElementById('ns-ai-api-key').value = GM_getValue('apiKey', ''); document.getElementById('ns-ai-api-model').value = GM_getValue('apiModel', 'gpt-3.5-turbo'); // 显示弹窗 document.getElementById('ns-ai-settings-modal').style.display = 'flex'; } // 关闭设置弹窗 function closeSettingsModal() { document.getElementById('ns-ai-settings-modal').style.display = 'none'; } // 保存设置 function saveSettings() { const apiUrl = document.getElementById('ns-ai-api-url').value.trim(); const apiKey = document.getElementById('ns-ai-api-key').value.trim(); const apiModel = document.getElementById('ns-ai-api-model').value.trim(); if (!apiUrl || !apiKey || !apiModel) { alert('API 地址、密钥和模型不能为空!'); return; } GM_setValue('apiUrl', apiUrl); GM_setValue('apiKey', apiKey); GM_setValue('apiModel', apiModel); alert('设置已保存!'); closeSettingsModal(); } // --- 脚本启动 --- // 等待页面加载完成后执行 window.addEventListener('load', createUI, false); })();