Linux Do Summary

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或关注我们的公众号极客氢云获取最新地址