您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Gives you the option to load all the subsequent comic pages on a FurAffinity comic page automatically. Even for pages without given Links
当前为
// ==UserScript== // @name Webcomic Autoloader 2.0 // @namespace Violentmonkey Scripts // @match *://*.furaffinity.net/* // @grant none // @version 1.6.1 // @author Midori Dragon // @description Gives you the option to load all the subsequent comic pages on a FurAffinity comic page automatically. Even for pages without given Links // @icon https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2 // @homepageURL https://gf.qytechs.cn/de/scripts/457759-furaffinity-webcomic-autoloader-2-0 // @supportURL https://gf.qytechs.cn/de/scripts/457759-furaffinity-webcomic-autoloader-2-0/feedback // @license MIT // ==/UserScript== // jshint esversion: 8 //User Options: let showSearchButton = JSON.parse(localStorage.getItem("wasetting_1")); if (showSearchButton == null || showSearchButton == undefined) showSearchButton = true; let loadingSpinSpeed = +localStorage.getItem("wasetting_2"); if (loadingSpinSpeed == null || loadingSpinSpeed == undefined || loadingSpinSpeed == 0 || isNaN(loadingSpinSpeed)) loadingSpinSpeed = 100; const matchList = ['net/view' ]; let settingsCount = 0; const isSettings = window.location.toString().includes('controls/settings'); let exSettings = JSON.parse(localStorage.getItem("wasettings")); if (exSettings == null) exSettings = false; addExSettings(); if (isSettings) { addExSettingsSidebar(); if (exSettings) createSettings(); } if (window.parent !== window) return; if (!matchList.some(x => window.location.toString().includes(x))) return; let color = "color: blue"; if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) color = "color: aqua"; console.info(`%cRunning: ${GM_info.script.name} v${GM_info.script.version} (Settings: "ShowSearch = ${showSearchButton}", "SpinSpeed = ${loadingSpinSpeed}")`, color); let lightboxPresent = false; let currLighboxNo = -1; let imgCount = 1; let rootHolder = document.getElementById("submissionImg"); rootHolder.setAttribute('imgno', 0); rootHolder.onclick = function() { lightboxPresent = true; currLighboxNo = 0; window.addEventListener('keydown', handleArrowKeys); let lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]'); lightbox.addEventListener('click', function() { lightboxPresent = false; currLighboxNo = -1; window.removeEventListener('keydown', handleArrowKeys); }); }; let counter = 5; let showLinks = false; let startImg = window.location.href; while (startImg.endsWith("/")) startImg = startImg.substring(0, startImg.length - 1); let pageCounter = 0; let openedLinks = [document.location.toString()]; let rotation; function insertAfter(newElement, referenceElement) { referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling); } function insertBreakAfter(referenceElement) { let br = document.createElement("br"); insertAfter(br, referenceElement); } function getNextLink(doc) { let links = doc.querySelectorAll('a[href]:not([class*="button standard mobile-fix"]), :not([class])'); let link; for (const elem of links) { if (elem.href && elem.textContent.toLowerCase().includes("next")) { try { let currImgCalc = elem.href; while (currImgCalc.endsWith("/")) currImgCalc = currImgCalc.substring(0, currImgCalc.length - 1); currImgCalc = currImgCalc.substring(currImgCalc.lastIndexOf('/'), currImgCalc.length); let startImgCalc = startImg.substring(startImg.lastIndexOf('/'), startImg.length); if (currImgCalc != startImgCalc && currImgCalc != "/#" && !openedLinks.includes(elem.href)) { link = elem.href; openedLinks.push(link); pageCounter++; } else if (openedLinks.includes(elem.href)) { return "loopinglink"; } } catch (ex) { console.error(ex); } } } return link; } function loadNextPage(nextLink) { if(nextLink) { if (nextLink.includes("http:")) nextLink = nextLink.replace(/^http:/, "https:"); let request = new XMLHttpRequest(); request.open('GET', nextLink, true); request.onload = function() { if (this.status >= 200 && this.status < 400) { // Success! parser = new DOMParser(); let nextPage = parser.parseFromString(this.response, "text/html"); let nl; if (nextPage && nextPage.getElementById("submissionImg")) { nl = getNextLink(nextPage); imgCount++; let img = nextPage.getElementById("submissionImg"); img.setAttribute('imgno', imgCount - 1); img.onclick = function() { doLightBox(img); }; rootHolder.parentNode.insertBefore(img, rootHolder.nextSibling); rootHolder = rootHolder.nextSibling; insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; if (showLinks) { let lnk = document.createElement('a'); let lnkURL = nextLink; lnk.innerHTML = lnkURL; lnk.href = lnkURL; insertAfter(lnk, rootHolder); rootHolder = rootHolder.nextSibling; insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; } else { let br = document.createElement('br'); insertAfter(br, rootHolder); rootHolder = rootHolder.nextSibling; } } if (nl == "loopinglink") { // searchNextSimularPage(); return; } else if (nl) loadNextPage(nl); } else { //We reached our target server, but it returned an error console.log("none"); } }; request.onerror = function() { //There was a connection error of some sort console.log("error"); }; request.send(); } } function loadPages(links, i) { if (i == links.length) return; let nextLink = links[i]; if(nextLink) { let request = new XMLHttpRequest(); request.open('GET', nextLink, true); request.onload = function() { if (this.status >= 200 && this.status < 400) { // Success! parser = new DOMParser(); let nextPage = parser.parseFromString(this.response, "text/html"); if (nextPage && nextPage.getElementById("submissionImg")) { imgCount++; let img = nextPage.getElementById("submissionImg"); img.setAttribute('imgno', imgCount - 1); img.onclick = function() { doLightBox(img); }; rootHolder.parentNode.insertBefore(img, rootHolder.nextSibling); rootHolder = rootHolder.nextSibling; insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; if (showLinks) { let lnk = document.createElement('a'); let lnkURL = nextLink; lnk.innerHTML = lnkURL; lnk.href = lnkURL; insertAfter(lnk, rootHolder); rootHolder = rootHolder.nextSibling; insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; } else { let br = document.createElement('br'); insertAfter(br, rootHolder); rootHolder = rootHolder.nextSibling; } } i++; loadPages(links, i); } else { //We reached our target server, but it returned an error console.log("none"); } }; request.onerror = function() { //There was a connection error of some sort console.log("error"); }; request.send(); } } function doLightBox(img) { let lightbox = document.createElement('div'); lightbox.className = 'lightbox lightbox-submission'; lightbox.onclick = function() { document.body.removeChild(lightbox); lightboxPresent = false; currLighboxNo = -1; window.removeEventListener('keydown', handleArrowKeys); }; let lightboxImg = img.cloneNode(false); lightboxImg.onclick = function(){}; lightbox.appendChild(lightboxImg); document.body.appendChild(lightbox); lightboxPresent = true; currLighboxNo = +img.getAttribute('imgno'); window.addEventListener('keydown', handleArrowKeys); } function navigateLightboxLeft() { if (currLighboxNo > 0) { currLighboxNo--; let lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]'); let lightboxImg = lightbox.querySelector('img'); let nextImg = document.querySelector('img[imgno="' + currLighboxNo + '"]'); lightboxImg.src = nextImg.src; } } function navigateLightboxRight() { if (currLighboxNo < imgCount - 1) { currLighboxNo++; let lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]'); let lightboxImg = lightbox.querySelector('img'); let nextImg = document.querySelector('img[imgno="' + currLighboxNo + '"]'); lightboxImg.src = nextImg.src; } } function handleArrowKeys(event) { if (event.keyCode === 37) { // left arrow navigateLightboxLeft(); } else if (event.keyCode === 39) { // right arrow navigateLightboxRight(); } } function startAutoloader() { let ab = document.getElementById("autoloaderButton"); ab.parentNode.removeChild(ab); let checkbox = document.getElementById("linksCheckbox"); checkbox.parentNode.removeChild(checkbox); let label = document.getElementById("showLinksLabel"); label.parentNode.removeChild(label); insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; loadNextPage(secondPage); } async function searchNextSimularPage() { let ab = document.getElementById("morePagesSearch"); if (ab) rotation = rotateText(ab, true); let checkbox = document.getElementById("linksCheckbox"); if (checkbox) checkbox.parentNode.removeChild(checkbox); let label = document.getElementById("showLinksLabel"); if (label) label.parentNode.removeChild(label); if (checkbox) { insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; } const submissionPage = document.getElementById("submission_page"); const container = submissionPage.querySelector('div[class="submission-id-sub-container"]'); let currTitle = container.querySelector('div[class="submission-title"]').querySelector('p').textContent; currTitle = generalizeString(currTitle, true, true, true, true, true); const author = container.querySelector('a'); let galleryLink = author.href.substring(0, author.href.length - 1); let currentSubmissionId = window.location.toString().substring(0, window.location.toString().length - 1); currentSubmissionId = "sid-" + currentSubmissionId.substring(currentSubmissionId.lastIndexOf("/") + 1); galleryLink = galleryLink.substring(galleryLink.lastIndexOf("/") + 1); galleryLink = "https://www.furaffinity.net/gallery/" + galleryLink; let figures = []; let currentFigureIndex = -1; let j = 0; while (currentFigureIndex == -1) { j++; gallery = await getHTML(galleryLink + "/" + j); let figuresNew = gallery.getElementsByTagName("figure"); if (!figuresNew || figuresNew.length == 0) { rotation.stop(); ab.value = "Nothing found... Search again"; return false; } figuresNew = Array.from(figuresNew); figures = figures.concat(figuresNew); currentFigureIndex = figuresNew.findIndex(figure => figure.id == currentSubmissionId); //console.log("j: " + j + " | index: " + currentFigureIndex); } currentFigureIndex = figures.findIndex(figure => figure.id == currentSubmissionId); //console.log("total index: " + currentFigureIndex); if (currentFigureIndex == 0) { rotation.stop(); ab.value = "Nothing found... Search again"; return false; } figures = figures.slice(0, currentFigureIndex); let pages = []; for (let i = figures.length - 1; i >= 0; i--) { const titleElem = figures[i].querySelector('figcaption').querySelector('a'); const title = generalizeString(titleElem.getAttribute("title"), true, true, true, true, true); if (title.includes(currTitle)) pages.push(titleElem.href); } if (pages.length == 0) { rotation.stop(); ab.value = "Nothing found... Search again"; return false; } else loadPages(pages, 0); ab.parentNode.removeChild(ab); return true; } function generalizeString(inputString, textToNumbers, removeSpecialChars, removeNumbers, removeSpaces, removeRoman) { let outputString = inputString.toLowerCase(); if (removeRoman) { const roman = [ "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X", "XI", "XII", "XIII", "XIV", "XV", "XVI", "XVII", "XVIII", "XIX", "XX"]; //Checks only up to 20 outputString = outputString.replace(new RegExp(`(?:^|[^a-zA-Z])(${roman.join("|") })(?:[^a-zA-Z]|$)`, "g"), ""); } if (textToNumbers) { const numbers = { zero: 0, one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10, eleven: 11, twelve: 12, thirteen: 13, fourteen: 14, fifteen: 15, sixteen: 16, seventeen: 17, eighteen: 18, nineteen: 19, twenty: 20, thirty: 30, forty: 40, fifty: 50, sixty: 60, seventy: 70, eighty: 80, ninety: 90, hundred: 100 }; outputString = outputString.replace(new RegExp(Object.keys(numbers).join("|"), "gi"), match => numbers[match.toLowerCase()]); } if (removeSpecialChars) outputString = outputString.replace(/[^a-zA-Z0-9 ]/g, ""); if (removeNumbers) outputString = outputString.replace(/[^a-zA-Z ]/g, ""); if (removeSpaces) outputString = outputString.replace(/\s/g, ""); return outputString; } function rotateText(element, isRotating) { const characters = [ "◜", "◠", "◝", "◞", "◡", "◟" ]; let index = 0; let intervalId; function updateText() { element.value = characters[index]; index = (index + 1) % characters.length; } function startRotation() { intervalId = setInterval(updateText, loadingSpinSpeed); } function stopRotation() { clearInterval(intervalId); } if (isRotating) { startRotation(); } return { start: startRotation, stop: stopRotation } } function setShowLinks() { if (showLinks) showLinks = false; else showLinks = true; let checkbox = document.getElementById("linksCheckbox"); checkbox.checked = showLinks; } async function getHTML(url) { try { const response = await fetch(url); const html = await response.text(); const parser = new DOMParser(); const doc = parser.parseFromString(html, "text/html"); return doc; } catch (error) { console.error(error); } } let secondPage = getNextLink(document); if(secondPage) { let img = document.getElementById("submissionImg"); insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; pageCounter++; let label = document.createElement('a'); label.id = "showLinksLabel"; label.innerHTML = "Show Links"; label.style.cursor = "pointer"; label.style.marginBottom = "30px"; label.style.marginLeft = "5px"; label.onclick = setShowLinks; insertAfter(label, rootHolder); let checkbox = document.createElement('input'); checkbox.value = "Show Links"; checkbox.type = "checkbox"; checkbox.id = "linksCheckbox"; checkbox.className = "checkbox standard mobile-fix"; checkbox.style.cursor = "pointer"; checkbox.style.marginBottom = "30px"; checkbox.style.marginLeft = "20px"; checkbox.onclick = setShowLinks; insertAfter(checkbox, rootHolder); let button = document.createElement('input'); button.value = "Enable Comic Autoloader"; button.type = "button"; button.id = "autoloaderButton"; button.className = "button standard mobile-fix"; button.style.marginTop = "10px"; button.style.marginBottom = "20px"; button.onclick = startAutoloader; insertAfter(button, rootHolder); } else if (showSearchButton) { let img = document.getElementById("submissionImg"); insertBreakAfter(rootHolder); rootHolder = rootHolder.nextSibling; let label = document.createElement('a'); label.id = "showLinksLabel"; label.innerHTML = "Show Links"; label.style.cursor = "pointer"; label.style.marginBottom = "30px"; label.style.marginLeft = "5px"; label.onclick = setShowLinks; insertAfter(label, rootHolder); let checkbox = document.createElement('input'); checkbox.value = "Show Links"; checkbox.type = "checkbox"; checkbox.id = "linksCheckbox"; checkbox.className = "checkbox standard mobile-fix"; checkbox.style.cursor = "pointer"; checkbox.style.marginBottom = "30px"; checkbox.style.marginLeft = "20px"; checkbox.onclick = setShowLinks; insertAfter(checkbox, rootHolder); let button = document.createElement('input'); button.value = "Search for more Pages in Series"; button.type = "button"; button.id = "morePagesSearch"; button.className = "button standard mobile-fix"; button.style.marginTop = "10px"; button.style.marginBottom = "20px"; button.onclick = searchNextSimularPage; insertAfter(button, rootHolder); } // ------------------------------ // // ---------- SETTINGS ---------- // // ------------------------------ // async function addExSettings() { const settings = document.querySelector('ul[class="navhideonmobile"]').querySelector('a[href="/controls/settings/"]').parentNode; if (document.getElementById("extension_settings")) { document.getElementById('midori_settings').addEventListener('click', function() { localStorage.setItem("wasettings", true.toString()); }); return; } let exSettingsHeader = document.createElement("h3"); exSettingsHeader.id = "extension_settings"; exSettingsHeader.textContent = "Extension Settings"; settings.appendChild(exSettingsHeader); let wasettings = document.createElement("a"); wasettings.id = "midori_settings"; wasettings.textContent = "Midori's Script Settings"; wasettings.style.cursor = "pointer"; wasettings.onclick = function() { localStorage.setItem("wasettings", true.toString()); window.location = "https://www.furaffinity.net/controls/settings"; } settings.appendChild(wasettings); } async function addExSettingsSidebar() { const settings = document.getElementById('controlpanelnav'); if (document.getElementById("extension_settings_side")) { document.getElementById('midori_settings_side').addEventListener('click', function() { localStorage.setItem("wasettings", true.toString()); }); return; } let exSettingsHeader = document.createElement("h3"); exSettingsHeader.id = "extension_settings_side"; exSettingsHeader.textContent = "Extension Settings"; settings.appendChild(exSettingsHeader); let wasettings = document.createElement("a"); wasettings.id = "midori_settings_side"; wasettings.textContent = "Midori's Script Settings"; wasettings.style.cursor = "pointer"; wasettings.onclick = function() { localStorage.setItem("wasettings", true.toString()); window.location = "https://www.furaffinity.net/controls/settings"; } settings.appendChild(wasettings); } async function createSettings() { localStorage.setItem("wasettings", false.toString()); const columnPage = document.getElementById("columnpage"); const content = columnPage.querySelector('div[class="content"]'); for (const section of content.querySelectorAll('section:not([class="exsettings"])')) section.parentNode.removeChild(section); const section = document.createElement("section"); section.className = 'exsettings'; const headerContainer = document.createElement("div"); headerContainer.className = "section-header"; const header = document.createElement("h2"); header.textContent = "Webcomic Autoloader Settings"; headerContainer.appendChild(header); section.appendChild(headerContainer); const bodyContainer = document.createElement("div"); bodyContainer.className = "section-body"; // Simular Search Button Settings const simularSearchButtonSetting = createSetting("Simular Search Button", "Sets wether the search for simular Pages button is shown", "boolean", "Show Search Button", (target) => { showSearchButton = target.checked; localStorage.setItem(target.id, showSearchButton.toString()); }); simularSearchButtonSetting.querySelector('[id*="setting"]').checked = showSearchButton; bodyContainer.appendChild(simularSearchButtonSetting); // Loading Animation Setting const loadingAnimationSetting = createSetting("Loading Animation", "Sets the spinning speed of the loading animation in milliseconds", "number", "", (target) => { loadingSpinSpeed = +target.value; localStorage.setItem(target.id, loadingSpinSpeed.toString()); }); loadingAnimationSetting.querySelector('[id*="setting"]').value = loadingSpinSpeed; bodyContainer.appendChild(loadingAnimationSetting); section.appendChild(bodyContainer); content.appendChild(section); } function createSetting(name, description, type, typeDescription, executeFunction) { const settingContainer = document.createElement("div"); settingContainer.className = "control-panel-item-container"; const settingName = document.createElement("div"); settingName.className = "control-panel-item-name"; const settingNameText = document.createElement("h4"); settingNameText.textContent = name; settingName.appendChild(settingNameText); settingContainer.appendChild(settingName); const settingDesc = document.createElement("div"); settingDesc.className = "control-panel-item-description"; const settingDescText = document.createTextNode(description); settingDesc.appendChild(settingDescText); settingContainer.appendChild(settingDesc); const settingOption = document.createElement("div"); settingOption.className = "control-panel-item-options"; if (type === "number") { settingsCount++; const settingInput = document.createElement("input"); settingInput.id = "igsetting_" + settingsCount; settingInput.type = "text"; settingInput.className = "textbox"; settingInput.addEventListener("keydown", (event) => { const currentValue = parseInt(settingInput.value) || 0; if (event.key === "ArrowUp") { settingInput.value = (currentValue + 1).toString(); executeFunction(settingInput); } else if (event.key === "ArrowDown") { if (currentValue != 0) settingInput.value = (currentValue - 1).toString(); executeFunction(settingInput); } }); settingInput.addEventListener("input", () => { settingInput.value = settingInput.value.replace(/[^0-9]/g, ""); if (settingInput.value < 0) settingInput.value = 0; }); settingInput.addEventListener("input", () => executeFunction(settingInput)); settingOption.appendChild(settingInput); } else if (type === "boolean") { settingsCount++; const settingCheckbox = document.createElement("input"); settingCheckbox.id = "wfsetting_" + settingsCount; settingCheckbox.type = "checkbox"; settingCheckbox.style.cursor = "pointer"; settingCheckbox.style.marginRight = "4px"; settingCheckbox.addEventListener("change", () => executeFunction(settingCheckbox)); settingOption.appendChild(settingCheckbox); const settingOptionLabel = document.createElement("label"); settingOptionLabel.textContent = typeDescription; settingOptionLabel.style.cursor = "pointer"; settingOptionLabel.addEventListener("click", () => { settingCheckbox.checked = !settingCheckbox.checked; executeFunction(settingCheckbox); }); settingOption.appendChild(settingOptionLabel); } else if (type === "action") { settingsCount++; const settingButton = document.createElement("button"); settingButton.id = "wfsetting_" + settingsCount; settingButton.type = "button"; settingButton.className = "button standard mobile-fix"; settingButton.textContent = typeDescription; settingButton.addEventListener("click", () => executeFunction(settingButton)); settingOption.appendChild(settingButton); } settingContainer.appendChild(settingOption); return settingContainer; }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址