您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add button to summarize and toggle content of the main post.
// ==UserScript== // @name Linux Do Summary // @namespace http://tampermonkey.net/ // @version 3.0 // @description Add button to summarize and toggle content of the main post. // @author Reno // @match https://linux.do/* // @grant GM_xmlhttpRequest // @license MIT // ==/UserScript== (function() { 'use strict'; let state = { originalContent: '', toggled: false, apiTested: false }; function init() { observeDOMChanges(); } function addButtonAndEventListener() { const post = document.querySelector('#post_1'); if (post && !document.getElementById('summaryToggleButton')) { const controlsContainer = document.querySelector('nav.post-controls'); if (controlsContainer) { const button = document.createElement('button'); button.id = 'summaryToggleButton'; button.textContent = '总结'; button.style.cssText = 'background-color: #4CAF50; color: white; padding: 5px 10px; border: none; border-radius: 5px; cursor: pointer;'; controlsContainer.appendChild(button); button.addEventListener('click', handleButtonClick); state = { originalContent: '', toggled: false, apiTested: state.apiTested }; } } } async function handleButtonClick() { const summaryToggleButton = document.getElementById('summaryToggleButton'); if (!state.toggled) { summaryToggleButton.disabled = true; summaryToggleButton.style.backgroundColor = '#808080'; let countdown = 10; summaryToggleButton.textContent = `${countdown} 秒`; const countdownInterval = setInterval(() => { countdown--; summaryToggleButton.textContent = `${countdown} 秒`; if (countdown <= 0) { clearInterval(countdownInterval); summaryToggleButton.textContent = 'ಠ_ಠ'; } }, 1000); const configExists = await checkAndSetConfig(); if (configExists) { try { const content = extractContent(); const summary = await fetchSummary(content); if (summary) { displaySummary(formatSummary(summary)); summaryToggleButton.textContent = '原文'; summaryToggleButton.style.backgroundColor = '#007bff'; state.toggled = true; window.scrollTo(0, 0); } } catch (error) { console.error('处理内容时出错: ', error); summaryToggleButton.textContent = '重试'; } } else { summaryToggleButton.textContent = '重试'; } clearInterval(countdownInterval); summaryToggleButton.disabled = false; } else { restoreOriginalContent(); summaryToggleButton.textContent = '总结'; summaryToggleButton.style.backgroundColor = '#4CAF50'; state.toggled = false; window.scrollTo(0, 0); } } async function checkAndSetConfig() { const settings = { 'base_url': 'https://api.openai.com/v1/chat/completions', 'apikey': 'sk-k4NKZr82mTRTLCw984Da25536c374f4dB429A1Ee9dDa1087', 'model': 'gpt-4' }; let configNeeded = false; for (let key in settings) { let storedValue = localStorage.getItem(key); if (!storedValue) { storedValue = prompt(`请输入 ${key},示例:\n${settings[key]}`, ''); if (storedValue) { localStorage.setItem(key, storedValue); } else { alert(`${key} 是必需的。没有 ${key},插件无法运行。`); return false; } configNeeded = true; } } if (configNeeded && !state.apiTested) { try { await testAPIConnection(); state.apiTested = true; } catch (error) { localStorage.removeItem('base_url'); localStorage.removeItem('apikey'); localStorage.removeItem('model'); alert('API测试失败,请检查设置'); return false; } } return true; } async function testAPIConnection() { const response = await fetch(localStorage.getItem('base_url'), { headers: { 'Authorization': `Bearer ${localStorage.getItem('apikey')}` } }); if (!response.ok) throw new Error('API连接失败'); } async function processContent() { const content = extractContent(); if (content) { try { const summary = await fetchSummary(content); const formattedSummary = formatSummary(summary); displaySummary(formattedSummary); } catch (error) { console.error('处理内容时出错: ', error); alert('处理错误,请重试'); } } } function extractContent() { let postStreamElement = document.querySelector('div.post-stream'); if (postStreamElement && postStreamElement.querySelector('#post_1')) { let articleElement = postStreamElement.querySelector('#post_1'); if (articleElement) { let cookedDiv = articleElement.querySelector('.cooked'); if (cookedDiv) { let elementsData = []; let index = 0; Array.from(cookedDiv.querySelectorAll('img, p, li')).forEach(node => { let tagName = node.tagName.toLowerCase() === 'li' ? 'p' : node.tagName.toLowerCase(); let textContent = node.textContent.trim(); let src = tagName === 'img' ? node.getAttribute('src')?.trim() : null; if (tagName === 'p' && textContent.includes('\n')) { let contents = textContent.split(/\n+/).map(line => line.trim()).filter(line => line.length > 0); elementsData.push({ index, tagName, textContent: contents[0], src }); index++; for (let i = 1; i < contents.length; i++) { elementsData.push({ index, tagName, textContent: contents[i], src }); index++; } } else { elementsData.push({ index, tagName, textContent, src }); index++; } }); let cleanedElementsData = elementsData.filter(({ tagName, textContent }) => tagName !== 'p' || textContent.length > 1); let uniqueElementsData = []; let uniqueTextContents = new Set(); cleanedElementsData.forEach(({ tagName, textContent, src }) => { let contentKey = `${tagName}_${textContent}_${src}`; if (!uniqueTextContents.has(contentKey)) { uniqueElementsData.push({ tagName, textContent, src }); uniqueTextContents.add(contentKey); } }); let htmlContent = ""; uniqueElementsData.forEach(({ tagName, textContent, src }) => { if (tagName === 'p') { htmlContent += `<p>${textContent}</p>`; } else if (tagName === 'img') { htmlContent += `<img src="${src}" alt="${textContent}">`; } }); return htmlContent; } } } return ''; } async function fetchSummary(textContent) { const BASE_URL = window.localStorage.getItem('base_url'); const API_KEY = window.localStorage.getItem('apikey'); const MODEL = window.localStorage.getItem('model'); const PROMPT = "以下是linux.do论坛的一个主题,帮我用中文简明扼要地梳理总结:"; const headers = { "Content-Type": "application/json", "Authorization": `Bearer ${API_KEY}` }; const body = JSON.stringify({ model: MODEL, messages: [{ role: "user", content: PROMPT + textContent }] }); try { const response = await fetch(BASE_URL, { method: "POST", headers, body }); if (!response.ok) throw new Error(`API请求失败,状态码: ${response.status}`); const jsonResponse = await response.json(); if (jsonResponse && jsonResponse.choices && jsonResponse.choices[0] && jsonResponse.choices[0].message) { return jsonResponse.choices[0].message.content; } else { throw new Error('API响应无效或格式不正确'); } } catch (error) { console.error(error.message); alert("无法加载摘要,请稍后再试。错误详情: " + error.message); return null; } } function formatSummary(text) { if (!text) return '无法加载摘要。'; text = text.replace(/\n/g, '<br>').replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); return text.replace(/^(#{1,6})\s(.*?)<br>/gm, (match, p1, p2) => `<h${p1.length}>${p2}</h${p1.length}><br>`) .replace(/- (.*?)<br>/g, '<li>$1</li><br>').replace(/<li>(.*?)<\/li><br><br>/g, '<ul><li>$1</li></ul><br>'); } function displaySummary(formattedSummary) { const contentElement = document.querySelector('#post_1 .cooked'); if (contentElement) { state.originalContent = contentElement.innerHTML; contentElement.innerHTML = formattedSummary; document.getElementById('summaryToggleButton').textContent = '原文'; state.toggled = true; } } function restoreOriginalContent() { const contentElement = document.querySelector('#post_1 .cooked'); if (contentElement && state.originalContent) { contentElement.innerHTML = state.originalContent; document.getElementById('summaryToggleButton').textContent = '总结'; state.toggled = false; } } function observeDOMChanges() { const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { if (mutation.addedNodes.length) { addButtonAndEventListener(); } }); }); observer.observe(document.body, { childList: true, subtree: true }); } init(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址