Add button to summarize and toggle content of the main post.
当前为
// ==UserScript==
// @name Linux Do Summary
// @namespace http://tampermonkey.net/
// @version 2.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
};
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 formatContentFromAPI(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 extractAndFormatContent() {
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 toggleSummary() {
const summaryToggleButton = document.getElementById('summaryToggleButton');
const cookedContent = document.querySelector('div.cooked');
if (!state.toggled && cookedContent) {
let countdown = 15;
summaryToggleButton.disabled = true;
summaryToggleButton.style.backgroundColor = '#808080';
summaryToggleButton.textContent = `${countdown} 秒`;
const countdownInterval = setInterval(() => {
countdown--;
summaryToggleButton.textContent = `${countdown} 秒`;
if (countdown <= 0) {
clearInterval(countdownInterval);
summaryToggleButton.textContent = '原文';
}
}, 1000);
const textContent = extractAndFormatContent();
const summary = await fetchSummary(textContent);
clearInterval(countdownInterval);
if (summary) {
state.originalContent = cookedContent.innerHTML;
cookedContent.innerHTML = formatContentFromAPI(summary);
window.scrollTo(0, 0);
summaryToggleButton.textContent = '原文';
summaryToggleButton.style.backgroundColor = '#007bff';
state.toggled = true;
} else {
summaryToggleButton.textContent = '重试';
}
summaryToggleButton.disabled = false;
} else if (state.toggled && cookedContent) {
cookedContent.innerHTML = state.originalContent;
summaryToggleButton.textContent = '总结';
window.scrollTo(0, 0);
summaryToggleButton.style.backgroundColor = '#4CAF50';
state.toggled = false;
}
}
function addButtonAndEventListener() {
const controlsContainer = document.querySelector('nav.post-controls');
if (controlsContainer && !document.querySelector('#summaryToggleButton')) {
state.originalContent = '';
state.toggled = false;
const newButton = document.createElement('button');
newButton.textContent = '总结';
newButton.id = 'summaryToggleButton';
newButton.style.cssText = 'margin-left: 10px; background-color: #4CAF50; color: white; border: none; border-radius: 5px; padding: 5px 10px; text-align: center; text-decoration: none; display: inline-block; font-size: 16px; cursor: pointer; transition-duration: 0.4s;';
controlsContainer.appendChild(newButton);
newButton.addEventListener('click', toggleSummary);
}
}
function observeDOMChanges() {
const targetNode = document.querySelector('body');
const config = { childList: true, subtree: true };
const callback = function(mutationsList, observer) {
for(const mutation of mutationsList) {
if (mutation.type === 'childList') {
addButtonAndEventListener();
break;
}
}
};
const observer = new MutationObserver(callback);
if (targetNode) {
observer.observe(targetNode, config);
}
}
observeDOMChanges();
})();