Linux Do Summary

Add button to summarize and toggle content of the main post

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

  1. // ==UserScript==
  2. // @name Linux Do Summary
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Add button to summarize and toggle content of the main post
  6. // @author Reno
  7. // @match https://linux.do/*
  8. // @grant GM_xmlhttpRequest
  9. // @license MIT
  10.  
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // 定义请求格式
  17. const BASE_URL = "https://api.openai.com/v1/chat/completions";
  18. const API_KEY = "Your_API_Key";
  19. const MODEL = "gpt-4-all";
  20. const PROMPT = "以下是linu.do论坛的一个主题,帮我用中文梳理总结:";
  21.  
  22. let originalContent = ''; // 存储原始内容
  23. let toggled = false; // 切换状态
  24.  
  25. // 提取并格式化主帖内容
  26. function extractAndFormat() {
  27. console.log('提取并格式化内容...');
  28. var postStreamElement = document.querySelector('div.post-stream');
  29. if (postStreamElement && postStreamElement.querySelector('#post_1')) {
  30. var articleElement = postStreamElement.querySelector('#post_1');
  31. if (articleElement) {
  32. var cookedDiv = articleElement.querySelector('.cooked');
  33. if (cookedDiv) {
  34. var elementsData = [];
  35. var index = 0;
  36.  
  37. // 提取并存储所有img、p和li元素,将li转换为p
  38. Array.from(cookedDiv.querySelectorAll('img, p, li')).forEach(node => {
  39. var tagName = node.tagName.toLowerCase() === 'li' ? 'p' : node.tagName.toLowerCase(); // 将li转换为p
  40. var textContent = node.textContent.trim();
  41. var src = tagName === 'img' ? node.getAttribute('src')?.trim() : null;
  42.  
  43. if (tagName === 'p' && textContent.includes('\n')) {
  44. var contents = textContent.split(/\n+/).map(line => line.trim()).filter(line => line.length > 0);
  45. elementsData.push({ index, tagName, textContent: contents[0], src });
  46. index++;
  47. for (var i = 1; i < contents.length; i++) {
  48. elementsData.push({ index, tagName, textContent: contents[i], src });
  49. index++;
  50. }
  51. } else {
  52. elementsData.push({ index, tagName, textContent, src });
  53. index++;
  54. }
  55. });
  56.  
  57. // 过滤掉不必要的元素和重复项
  58. var cleanedElementsData = elementsData.filter(({ tagName, textContent }) => tagName !== 'p' || textContent.length > 1);
  59. var uniqueElementsData = [];
  60. var uniqueTextContents = new Set();
  61. cleanedElementsData.forEach(({ tagName, textContent, src }) => {
  62. var contentKey = `${tagName}_${textContent}_${src}`;
  63. if (tagName === 'img' || !uniqueTextContents.has(contentKey)) {
  64. uniqueElementsData.push({ tagName, textContent, src });
  65. uniqueTextContents.add(contentKey);
  66. }
  67. });
  68.  
  69. // 转换为HTML
  70. var htmlContent = "";
  71. uniqueElementsData.forEach(({ tagName, textContent, src }) => {
  72. if (tagName === 'p') {
  73. htmlContent += `<p>${textContent}</p>`;
  74. } else if (tagName === 'img') {
  75. htmlContent += `<img src="${src}" alt="${textContent}">`;
  76. }
  77. });
  78.  
  79. return htmlContent; // 返回最终的HTML字符串
  80. }
  81. }
  82. }
  83. return '';
  84. }
  85.  
  86. // 发送内容到API
  87. function sendToAPI(textContent, callback) {
  88. console.log('向API发送内容...');
  89. var xhr = new XMLHttpRequest();
  90. xhr.open("POST", BASE_URL);
  91. xhr.setRequestHeader("Content-Type", "application/json");
  92. xhr.setRequestHeader("Authorization", `Bearer ${API_KEY}`);
  93. xhr.onload = function() {
  94. if (xhr.status === 200) {
  95. var jsonResponse = JSON.parse(xhr.responseText);
  96. if(jsonResponse && jsonResponse.choices && jsonResponse.choices[0] && jsonResponse.choices[0].message) {
  97. callback(jsonResponse.choices[0].message.content);
  98. }
  99. }
  100. };
  101. xhr.onerror = function() {
  102. console.error('错误:', xhr.statusText);
  103. callback('');
  104. };
  105. xhr.send(JSON.stringify({ model: MODEL, messages: [{ role: "user", content: PROMPT + textContent }] }));
  106. }
  107.  
  108. // 格式化从API接收到的内容
  109. function formatContent(text) {
  110. console.log('格式化内容...');
  111. // 处理换行
  112. text = text.replace(/\n/g, '<br>');
  113.  
  114. // 处理加粗
  115. text = text.replace(/\*\*\*\*(.*?)\*\*\*\*/g, '<strong>$1</strong>');
  116.  
  117. // 处理标题
  118. text = text.replace(/^(#{1,6})\s(.*?)<br>/gm, function(match, p1, p2) {
  119. const level = p1.length;
  120. return `<h${level}>${p2}</h${level}><br>`;
  121. });
  122.  
  123. // 处理列表
  124. text = text.replace(/- (.*?)<br>/g, '<li>$1</li><br>');
  125. text = text.replace(/<li>(.*?)<\/li><br><br>/g, '<ul><li>$1</li></ul><br>');
  126.  
  127. return text;
  128. }
  129.  
  130. // 检查是否存在post_1元素和按钮
  131. function checkAndAddButton() {
  132. console.log('检查帖子元素和按钮...');
  133. const postElement = document.querySelector('#post_1');
  134. const buttonExists = document.querySelector('#summaryToggleButton');
  135. if (postElement && !buttonExists) {
  136. addButtonAndProcessData();
  137. }
  138. }
  139.  
  140. // 添加总结按钮并附加事件处理程序
  141. function addButtonAndProcessData() {
  142. console.log('添加按钮并处理数据...');
  143. const controlsContainer = document.querySelector('nav.post-controls');
  144. if (controlsContainer) {
  145. const newButton = document.createElement('button');
  146. newButton.textContent = '总结';
  147. newButton.id = 'summaryToggleButton'; // 给按钮定义id
  148. 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;'; // 添加样式
  149. controlsContainer.appendChild(newButton);
  150.  
  151. // 初始化状态
  152. originalContent = '';
  153. toggled = false;
  154.  
  155. newButton.addEventListener('click', function() {
  156. console.log('按钮点击...');
  157. const cookedContent = document.querySelector('div.cooked');
  158. if (cookedContent) {
  159. if (!toggled) {
  160. originalContent = cookedContent.innerHTML;
  161. const textContent = extractAndFormat();
  162. sendToAPI(textContent, function(summary) {
  163. console.log('从API接收到摘要...');
  164. cookedContent.innerHTML = formatContent(summary) || '内容加载中...';
  165. window.scrollTo(0, 0);
  166. });
  167. } else {
  168. cookedContent.innerHTML = originalContent;
  169. }
  170. toggled = !toggled;
  171. }
  172. });
  173. }
  174. }
  175.  
  176. // 持续检查帖子元素和按钮的存在性
  177. setInterval(checkAndAddButton, 5000); // 每5秒检查一次是否存在post_1和按钮
  178. })();