// ==UserScript== // @name 关键词高亮 // @namespace http://tampermonkey.net/ // @version 1.6 // @description Highlight keywords on a webpage, including their inflected forms such as plurals, tenses, and adverbs // @author You // @match *://*/* // @grant GM_addStyle // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @downloadURL none // ==/UserScript== // CSS for highlighted text GM_addStyle(` .highlighted { color: blue; font-weight: bold; } `); // Function to get stored keywords async function getStoredKeywords() { return (await GM_getValue("keywords", [])).map(k => k.trim()).filter(k => k); } // Function to save keywords async function setStoredKeywords(keywords) { await GM_setValue("keywords", keywords); } // Function to get deleted keywords async function getDeletedKeywords() { return (await GM_getValue("deletedKeywords", [])).map(k => k.trim()).filter(k => k); } // Function to save deleted keywords async function setDeletedKeywords(deletedKeywords) { await GM_setValue("deletedKeywords", deletedKeywords); } // Function to highlight keywords on the page async function THmo_doHighlight(container) { const keywords = await getStoredKeywords(); if (!keywords.length) return; // Generate the enhanced regex for each keyword const keywordPatterns = keywords.map(wordText => { return `\\b(${wordText}|` + `${wordText}s?|` + `${wordText.replace(/y$/, 'i')}es?|` + `${wordText}ed|` + `${wordText}ing|` + `${wordText}d|` + `${wordText}er|` + `${wordText}est|` + `${wordText}ly|` + `${wordText.replace(/y$/, 'ily')}|` + `${wordText.replace(/ic$/, 'ically')}|` + `${wordText.replace(/le$/, 'ly')})\\b`; }); const keywordRegex = new RegExp(keywordPatterns.join("|"), "gi"); // Use a tree walker to find text nodes const walker = document.createTreeWalker( container, NodeFilter.SHOW_TEXT, { acceptNode: function(node) { return keywordRegex.test(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; } } ); const nodesToHighlight = []; while (walker.nextNode()) { nodesToHighlight.push(walker.currentNode); } nodesToHighlight.forEach(node => { const span = document.createElement("span"); span.innerHTML = node.nodeValue.replace(keywordRegex, match => `${match}`); node.parentNode.replaceChild(span, node); }); } // Function to load keywords from a URL with a timestamp async function loadKeywordsFromFile(url) { try { const timestamp = new Date().getTime(); const response = await fetch(`${url}?timestamp=${timestamp}`); if (!response.ok) { alert(`Failed to fetch keywords from URL: ${response.statusText}`); return []; } const text = await response.text(); return text.split("\n").map(line => line.trim()).filter(line => line); } catch (error) { alert(`Error fetching keywords from URL: ${error.message}`); return []; } } // Menu command to load keywords from a URL GM_registerMenuCommand("Load Keywords from URL", async () => { const url = prompt("Enter the URL to load keywords from:", "https://example.com/keywords.txt"); if (!url) return; const newKeywords = await loadKeywordsFromFile(url); if (newKeywords.length > 0) { const storedKeywords = await getStoredKeywords(); const combinedKeywords = [...new Set([...storedKeywords, ...newKeywords])]; await setStoredKeywords(combinedKeywords); alert(`Loaded ${newKeywords.length} keywords from the URL.`); await THmo_doHighlight(document.body); } else { alert("No keywords were loaded from the provided URL."); } }); // Menu command to manually add a keyword GM_registerMenuCommand("Add Keyword", async () => { const keyword = prompt("Enter a keyword to add:"); if (!keyword) return; const storedKeywords = await getStoredKeywords(); if (!storedKeywords.includes(keyword)) { storedKeywords.push(keyword); await setStoredKeywords(storedKeywords); alert(`Added keyword: ${keyword}`); await THmo_doHighlight(document.body); } else { alert(`Keyword "${keyword}" is already in the list.`); } }); // Menu command to view all keywords GM_registerMenuCommand("View Keywords", async () => { const storedKeywords = await getStoredKeywords(); alert(`Stored Keywords:\n${storedKeywords.join("\n")}`); }); // Menu command to clear all keywords GM_registerMenuCommand("Clear Keywords", async () => { if (confirm("Are you sure you want to clear all keywords?")) { await setStoredKeywords([]); alert("All keywords cleared."); } }); // Menu command to delete a specific keyword GM_registerMenuCommand("Delete Keyword", async () => { const storedKeywords = await getStoredKeywords(); if (!storedKeywords.length) { alert("No keywords to delete."); return; } const keywordToDelete = prompt(`Enter a keyword to delete:\n${storedKeywords.join(", ")}`); if (!keywordToDelete) return; if (storedKeywords.includes(keywordToDelete)) { const updatedKeywords = storedKeywords.filter(k => k !== keywordToDelete); const deletedKeywords = await getDeletedKeywords(); await setStoredKeywords(updatedKeywords); await setDeletedKeywords([...new Set([...deletedKeywords, keywordToDelete])]); alert(`Deleted keyword: ${keywordToDelete}`); await THmo_doHighlight(document.body); } else { alert(`Keyword "${keywordToDelete}" not found.`); } }); // New menu command to cancel highlights from a URL GM_registerMenuCommand("Cancel Highlights from URL", async () => { const url = prompt("Enter the URL to cancel highlights from:", "https://example.com/cancel_keywords.txt"); if (!url) return; const cancelKeywords = await loadKeywordsFromFile(url); if (cancelKeywords.length > 0) { const storedKeywords = await getStoredKeywords(); const updatedKeywords = storedKeywords.filter(keyword => !cancelKeywords.includes(keyword)); await setStoredKeywords(updatedKeywords); alert(`Cancelled ${cancelKeywords.length} highlights from the URL.`); await THmo_doHighlight(document.body); } else { alert("No keywords to cancel from the provided URL."); } }); // Initial highlighting (async () => { await THmo_doHighlight(document.body); })();