您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Bookmark a work directly from any page and go back from bookmarking to browsing quickly
// ==UserScript== // @name AO3 Bookmark Improver // @namespace http://tampermonkey.net/ // @version 1.0 // @license MIT // @description Bookmark a work directly from any page and go back from bookmarking to browsing quickly // @author sunkitten_shash // @match https://archiveofourown.org/* // @match http://archiveofourown.org/* // @require https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js // @grant GM.getValue // @grant GM.setValue // @grant GM.registerMenuCommand // ==/UserScript== // Much of the popup settings code heavily references BrickGrass' Blanket Permission Highlighter // (https://github.com/BrickGrass/Blanket-Permission-Highlighter) (function() { 'use strict'; // ---HTML AND CSS--- // Styles for settings menu const css = ` #bookmark-settings { position: fixed; z-index: 21; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; background-color: rgba(0, 0, 0, 0.4); } #bookmark-settings-content { background-color: #fff; color: #2a2a2a; margin: 10% auto; padding: 1em; width: 500px; } #bookmark-settings-content form { margin: 1em auto; } #bookmark-settings a { color: #111; } #bookmark-settings a:hover { color: #999; } #bookmark-settings .progress { color: green; font-size: .75rem; } #bookmark-settings button { background: #eee; color: #444; width: auto; font-size: 100%; line-height: 1.286; height: 1.286em; vertical-align: middle; display: inline-block; padding: 0.25em 0.75em; white-space: nowrap; overflow: visible; position: relative; text-decoration: none; border: 1px solid #bbb; border-bottom: 1px solid #aaa; background-image: -moz-linear-gradient(#fff 2%,#ddd 95%,#bbb 100%); background-image: -webkit-linear-gradient(#fff 2%,#ddd 95%,#bbb 100%); background-image: -o-linear-gradient(#fff 2%,#ddd 95%,#bbb 100%); background-image: -ms-linear-gradient(#fff 2%,#ddd 95%,#bbb 100%); background-image: linear-gradient(#fff 2%,#ddd 95%,#bbb 100%); border-radius: 0.25em; box-shadow: none; } @media only screen and (max-width: 625px) { #bookmark-settings-content { width: 80%; } }`; const bookmark_settings_html = ` <div id="bookmark-settings"> <div id="bookmark-settings-content"> <h2>Ao3 Bookmark Improver Settings</h2> <br><br> <button id="bookmark-update">Update bookmarks list</button> <p>For if you have created a lot of bookmarks in a different browser or when not running this script</p><br> <button id="bookmark-clear">Clear bookmarks list</button> <p>Clear your entire bookmarks list, for if you have deleted a lot of bookmarks, switched users, or the list seems wrong</p> <button id="bookmark-settings-close">Close</button> </div> </div>` // ---GLOBAL VARIABLES--- // variable that saves the scroll position on a page var scrollPos = 0; // cutoff variable for updating bookmarks let endOfPage = false; // abstracting out getting/setting the bookmark id lists into functions doesn't work // therefore their names are global variables for easier switching to new variables let workListName = "bookmarkWorkIds"; let bookmarkListName = "bookmarkBookmarkIds"; // ---SETTINGS PAGE CODE--- GM.registerMenuCommand("AO3 Bookmark Improver Settings", function() { const settings_menu_exists = $("#bookmark-settings").length; if (settings_menu_exists) { console.log("settings already open"); return; } $("body").prepend(bookmark_settings_html); $("#bookmark-update").click(updateBookmarkList); $("#bookmark-clear").click(clearBookmarks); $("#bookmark-settings-close").click(settings_close); }); // close the settings dialog function settings_close() { $("#bookmark-settings").remove(); window.location.reload(); } // clear your entire bookmarks list async function clearBookmarks() { GM.setValue(workListName, []); GM.setValue(bookmarkListName, []); console.log("clear bookmarks"); $("#bookmark-clear").after(`<p id="bookmark-clear-feedback" class="progress">Bookmark list cleared!</p>`); await new Promise(resolve => setTimeout(resolve, 5000)); $("#bookmark-clear-feedback").remove(); } // Goes through all of the user's bookmarks until it reaches the end or until there's < n (n being 10 here) // new bookmarks on a page, at which point it figures it's updated enough and terminates // This runs slowly. There's a 5 second pause in between requesting each page, and if there's an error, it waits // 5 minutes (since it assumes that means rate limiting) async function updateBookmarkList() { // get your userId from the little greeting in the corner let userId = $("#greeting > .user > .dropdown > .dropdown-toggle").attr("href").split('/')[2]; let pageNum = 1; let newBookmarksNum = 0; endOfPage = false; let bookmarkWorkIds = await GM.getValue(workListName, []); let bookmarkBookmarkIds = await GM.getValue(bookmarkListName, []); console.log("initial bookmarks length: " + bookmarkWorkIds.length); $("button#bookmark-update").after(`<p id="bookmark-update-progress" class="progress">On page ${pageNum}...</p>`); // loop to go through all the bookmark pages that have new-to-us bookmarks while (!endOfPage) { //while (counter < 3) { // didn't feel like wrapping the entire thing in a timeout or making another function for it // bc I'm lazy, so here's just a timeout for 5 secs // this doesn't usually run into rate limiting for me //console.log({ counter }); //counter++; await new Promise(resolve => setTimeout(resolve, 5000)); try { await $.get(`https://archiveofourown.org/users/${userId}/bookmarks?page=${pageNum}`, (data) => { console.log(pageNum); /*if ($("#bookmark-update-progress").length > 0) { console.log("already there"); console.log($("#bookmark-update-progress")); $("#bookmark-update-progress").innerText = `page num: ${pageNum}`; } else { console.log("not there"); $("button#bookmark-update").after(`<p id="bookmark-update-progress" style="font-size:.75rem; color:green">page num: ${pageNum}</p>`); }*/ $("#bookmark-update-progress").text(`On page ${pageNum}...`); //$("button#bookmark-update").after(`<p id="bookmark-update-progress" style="font-size:.5rem">page num: ${pageNum}</p>`); //console.log($("button#bookmark-update")); let workLinks = $("li[role=article] > .header > .heading:first-child > a:not([href*=users]):not([href*=gifts])", data); let bookmarkLinks = $(`li[role=article] > .own > .actions > li > a:contains("Edit")`, data); if (workLinks.length === 0) { console.log("no work links"); endOfPage = true; } else { let work_id = ""; // for each of the links in the page // see if the work id is already in your bookmarks // if it's not, add it to bookmarks for (var i = 0, workLink; i < workLinks.length; i++) { workLink = $(workLinks[i]); work_id = workLink.attr("href").split('/')[2]; let bookmark_id = $(bookmarkLinks[i]).attr("href").split('/')[2]; // if this work_id is not in the bookmarks list if (!bookmarkWorkIds.includes(work_id)) { bookmarkWorkIds.push(work_id); bookmarkBookmarkIds.push(bookmark_id); newBookmarksNum++; } } // end for loop console.log("new bookmarks num: " + newBookmarksNum); // if you have more than 10 deleted or unrevelead bookmarks in a page, well, sucks to be you I guess // otherwise good chance you've exhausted the bookmarks that you need to update, good job if (newBookmarksNum < 10) { console.log(`Terminating bookmarks list update on ${pageNum} with ${newBookmarksNum} new bookmarks`); endOfPage = true; } } // endif pageNum++; newBookmarksNum = 0; }) // end the get thingy } // end try // if there's an error, wait five minutes cause it's probably rate limiting you catch (e) { console.log("Error requesting bookmarks page: " + e); await new Promise(resolve => setTimeout(resolve, 50000)) } if (endOfPage) { console.log("Done updating bookmarks list"); $("#bookmark-update-progress").text("Done updating bookmarks!"); break; } } // end while loop GM.setValue(workListName, bookmarkWorkIds); GM.setValue(bookmarkListName, bookmarkBookmarkIds); await new Promise(resolve => setTimeout(resolve, 5000)); $("#bookmark-update-progress").remove(); } async function doFunctions(url) { if (url.includes("archiveofourown.org/bookmarks/")) { handleNewBookmark(url); } let userId = $("#greeting > .user > .dropdown > .dropdown-toggle").attr("href").split('/')[2]; // this is the slightly nuclear option: if it's one of your pages, just don't show the bookmark button at all // it also eliminates all bookmark pages and lists of a user's collections (but not the list of works in the collection) // the reason we're eliminating all your pages is that they already have the edit navigation // section, so there's duplication if (!url.includes("/bookmarks") && !(url.includes("users") && url.includes("collection"))) { if (url.includes(userId)) { addYourSaveButtons(url); } else { addSaveButtons(url); } } } // adds buttons to create bookmarks on pages w/ your works async function addYourSaveButtons(url) { let bookmarkWorkIds = await GM.getValue(workListName, []); let bookmarkBookmarkIds = await GM.getValue(bookmarkListName, []); for (var i = 0, link, links = $("li[role=article] > .header > .heading:first-child > a:not([href*=users]):not([href*=gifts])"); i < links.length; i++) { let link = $(links[i]); let work_id = link.attr("href").split('/')[2]; let btnText = 'Save'; let urlModifier = `works/${work_id}/bookmarks/new`; if (bookmarkWorkIds.includes(work_id)) { let index = bookmarkWorkIds.indexOf(work_id); let bookmark_id = bookmarkBookmarkIds[index]; btnText = 'Saved'; urlModifier = `bookmarks/${bookmark_id}/edit`; } link.closest(".header") .nextAll(".stats") .after(`<ul class="actions" role="navigation"> <li> <a id="bookmark_form_trigger_` + work_id + `" data-remote="true" href="https://archiveofourown.org/` + urlModifier + `">${btnText}</a> </li> </ul>`); // when you click on this work's bookmark form trigger, it adds a div after it and loads in the bookmark form part // of the bookmark page $("#bookmark_form_trigger_" + work_id).click(function() { link.closest(".header") .nextAll(".actions") .after("<div id='bookmark_ext_div'></div>"); $("#bookmark_ext_div").load(`https://archiveofourown.org/${urlModifier} #bookmark-form`, () => { $("legend:contains('Bookmark')") .after(`<p class="close actions"><a id="bookmark-form-close">×</a></p>`); $("#bookmark-form-close").click(() => $("#bookmark_ext_div").remove()); }); }); } $("a[id^=bookmark_form_trigger]").click(saveScrollPos); } // adds the buttons to create bookmarks async function addSaveButtons(url) { let bookmarkWorkIds = await GM.getValue(workListName, []); let bookmarkBookmarkIds = await GM.getValue(bookmarkListName, []); // go through all of the works on the page // and add the save/saved button and its functionality for (var i = 0, link, links = $("li[role=article]:not([id*=bookmark]) > .header > .heading:first-child > a:not([href*=users]):not([href*=gifts])"); i < links.length; i++) { let link = $(links[i]); let work_id = link.attr("href").split('/')[2]; let btnText = 'Save'; let urlModifier = `works/${work_id}/bookmarks/new`; // if this is already bookmarked // then we need to indicate that and put in the proper link to edit the bookmark if (bookmarkWorkIds.includes(work_id)) { let index = bookmarkWorkIds.indexOf(work_id); let bookmark_id = bookmarkBookmarkIds[index]; btnText = 'Saved'; urlModifier = `bookmarks/${bookmark_id}/edit`; } link.closest(".header") .nextAll(".stats") .after(`<ul class="actions" role="navigation"> <li> <a id="bookmark_form_trigger_` + work_id + `" data-remote="true" href="https://archiveofourown.org/` + urlModifier + `">${btnText}</a> </li> </ul>`); // when you click on this work's bookmark form trigger, it adds a div after it and loads in the bookmark form part // of the bookmark page $("#bookmark_form_trigger_" + work_id).click(function() { link.closest(".header") .nextAll(".actions") .after("<div id='bookmark_ext_div'></div>"); $("#bookmark_ext_div").load(`https://archiveofourown.org/${urlModifier} #bookmark-form`, () => { $("legend:contains('Bookmark')") .after(`<p class="close actions"><a id="bookmark-form-close">×</a></p>`); $("#bookmark-form-close").click(() => $("#bookmark_ext_div").remove()); }); }); } // end for loop // after we've loaded in all our bookmark form triggers, set the onclick to save the scroll position for the back button $("a[id^=bookmark_form_trigger]").click(saveScrollPos); } // end addSaveButtons // on the dedicated bookmark page (usually when creating/updating a bookmark) // first of all, if it has the flash notice to show that it's a newly created bookmark // then get both the work id and the bookmark id and add them to the global list of created bookmarks // second, it also adds the back button to get back to where you were browsing async function handleNewBookmark(url) { let bookmarkWorkIds = await GM.getValue(workListName, []); let bookmarkBookmarkIds = await GM.getValue(bookmarkListName, []); // if this is a newly created bookmark if ($("div.flash.notice").text().includes("Bookmark was successfully created. It should appear in bookmark listings within the next few minutes.")) { let links = $("li[role=article] > .header > .heading:first-child > a:not([href*=users])"); let link = $(links[0]); let work_id = link.attr("href").split('/')[2]; let bookmark_id = url.split('/')[4]; if (!bookmarkWorkIds.includes(work_id)) { console.log("this is a new work bookmarked!"); bookmarkWorkIds.push(work_id); bookmarkBookmarkIds.push(bookmark_id); GM.setValue(workListName, bookmarkWorkIds); GM.setValue(bookmarkListName, bookmarkBookmarkIds); } } // back button addButton(); } // add back button which redirects to the previous page // for "you created a new bookmark" or "you updated this bookmark" pages function addButton() { $(".bookmarks-show > .navigation").prepend(`<li><a href="${document.referrer}" id="backButton">← Go Back</a></li>`); } // scroll to saved position on page async function scrollToPos() { scrollPos = await GM.getValue("scroll"); window.scrollTo(0, scrollPos); } // save the current position that the page is scrolled to function saveScrollPos() { scrollPos = window.scrollY; GM.setValue("scroll", scrollPos); } // when the page loads $(document).ready(function() { let url = window.location.href; // add custom CSS for settings menu let head = document.getElementsByTagName('head')[0]; if (head) { let style = document.createElement('style'); style.setAttribute('type', 'text/css'); style.textContent = css; head.appendChild(style); } // if we're coming from a bookmarks page, scroll to your previous position on the page if (document.referrer.includes("archiveofourown.org/bookmarks/")) { scrollToPos(); } doFunctions(url); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址