Webcomic Autoloader 2.0

Gives you the option to load all the subsequent comic pages on a FurAffinity comic page automatically. Even for pages without given Links

目前為 2024-01-05 提交的版本,檢視 最新版本

// ==UserScript==
// @name        Webcomic Autoloader 2.0
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require     https://gf.qytechs.cn/scripts/475041-furaffinity-custom-settings/code/Furaffinity-Custom-Settings.js
// @require     https://gf.qytechs.cn/scripts/483952-furaffinity-request-helper/code/Furaffinity-Request-Helper.js
// @grant       none
// @version     2.0.0
// @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

const matchList = ['net/view'];
const nextText = "next";
const prevText = "prev";
const firstText = "first";

CustomSettings.name = "Extension Settings";
CustomSettings.provider = "Midori's Script Settings";
CustomSettings.headerName = `${GM_info.script.name} Settings`;
const showSearchButtonSetting = CustomSettings.newSetting("Simular Search Button", "Sets wether the search for simular Pages button is show.", SettingTypes.Boolean, "Show Search Button", true);
const loadingSpinSpeedSetting = CustomSettings.newSetting("Loading Animation", "Sets the spinning speed of the loading animation in milliseconds.", SettingTypes.Number, "", 100);
CustomSettings.loadSettings();

if (window.parent !== window)
    return;

let color = "color: blue";
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches)
    color = "color: aqua";

if (window.location.toString().includes("?extension")) {
    console.info(`%cSettings: ${GM_info.script.name} v${GM_info.script.version}`, color);
    return;
}

if (!matchList.some(x => window.location.toString().includes(x)))
    return;

console.info(`%cRunning: ${GM_info.script.name} v${GM_info.script.version} ${CustomSettings.toString()}`, color);

const requestHelper = new FARequestHelper(2);

let lightboxPresent = false;
let currLightboxNo = -1;
let imgCount = 1;

const rootSubmissionImg = document.getElementById("submissionImg");
rootSubmissionImg.setAttribute('imgno', 0);
rootSubmissionImg.onclick = () => {
    lightboxPresent = true;
    currLightboxNo = 0;
    window.addEventListener('keydown', handleArrowKeys);
    const lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]');
    lightbox.onclick = () => {
        lightboxPresent = false;
        currLightboxNo = -1;
        window.removeEventListener('keydown', handleArrowKeys);
    };
};
const openedSids = [getIdFromUrl(window.location.toString())];

createLoaderButton();

class TextRotation {
    constructor(element) {
        this.element = element;
        this.isRotating = false;
        this.characters = ["◜", "◠", "◝", "◞", "◡", "◟"];
        this.delay = 100;
        this._currIndex = -1;
        this._intervalId;
    }

    start() {
        this.isRotating = true;
        this._currIndex = 0;
        this._intervalId = setInterval(() => {
            if (this._currIndex >= this.characters.length)
                this._currIndex = 0;
            this.element.textContent = this.characters[this._currIndex];
            this._currIndex++;
        }, this.delay);
    }

    stop() {
        this.isRotating = false;
        clearInterval(this._intervalId);
    }
}

function createLoaderButton() {
    const hasSecondPage = getNavigationIds(document).next;

    const autoLoaderButton = document.createElement('button');
    autoLoaderButton.id = "autoloaderbutton";
    autoLoaderButton.className = "button standard mobile-fix";
    autoLoaderButton.type = "button";
    autoLoaderButton.style.marginTop = "10px";
    autoLoaderButton.style.marginBottom = "20px";

    if (hasSecondPage) {
        autoLoaderButton.textContent = "Enable Comic Autoloader";
        autoLoaderButton.onclick = startAutoloader;
        insertAfter(autoLoaderButton, rootSubmissionImg);
    } else if (showSearchButtonSetting.value) {
        autoLoaderButton.textContent = "Search for simular Pages";
        autoLoaderButton.onclick = startSimularSearch;
        insertAfter(autoLoaderButton, rootSubmissionImg);
    }
}

async function startAutoloader() {
    const autoLoaderButton = document.getElementById("autoloaderbutton");
    autoLoaderButton.parentNode.removeChild(autoLoaderButton);

    let sids = getNavigationIds(document);
    let lastSubmissionImg = document.getElementById("submissionImg");
    while (sids.next) {
        const newDoc = await loadPage(sids.next, lastSubmissionImg);
        lastSubmissionImg = document.getElementById("columnpage").querySelector('img[imgno="' + (openedSids.length - 1) + '"]');
        sids = getNavigationIds(newDoc);
    }
}

async function startSimularSearch() {
    const autoLoaderButton = document.getElementById("autoloaderbutton");
    const rotation = new TextRotation(autoLoaderButton);
    rotation.delay = loadingSpinSpeedSetting.value;
    rotation.start();
    const result = await searchAllSimularPages();
    rotation.stop();
    if (result)
        autoLoaderButton.parentNode.removeChild(autoLoaderButton);
    else
        autoLoaderButton.textContent = "Nothing found... Search again";
}

