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;

})({});