// ==UserScript== // @name AI网页内容总结 // @namespace http://tampermonkey.net/ // @version 0.2 // @description 自动调用AI总结网页内容并流式显示 // @author AiCoder // @match *://*/* // @connect * // @license MIT // @grant GM_xmlhttpRequest // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @require https://cdn.jsdelivr.net/npm/turndown@7.1.1/dist/turndown.min.js // @downloadURL https://update.greasyfork.icu/scripts/529783/AI%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.user.js // @updateURL https://update.greasyfork.icu/scripts/529783/AI%E7%BD%91%E9%A1%B5%E5%86%85%E5%AE%B9%E6%80%BB%E7%BB%93.meta.js // ==/UserScript== (function() { 'use strict'; // 配置参数 const CONFIG = { // 替换为你的API密钥和端点 apiKey: 'YOUR_API_KEY_HERE', apiEndpoint: 'https://api.openai.com/v1/chat/completions', model: 'gpt-3.5-turbo', maxTokens: 1000, temperature: 0.7, // UI配置 uiPosition: 'top-right', // 可选: top-left, top-right, bottom-left, bottom-right theme: 'light', // 可选: light, dark // 自动触发设置 autoSummarize: true, // 是否自动总结 delay: 500, // 页面加载后延迟多少毫秒开始总结 // 自动总结域名列表 autoSummarizeDomains: ['juejin.cn', 'zhihu.com', 'csdn.net', 'jianshu.com'], // 域名黑名单,支持通配符 * blacklistDomains: ['*google.com', '*facebook.com', '*twitter.com', '*baidu.com', "*youtube.com", "*greasyfork.org"] }; // 保存用户配置 const savedConfig = GM_getValue('aiSummaryConfig'); if (savedConfig) { Object.assign(CONFIG, JSON.parse(savedConfig)); } // 添加样式 GM_addStyle(` #ai-summary-container { position: fixed; width: 350px; max-height: 500px; background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'}; color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'}; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); z-index: 9999; overflow: hidden; font-family: Arial, sans-serif; transition: all 0.3s ease; opacity: 0.95; } #ai-summary-container:hover { opacity: 1; box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); } #ai-summary-header { padding: 10px 15px; background-color: ${CONFIG.theme === 'light' ? '#f0f0f0' : '#444444'}; border-bottom: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; display: flex; justify-content: space-between; align-items: center; cursor: move; } #ai-summary-title { font-weight: bold; font-size: 14px; margin: 0; } #ai-summary-controls { display: flex; gap: 5px; } #ai-summary-controls button { background: none; border: none; cursor: pointer; font-size: 16px; color: ${CONFIG.theme === 'light' ? '#555' : '#ccc'}; padding: 0; width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; border-radius: 4px; } #ai-summary-controls button:hover { background-color: ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; } #ai-summary-content { padding: 15px; overflow-y: auto; max-height: 400px; font-size: 14px; line-height: 1.5; } #ai-summary-content.loading { opacity: 0.7; } #ai-summary-content p { margin: 0 0 10px 0; } #ai-summary-footer { padding: 8px 15px; border-top: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; display: flex; justify-content: space-between; font-size: 12px; color: ${CONFIG.theme === 'light' ? '#888' : '#aaa'}; } #ai-summary-settings { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#2d2d2d'}; border: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; border-radius: 8px; padding: 0; width: 450px; max-height: 85vh; z-index: 10001; box-shadow: 0 4px 20px rgba(0,0,0,0.3); overflow: hidden; display: none; flex-direction: column; } #ai-summary-settings.visible { display: flex; } .settings-header { padding: 12px 15px; background-color: ${CONFIG.theme === 'light' ? '#f5f5f5' : '#333333'}; border-bottom: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; font-weight: bold; font-size: 16px; display: flex; justify-content: space-between; align-items: center; } .settings-header .close-settings { cursor: pointer; font-size: 20px; color: ${CONFIG.theme === 'light' ? '#666' : '#aaa'}; } .settings-header .close-settings:hover { color: ${CONFIG.theme === 'light' ? '#333' : '#fff'}; } .settings-scroll-area { flex: 1; overflow-y: auto; padding: 15px; max-height: 60vh; } .settings-group { margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px dashed ${CONFIG.theme === 'light' ? '#eee' : '#444'}; } .settings-group:last-child { border-bottom: none; } .settings-group label { display: block; margin-bottom: 5px; font-weight: bold; font-size: 14px; } .checkbox-group label { display: flex; align-items: center; font-weight: bold; } .checkbox-group input { margin-right: 8px; } .settings-group small { display: block; margin-top: 4px; font-size: 12px; color: ${CONFIG.theme === 'light' ? '#888' : '#aaa'}; } .settings-group input[type="text"], .settings-group input[type="password"], .settings-group input[type="number"], .settings-group select, .settings-group textarea { width: 100%; padding: 8px; border: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; border-radius: 4px; background-color: ${CONFIG.theme === 'light' ? '#ffffff' : '#333333'}; color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'}; font-size: 14px; } .settings-group textarea { min-height: 80px; resize: vertical; } .settings-group input[type="range"] { width: 100%; margin: 8px 0; } .settings-group input[type="checkbox"] { width: auto; } .settings-actions { padding: 12px 15px; background-color: ${CONFIG.theme === 'light' ? '#f5f5f5' : '#333333'}; border-top: 1px solid ${CONFIG.theme === 'light' ? '#e0e0e0' : '#555555'}; display: flex; justify-content: flex-end; gap: 10px; } .settings-actions button { padding: 8px 16px; border: none; border-radius: 4px; cursor: pointer; font-size: 14px; font-weight: bold; transition: all 0.2s; } #save-settings { background-color: #4CAF50; color: white; } #save-settings:hover { background-color: #45a049; box-shadow: 0 2px 5px rgba(0,0,0,0.2); } #cancel-settings { background-color: ${CONFIG.theme === 'light' ? '#e0e0e0' : '#444444'}; color: ${CONFIG.theme === 'light' ? '#333333' : '#f0f0f0'}; } #cancel-settings:hover { background-color: ${CONFIG.theme === 'light' ? '#d0d0d0' : '#555555'}; } .cursor-pointer { cursor: pointer; } .typing-effect { border-right: 2px solid ${CONFIG.theme === 'light' ? '#333' : '#f0f0f0'}; white-space: nowrap; overflow: hidden; animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite; } @keyframes typing { from { width: 0 } to { width: 100% } } @keyframes blink-caret { from, to { border-color: transparent } 50% { border-color: ${CONFIG.theme === 'light' ? '#333' : '#f0f0f0'} } } @keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } } .settings-backdrop { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); z-index: 10000; display: none; } .settings-backdrop.visible { display: block; } `); // 检查域名是否匹配通配符规则 function domainMatchesPattern(domain, pattern) { // 转换通配符为正则表达式 try { const regexPattern = pattern.replace(/\./g, '\\.').replace(/\*/g, '.*'); const regex = new RegExp(`^${regexPattern}$`); return regex.test(domain); } catch (error) { console.error('域名匹配错误:', error); return false; } } // 检查当前域名是否在黑名单中 function isCurrentDomainBlacklisted() { const currentDomain = window.location.hostname; for (const pattern of CONFIG.blacklistDomains) { if (domainMatchesPattern(currentDomain, pattern)) { console.log(`当前域名 ${currentDomain} 匹配黑名单规则 ${pattern},不创建UI`); return true; } } return false; } // 创建UI function createUI() { console.log('创建UI组件...'); // 创建主容器 const container = document.createElement('div'); container.id = 'ai-summary-container'; container.innerHTML = `
点击刷新按钮开始总结当前网页内容...
设置已保存
'; setTimeout(() => { contentElement.innerHTML = '点击刷新按钮开始总结当前网页内容...
'; }, 2000); } // 根据配置更新UI function updateUIWithConfig() { // 更新位置 updateUIPosition(CONFIG.uiPosition); // 更新主题 if (CONFIG.theme === 'light') { const container = document.getElementById('ai-summary-container'); container.style.backgroundColor = '#ffffff'; container.style.color = '#333333'; } else { const container = document.getElementById('ai-summary-container'); container.style.backgroundColor = '#2d2d2d'; container.style.color = '#f0f0f0'; } // 更新自动总结开关文本 document.getElementById('ai-summary-toggle').textContent = `自动总结: ${CONFIG.autoSummarize ? '开启' : '关闭'}`; } // 使元素可拖拽 function makeElementDraggable(element) { let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const header = document.getElementById('ai-summary-header'); if (header) { header.onmousedown = dragMouseDown; } else { element.onmousedown = dragMouseDown; } function dragMouseDown(e) { e = e || window.event; e.preventDefault(); // 获取鼠标位置 pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; // 鼠标移动时调用elementDrag document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); // 计算新位置 pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; // 设置元素的新位置 element.style.top = (element.offsetTop - pos2) + 'px'; element.style.left = (element.offsetLeft - pos1) + 'px'; // 重置位置配置,因为用户手动拖动了 CONFIG.uiPosition = 'custom'; } function closeDragElement() { // 停止移动 document.onmouseup = null; document.onmousemove = null; } } // 提取网页内容 function extractPageContent() { // 获取页面标题 const title = document.title; // 使用Turndown将HTML转换为Markdown const turndownService = new TurndownService({ headingStyle: 'atx', codeBlockStyle: 'fenced', emDelimiter: '_', hr: '---', bulletListMarker: '-', }); // 自定义规则以更好地处理内容 turndownService.addRule('removeAds', { filter: function(node) { // 过滤掉可能的广告元素 return node.className && ( node.className.includes('ad') || node.className.includes('banner') || node.className.includes('sidebar') || node.id && (node.id.includes('ad') || node.id.includes('banner')) ); }, replacement: function() { return ''; } }); // 添加自定义规则,忽略一些不需要的元素 turndownService.addRule('ignoreNavAndFooter', { filter: function(node) { return ( node.nodeName.toLowerCase() === 'nav' || node.nodeName.toLowerCase() === 'footer' || node.classList.contains('nav') || node.classList.contains('footer') || node.classList.contains('menu') || node.id === 'footer' || node.id === 'nav' || node.id === 'menu' ); }, replacement: function() { return ''; } }); // 尝试获取文章内容 let content = ''; let htmlContent = ''; // 尝试获取文章内容 const articleElements = document.querySelectorAll('article, .article, .post, .content, main, .main-content, [role="main"]'); if (articleElements.length > 0) { // 使用第一个找到的文章元素 htmlContent = articleElements[0].innerHTML; } else { // 如果没有找到文章元素,尝试获取所有段落 const paragraphs = document.querySelectorAll('p'); if (paragraphs.length > 0) { // 创建一个临时容器来存放所有段落 const tempContainer = document.createElement('div'); paragraphs.forEach(p => { // 只添加有实际内容的段落 if (p.textContent.trim().length > 0) { tempContainer.appendChild(p.cloneNode(true)); } }); htmlContent = tempContainer.innerHTML; } else { // 如果没有找到段落,获取body的内容 // 但排除一些常见的非内容区域 const body = document.body.cloneNode(true); const elementsToRemove = body.querySelectorAll('header, footer, nav, aside, script, style, .sidebar, .ad, .advertisement, .banner, .navigation, .related, .recommended'); elementsToRemove.forEach(el => el.remove()); htmlContent = body.innerHTML; } } // 将HTML转换为Markdown content = turndownService.turndown(htmlContent); // 清理内容(删除多余空白行) content = content.replace(/\n{3,}/g, '\n\n').trim(); // 如果内容太长,截取前10000个字符 if (content.length > 10000) { content = content.substring(0, 10000) + '...'; } return { title, content }; } // 调用AI API进行总结 function summarizeContent(isAuto = false) { // 显示加载状态 const contentElement = document.getElementById('ai-summary-content'); contentElement.classList.add('loading'); contentElement.innerHTML = isAuto ? '正在自动总结内容,请稍候...
' : '正在总结内容,请稍候...
'; // 提取页面内容 const { title, content } = extractPageContent(); // 如果API密钥未设置,显示提示 if (CONFIG.apiKey === 'YOUR_API_KEY_HERE') { contentElement.classList.remove('loading'); contentElement.innerHTML = '请先在设置中配置你的API密钥
'; return; } // 准备请求数据 const requestData = { model: CONFIG.model, messages: [ { role: 'system', content: '你是一个专业的内容总结助手。请简洁明了地总结以下网页内容的要点,包含主要观点、关键信息和重要细节。通俗易懂,突出重点。' }, { role: 'user', content: `网页标题: ${title}\n\n网页内容: ${content}\n\n请总结这个网页的主要内容,突出关键信息。` } ], max_tokens: CONFIG.maxTokens, temperature: CONFIG.temperature, stream: true }; // 发送API请求 let summaryText = ''; let lastResponseLength = 0; // 添加此变量来跟踪响应长度 contentElement.innerHTML = ''; GM_xmlhttpRequest({ method: 'POST', url: CONFIG.apiEndpoint, headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${CONFIG.apiKey}` }, data: JSON.stringify(requestData), timeout: 30000, // 设置30秒超时 onloadstart: function() { // 创建一个段落用于显示流式响应 const paragraph = document.createElement('p'); contentElement.appendChild(paragraph); console.log('开始接收流式响应...'); }, onreadystatechange: function(response) { try { // 处理流式响应 const responseText = response.responseText || ''; // 只处理新数据 if (responseText.length <= lastResponseLength) { return; } // 计算新数据 const newResponseText = responseText.substring(lastResponseLength); lastResponseLength = responseText.length; console.log(`接收到新数据,长度: ${newResponseText.length}, 总长度: ${responseText.length}`); // 将新响应拆分为各个数据行 const lines = newResponseText.split('\n'); let newContent = ''; for (const line of lines) { if (line.startsWith('data: ') && line !== 'data: [DONE]') { try { const jsonStr = line.substring(6); if (jsonStr.trim() === '') continue; const data = JSON.parse(jsonStr); if (data.choices && data.choices[0].delta && data.choices[0].delta.content) { newContent += data.choices[0].delta.content; } } catch (e) { // 可能是不完整的JSON,忽略错误 console.log('解析单行数据时出错 (可能是不完整的JSON):', e.message); } } } // 只要有新内容就立即更新UI if (newContent) { summaryText += newContent; const paragraph = contentElement.querySelector('p'); if (paragraph) { paragraph.innerHTML = renderMarkdown(summaryText); contentElement.scrollTop = contentElement.scrollHeight; // 滚动到底部 } } } catch (error) { console.error('处理流式响应时出错:', error); } }, onload: function(response) { contentElement.classList.remove('loading'); if (response.status !== 200) { contentElement.innerHTML = `API请求失败: ${response.status} ${response.statusText}
`; console.error('API请求失败:', response.status, response.statusText, response.responseText); return; } // 确保我们有完整的内容 if (summaryText.trim() === '') { console.log('尝试从完整响应中提取内容...'); // 提取完整响应内容的逻辑... // ... existing code for handling complete response ... } else { console.log('流式响应已完成,总内容长度:', summaryText.length); } }, onerror: function(error) { contentElement.classList.remove('loading'); contentElement.innerHTML = `请求出错: ${error}
`; console.error('API请求出错:', error); }, ontimeout: function() { contentElement.classList.remove('loading'); contentElement.innerHTML = '请求超时,请检查网络连接或API端点是否正确
'; console.error('API请求超时'); } }); } // 在页面加载完成后初始化 // 渲染Markdown文本为HTML function renderMarkdown(text) { if (!text) return ''; // 基本Markdown语法转换 let html = text // 标题 .replace(/^### (.+)$/gm, '$1
')
// 行内代码
.replace(/`(.+?)`/g, '$1
')
// 链接
.replace(/\[(.+?)\]\((.+?)\)/g, '$1')
// 无序列表
.replace(/^- (.+)$/gm, ''); // 包装在段落标签中 html = '
' + html + '
'; // 修复列表 html = html.replace(/