您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
trace browsing history and preserve tables (HTML passthrough)
当前为
// ==UserScript== // @name MemTrace // @namespace Violentmonkey Scripts // @version 0.6 // @description trace browsing history and preserve tables (HTML passthrough) // @author fankaidev // @match *://*/* // @exclude *://cubox.pro/* // @exclude *://localhost:*/* // @exclude *://127.0.0.1:*/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // @require https://cdnjs.cloudflare.com/ajax/libs/turndown/7.1.1/turndown.min.js // @require https://unpkg.com/[email protected]/dist/turndown-plugin-gfm.js // @require https://cdn.jsdelivr.net/npm/[email protected]/dist/purify.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/crypto-js.js // @require https://cdn.jsdelivr.net/npm/@mozilla/[email protected]/Readability.min.js // @license MIT // ==/UserScript== (function () { "use strict"; function md5(input) { return CryptoJS.MD5(input).toString(); } let hrefHistory = []; // Function to get or initialize global state function getGlobalState(key, defaultValue) { return GM_getValue(key, defaultValue); } // Function to update global state function updateGlobalState(key, value) { GM_setValue(key, value); } // Function to get the endpoint function getEndpoint() { let endpoint = getGlobalState("endpoint", null); if (!endpoint) { endpoint = prompt("[MemTrace] Please enter the endpoint URL:", "https://api.example.com/endpoint"); if (endpoint) { updateGlobalState("endpoint", endpoint); } else { console.error("[MemTrace] No endpoint provided. Script will not function correctly."); } } return endpoint; } // Function to change the endpoint function changeEndpoint() { let newEndpoint = prompt("[MemTrace] Enter new endpoint URL:", getGlobalState("endpoint", "")); if (newEndpoint) { updateGlobalState("endpoint", newEndpoint); console.log("[MemTrace] Endpoint updated to", newEndpoint); } } // Register menu command to change endpoint GM_registerMenuCommand("Change MemTrace Endpoint", changeEndpoint); function processPage() { const article = new Readability(document.cloneNode(true)).parse().content; // console.log("article", article); const turndownService = new TurndownService({ keepReplacement: function (content, node) { return node.isBlock ? "\n\n" + node.outerHTML + "\n\n" : node.outerHTML; }, }); // Add a rule to keep tables turndownService.addRule("tables", { filter: ["table"], replacement: function (content, node) { return node.outerHTML; }, }); // Uncomment the following line if you want to use the GFM table plugin instead // turndownService.use(turndownPluginGfm.tables); return turndownService.turndown(article); } function savePage(markdown) { const url = window.location.href.split("#")[0]; let data = { title: document.title, source: "chrome", id: md5(url), markdown: markdown, url: url, }; console.log("[MemTrace] saving page", data); GM_xmlhttpRequest({ method: "POST", url: getEndpoint(), data: JSON.stringify(data), headers: { "Content-Type": "application/json", }, onload: function (response) { if (response.status === 200) { console.log("[MemTrace] saved page"); } else { console.error("Failed to save to MemTrace", response.responseText); } }, onerror: function (error) { console.error("Request failed:", error); }, }); } function parseRedditReply(reply, depth) { let replyText = ""; replyText += "\n---\n"; const ts = new Date(reply.data.created * 1000).toISOString(); replyText += ">".repeat(depth) + ` **${reply.data.author}** - ${ts}\n`; replyText += ">".repeat(depth) + "\n"; const lines = reply.data.body.split("\n"); for (const line of lines) { replyText += ">".repeat(depth) + " " + line + "\n"; } if (!reply.data.replies) { return replyText; } for (const child of reply.data.replies.data.children) { replyText += parseRedditReply(child, depth + 1); } return replyText; } function processRedditPage() { console.log("[MemTrace] processing reddit page"); fetch(window.location.href + ".json") .then((response) => response.json()) .then((responseJson) => { const page = responseJson; const post = page[0].data.children[0].data; const ts = new Date(post.created * 1000).toISOString(); let markdown = `*${post.subreddit_name_prefixed}*\n\n`; markdown += `**${post.author}** - ${ts}\n\n`; markdown += `${post.selftext}\n\n`; for (const reply of page[1].data.children) { markdown += parseRedditReply(reply, 1); } savePage(markdown); }); } function process() { const url = window.location.href.split("#")[0]; if (hrefHistory.includes(url)) { console.log("[MemTrace] skip processed url", url); return; } console.log("[MemTrace] processing url", url); hrefHistory.push(url); if (/reddit.com\/r\/[^/]+\/comments/.test(url)) { processRedditPage(); } else { const markdown = processPage(); if (markdown.length < 100) { console.log("[MemTrace] fail to parse page"); return; } savePage(markdown); } } function scheduleProcess() { if (document.contentType != "text/html") { return; } if (window.self === window.top) { console.log(`[MemTrace] type=${document.contentType} href=${window.location.href}`); setTimeout(() => { process(); }, 5000); } } // Intercept pushState and replaceState const originalPushState = history.pushState; const originalReplaceState = history.replaceState; history.pushState = function () { originalPushState.apply(this, arguments); scheduleProcess(); }; history.replaceState = function () { originalReplaceState.apply(this, arguments); scheduleProcess(); }; window.addEventListener("load", function () { scheduleProcess(); }); window.addEventListener("popstate", function (event) { scheduleProcess(); }); })();