Linux Do Summary

Add button to summarize and toggle content of the main post

目前為 2024-04-04 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Linux Do Summary
// @namespace    http://tampermonkey.net/
// @version      1.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';

    // 定义请求格式
    const BASE_URL = "https://api.openai.com/v1/chat/completions";
    const API_KEY = "Your_API_Key";
    const MODEL = "gpt-4-all";
    const PROMPT = "以下是linu.do论坛的一个主题,帮我用中文梳理总结:";

    let originalContent = ''; // 存储原始内容
    let toggled = false; // 切换状态

    // 提取并格式化主帖内容
    function extractAndFormat() {
        console.log('提取并格式化内容...');
        var postStreamElement = document.querySelector('div.post-stream');
        if (postStreamElement && postStreamElement.querySelector('#post_1')) {
            var articleElement = postStreamElement.querySelector('#post_1');
            if (articleElement) {
                var cookedDiv = articleElement.querySelector('.cooked');
                if (cookedDiv) {
                    var elementsData = [];
                    var index = 0;

                    // 提取并存储所有img、p和li元素,将li转换为p
                    Array.from(cookedDiv.querySelectorAll('img, p, li')).forEach(node => {
                        var tagName = node.tagName.toLowerCase() === 'li' ? 'p' : node.tagName.toLowerCase(); // 将li转换为p
                        var textContent = node.textContent.trim();
                        var src = tagName === 'img' ? node.getAttribute('src')?.trim() : null;

                        if (tagName === 'p' && textContent.includes('\n')) {
                            var contents = textContent.split(/\n+/).map(line => line.trim()).filter(line => line.length > 0);
                            elementsData.push({ index, tagName, textContent: contents[0], src });
                            index++;
                            for (var i = 1; i < contents.length; i++) {
                                elementsData.push({ index, tagName, textContent: contents[i], src });
                                index++;
                            }
                        } else {
                            elementsData.push({ index, tagName, textContent, src });
                            index++;
                        }
                    });

                    // 过滤掉不必要的元素和重复项
                    var cleanedElementsData = elementsData.filter(({ tagName, textContent }) => tagName !== 'p' || textContent.length > 1);
                    var uniqueElementsData = [];
                    var uniqueTextContents = new Set();
                    cleanedElementsData.forEach(({ tagName, textContent, src }) => {
                        var contentKey = `${tagName}_${textContent}_${src}`;
                        if (tagName === 'img' || !uniqueTextContents.has(contentKey)) {
                            uniqueElementsData.push({ tagName, textContent, src });
                            uniqueTextContents.add(contentKey);
                        }
                    });

                    // 转换为HTML
                    var 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; // 返回最终的HTML字符串
                }
            }
        }
        return '';
    }

    // 发送内容到API
    function sendToAPI(textContent, callback) {
        console.log('向API发送内容...');
        var xhr = new XMLHttpRequest();
        xhr.open("POST", BASE_URL);
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.setRequestHeader("Authorization", `Bearer ${API_KEY}`);
        xhr.onload = function() {
            if (xhr.status === 200) {
                var jsonResponse = JSON.parse(xhr.responseText);
                if(jsonResponse && jsonResponse.choices && jsonResponse.choices[0] && jsonResponse.choices[0].message) {
                    callback(jsonResponse.choices[0].message.content);
                }
            }
        };
        xhr.onerror = function() {
            console.error('错误:', xhr.statusText);
            callback('');
        };
        xhr.send(JSON.stringify({ model: MODEL, messages: [{ role: "user", content: PROMPT + textContent }] }));
    }

    // 格式化从API接收到的内容
    function formatContent(text) {
        console.log('格式化内容...');
        // 处理换行
        text = text.replace(/\n/g, '<br>');

        // 处理加粗
        text = text.replace(/\*\*\*\*(.*?)\*\*\*\*/g, '<strong>$1</strong>');

        // 处理标题
        text = text.replace(/^(#{1,6})\s(.*?)<br>/gm, function(match, p1, p2) {
            const level = p1.length;
            return `<h${level}>${p2}</h${level}><br>`;
        });

        // 处理列表
        text = text.replace(/- (.*?)<br>/g, '<li>$1</li><br>');
        text = text.replace(/<li>(.*?)<\/li><br><br>/g, '<ul><li>$1</li></ul><br>');

        return text;
    }

    // 检查是否存在post_1元素和按钮
    function checkAndAddButton() {
        console.log('检查帖子元素和按钮...');
        const postElement = document.querySelector('#post_1');
        const buttonExists = document.querySelector('#summaryToggleButton');
        if (postElement && !buttonExists) {
            addButtonAndProcessData();
        }
    }

    // 添加总结按钮并附加事件处理程序
    function addButtonAndProcessData() {
        console.log('添加按钮并处理数据...');
        const controlsContainer = document.querySelector('nav.post-controls');
        if (controlsContainer) {
            const newButton = document.createElement('button');
            newButton.textContent = '总结';
            newButton.id = 'summaryToggleButton'; // 给按钮定义id
            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);

            // 初始化状态
            originalContent = '';
            toggled = false;

            newButton.addEventListener('click', function() {
                console.log('按钮点击...');
                const cookedContent = document.querySelector('div.cooked');
                if (cookedContent) {
                    if (!toggled) {
                        originalContent = cookedContent.innerHTML;
                        const textContent = extractAndFormat();
                        sendToAPI(textContent, function(summary) {
                            console.log('从API接收到摘要...');
                            cookedContent.innerHTML = formatContent(summary) || '内容加载中...';
                            window.scrollTo(0, 0);
                        });
                    } else {
                        cookedContent.innerHTML = originalContent;
                    }
                    toggled = !toggled;
                }
            });
        }
    }

    // 持续检查帖子元素和按钮的存在性
    setInterval(checkAndAddButton, 5000); // 每5秒检查一次是否存在post_1和按钮
})();