- // ==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();
- })();