您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Tracks clicked/visited links. Copies unvisited links from page and also Filters user-pasted URLs against history.
当前为
- // ==UserScript==
- // @name Copy Unvisited Links (Custom History v1.9 - Filter Pasted URLs)
- // @namespace http://tampermonkey.net/
- // @version 1.9
- // @description Tracks clicked/visited links. Copies unvisited links from page and also Filters user-pasted URLs against history.
- // @author Greg Cromwell / Gemini AI
- // @license MIT
- // @match *://*/*
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_setClipboard
- // @run-at document-idle
- // ==/UserScript==
- (function() {
- 'use strict';
- // --- Configuration ---
- const SCRIPT_VERSION = "1.9";
- const CONSOLE_PREFIX = `[Custom History v${SCRIPT_VERSION}]`;
- const CUSTOM_HISTORY_KEY = `customUserVisitedLinks_v${SCRIPT_VERSION}`;
- const MAX_CUSTOM_HISTORY_SIZE = 10000;
- const COPY_BUTTON_ID = `copyCustomUnvisitedBtn_v${SCRIPT_VERSION}`;
- const PASTE_FILTER_BUTTON_ID = `pasteFilterBtn_v${SCRIPT_VERSION}`;
- const MODAL_ID = `pasteFilterModal_v${SCRIPT_VERSION}`;
- const TEXTAREA_ID = `pasteFilterTextarea_v${SCRIPT_VERSION}`;
- console.log(`${CONSOLE_PREFIX} Script loading.`);
- // --- 1. URL Normalization ---
- function normalizeUrl(url) {
- try {
- const urlObj = new URL(url, document.baseURI);
- let normalized = `${urlObj.protocol}//${urlObj.hostname}${urlObj.port ? ':' + urlObj.port : ''}${urlObj.pathname}`;
- if (normalized.length > 1 && normalized.endsWith('/')) {
- normalized = normalized.slice(0, -1);
- }
- return normalized.toLowerCase();
- } catch (e) {
- console.warn(`${CONSOLE_PREFIX} Could not normalize URL: "${url}"`, e.message);
- return null;
- }
- }
- // --- 2. Custom History Management ---
- async function getCustomHistorySet() {
- const historyArray = await GM_getValue(CUSTOM_HISTORY_KEY, []);
- return new Set(Array.isArray(historyArray) ? historyArray : []);
- }
- async function addUrlToCustomHistory(url) {
- const normalizedUrl = normalizeUrl(url);
- if (!normalizedUrl) return;
- let historyArray = await GM_getValue(CUSTOM_HISTORY_KEY, []);
- if (!Array.isArray(historyArray)) {
- historyArray = [];
- }
- const tempHistorySet = new Set(historyArray);
- if (!tempHistorySet.has(normalizedUrl)) {
- historyArray.push(normalizedUrl);
- if (historyArray.length > MAX_CUSTOM_HISTORY_SIZE) {
- historyArray = historyArray.slice(historyArray.length - MAX_CUSTOM_HISTORY_SIZE);
- }
- await GM_setValue(CUSTOM_HISTORY_KEY, historyArray);
- }
- }
- // --- 3. Link Processing: Attach Click Listeners ---
- async function processLinksOnPageForHistory() {
- const links = document.querySelectorAll('a[href]');
- links.forEach(link => {
- const linkHref = link.href;
- link.addEventListener('click', function handleLinkClick() {
- addUrlToCustomHistory(linkHref);
- });
- });
- }
- // --- 4. Button Functionality ---
- // Function for the first button: Copy "New" Links from page
- async function handleCopyUnvisitedClick() {
- console.log(`${CONSOLE_PREFIX} handleCopyUnvisitedClick called.`);
- alert("Processing page links... Check console (F12) for progress.");
- const visitedSet = await getCustomHistorySet();
- const unvisitedLinksToCopy = [];
- let pageLinkCount = 0;
- const allPageLinks = document.querySelectorAll('a[href]');
- pageLinkCount = allPageLinks.length;
- allPageLinks.forEach(link => {
- const originalHref = link.href;
- const normalizedHref = normalizeUrl(originalHref);
- if (normalizedHref && !normalizedHref.startsWith('javascript:') && !normalizedHref.startsWith('mailto:')) {
- if (!visitedSet.has(normalizedHref)) {
- unvisitedLinksToCopy.push(originalHref);
- }
- }
- });
- const uniqueUnvisitedLinks = [...new Set(unvisitedLinksToCopy)];
- if (uniqueUnvisitedLinks.length > 0) {
- GM_setClipboard(uniqueUnvisitedLinks.join('\n'), 'text');
- alert(`Copied ${uniqueUnvisitedLinks.length} unique "new" link(s) (not in custom history) out of ${pageLinkCount} links to clipboard.`);
- } else {
- alert(`No "new" links found on this page (out of ${pageLinkCount} links) based on your custom history.`);
- }
- console.log(`${CONSOLE_PREFIX} handleCopyUnvisitedClick finished.`);
- }
- // --- Modal for Pasting Links ---
- function showPasteModal(callback) {
- if (document.getElementById(MODAL_ID)) {
- document.getElementById(MODAL_ID).style.display = 'flex';
- document.getElementById(TEXTAREA_ID).value = ''; // Clear previous content
- document.getElementById(TEXTAREA_ID).focus();
- return;
- }
- const modalOverlay = document.createElement('div');
- modalOverlay.id = MODAL_ID;
- Object.assign(modalOverlay.style, {
- position: 'fixed', top: '0', left: '0', width: '100%', height: '100%',
- backgroundColor: 'rgba(0,0,0,0.6)', display: 'flex',
- justifyContent: 'center', alignItems: 'center', zIndex: '10001'
- });
- const modalContent = document.createElement('div');
- Object.assign(modalContent.style, {
- background: 'white', padding: '25px', borderRadius: '8px',
- boxShadow: '0 4px 15px rgba(0,0,0,0.2)', minWidth: '350px', maxWidth: '90%',
- display: 'flex', flexDirection: 'column', gap: '15px'
- });
- const title = document.createElement('h3');
- title.textContent = 'Filter Pasted Links';
- Object.assign(title.style, { margin: '0 0 10px 0', textAlign: 'center' });
- const instruction = document.createElement('p');
- instruction.textContent = 'Paste your list of URLs below (one URL per line):';
- Object.assign(instruction.style, { margin: '0 0 5px 0', fontSize: '14px' });
- const textarea = document.createElement('textarea');
- textarea.id = TEXTAREA_ID;
- Object.assign(textarea.style, {
- width: 'calc(100% - 20px)', minHeight: '150px', border: '1px solid #ccc',
- borderRadius: '4px', padding: '10px', fontSize: '13px', resize: 'vertical'
- });
- const buttonContainer = document.createElement('div');
- Object.assign(buttonContainer.style, { display: 'flex', justifyContent: 'flex-end', gap: '10px', marginTop: '10px' });
- const processButton = document.createElement('button');
- processButton.textContent = 'Filter & Copy';
- Object.assign(processButton.style, {
- padding: '8px 15px', background: '#4CAF50', color: 'white',
- border: 'none', borderRadius: '4px', cursor: 'pointer'
- });
- const cancelButton = document.createElement('button');
- cancelButton.textContent = 'Cancel';
- Object.assign(cancelButton.style, {
- padding: '8px 15px', background: '#f44336', color: 'white',
- border: 'none', borderRadius: '4px', cursor: 'pointer'
- });
- processButton.onclick = () => {
- const text = textarea.value;
- modalOverlay.style.display = 'none';
- callback(text);
- };
- cancelButton.onclick = () => {
- modalOverlay.style.display = 'none';
- };
- modalOverlay.onclick = (event) => { // Close if backdrop is clicked
- if (event.target === modalOverlay) {
- modalOverlay.style.display = 'none';
- }
- };
- buttonContainer.append(cancelButton, processButton);
- modalContent.append(title, instruction, textarea, buttonContainer);
- modalOverlay.appendChild(modalContent);
- document.body.appendChild(modalOverlay);
- textarea.focus();
- }
- // Function for the second button: Process Pasted Text
- async function handleFilterPastedContentClick() {
- console.log(`${CONSOLE_PREFIX} handleFilterPastedContentClick called.`);
- showPasteModal(async (pastedText) => {
- if (!pastedText || pastedText.trim() === "") {
- alert("No text was pasted or processed.");
- console.log(`${CONSOLE_PREFIX} No text provided in modal.`);
- return;
- }
- const urlsFromPastedText = pastedText.split('\n').map(line => line.trim()).filter(line => line.length > 0);
- if (urlsFromPastedText.length === 0) {
- alert("No valid URLs found in the pasted text after parsing.");
- console.log(`${CONSOLE_PREFIX} No URLs found in pasted content.`);
- return;
- }
- console.log(`${CONSOLE_PREFIX} Found ${urlsFromPastedText.length} lines/URLs in pasted text.`);
- const visitedSet = await getCustomHistorySet();
- const unvisitedFromPasted = [];
- urlsFromPastedText.forEach(url => {
- const normalizedUrl = normalizeUrl(url);
- if (normalizedUrl && !normalizedUrl.startsWith('javascript:') && !normalizedUrl.startsWith('mailto:')) {
- if (!visitedSet.has(normalizedUrl)) {
- unvisitedFromPasted.push(url); // Store original URL
- }
- }
- });
- const uniqueUnvisitedFromPasted = [...new Set(unvisitedFromPasted)];
- console.log(`${CONSOLE_PREFIX} Found ${uniqueUnvisitedFromPasted.length} unique unvisited links from pasted text.`);
- if (uniqueUnvisitedFromPasted.length > 0) {
- GM_setClipboard(uniqueUnvisitedFromPasted.join('\n'), 'text');
- alert(`Filtered Pasted Text: ${uniqueUnvisitedFromPasted.length} unique "new" link(s) (not in your custom history) out of ${urlsFromPastedText.length} pasted links are now on your clipboard.`);
- } else {
- alert(`No "new" links found in your pasted text (out of ${urlsFromPastedText.length} lines) based on your custom history. Clipboard not changed.`);
- }
- console.log(`${CONSOLE_PREFIX} handleFilterPastedContentClick processing finished.`);
- });
- }
- // --- UI Element Creation ---
- function createAndAddButtons() {
- // Button 1: Copy "New" Links from Page
- if (!document.getElementById(COPY_BUTTON_ID)) {
- const button1 = document.createElement('button');
- button1.id = COPY_BUTTON_ID;
- button1.textContent = `CpNew v${SCRIPT_VERSION.substring(0,3)}`; // Shorter text
- button1.title = `Copy "New" Links from Page (v${SCRIPT_VERSION})`;
- Object.assign(button1.style, {
- position: 'fixed', top: '75px', right: '1px', zIndex: '3001',
- fontSize: '10px', padding: '1px 2px', margin: '0', lineHeight: '1',
- minWidth: '30px', backgroundColor: 'lightblue', border: '1px solid gray',
- borderRadius: '2px', cursor: 'pointer', boxShadow: '1px 1px 1px rgba(0,0,0,0.1)'
- });
- button1.addEventListener('click', handleCopyUnvisitedClick);
- appendElementToBody(button1, "Copy New Links button");
- }
- // Button 2: Paste & Filter
- if (!document.getElementById(PASTE_FILTER_BUTTON_ID)) {
- const button2 = document.createElement('button');
- button2.id = PASTE_FILTER_BUTTON_ID;
- button2.textContent = `Paste&Filt`; // Shorter text
- button2.title = `Paste and Filter URLs against Custom History (v${SCRIPT_VERSION})`;
- Object.assign(button2.style, {
- position: 'fixed', top: '95px', right: '1px', zIndex: '3000',
- fontSize: '10px', padding: '1px 2px', margin: '0', lineHeight: '1',
- minWidth: '30px', backgroundColor: 'lightgreen', border: '1px solid gray',
- borderRadius: '2px', cursor: 'pointer', boxShadow: '1px 1px 1px rgba(0,0,0,0.1)'
- });
- button2.addEventListener('click', handleFilterPastedContentClick);
- appendElementToBody(button2, "Paste & Filter button");
- }
- }
- function appendElementToBody(element, elementName) {
- if (document.body) {
- document.body.appendChild(element);
- } else {
- window.addEventListener('DOMContentLoaded', () => {
- if (document.body) document.body.appendChild(element);
- else console.error(`${CONSOLE_PREFIX} ${elementName}: document.body still not found.`);
- }, { once: true });
- }
- }
- // --- 5. Initialization and Dynamic Content Handling ---
- function initializeScript() {
- addUrlToCustomHistory(window.location.href);
- processLinksOnPageForHistory();
- createAndAddButtons();
- }
- try {
- initializeScript();
- } catch(e) {
- console.error(`${CONSOLE_PREFIX} CRITICAL ERROR during script initialization:`, e);
- alert(`[Custom History v${SCRIPT_VERSION}] CRITICAL ERROR: ${e.message}. Check console.`);
- }
- const observer = new MutationObserver((mutationsList) => {
- if (!document.getElementById(COPY_BUTTON_ID) || !document.getElementById(PASTE_FILTER_BUTTON_ID)) {
- createAndAddButtons();
- }
- for (const mutation of mutationsList) {
- if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
- let hasNewLinks = false;
- mutation.addedNodes.forEach(node => {
- if (node.nodeType === Node.ELEMENT_NODE && (node.matches('a[href]') || node.querySelector('a[href]'))) {
- hasNewLinks = true;
- }
- });
- if (hasNewLinks) {
- processLinksOnPageForHistory();
- break;
- }
- }
- }
- });
- if (document.body) {
- observer.observe(document.body, { childList: true, subtree: true });
- } else {
- window.addEventListener('DOMContentLoaded', () => {
- if (document.body) observer.observe(document.body, { childList: true, subtree: true });
- else console.error(`${CONSOLE_PREFIX} MutationObserver: document.body not found.`);
- }, { once: true });
- }
- window[`clearUserLinkHistory_v${SCRIPT_VERSION.replace(/\./g, '_')}`] = async () => {
- console.log(`${CONSOLE_PREFIX} clearUserLinkHistory called by user.`);
- if (confirm(`Are you sure you want to clear all custom link history for script version ${SCRIPT_VERSION}?`)) {
- await GM_setValue(CUSTOM_HISTORY_KEY, []);
- alert(`Custom link history (v${SCRIPT_VERSION}) cleared. Reload page for full effect.`);
- } else {
- alert(`Custom link history (v${SCRIPT_VERSION}) clearing cancelled.`);
- }
- };
- console.log(`${CONSOLE_PREFIX} Script loaded. Type 'clearUserLinkHistory_v${SCRIPT_VERSION.replace(/\./g, '_')}()' in console to clear history.`);
- })();