MemTrace

trace browsing history and preserve tables (HTML passthrough)

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

  1. // ==UserScript==
  2. // @name MemTrace
  3. // @namespace Violentmonkey Scripts
  4. // @version 0.4
  5. // @description trace browsing history and preserve tables (HTML passthrough)
  6. // @author fankaidev
  7. // @match *://*/*
  8. // @exclude *://cubox.pro/*
  9. // @exclude *://localhost:*/*
  10. // @exclude *://127.0.0.1:*/*
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_setValue
  13. // @grant GM_getValue
  14. // @grant GM_registerMenuCommand
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.1/turndown.min.js
  16. // @require https://unpkg.com/turndown-plugin-gfm@1.0.2/dist/turndown-plugin-gfm.js
  17. // @require https://cdn.jsdelivr.net/npm/dompurify@3.1.5/dist/purify.min.js
  18. // @require https://cdn.jsdelivr.net/npm/crypto-js@4.1.1/crypto-js.js
  19. // @require https://cdn.jsdelivr.net/npm/@mozilla/readability@0.5.0/Readability.min.js
  20. // @license MIT
  21. // ==/UserScript==
  22.  
  23. (function () {
  24. "use strict";
  25. function md5(input) {
  26. return CryptoJS.MD5(input).toString();
  27. }
  28. let hrefHistory = [];
  29.  
  30. // Function to get or initialize global state
  31. function getGlobalState(key, defaultValue) {
  32. return GM_getValue(key, defaultValue);
  33. }
  34.  
  35. // Function to update global state
  36. function updateGlobalState(key, value) {
  37. GM_setValue(key, value);
  38. }
  39.  
  40. // Function to get the endpoint
  41. function getEndpoint() {
  42. let endpoint = getGlobalState("endpoint", null);
  43. if (!endpoint) {
  44. endpoint = prompt("[MemTrace] Please enter the endpoint URL:", "https://api.example.com/endpoint");
  45. if (endpoint) {
  46. updateGlobalState("endpoint", endpoint);
  47. } else {
  48. console.error("[MemTrace] No endpoint provided. Script will not function correctly.");
  49. }
  50. }
  51. return endpoint;
  52. }
  53.  
  54. // Function to change the endpoint
  55. function changeEndpoint() {
  56. let newEndpoint = prompt("[MemTrace] Enter new endpoint URL:", getGlobalState("endpoint", ""));
  57. if (newEndpoint) {
  58. updateGlobalState("endpoint", newEndpoint);
  59. console.log("[MemTrace] Endpoint updated to", newEndpoint);
  60. }
  61. }
  62.  
  63. // Register menu command to change endpoint
  64. GM_registerMenuCommand("Change MemTrace Endpoint", changeEndpoint);
  65.  
  66. function processPage() {
  67. const article = new Readability(document.cloneNode(true)).parse().content;
  68. // console.log("article", article);
  69.  
  70. const turndownService = new TurndownService({
  71. keepReplacement: function (content, node) {
  72. return node.isBlock ? "\n\n" + node.outerHTML + "\n\n" : node.outerHTML;
  73. },
  74. });
  75.  
  76. // Add a rule to keep tables
  77. turndownService.addRule("tables", {
  78. filter: ["table"],
  79. replacement: function (content, node) {
  80. return node.outerHTML;
  81. },
  82. });
  83.  
  84. // Uncomment the following line if you want to use the GFM table plugin instead
  85. // turndownService.use(turndownPluginGfm.tables);
  86.  
  87. return turndownService.turndown(article);
  88. }
  89.  
  90. function parseRedditReply(reply, depth) {
  91. let replyText = "";
  92. replyText += "\n---\n";
  93. replyText += ">".repeat(depth) + `**${reply.data.author}**\n`;
  94. replyText += ">".repeat(depth) + "\n";
  95. const lines = reply.data.body.split("\n");
  96. for (const line of lines) {
  97. replyText += (">".repeat(depth), line);
  98. }
  99.  
  100. if (!reply.data.replies) {
  101. return replyText;
  102. }
  103. for (const child of reply.data.replies.data.children) {
  104. replyText += parseRedditReply(child, depth + 1);
  105. }
  106. return replyText;
  107. }
  108.  
  109. function savePage(markdown) {
  110. const url = window.location.href.split("#")[0];
  111. let data = {
  112. title: document.title,
  113. source: "chrome",
  114. id: md5(url),
  115. markdown: markdown,
  116. url: url,
  117. };
  118. console.log("[MemTrace] saving page", data);
  119. GM_xmlhttpRequest({
  120. method: "POST",
  121. url: getEndpoint(),
  122. data: JSON.stringify(data),
  123. headers: {
  124. "Content-Type": "application/json",
  125. },
  126. onload: function (response) {
  127. if (response.status === 200) {
  128. console.log("[MemTrace] saved page");
  129. } else {
  130. console.error("Failed to save to MemTrace", response.responseText);
  131. }
  132. },
  133. onerror: function (error) {
  134. console.error("Request failed:", error);
  135. },
  136. });
  137. }
  138.  
  139. function processRedditPage() {
  140. console.log("[MemTrace] processing reddit page");
  141. fetch(window.location.href + ".json")
  142. .then((response) => response.json())
  143. .then((responseJson) => {
  144. const page = responseJson;
  145. const post = page[0].data.children[0].data;
  146. let markdown = `*${post["subreddit_name_prefixed"]}*\n\n**${post["author"]}**\n\n${post["selftext"]}\n\n`;
  147. for (const reply of page[1].data.children) {
  148. markdown += parseRedditReply(reply, 1);
  149. }
  150. savePage(markdown);
  151. });
  152. }
  153.  
  154. function process() {
  155. const url = window.location.href.split("#")[0];
  156. if (hrefHistory.includes(url)) {
  157. console.log("[MemTrace] skip processed url", url);
  158. return;
  159. }
  160. console.log("[MemTrace] processing url", url);
  161. hrefHistory.push(url);
  162.  
  163. if (/reddit.com\/r\/[^/]+\/comments/.test(url)) {
  164. processRedditPage();
  165. } else {
  166. processPage();
  167. if (markdown.length < 100) {
  168. console.log("[MemTrace] fail to parse page");
  169. return;
  170. }
  171. savePage(markdown);
  172. }
  173. }
  174. function scheduleProcess() {
  175. if (window.self === window.top) {
  176. console.log(`[MemTrace] current href is`, window.location.href);
  177. setTimeout(() => {
  178. process();
  179. }, 5000);
  180. }
  181. }
  182. // Intercept pushState and replaceState
  183. const originalPushState = history.pushState;
  184. const originalReplaceState = history.replaceState;
  185. history.pushState = function () {
  186. originalPushState.apply(this, arguments);
  187. scheduleProcess();
  188. };
  189.  
  190. history.replaceState = function () {
  191. originalReplaceState.apply(this, arguments);
  192. scheduleProcess();
  193. };
  194. window.addEventListener("load", function () {
  195. scheduleProcess();
  196. });
  197. window.addEventListener("popstate", function (event) {
  198. scheduleProcess();
  199. });
  200. })();