FA Webcomic Auto Loader

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

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Для установки этого скрипта вам необходимо установить расширение, такое как Tampermonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name        FA Webcomic Auto Loader
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require     https://greasyfork.org/scripts/525666-furaffinity-prototype-extensions/code/525666-furaffinity-prototype-extensions.js
// @require     https://greasyfork.org/scripts/483952-furaffinity-request-helper/code/483952-furaffinity-request-helper.js
// @require     https://greasyfork.org/scripts/485827-furaffinity-match-list/code/485827-furaffinity-match-list.js
// @require     https://greasyfork.org/scripts/485153-furaffinity-loading-animations/code/485153-furaffinity-loading-animations.js
// @require     https://greasyfork.org/scripts/475041-furaffinity-custom-settings/code/475041-furaffinity-custom-settings.js
// @grant       GM_info
// @version     2.2.10
// @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
// @license     MIT
// @homepageURL https://greasyfork.org/scripts/457759-fa-webcomic-auto-loader
// @supportURL  https://greasyfork.org/scripts/457759-fa-webcomic-auto-loader/feedback
// ==/UserScript==
// jshint esversion: 11
(function (exports) {
    'use strict';

    var LogLevel;
    (function (LogLevel) {
        LogLevel[LogLevel["Error"] = 1] = "Error";
        LogLevel[LogLevel["Warning"] = 2] = "Warning";
        LogLevel[LogLevel["Info"] = 3] = "Info";
    })(LogLevel || (LogLevel = {}));
    class Logger {
        static log(logLevel = LogLevel.Warning, ...args) {
            if (window.__FF_GLOBAL_LOG_LEVEL__ == null) {
                window.__FF_GLOBAL_LOG_LEVEL__ = LogLevel.Error;
            }
            if (logLevel > window.__FF_GLOBAL_LOG_LEVEL__) {
                return;
            }
            switch (logLevel) {
                case LogLevel.Error:
                    console.error(...args);
                    break;
                case LogLevel.Warning:
                    console.warn(...args);
                    break;
                case LogLevel.Info:
                    console.log(...args);
                    break;
            }
        }
        static setLogLevel(logLevel) {
            window.__FF_GLOBAL_LOG_LEVEL__ = logLevel;
        }
        static logError(...args) {
            Logger.log(LogLevel.Error, ...args);
        }
        static logWarning(...args) {
            Logger.log(LogLevel.Warning, ...args);
        }
        static logInfo(...args) {
            Logger.log(LogLevel.Info, ...args);
        }
    }

    function checkTags (element) {
        const userLoggedIn = document.body.getAttribute('data-user-logged-in') === '1';
        if (!userLoggedIn) {
            Logger.logWarning('User is not logged in, skipping tag check');
            setBlockedState(element, false);
            return;
        }
        const tagsHideMissingTags = document.body.getAttribute('data-tag-blocklist-hide-tagless') === '1';
        const tags = element.getAttribute('data-tags')?.trim().split(/\s+/);
        let blockReason = '';
        if (tags != null && tags.length > 0 && tags[0] !== '') {
            // image has tags
            const blockedTags = getBannedTags(tags);
            if (blockedTags.length <= 0) {
                setBlockedState(element, false);
            }
            else {
                setBlockedState(element, true);
                Logger.logInfo(`${element.id} blocked tags: ${blockedTags.join(', ')}`);
                // provide hint
                blockReason = 'Blocked tags:\n';
                for (const tag of blockedTags) {
                    blockReason += '• ' + tag + '\n';
                }
            }
        }
        else {
            // image has no tags
            setBlockedState(element, tagsHideMissingTags);
            // provide hint
            if (tagsHideMissingTags) {
                blockReason = 'Content is missing tags.';
            }
        }
        if (blockReason !== '' && element.id !== 'submissionImg') {
            // apply hint to everything but main image on submission view page
            //element.setAttribute('data-block-reason', block_reason);
            element.setAttribute('title', blockReason);
        }
    }
    function getBannedTags(tags) {
        const blockedTags = document.body.getAttribute('data-tag-blocklist') ?? '';
        const tagsBlocklist = Array.from(blockedTags.split(' '));
        let bTags = [];
        if (tags == null || tags.length === 0) {
            return [];
        }
        for (const tag of tags) {
            for (const blockedTag of tagsBlocklist) {
                if (tag === blockedTag) {
                    bTags.push(blockedTag);
                }
            }
        }
        // Remove dupes and return
        return [...new Set(bTags)];
    }
    function setBlockedState(element, isBlocked) {
        element.classList[isBlocked ? 'add' : 'remove']('blocked-content');
    }

    function getCurrViewSid (doc) {
        let ogUrl = doc.querySelector('meta[property="og:url"]').getAttribute('content');
        if (ogUrl == null) {
            return -1;
        }
        ogUrl = ogUrl.trimEnd('/');
        return parseInt(ogUrl.split('/').pop());
    }

    class string {
        static isNullOrWhitespace(str) {
            return str == null || str.trim() === '';
        }
        static isNullOrEmpty(str) {
            return str == null || str === '';
        }
    }

    class ComicNavigation {
        prevId = -1;
        firstId = -1;
        nextId = -1;
        constructor(prevId, firstId, nextId) {
            this.prevId = prevId;
            this.firstId = firstId;
            this.nextId = nextId;
        }
        static fromElement(elem) {
            const comicNav = new ComicNavigation(-1, -1, -1);
            const navElems = elem.querySelectorAll('a[href*="view"]');
            if (navElems == null || navElems.length === 0) {
                return null;
            }
            for (const navElem of Array.from(navElems)) {
                const navText = navElem?.textContent?.toLowerCase();
                if (string.isNullOrWhitespace(navText)) {
                    continue;
                }
                let idText = navElem.getAttribute('href');
                if (string.isNullOrWhitespace(idText)) {
                    continue;
                }
                const i = idText.search(/[?#]/);
                idText = i === -1 ? idText : idText.slice(0, i);
                idText = idText.trimEnd('/');
                idText = idText.split('/').pop();
                if (navText.includes('prev')) {
                    comicNav.prevId = parseInt(idText);
                }
                else if (navText.includes('next')) {
                    comicNav.nextId = parseInt(idText);
                }
                else if (navText.includes('start') || navText.includes('first')) {
                    comicNav.firstId = parseInt(idText);
                }
            }
            return comicNav;
        }
    }

    class AutoLoaderSearch {
        rootImg;
        rootSid;
        currComicNav;
        currImgIndex = 1;
        currSid = -1;
        constructor(rootImg, rootSid, comicNav) {
            this.rootImg = rootImg;
            this.rootSid = rootSid;
            this.currComicNav = comicNav;
        }
        async search() {
            const loadedImgs = {};
            loadedImgs[this.rootSid] = this.rootImg;
            Logger.logInfo(`${scriptName}: starting search...`);
            do {
                try {
                    if (this.currComicNav == null) {
                        break;
                    }
                    const img = await this.getPage(this.currComicNav.nextId);
                    if (img == null) {
                        break;
                    }
                    if (this.currSid in loadedImgs) {
                        break;
                    }
                    Logger.logInfo(`${scriptName}: found image with sid '${this.currSid}'`);
                    loadedImgs[this.currSid] = img;
                    this.currImgIndex++;
                }
                catch (error) {
                    Logger.logError(error);
                    break;
                }
            } while (this.currComicNav?.nextId !== -1);
            Logger.logInfo(`${scriptName}: finished search. Found ${Object.keys(loadedImgs).length} images.`);
            return loadedImgs;
        }
        async getPage(sid) {
            if (sid <= 0) {
                return undefined;
            }
            const page = (await requestHelper.SubmissionRequests.getSubmissionPage(sid));
            const img = page.getElementById('submissionImg');
            img.setAttribute('wal-index', this.currImgIndex.toString());
            img.setAttribute('wal-sid', sid.toString());
            this.currSid = getCurrViewSid(page);
            const descriptionElem = page.getElementById('columnpage')?.querySelector('div[class*="submission-description"]');
            if (descriptionElem != null) {
                this.currComicNav = ComicNavigation.fromElement(descriptionElem);
            }
            else {
                this.currComicNav = null;
            }
            return img;
        }
    }

    function isSubmissionPageInGallery (doc) {
        const columnPage = doc.getElementById('columnpage');
        const favNav = columnPage?.querySelector('div[class*="favorite-nav"]');
        const mainGalleryButton = favNav?.querySelector('a[title*="submissions"]');
        if (mainGalleryButton != null && mainGalleryButton.href.includes('gallery')) {
            return true;
        }
        return false;
    }

    function isSubmissionPageInScraps (doc) {
        const columnPage = doc.getElementById('columnpage');
        const favNav = columnPage?.querySelector('div[class*="favorite-nav"]');
        const mainGalleryButton = favNav?.querySelector('a[title*="submissions"]');
        if (mainGalleryButton != null && mainGalleryButton.href.includes('scraps')) {
            return true;
        }
        return false;
    }

    function getCurrGalleryFolder () {
        const url = window.location.toString().toLowerCase();
        if (!url.includes('gallery') || !url.includes('folder')) {
            return;
        }
        const parts = url.split('/');
        const folderIdIndex = parts.indexOf('folder') + 1;
        if (folderIdIndex >= parts.length) {
            return;
        }
        const folderId = parts[folderIdIndex];
        return parseInt(folderId);
    }

    function generalizeString (inputString, textToNumbers, removeCommonPhrases, removeSpecialChars, removeNumbers, removeSpaces, removeRoman) {
        let outputString = inputString.toLowerCase();
        {
            const commonPhrases = ['page', 'part', 'book', 'episode'];
            outputString = outputString.replace(new RegExp(`(?:^|\\s)(${commonPhrases.join('|')})(?:\\s|$)`, 'g'), '');
        }
        {
            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'), '');
        }
        {
            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()].toString());
        }
        {
            outputString = outputString.replace(/[^a-zA-Z0-9 ]/g, '');
        }
        {
            outputString = outputString.replace(/[^a-zA-Z ]/g, '');
        }
        {
            outputString = outputString.replace(/\s/g, '');
        }
        return outputString;
    }

    function figureTitleIsGenerallyEqual (figure, title) {
        const figCaption = figure.querySelector('figcaption');
        const titleElem = figCaption?.querySelector('a[href*="view"]');
        if (titleElem != null) {
            const figTitle = titleElem.title.toLowerCase();
            const figTitleGeneralized = generalizeString(figTitle);
            const currTitleGeneralized = generalizeString(title);
            if (string.isNullOrWhitespace(figTitleGeneralized) || string.isNullOrWhitespace(currTitleGeneralized)) {
                return false;
            }
            return figTitleGeneralized.includes(currTitleGeneralized) || currTitleGeneralized.includes(figTitleGeneralized);
        }
        return false;
    }

    function getDocUsername (doc) {
        const columnPage = doc.getElementById('columnpage');
        const submissionIdContainer = columnPage?.querySelector('div[class*="submission-id-container"]');
        const usernameContainer = submissionIdContainer?.querySelector('a[href*="user"]');
        if (usernameContainer != null) {
            let username = usernameContainer.href;
            username = username.trimEnd('/');
            username = username.split('/').pop();
            return username;
        }
    }

    class BackwardSearch {
        currSubmissionPageNo;
        sidToIgnore = [];
        _currSid;
        _amount;
        constructor(currSid, amount, currSubmissionPageNo) {
            this._currSid = currSid;
            this._amount = amount;
            this.currSubmissionPageNo = currSubmissionPageNo;
            this.sidToIgnore.push(currSid);
        }
        async search() {
            const isInGallery = isSubmissionPageInGallery(document);
            const isInScraps = isSubmissionPageInScraps(document);
            if (!isInGallery && !isInScraps) {
                return {};
            }
            const columnpage = document.getElementById('columnpage');
            const submissionIdContainer = columnpage?.querySelector('div[class*="submission-id-container"]');
            const submissionTitle = submissionIdContainer?.querySelector('div[class*="submission-title"]');
            const currTitle = submissionTitle?.querySelector('h2')?.querySelector('p')?.textContent;
            if (string.isNullOrWhitespace(currTitle)) {
                return {};
            }
            const currUsername = getDocUsername(document);
            const folderId = getCurrGalleryFolder();
            Logger.logInfo(`${scriptName}: finding submission page...`);
            if (this.currSubmissionPageNo == null || this.currSubmissionPageNo < 1) {
                if (isInGallery) {
                    this.currSubmissionPageNo = await requestHelper.UserRequests.GalleryRequests.Gallery.getSubmissionPageNo(currUsername, this._currSid, folderId, -1, -1);
                }
                else if (isInScraps) {
                    this.currSubmissionPageNo = await requestHelper.UserRequests.GalleryRequests.Scraps.getSubmissionPageNo(currUsername, this._currSid, -1, -1);
                }
            }
            Logger.logInfo(`${scriptName}: found submission on page '${this.currSubmissionPageNo}'`);
            Logger.logInfo(`${scriptName}: searching figures backward...`);
            let figures = [];
            if (isInGallery) {
                figures = await requestHelper.UserRequests.GalleryRequests.Gallery.getFiguresInFolderBetweenPages(currUsername, folderId, this.currSubmissionPageNo, this.currSubmissionPageNo + this._amount);
            }
            else if (isInScraps) {
                figures = await requestHelper.UserRequests.GalleryRequests.Scraps.getFiguresBetweenPages(currUsername, this.currSubmissionPageNo, this.currSubmissionPageNo + this._amount);
            }
            let figuresFlattend = figures.flat();
            figuresFlattend = figuresFlattend.filter(figure => !this.sidToIgnore.includes(parseInt(figure.id.trimStart('sid-'))));
            figuresFlattend = figuresFlattend.filter(figure => figureTitleIsGenerallyEqual(figure, currTitle));
            figuresFlattend.reverse();
            Logger.logInfo(`${scriptName}: searching figures backward found '${figuresFlattend.length}' figures`);
            Logger.logInfo(`${scriptName}: loading submission pages...`);
            const result = {};
            for (let i = 0; i < figuresFlattend.length; i++) {
                const figureSid = figuresFlattend[i].id.trimStart('sid-');
                const subDoc = await requestHelper.SubmissionRequests.getSubmissionPage(parseInt(figureSid));
                const img = subDoc?.getElementById('submissionImg');
                if (img == null) {
                    continue;
                }
                img.setAttribute('wal-index', (-(figuresFlattend.length - i)).toString());
                img.setAttribute('wal-sid', figureSid);
                result[parseInt(figureSid)] = img;
                Logger.logInfo(`${scriptName}: loaded submission '${figureSid}' with index '${(-(figuresFlattend.length - i)).toString()}'`);
            }
            return result;
        }
    }

    class ForwardSearch {
        currSubmissionPageNo;
        sidToIgnore = [];
        _currSid;
        constructor(currSid, currSubmissionPageNo) {
            this._currSid = currSid;
            this.currSubmissionPageNo = currSubmissionPageNo;
            this.sidToIgnore.push(currSid);
        }
        async search() {
            const isInGallery = isSubmissionPageInGallery(document);
            const isInScraps = isSubmissionPageInScraps(document);
            if (!isInGallery && !isInScraps) {
                return {};
            }
            const columnpage = document.getElementById('columnpage');
            const submissionIdContainer = columnpage?.querySelector('div[class*="submission-id-container"]');
            const submissionTitle = submissionIdContainer?.querySelector('div[class*="submission-title"]');
            const currTitle = submissionTitle?.querySelector('h2')?.querySelector('p')?.textContent;
            if (string.isNullOrWhitespace(currTitle)) {
                return {};
            }
            const currUsername = getDocUsername(document);
            const folderId = getCurrGalleryFolder();
            Logger.logInfo(`${scriptName}: finding submission page...`);
            if (this.currSubmissionPageNo == null || this.currSubmissionPageNo < 1) {
                if (isInGallery) {
                    this.currSubmissionPageNo = await requestHelper.UserRequests.GalleryRequests.Gallery.getSubmissionPageNo(currUsername, this._currSid, folderId, -1, -1);
                }
                else if (isInScraps) {
                    this.currSubmissionPageNo = await requestHelper.UserRequests.GalleryRequests.Scraps.getSubmissionPageNo(currUsername, this._currSid, -1, -1);
                }
            }
            Logger.logInfo(`${scriptName}: found submission on page '${this.currSubmissionPageNo}'`);
            Logger.logInfo(`${scriptName}: searching figures forward...`);
            let figures = [];
            if (isInGallery) {
                figures = await requestHelper.UserRequests.GalleryRequests.Gallery.getFiguresInFolderBetweenIds(currUsername, folderId, undefined, this._currSid);
            }
            else if (isInScraps) {
                figures = await requestHelper.UserRequests.GalleryRequests.Scraps.getFiguresBetweenIds(currUsername, undefined, this._currSid);
            }
            let figuresFlattend = figures.flat();
            figuresFlattend = figuresFlattend.filter(figure => !this.sidToIgnore.includes(parseInt(figure.id.trimStart('sid-'))));
            figuresFlattend = figuresFlattend.filter(figure => figureTitleIsGenerallyEqual(figure, currTitle));
            figuresFlattend.reverse();
            Logger.logInfo(`${scriptName}: searching figures forward found '${figuresFlattend.length}' figures`);
            Logger.logInfo(`${scriptName}: loading submission pages...`);
            const result = {};
            for (let i = 0; i < figuresFlattend.length; i++) {
                const figureSid = figuresFlattend[i].id.trimStart('sid-');
                const subDoc = await requestHelper.SubmissionRequests.getSubmissionPage(parseInt(figureSid));
                const img = subDoc?.getElementById('submissionImg');
                if (img == null) {
                    continue;
                }
                img.setAttribute('wal-index', (i + 1).toString());
                img.setAttribute('wal-sid', figureSid);
                result[parseInt(figureSid)] = img;
                Logger.logInfo(`${scriptName}: loaded submission '${figureSid}' with index '${(i + 1).toString()}'`);
            }
            return result;
        }
    }

    class LightboxHTML {
        static get html() {
            return `
<div class="viewer-container viewer-backdrop viewer-fixed viewer-fade viewer-in hidden" tabindex="-1" touch-action="none"
    id="viewer0" style="z-index: 999999900;" role="dialog" aria-labelledby="viewerTitle0" aria-modal="true">
    <div class="viewer-canvas" data-viewer-action="hide">
    </div>
</div>`;
        }
    }

    function styleInject(css, ref) {
      if ( ref === void 0 ) ref = {};
      var insertAt = ref.insertAt;

      if (typeof document === 'undefined') { return; }

      var head = document.head || document.getElementsByTagName('head')[0];
      var style = document.createElement('style');
      style.type = 'text/css';

      if (insertAt === 'top') {
        if (head.firstChild) {
          head.insertBefore(style, head.firstChild);
        } else {
          head.appendChild(style);
        }
      } else {
        head.appendChild(style);
      }

      if (style.styleSheet) {
        style.styleSheet.cssText = css;
      } else {
        style.appendChild(document.createTextNode(css));
      }
    }

    var css_248z = ".wal-lightbox-nav {\n    position: fixed;\n    left: 50%;\n    bottom: 10px;\n    transform: translateX(-50%);\n    opacity: 0.7;\n    transition: opacity 0.2s linear;\n    z-index: 100000000;\n}\n\n.wal-lightbox-nav:hover {\n    opacity: 1;\n}\n\n.wal-no-select {\n    user-select: none;\n    -webkit-user-select: none;\n    -moz-user-select: none;\n    -ms-user-select: none;\n}";
    styleInject(css_248z);

    class Lightbox {
        currWalIndex = 0;
        _lightboxContainer;
        _lightboxNavContainer;
        _imgCount = -1;
        _boundHandleArrowKeys;
        constructor(orgSid, imgs) {
            this.initializeViewerCanvas();
            this._lightboxContainer = document.body.querySelector('div[class*="viewer-canvas"]');
            this._imgCount = Object.keys(imgs).length;
            const columnpage = document.getElementById('columnpage');
            const orgImg = columnpage.querySelector(`img[wal-sid="${orgSid}"]`);
            const orgImgClone = orgImg.readdToDom();
            imgs[orgSid] = orgImgClone;
            this.prepareOrgLightbox();
            this.addSubmissionToLightbox(imgs);
            if (customLightboxShowNavSetting.value) {
                this._lightboxNavContainer = this.createNavigationButtons();
                this._lightboxContainer.insertAfterThis(this._lightboxNavContainer);
            }
            this._boundHandleArrowKeys = this.handleArrowKeys.bind(this);
        }
        get isHidden() {
            return this._lightboxContainer.parentElement?.classList.contains('hidden') ?? false;
        }
        set isHidden(value) {
            if (this.isHidden === value) {
                return;
            }
            if (value) {
                window.removeEventListener('keydown', this._boundHandleArrowKeys);
                this._lightboxContainer.parentElement?.classList.add('hidden');
                this._lightboxNavContainer?.classList.add('hidden');
                for (const child of Array.from(this._lightboxContainer.children)) {
                    child.classList.add('hidden');
                }
            }
            else {
                window.addEventListener('keydown', this._boundHandleArrowKeys);
                this._lightboxContainer.children[this.currWalIndex]?.classList.remove('hidden');
                this._lightboxContainer.parentElement?.classList.remove('hidden');
                this._lightboxNavContainer?.classList.remove('hidden');
            }
        }
        navigateLeft() {
            if (this.currWalIndex > 0) {
                Logger.logInfo(`${scriptName}: navigating left '${this.currWalIndex} -> ${this.currWalIndex - 1}'`);
                const currImg = this._lightboxContainer.children[this.currWalIndex];
                const prevImg = this._lightboxContainer.children[this.currWalIndex - 1];
                if (currImg != null && prevImg != null) {
                    currImg.classList.add('hidden');
                    prevImg.classList.remove('hidden');
                }
                this.currWalIndex--;
            }
        }
        navigateRight() {
            if (this.currWalIndex + 1 < this._imgCount) {
                Logger.logInfo(`${scriptName}: navigating right '${this.currWalIndex} -> ${this.currWalIndex + 1}'`);
                const currImg = this._lightboxContainer.children[this.currWalIndex];
                const nextImg = this._lightboxContainer.children[this.currWalIndex + 1];
                if (currImg != null && nextImg != null) {
                    currImg.classList.add('hidden');
                    nextImg.classList.remove('hidden');
                }
                this.currWalIndex++;
            }
        }
        handleArrowKeys(event) {
            switch (event.key) {
                case 'ArrowLeft':
                case 'ArrowUp':
                    this.navigateLeft();
                    break;
                case 'ArrowRight':
                case 'ArrowDown':
                    this.navigateRight();
                    break;
            }
            event.preventDefault();
        }
        getIndexOfClickedImage(img) {
            let clickedWalIndex = img.getAttribute('wal-index');
            if (!string.isNullOrWhitespace(clickedWalIndex)) {
                this.currWalIndex = parseInt(clickedWalIndex);
                const clickedImg = this._lightboxContainer.querySelector(`img[wal-index="${this.currWalIndex}"]`);
                const clickedIndex = clickedImg?.getIndexOfThis();
                return clickedIndex;
            }
        }
        prepareOrgLightbox() {
            this._lightboxContainer.innerHTML = '';
            this._lightboxContainer = this._lightboxContainer.readdToDom();
            this._lightboxContainer.addEventListener('click', () => {
                this.isHidden = true;
            });
        }
        addSubmissionToLightbox(imgs) {
            // Convert record to array and sort by wal-index
            const sortedImages = Object.values(imgs)
                .sort((a, b) => {
                const indexA = parseInt(a.getAttribute('wal-index') ?? '0');
                const indexB = parseInt(b.getAttribute('wal-index') ?? '0');
                return indexA - indexB;
            });
            for (const img of sortedImages) {
                img.addEventListener('click', () => {
                    this.currWalIndex = this.getIndexOfClickedImage(img) ?? 0;
                    this.isHidden = false;
                });
                const clone = img.cloneNode(false);
                clone.classList.add('hidden');
                clone.style.height = '100%';
                clone.style.width = '100%';
                clone.style.objectFit = 'contain';
                this._lightboxContainer.appendChild(clone);
            }
        }
        createNavigationButtons() {
            const container = document.createElement('div');
            container.classList.add('wal-lightbox-nav', 'hidden', 'wal-no-select');
            const leftButton = document.createElement('a');
            leftButton.classList.add('button', 'standard', 'mobile-fix');
            leftButton.textContent = '<---';
            leftButton.style.marginRight = '4px';
            leftButton.addEventListener('click', this.navigateLeft.bind(this));
            container.appendChild(leftButton);
            const closeButton = document.createElement('a');
            closeButton.classList.add('button', 'standard', 'mobile-fix');
            closeButton.textContent = 'Close';
            closeButton.addEventListener('click', () => {
                this.isHidden = true;
            });
            container.appendChild(closeButton);
            const rightButton = document.createElement('a');
            rightButton.classList.add('button', 'standard', 'mobile-fix');
            rightButton.textContent = '--->';
            rightButton.style.marginLeft = '4px';
            rightButton.addEventListener('click', this.navigateRight.bind(this));
            container.appendChild(rightButton);
            return container;
        }
        initializeViewerCanvas() {
            const viewerCanvas = document.body.querySelector('div[class*="viewer-canvas"]');
            if (!viewerCanvas) {
                const viewerTemp = document.createElement('div');
                viewerTemp.innerHTML = LightboxHTML.html;
                const viewerContainer = viewerTemp.firstElementChild;
                document.body.appendChild(viewerContainer);
                Logger.logInfo(`${scriptName}: Created viewer canvas structure in hidden state`);
            }
        }
    }

    class AutoLoader {
        submissionImg;
        currComicNav = null;
        currSid = -1;
        _loadingSpinner;
        _comicNavExists = false;
        _searchButton;
        constructor() {
            this.currSid = getCurrViewSid(document);
            this.submissionImg = document.getElementById('submissionImg');
            this.submissionImg.setAttribute('wal-index', '0');
            this.submissionImg.setAttribute('wal-sid', this.currSid.toString());
            this._searchButton = document.createElement('a');
            this._searchButton.id = 'wal-search-button';
            this._searchButton.classList.add('wal-button', 'button', 'standard', 'mobile-fix');
            this._searchButton.type = 'button';
            this._searchButton.style.margin = '20px 0 10px 0';
            this.submissionImg.parentNode.appendChild(document.createElement('br'));
            this.submissionImg.parentNode.appendChild(this._searchButton);
            const descriptionElem = document.getElementById('columnpage')?.querySelector('div[class*="submission-description"]');
            if (descriptionElem != null) {
                this.currComicNav = ComicNavigation.fromElement(descriptionElem);
                if (this.currComicNav != null) {
                    if (this.currComicNav.prevId !== -1 || this.currComicNav.firstId !== -1 || this.currComicNav.nextId !== -1) {
                        this._comicNavExists = true;
                        if (overwriteNavButtonsSetting.value) {
                            this.overwriteNavButtons();
                        }
                    }
                }
            }
            this.updateSearchButton(this.comicNavExists);
            const loadingSpinnerContainer = document.createElement('div');
            loadingSpinnerContainer.classList.add('wal-loading-spinner');
            loadingSpinnerContainer.style.margin = '20px 0 20px 0';
            this._loadingSpinner = new window.FALoadingSpinner(loadingSpinnerContainer);
            this._loadingSpinner.delay = loadingSpinSpeedSetting.value;
            this._loadingSpinner.spinnerThickness = 6;
            this.submissionImg.parentNode.appendChild(loadingSpinnerContainer);
        }
        get comicNavExists() {
            return this._comicNavExists;
        }
        set comicNavExists(value) {
            if (value === this.comicNavExists) {
                return;
            }
            this._comicNavExists = value;
            this.updateSearchButton(value);
        }
        startAutoloader() {
            void this.startAutoLoaderAsync();
        }
        async startAutoLoaderAsync() {
            this._loadingSpinner.visible = true;
            const autoLoader = new AutoLoaderSearch(this.submissionImg, this.currSid, this.currComicNav);
            const submissions = await autoLoader.search();
            const submissionIds = Object.keys(submissions).map(Number);
            if (submissionIds.length === 0 || (submissionIds.length === 1 && submissionIds[0] === this.currSid)) {
                this.comicNavExists = false;
            }
            else {
                this.addLoadedSubmissions(submissions);
                if (useCustomLightboxSetting.value) {
                    new Lightbox(this.currSid, submissions);
                }
            }
            this._loadingSpinner.visible = false;
        }
        startSimilarSearch() {
            void this.startSimilarSearchAsync();
        }
        async startSimilarSearchAsync() {
            this._loadingSpinner.visible = true;
            const forwardSearch = new ForwardSearch(this.currSid);
            const submissionsAfter = await forwardSearch.search();
            const backwardSearch = new BackwardSearch(this.currSid, backwardSearchSetting.value, forwardSearch.currSubmissionPageNo);
            backwardSearch.sidToIgnore.push(...Object.keys(submissionsAfter).map(Number));
            const submissionsBefore = await backwardSearch.search();
            this.addLoadedSubmissions(submissionsBefore, submissionsAfter);
            if (useCustomLightboxSetting.value) {
                new Lightbox(this.currSid, { ...submissionsBefore, ...submissionsAfter });
            }
            this._loadingSpinner.visible = false;
        }
        addLoadedSubmissions(...imgsArr) {
            const columnpage = document.getElementById('columnpage');
            for (const imgs of imgsArr) {
                Logger.logInfo(`${scriptName}: adding '${Object.keys(imgs).length}' submissions...`);
                let prevSid = this.currSid;
                for (const sid of Object.keys(imgs).map(Number)) {
                    if (imgs[sid].getAttribute('wal-sid') === this.currSid.toString()) {
                        continue;
                    }
                    const lastImg = columnpage.querySelector(`img[wal-sid="${prevSid}"]`);
                    const lastIndex = parseInt(lastImg.getAttribute('wal-index'));
                    const currIndex = parseInt(imgs[sid].getAttribute('wal-index'));
                    if (currIndex < lastIndex) {
                        lastImg.insertBeforeThis(imgs[sid]);
                        imgs[sid].insertAfterThis(document.createElement('br'));
                        imgs[sid].insertAfterThis(document.createElement('br'));
                        checkTags(imgs[sid]);
                        Logger.logInfo(`${scriptName}: added submission ${sid} before submission ${prevSid}`);
                    }
                    else {
                        lastImg.insertAfterThis(imgs[sid]);
                        imgs[sid].insertBeforeThis(document.createElement('br'));
                        imgs[sid].insertBeforeThis(document.createElement('br'));
                        checkTags(imgs[sid]);
                        Logger.logInfo(`${scriptName}: added submission ${sid} after submission ${prevSid}`);
                    }
                    prevSid = sid;
                }
            }
        }
        overwriteNavButtons() {
            if (!this.comicNavExists) {
                return;
            }
            const columnpage = document.getElementById('columnpage');
            const favoriteNav = columnpage?.querySelector('div[class*="favorite-nav"]');
            let prevButton = favoriteNav?.children[0];
            if (prevButton != null && this.currComicNav.prevId !== -1) {
                if (prevButton.textContent?.toLowerCase()?.includes('prev') ?? false) {
                    prevButton.href = `/view/${this.currComicNav.prevId}/`;
                }
                else {
                    const prevButtonReal = document.createElement('a');
                    prevButtonReal.href = `/view/${this.currComicNav.prevId}/`;
                    prevButtonReal.classList.add('button', 'standard', 'mobile-fix');
                    prevButtonReal.textContent = 'Prev';
                    prevButtonReal.style.marginRight = '4px';
                    prevButton.insertBeforeThis(prevButtonReal);
                }
            }
            let nextButton = favoriteNav?.children[favoriteNav.children.length - 1];
            if (nextButton != null && this.currComicNav.nextId !== -1) {
                if (nextButton.textContent?.toLowerCase()?.includes('next') ?? false) {
                    nextButton.href = `/view/${this.currComicNav.nextId}/`;
                }
                else {
                    const nextButtonReal = document.createElement('a');
                    nextButtonReal.href = `/view/${this.currComicNav.nextId}/`;
                    nextButtonReal.classList.add('button', 'standard', 'mobile-fix');
                    nextButtonReal.textContent = 'Next';
                    nextButtonReal.style.marginLeft = '4px';
                    nextButton.insertAfterThis(nextButtonReal);
                }
            }
        }
        updateSearchButton(showAutoLoader) {
            this._searchButton.style.display = 'inline-block';
            this._searchButton.textContent = showAutoLoader ? 'Auto load Pages' : 'Search for similar Pages';
            if (showAutoLoader) {
                this._searchButton.onclick = () => {
                    this.startAutoloader();
                    this._searchButton.style.display = 'none';
                };
            }
            else {
                this._searchButton.onclick = () => {
                    this.startSimilarSearch();
                    this._searchButton.style.display = 'none';
                };
            }
        }
    }

    const scriptName = 'FA Webcomic Auto Loader';
    const customSettings = new window.FACustomSettings('Furaffinity Features Settings', `${scriptName} Settings`);
    const showSearchButtonSetting = customSettings.newSetting(window.FASettingType.Boolean, 'Similar Search Button');
    showSearchButtonSetting.description = 'Sets wether the search for similar Pages button is show.';
    showSearchButtonSetting.defaultValue = true;
    const loadingSpinSpeedSetting = customSettings.newSetting(window.FASettingType.Number, 'Loading Animation');
    loadingSpinSpeedSetting.description = 'Sets the duration that the loading animation takes for a full rotation in milliseconds.';
    loadingSpinSpeedSetting.defaultValue = 1000;
    const backwardSearchSetting = customSettings.newSetting(window.FASettingType.Number, 'Backward Search Amount');
    backwardSearchSetting.description = 'Sets the amount of similar pages to search backward. (More Pages take longer)';
    backwardSearchSetting.defaultValue = 3;
    const overwriteNavButtonsSetting = customSettings.newSetting(window.FASettingType.Boolean, 'Overwrite Nav Buttons');
    overwriteNavButtonsSetting.description = 'Sets wether the default Navigation Buttons (next/prev) are overwritten by the Auto-Loader. (Works only if comic navigation is present)';
    overwriteNavButtonsSetting.defaultValue = true;
    const useCustomLightboxSetting = customSettings.newSetting(window.FASettingType.Boolean, 'Use Custom Lightbox');
    useCustomLightboxSetting.description = 'Sets wether the default Lightbox (fullscreen view) is overwritten by the Auto-Loader.';
    useCustomLightboxSetting.defaultValue = true;
    const customLightboxShowNavSetting = customSettings.newSetting(window.FASettingType.Boolean, 'Custom Lightbox Show Nav');
    customLightboxShowNavSetting.description = 'Sets wether the Lightbox Navigation (next/prev) is shown in the Custom Lightbox.';
    customLightboxShowNavSetting.defaultValue = true;
    customSettings.loadSettings();
    const requestHelper = new window.FARequestHelper(2);
    if (customSettings.isFeatureEnabled) {
        const matchList = new window.FAMatchList(customSettings);
        matchList.matches = ['net/view'];
        matchList.runInIFrame = false;
        if (matchList.hasMatch) {
            new AutoLoader();
        }
    }

    exports.backwardSearchSetting = backwardSearchSetting;
    exports.customLightboxShowNavSetting = customLightboxShowNavSetting;
    exports.loadingSpinSpeedSetting = loadingSpinSpeedSetting;
    exports.overwriteNavButtonsSetting = overwriteNavButtonsSetting;
    exports.requestHelper = requestHelper;
    exports.scriptName = scriptName;
    exports.showSearchButtonSetting = showSearchButtonSetting;
    exports.useCustomLightboxSetting = useCustomLightboxSetting;

    return exports;

})({});