function getNavigationIds(doc) {
    let nextSid;
    let prevSid;
    let startSid;
    if (doc) {
        const links = doc.querySelectorAll('a[href]:not([class*="button standard mobile-fix"]), :not([class])');
        for (const elem of links) {
            const navText = elem.textContent.toLowerCase();
            if (navText.includes(nextText))
                nextSid = getIdFromUrl(elem.href);
            if (navText.includes(prevText))
                prevSid = getIdFromUrl(elem.href);
            if (navText.includes(firstText))
                startSid = getIdFromUrl(elem.href);
        }
    }
    const sids = { next: nextSid, prev: prevSid, start: startSid };
    return sids;
}

async function loadPage(sid, lastSubmissionImg) {
    if (sid && !openedSids.includes(sid)) {
        const submissionPage = await requestHelper.SubmissionRequests.getSubmissionPage(sid);
        if (submissionPage && submissionPage.getElementById("submissionImg")) {
            openedSids.push(sid);
            const submissionImg = submissionPage.getElementById("submissionImg");
            submissionImg.setAttribute('imgno', openedSids.length - 1);
            submissionImg.onclick = () => showLightBox(submissionImg);

            insertAfter(submissionImg, lastSubmissionImg);
            insertBreakBefore(submissionImg);
            insertBreakBefore(submissionImg);

            return submissionPage;
        }
    }
}

async function searchAllSimularPages() {
    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[href]');

    let user = author.href;
    if (user.endsWith("/"))
        user = user.substring(0, user.length - 1);
    user = user.substring(user.lastIndexOf("/") + 1);

    const sid = getIdFromUrl(window.location.toString());

    const galleryPages = await requestHelper.UserRequests.GalleryRequests.Gallery.getFiguresTillId(user, sid);
    const simularFigures = [];
    for (const figures of galleryPages) {
        for (const figure of figures) {
            const title = getTitleFromFigure(figure);
            if (title.includes(currTitle) || currTitle.includes(title))
                simularFigures.push(figure);
        }
    }
    if (simularFigures.length === 0)
        return false;
    simularFigures.reverse();
    const simularSids = simularFigures.map(figure => figure.id.toString().replace('sid-', ''));

    let lastSubmissionImg = document.getElementById("submissionImg");
    for (const sid of simularSids) {
        await loadPage(sid, lastSubmissionImg);
        lastSubmissionImg = document.getElementById("columnpage").querySelector('img[imgno="' + (openedSids.length - 1) + '"]');
    }
    return true;
}

function getTitleFromFigure(figure) {
    const figcaption = figure.querySelector('figcaption');
    let title = figcaption.querySelector('a[href]').textContent;
    title = generalizeString(title, true, true, true, true, true);
    return title;
}

function getIdFromUrl(url) {
    try {
        const firstNumberIndex = url.search(/\d/);
        const lastNumberIndex = url.lastIndexOf(url.match(/\d(?=\D*$)/));
        const id = url.substring(firstNumberIndex, lastNumberIndex + 1);
        return id;
    } catch {
        return;
    }
}

function showLightBox(img) {
    const lightbox = document.createElement('div');
    lightbox.className = 'lightbox lightbox-submission';
    lightbox.onclick = () => {
        document.body.removeChild(lightbox);
        lightboxPresent = false;
        currLightboxNo = -1;
        window.removeEventListener('keydown', handleArrowKeys);
    };
    const lightboxImg = img.cloneNode(false);
    lightboxImg.removeEventListener("click");
    lightbox.appendChild(lightboxImg);
    document.body.appendChild(lightbox);
    lightboxPresent = true;
    currLightboxNo = +img.getAttribute('imgno');
    window.addEventListener('keydown', handleArrowKeys);
}

function navigateLightboxLeft() {
    if (currLightboxNo > 0) {
        currLightboxNo--;
        const lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]');
        const lightboxImg = lightbox.querySelector('img');
        const nextImg = document.querySelector('img[imgno="' + currLightboxNo + '"]');
        lightboxImg.src = nextImg.src;
    }
}

function navigateLightboxRight() {
    if (currLightboxNo < openedSids.length - 1) {
        currLightboxNo++;
        const lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]');
        const lightboxImg = lightbox.querySelector('img');
        const nextImg = document.querySelector('img[imgno="' + currLightboxNo + '"]');
        lightboxImg.src = nextImg.src;
    }
}

function handleArrowKeys(event) {
    if (event.keyCode === 37) { // left arrow
        navigateLightboxLeft();
    } else if (event.keyCode === 39) { // right arrow
        navigateLightboxRight();
    }
}

function insertAfter(newNode, referenceNode) {
    referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
}

function insertBreakAfter(referenceNode) {
    insertAfter(document.createElement("br"), referenceNode);
}

function insertBreakBefore(referenceNode) {
    referenceNode.parentNode.insertBefore(document.createElement("br"), referenceNode);
}

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;
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址