您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add Export-Import buttons at the bottom of the page. Rename+color Seen/Unseen buttons and Title. Mark as seen on open from AO3 only.
当前为
// ==UserScript== // @name Extend "AO3: Kudosed and seen history" @Min_ // @description Add Export-Import buttons at the bottom of the page. Rename+color Seen/Unseen buttons and Title. Mark as seen on open from AO3 only. // @author C89sd // @version 1.1 // @match https://archiveofourown.org/* // @namespace https://gf.qytechs.cn/users/1376767 // ==/UserScript== const MARK_SEEN_FROM_AO3_ONLY = true; (function() { 'use strict'; const footer = document.createElement('div'); footer.style.width = '100%'; footer.style.paddingTop = '5px'; footer.style.paddingBottom = '5px'; footer.style.display = 'flex'; footer.style.justifyContent = 'center'; footer.style.gap = '10px'; footer.classList.add('footer'); // Turn title into a link const firstH1 = document.querySelector('h2.title.heading'); var firstH1link = null; if (firstH1) { const title = firstH1.lastChild ? firstH1.lastChild : firstH1; const titleLink = document.createElement('a'); titleLink.href = window.location.href; if (title) { const titleClone = title.cloneNode(true); titleLink.appendChild(titleClone); title.parentNode.replaceChild(titleLink, title); } firstH1link = titleLink; } const BTN_1 = ['button']; const BTN_2 = ['button', 'button--link']; // Create Export Button const exportButton = document.createElement('button'); exportButton.textContent = 'Export'; exportButton.classList.add(...BTN_1); exportButton.addEventListener('click', exportToTxt); footer.appendChild(exportButton); // Create Import Button const importButton = document.createElement('button'); importButton.textContent = 'Import'; importButton.classList.add(...BTN_1); // Create hidden file input const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.style.display = 'none'; // Hide the input element // Trigger file input on "Restore" button click importButton.addEventListener('click', () => { fileInput.click(); // Open the file dialog when the button is clicked }); // Listen for file selection and handle the import fileInput.addEventListener('change', importFromTxt); footer.appendChild(importButton); // Append footer to the page const xFooter = document.getElementById('footer'); if (xFooter) { xFooter.insertAdjacentElement('beforebegin', footer); } else { document.body.appendChild(footer); } // Export function function exportToTxt() { var kudos_history = { kudosed: { list: localStorage.getItem('kudoshistory_kudosed') || ',' }, seen: { list: localStorage.getItem('kudoshistory_seen') || ',' }, bookmarked: { list: localStorage.getItem('kudoshistory_bookmarked') || ',' }, skipped: { list: localStorage.getItem('kudoshistory_skipped') || ',' }, checked: { list: localStorage.getItem('kudoshistory_checked') || ',' } }; var export_lists = { kudosed: kudos_history.kudosed.list, seen: kudos_history.seen.list, bookmarked: kudos_history.bookmarked.list, skipped: kudos_history.skipped.list, checked: kudos_history.checked.list }; var textToSave = JSON.stringify(export_lists, null, 2); var blob = new Blob([textToSave], { type: "text/plain" }); var a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = 'AO3_kudoshistory.json'; document.body.appendChild(a); a.click(); document.body.removeChild(a); } // Import function function importFromTxt(event) { var file = event.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function(e) { try { var importedData = JSON.parse(e.target.result); if (!importedData.kudosed || !importedData.seen || !importedData.bookmarked || !importedData.skipped || !importedData.checked) { throw new Error("Missing data."); } if (importedData.kudosed) localStorage.setItem('kudoshistory_kudosed', importedData.kudosed); if (importedData.seen) localStorage.setItem('kudoshistory_seen', importedData.seen); if (importedData.bookmarked) localStorage.setItem('kudoshistory_bookmarked', importedData.bookmarked); if (importedData.skipped) localStorage.setItem('kudoshistory_skipped', importedData.skipped); if (importedData.checked) localStorage.setItem('kudoshistory_checked', importedData.checked); alert("Data imported successfully!"); } catch (error) { alert("Invalid file format."); } }; reader.readAsText(file); } // ========================================== let wasClicked = false; // Step 1: Wait for the button to exist and click it if it shows "Seen" function waitForSeenButton() { let attempts = 0; const maxAttempts = 100; // Stop after ~5 seconds (100 * 50ms) const buttonCheckInterval = setInterval(function() { attempts++; const seenButton = document.querySelector('.kh-seen-button a'); if (seenButton) { clearInterval(buttonCheckInterval); if (seenButton.textContent.includes('Seen ✓')) { if (!MARK_SEEN_FROM_AO3_ONLY || document.referrer.includes("archiveofourown.org")) { seenButton.click(); wasClicked = true; } } else { wasClicked = false; } // Move to Step 2 setupButtonObserver(); } else if (attempts >= maxAttempts) { clearInterval(buttonCheckInterval); } }, 50); } // Step 2: Monitor the button text and toggle it function setupButtonObserver() { toggleButtonText(true, wasClicked); // Button to observe const targetNode = document.querySelector('.kh-seen-button'); if (!targetNode) { return; } const observer = new MutationObserver(function(mutations) { mutations.forEach(function(mutation) { if (mutation.type === 'childList' || mutation.type === 'characterData') { toggleButtonText(false, false); } }); }); const config = { childList: true, characterData: true, subtree: true }; observer.observe(targetNode, config); } function toggleButtonText(isFirst = false, wasClicked = false) { const buttonElement = document.querySelector('.kh-seen-button a'); if (!buttonElement) return; // Ignore changes we made ourselves if (buttonElement.textContent === "SEEN Now (click to unmark)" || buttonElement.textContent === "Old SEEN (click to unmark)" || buttonElement.textContent === "SEEN (click to unmark)" || buttonElement.textContent === "NOT SEEN (click to mark)" || buttonElement.textContent === "UNSEEN (click to mark)") { return; } const state_seen = buttonElement.textContent.includes('Unseen ✗') ? true : buttonElement.textContent.includes('Seen ✓') ? false : null; if (state_seen === null) { alert('[AO3 Patch] Unknown text: ' + buttonElement.textContent); return; } const GREEN = "#33cc70"; // "#33cc70"; const GREEN_DARKER = "#00a13a"; // "#149b49"; const RED = "#ff6d50"; buttonElement.textContent = state_seen ? (isFirst ? (wasClicked ? "SEEN Now (click to unmark)" : "Old SEEN (click to unmark)") : "SEEN (click to unmark)") : "UNSEEN (click to mark)"; const color = state_seen ? (isFirst && !wasClicked ? GREEN_DARKER : GREEN) : RED; buttonElement.style.backgroundColor = color; buttonElement.style.padding = "2px 6px"; buttonElement.style.borderRadius = "3px"; buttonElement.style.boxShadow = "none"; buttonElement.style.backgroundImage = "none"; firstH1link.style.color = color; if (isFirst && wasClicked) { // blink buttonElement.style.transition = "background-color 150ms ease"; buttonElement.style.backgroundColor = GREEN; setTimeout(() => { buttonElement.style.backgroundColor = "#00e64b"; }, 150); setTimeout(() => { buttonElement.style.transition = "background-color 200ms linear"; buttonElement.style.backgroundColor = GREEN; }, 200); } else if (!isFirst) { buttonElement.style.transition = "none"; // Clear transition for subsequent calls buttonElement.style.backgroundColor = state_seen ? GREEN : RED; } } // Start the process waitForSeenButton(); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址