Linux Do Summary

Add button to summarize and toggle content of the main post

当前为 2024-04-04 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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和按钮
})();