FA Infini-Gallery

Automatically loads the next page of the gallery as you reach the bottom

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name        FA Infini-Gallery
// @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/528997-furaffinity-message-box/code/528997-furaffinity-message-box.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.13
// @author      Midori Dragon
// @description Automatically loads the next page of the gallery as you reach the bottom
// @icon        https://raw.githubusercontent.com/Midori-Dragon/furaffinity-features/refs/heads/main/assets/icons/fa_logo.svg
// @license     MIT
// @homepageURL https://greasyfork.org/scripts/462632-fa-infini-gallery
// @supportURL  https://greasyfork.org/scripts/462632-fa-infini-gallery/feedback
// ==/UserScript==
// jshint esversion: 11
(function (exports) {
    'use strict';

    function createSeparatorElem(pageNo) {
        // Create the main container for the separator
        const nextPageDescContainer = document.createElement('div');
        nextPageDescContainer.className = 'folder-description';
        nextPageDescContainer.style.marginTop = '6px';
        nextPageDescContainer.style.marginBottom = '6px';
        // Create the inner container for the page description
        const nextPageDesc = document.createElement('div');
        nextPageDesc.className = 'container-item-top';
        // Create the text element for the page number
        const nextPageDescText = document.createElement('h3');
        const regex = /%page%/g;
        const pageString = pageSeparatorTextSetting.value.replace(regex, pageNo.toString());
        nextPageDescText.textContent = pageString;
        // Append text element to the inner container
        nextPageDesc.appendChild(nextPageDescText);
        // Append inner container to the main container
        nextPageDescContainer.appendChild(nextPageDesc);
        // Return the complete separator element
        return nextPageDescContainer;
    }
    function getFiguresFromPage(page) {
        const figures = page.querySelectorAll('figure[class*="t"]');
        return figures == null ? [] : Array.from(figures).map(figure => figure);
    }
    function getUserNameFromUrl(url) {
        if (url.includes('?')) {
            url = url.substring(0, url.indexOf('?'));
        }
        url = url.trimEnd('/');
        if (url.includes('/folder/')) {
            url = url.substring(0, url.indexOf('/folder/'));
        }
        return url.substring(url.lastIndexOf('/') + 1);
    }
    function isElementOnScreen(element) {
        // Get the bounding rectangle of the element
        const rect = element.getBoundingClientRect();
        // Calculate the window height, considering both window and document height
        const windowHeight = (window.innerHeight || document.documentElement.clientHeight) * 2;
        // Check if the element is within the visible area of the screen
        return (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
    }
    function getFolderIdFromUrl(url) {
        const match = url.match(/\/folder\/(\d+)(?=\/|$)/);
        return match ? match[1] : null;
    }
    function getFolderNameFromUrl(url) {
        const match = url.match(/\/folder\/\d+\/([^/?]+)(?=\/|$)/);
        return match ? match[1] : null;
    }
    function anyGalleryExistsOnDOM() {
        const galleries = document.body.querySelectorAll('section[id*="gallery"]');
        const watchesGalleries = galleries.length === 0 ? document.body.querySelectorAll('div[class*="flex-watchlist"]') : [];
        return galleries.length !== 0 || watchesGalleries.length !== 0;
    }

    var LogLevel;
    (function (LogLevel) {
        LogLevel[LogLevel["Error"] = 1] = "Error";
        LogLevel[LogLevel["Warning"] = 2] = "Warning";
        LogLevel[LogLevel["Info"] = 3] = "Info";
    })(LogLevel || (LogLevel = {}));
    class Logger {
        static get _logLevel() {
            window.__FF_GLOBAL_LOG_LEVEL__ ??= LogLevel.Error;
            return window.__FF_GLOBAL_LOG_LEVEL__;
        }
        static setLogLevel(logLevel) {
            window.__FF_GLOBAL_LOG_LEVEL__ = logLevel;
        }
        static get logError() {
            return LogLevel.Error <= Logger._logLevel ? console.error.bind(console) : () => { };
        }
        static get logWarning() {
            return LogLevel.Warning <= Logger._logLevel ? console.warn.bind(console) : () => { };
        }
        static get logInfo() {
            return LogLevel.Info <= Logger._logLevel ? console.log.bind(console) : () => { };
        }
    }

    class BrowsePage {
        pageNo;
        gallery;
        constructor(pageNo) {
            this.pageNo = pageNo;
            this.gallery = document.querySelector('section[id*="gallery"]');
        }
        async getPage() {
            Logger.logInfo(`Getting page BrowsePage '${this.pageNo}'`);
            const page = await requestHelper.UserRequests.SearchRequests.Browse.getPage(this.pageNo, this.getBrowseOptions());
            return page;
        }
        getBrowseOptions() {
            const currBrowseOptions = requestHelper.UserRequests.SearchRequests.Browse.newBrowseOptions;
            const sideBar = document.getElementById('sidebar-options');
            // Get the option containers
            const optionContainers = sideBar?.querySelectorAll('div[class*="browse-search-flex-item"]');
            for (const optionContainer of Array.from(optionContainers ?? [])) {
                try {
                    // Get the name of the option from the strong element
                    let optionName = optionContainer?.querySelector('strong')?.textContent?.toLowerCase() ?? '';
                    optionName = optionName.trimEnd(':');
                    // Get the value of the option from the selected option element
                    const optionValue = optionContainer?.querySelector('option[selected]')?.getAttribute('value');
                    // Set the option in the browse options object
                    if (optionValue == null) {
                        continue;
                    }
                    switch (optionName) {
                        case 'category':
                            currBrowseOptions.category = parseInt(optionValue);
                            break;
                        case 'type':
                            currBrowseOptions.type = parseInt(optionValue);
                            break;
                        case 'species':
                            currBrowseOptions.species = parseInt(optionValue);
                            break;
                        case 'gender':
                            currBrowseOptions.gender = optionValue;
                            break;
                        case 'results':
                            currBrowseOptions.perPage = parseInt(optionValue);
                            break;
                        case 'ratingGeneral':
                            currBrowseOptions.ratingGeneral = optionValue === 'true';
                            break;
                        case 'ratingMature':
                            currBrowseOptions.ratingMature = optionValue === 'true';
                            break;
                        case 'ratingAdult':
                            currBrowseOptions.ratingAdult = optionValue === 'true';
                            break;
                    }
                }
                catch { }
            }
            // Get the checkbox elements
            const checkBoxes = sideBar?.querySelectorAll('input[type="checkbox"]');
            for (const checkbox of Array.from(checkBoxes ?? [])) {
                // Set the option in the browse options object
                switch (checkbox.getAttribute('name')) {
                    case 'rating_general':
                        currBrowseOptions.ratingGeneral = checkbox.hasAttribute('checked');
                        break;
                    case 'rating_mature':
                        currBrowseOptions.ratingMature = checkbox.hasAttribute('checked');
                        break;
                    case 'rating_adult':
                        currBrowseOptions.ratingAdult = checkbox.hasAttribute('checked');
                        break;
                }
            }
            return currBrowseOptions;
        }
        async loadPage(prevFigures) {
            const page = await this.getPage();
            if (page == null) {
                throw new Error('No page found');
            }
            prevFigures ??= [];
            const prevSids = prevFigures.map(figure => figure.id);
            let figures = getFiguresFromPage(page);
            figures = figures.filter(figure => !prevSids.includes(figure.id));
            if (figures.length !== 0) {
                // Check if we should show a page separator
                if (showPageSeparatorSetting.value) {
                    const separator = createSeparatorElem(this.pageNo);
                    this.gallery.appendChild(separator);
                }
                // Add the figures to the gallery
                for (const figure of figures) {
                    this.gallery.appendChild(figure);
                }
            }
            else {
                throw new Error('No figures found');
            }
            window.dispatchEvent(new CustomEvent('ei-update-embedded')); //Embedded Image Viewer Integration
            return figures;
        }
    }

    class FavoritesPage {
        dataFavId;
        pageNo;
        gallery;
        constructor(dataFavId, pageNo) {
            this.dataFavId = dataFavId;
            this.pageNo = pageNo;
            this.gallery = document.querySelector('section[id*="gallery"]');
        }
        async getPage() {
            Logger.logInfo(`Getting page FavoritesPage '${this.pageNo}'`);
            const username = getUserNameFromUrl(window.location.toString());
            const page = await requestHelper.UserRequests.GalleryRequests.Favorites.getPage(username, this.dataFavId);
            return page;
        }
        async loadPage(prevFigures) {
            const page = await this.getPage();
            if (page == null) {
                throw new Error('No page found');
            }
            prevFigures ??= [];
            const prevSids = prevFigures.map(figure => figure.id);
            let figures = getFiguresFromPage(page);
            figures = figures.filter(figure => !prevSids.includes(figure.id));
            if (figures.length !== 0) {
                // Check if on last page
                if (this.dataFavId === figures[figures.length - 1].getAttribute('data-fav-id')) {
                    throw new Error('Last page reached');
                }
                // Check if we should show a page separator
                if (showPageSeparatorSetting.value) {
                    const separator = createSeparatorElem(this.pageNo);
                    this.gallery.appendChild(separator);
                }
                // Add the figures to the gallery
                for (const figure of figures) {
                    this.gallery.appendChild(figure);
                }
            }
            else {
                throw new Error('No figures found');
            }
            window.dispatchEvent(new CustomEvent('ei-update-embedded')); //Embedded Image Viewer Integration
            return figures;
        }
    }

    class GalleryPage {
        pageNo;
        gallery;
        isInFolder;
        constructor(pageNo) {
            this.pageNo = pageNo;
            this.gallery = document.querySelector('section[id*="gallery"]');
            this.isInFolder = window.location.toString().includes('/folder/');
        }
        async getPage() {
            Logger.logInfo(`Getting page GalleryPage '${this.pageNo}'`);
            const username = getUserNameFromUrl(window.location.toString());
            let page;
            if (this.isInFolder === true) {
                const folderIdStr = getFolderIdFromUrl(window.location.toString());
                const folder = folderIdStr != null ? { id: parseInt(folderIdStr), name: getFolderNameFromUrl(window.location.toString()) ?? undefined } : undefined;
                page = await requestHelper.UserRequests.GalleryRequests.Gallery.getPageInFolder(username, folder, this.pageNo);
            }
            else {
                page = await requestHelper.UserRequests.GalleryRequests.Gallery.getPage(username, this.pageNo);
            }
            return page;
        }
        async loadPage(prevFigures) {
            const page = await this.getPage();
            if (page == null) {
                throw new Error('No page found');
            }
            prevFigures ??= [];
            const prevSids = prevFigures.map(figure => figure.id);
            let figures = getFiguresFromPage(page);
            figures = figures.filter(figure => !prevSids.includes(figure.id));
            if (figures.length !== 0) {
                // Check if we should show a page separator
                if (showPageSeparatorSetting.value) {
                    const separator = createSeparatorElem(this.pageNo);
                    this.gallery.appendChild(separator);
                }
                // Add the figures to the gallery
                for (const figure of figures) {
                    this.gallery.appendChild(figure);
                }
            }
            else {
                throw new Error('No figures found');
            }
            window.dispatchEvent(new CustomEvent('ei-update-embedded')); //Embedded Image Viewer Integration
            return figures;
        }
    }

    class ScrapsPage {
        pageNo;
        gallery;
        constructor(pageNo) {
            this.pageNo = pageNo;
            this.gallery = document.querySelector('section[id*="gallery"]');
        }
        async getPage() {
            Logger.logInfo(`Getting page ScrapsPage '${this.pageNo}'`);
            const username = getUserNameFromUrl(window.location.toString());
            const page = await requestHelper.UserRequests.GalleryRequests.Scraps.getPage(username, this.pageNo);
            return page;
        }
        async loadPage(prevFigures) {
            const page = await this.getPage();
            if (page == null) {
                throw new Error('No page found');
            }
            prevFigures ??= [];
            const prevSids = prevFigures.map(figure => figure.id);
            let figures = getFiguresFromPage(page);
            figures = figures.filter(figure => !prevSids.includes(figure.id));
            if (figures.length !== 0) {
                // Check if we should show a page separator
                if (showPageSeparatorSetting.value) {
                    const separator = createSeparatorElem(this.pageNo);
                    this.gallery.appendChild(separator);
                }
                // Add the figures to the gallery
                for (const figure of figures) {
                    this.gallery.appendChild(figure);
                }
            }
            else {
                throw new Error('No figures found');
            }
            window.dispatchEvent(new CustomEvent('ei-update-embedded')); //Embedded Image Viewer Integration
            return figures;
        }
    }

    class SearchPage {
        pageNo;
        gallery;
        constructor(pageNo) {
            this.pageNo = pageNo;
            this.gallery = document.querySelector('section[id*="gallery"]');
        }
        async getPage() {
            Logger.logInfo(`Getting page SearchPage '${this.pageNo}'`);
            const page = await requestHelper.UserRequests.SearchRequests.Search.getPage(this.pageNo, this.getSearchOptionsNew());
            return page;
        }
        getSearchOptionsNew() {
            const searchOptions = requestHelper.UserRequests.SearchRequests.Search.newSearchOptions;
            const sideBarOptions = document.getElementById('sidebar-options');
            if (sideBarOptions == null) {
                return searchOptions;
            }
            const searchInput = sideBarOptions.querySelector('textarea[class*="search-query"]');
            if (searchInput != null && searchInput instanceof HTMLTextAreaElement) {
                searchOptions.input = searchInput.value;
            }
            const searchContainer = document.getElementById('search-advanced');
            if (searchContainer == null) {
                return searchOptions;
            }
            // Get the option values
            const options = searchContainer.querySelectorAll('option[selected]');
            for (const option of Array.from(options)) {
                let name = option.parentElement?.getAttribute('name');
                name ??= option.parentElement?.parentElement?.getAttribute('name');
                const value = option.getAttribute('value');
                if (value == null) {
                    continue;
                }
                switch (name) {
                    case 'order-by':
                        searchOptions.orderBy = value;
                        break;
                    case 'order-direction':
                        searchOptions.orderDirection = value;
                        break;
                    case 'perpage':
                        searchOptions.perPage = parseInt(value);
                        break;
                    case 'category':
                        searchOptions.category = parseInt(value);
                        break;
                    case 'arttype':
                        searchOptions.type = parseInt(value);
                        break;
                    case 'species':
                        searchOptions.species = parseInt(value);
                        break;
                }
            }
            // Get the radio button values
            const radioButtons = searchContainer.querySelectorAll('input[type="radio"][checked]');
            for (const radioButton of Array.from(radioButtons)) {
                const name = radioButton.getAttribute('name');
                const value = radioButton.getAttribute('value');
                switch (name) {
                    case 'range':
                        searchOptions.range = value ?? undefined;
                        if (value === 'manual') {
                            const rangeContainer = searchContainer.querySelector('div[class*="jsManualRangeContainer"]');
                            const rangeFrom = rangeContainer?.querySelector('input[type="date"][name="range_from"]');
                            searchOptions.rangeFrom = rangeFrom?.getAttribute('value') ?? undefined;
                            const rangeTo = rangeContainer?.querySelector('input[type="date"][name="range_to"]');
                            searchOptions.rangeTo = rangeTo?.getAttribute('value') ?? undefined;
                        }
                        break;
                    case 'mode':
                        searchOptions.matching = value ?? undefined;
                        break;
                }
            }
            // Get the checkbox values
            const checkBoxes = searchContainer?.querySelectorAll('input[type="checkbox"]');
            for (const checkBox of Array.from(checkBoxes ?? [])) {
                switch (checkBox.getAttribute('name')) {
                    case 'rating-general':
                        searchOptions.ratingGeneral = checkBox.hasAttribute('checked');
                        break;
                    case 'rating-mature':
                        searchOptions.ratingMature = checkBox.hasAttribute('checked');
                        break;
                    case 'rating-adult':
                        searchOptions.ratingAdult = checkBox.hasAttribute('checked');
                        break;
                    case 'type-art':
                        searchOptions.typeArt = checkBox.hasAttribute('checked');
                        break;
                    case 'type-music':
                        searchOptions.typeMusic = checkBox.hasAttribute('checked');
                        break;
                    case 'type-flash':
                        searchOptions.typeFlash = checkBox.hasAttribute('checked');
                        break;
                    case 'type-story':
                        searchOptions.typeStory = checkBox.hasAttribute('checked');
                        break;
                    case 'type-photo':
                        searchOptions.typePhotos = checkBox.hasAttribute('checked');
                        break;
                    case 'type-poetry':
                        searchOptions.typePoetry = checkBox.hasAttribute('checked');
                        break;
                }
            }
            return searchOptions;
        }
        getSearchOptions() {
            const searchOptions = requestHelper.UserRequests.SearchRequests.Search.newSearchOptions;
            // Get the input value
            const input = document.getElementById('q');
            searchOptions.input = input?.getAttribute('value') ?? '';
            // Get the selected options
            const searchContainer = document.getElementById('search-advanced');
            const options = searchContainer?.querySelectorAll('option[selected]');
            for (const option of Array.from(options ?? [])) {
                const name = option.parentNode.getAttribute('name');
                const value = option.getAttribute('value');
                switch (name) {
                    case 'order-by':
                        searchOptions.orderBy = value ?? undefined;
                        break;
                    case 'order-direction':
                        searchOptions.orderDirection = value ?? undefined;
                        break;
                }
            }
            // Get the selected radio buttons
            const radioButtons = searchContainer?.querySelectorAll('input[type="radio"][checked]');
            for (const radioButton of Array.from(radioButtons ?? [])) {
                const name = radioButton.getAttribute('name');
                const value = radioButton.getAttribute('value');
                switch (name) {
                    case 'range':
                        searchOptions.range = value ?? undefined;
                        break;
                    case 'mode':
                        searchOptions.matching = value ?? undefined;
                        break;
                }
                if (value === 'manual') {
                    // Get the range values
                    const rangeFrom = searchContainer?.querySelector('input[type="date"][name="range_from"]');
                    searchOptions.rangeFrom = rangeFrom?.getAttribute('value') ?? undefined;
                    const rangeTo = searchContainer?.querySelector('input[type="date"][name="range_to"]');
                    searchOptions.rangeTo = rangeTo?.getAttribute('value') ?? undefined;
                }
            }
            // Get the selected checkboxes
            const checkBoxes = searchContainer?.querySelectorAll('input[type="checkbox"]');
            for (const checkBox of Array.from(checkBoxes ?? [])) {
                switch (checkBox.getAttribute('name')) {
                    case 'rating-general':
                        searchOptions.ratingGeneral = checkBox.hasAttribute('checked');
                        break;
                    case 'rating-mature':
                        searchOptions.ratingMature = checkBox.hasAttribute('checked');
                        break;
                    case 'rating-adult':
                        searchOptions.ratingAdult = checkBox.hasAttribute('checked');
                        break;
                    case 'type-art':
                        searchOptions.typeArt = checkBox.hasAttribute('checked');
                        break;
                    case 'type-music':
                        searchOptions.typeMusic = checkBox.hasAttribute('checked');
                        break;
                    case 'type-flash':
                        searchOptions.typeFlash = checkBox.hasAttribute('checked');
                        break;
                    case 'type-story':
                        searchOptions.typeStory = checkBox.hasAttribute('checked');
                        break;
                    case 'type-photo':
                        searchOptions.typePhotos = checkBox.hasAttribute('checked');
                        break;
                    case 'type-poetry':
                        searchOptions.typePoetry = checkBox.hasAttribute('checked');
                        break;
                }
            }
            return searchOptions;
        }
        async loadPage(prevFigures) {
            const page = await this.getPage();
            if (page == null) {
                throw new Error('No page found');
            }
            prevFigures ??= [];
            const prevSids = prevFigures.map(figure => figure.id);
            let figures = getFiguresFromPage(page);
            figures = figures.filter(figure => !prevSids.includes(figure.id));
            if (figures.length !== 0) {
                // Check if we should show a page separator
                if (showPageSeparatorSetting.value) {
                    const separator = createSeparatorElem(this.pageNo);
                    this.gallery.appendChild(separator);
                }
                // Add the figures to the gallery
                for (const figure of figures) {
                    this.gallery.appendChild(figure);
                }
            }
            else {
                throw new Error('No figures found');
            }
            window.dispatchEvent(new CustomEvent('ei-update-embedded')); //Embedded Image Viewer Integration
            return figures;
        }
    }

    function getWatchesFromPage(page) {
        try {
            const watchList = [];
            const pageColumnPage = page.getElementById('columnpage');
            const pageSectionBody = pageColumnPage.querySelector('div[class="section-body"]');
            const pageWatches = pageSectionBody.querySelector('div[class="flex-watchlist"]');
            const watches = pageWatches.querySelectorAll('div[class="flex-item-watchlist aligncenter"]');
            for (const watch of Array.from(watches)) {
                watchList.push(watch);
            }
            return watchList;
        }
        catch (error) {
            Logger.logError('Failed to parse watches from page:', error);
            return [];
        }
    }

    class WatchesPage {
        pageNo;
        gallery;
        constructor(pageNo) {
            this.pageNo = pageNo;
            const columnpage = document.getElementById('columnpage');
            this.gallery = columnpage.querySelector('div[class="section-body"]');
            this.gallery.style.display = 'flex';
            this.gallery.style.flexWrap = 'wrap';
        }
        async getPage() {
            Logger.logInfo(`Getting page WatchesPage '${this.pageNo}'`);
            const page = await requestHelper.PersonalUserRequests.ManageContent.getWatchesPage(this.pageNo);
            return page;
        }
        async loadPage(prevWatches) {
            const page = await this.getPage();
            if (page == null) {
                Logger.logError('No page found');
                throw new Error('No page found');
            }
            prevWatches ??= [];
            const prevHrefs = prevWatches.map(watch => watch.querySelector('a[href]')?.href);
            let watches = getWatchesFromPage(page);
            watches = watches.filter(watch => !prevHrefs.includes(watch.querySelector('a[href]')?.href));
            if (watches.length !== 0) {
                // Check if we should show a page separator
                if (showPageSeparatorSetting.value) {
                    const separator = createSeparatorElem(this.pageNo);
                    separator.style.width = 'fit-content';
                    separator.style.margin = '14px auto';
                    this.gallery.appendChild(document.createElement('br'));
                    this.gallery.appendChild(separator);
                    this.gallery.appendChild(document.createElement('br'));
                }
                // Add the watches to the gallery
                const watchesContainer = document.createElement('div');
                watchesContainer.className = 'flex-watchlist';
                this.gallery.appendChild(watchesContainer);
                watchesContainer.append(...watches);
            }
            else {
                Logger.logError('No watches found');
                throw new Error('No watches found');
            }
            return watches;
        }
    }

    class GalleryManager {
        pageNo = 1;
        prevFigures = [];
        currDataFavId = '';
        isGallery;
        isFavorites;
        isScraps;
        isBrowse;
        isSearch;
        isWatches;
        constructor() {
            this.isGallery = window.location.toString().toLowerCase().includes('net/gallery');
            this.isFavorites = window.location.toString().toLowerCase().includes('net/favorites');
            this.isScraps = window.location.toString().toLowerCase().includes('net/scraps');
            this.isBrowse = window.location.toString().toLowerCase().includes('net/browse');
            if (this.isBrowse) {
                const pageOption = document.getElementById('manual-page');
                if (pageOption instanceof HTMLInputElement) {
                    this.pageNo = parseInt(pageOption.value);
                }
            }
            this.isSearch = window.location.toString().toLowerCase().includes('net/search');
            if (this.isSearch) {
                const searchAdvanced = document.getElementById('search-advanced');
                const pageStartInput = searchAdvanced?.querySelector('input[class*="js-pageNumInput"]');
                if (pageStartInput != null && pageStartInput instanceof HTMLInputElement) {
                    this.pageNo = parseInt(pageStartInput.value);
                }
            }
            this.isWatches = window.location.toString().toLowerCase().includes('net/controls/buddylist');
            if (this.isWatches) {
                const columnpage = document.getElementById('columnpage');
                const gallery = columnpage?.querySelector('div[class="section-body"]');
                const paginationLinks = gallery?.querySelector('div[class*="pagination-links"]');
                if (paginationLinks != null) {
                    const paginationLinksElem = paginationLinks;
                    paginationLinksElem.style.display = 'none';
                    paginationLinksElem.insertBeforeThis(document.createElement('br'));
                }
            }
        }
        async loadNextPage() {
            this.pageNo++;
            if (this.isFavorites) {
                const gallery = document.body.querySelector('section[id*="gallery"]');
                const figures = gallery?.getElementsByTagName('figure');
                if (figures != null && figures.length !== 0) {
                    const lastFigureFavId = figures[figures.length - 1].getAttribute('data-fav-id');
                    if (lastFigureFavId != null) {
                        this.currDataFavId = lastFigureFavId;
                    }
                }
            }
            let nextPage;
            if (this.isGallery) {
                nextPage = new GalleryPage(this.pageNo);
            }
            else if (this.isFavorites) {
                nextPage = new FavoritesPage(this.currDataFavId, this.pageNo);
            }
            else if (this.isScraps) {
                nextPage = new ScrapsPage(this.pageNo);
            }
            else if (this.isBrowse) {
                nextPage = new BrowsePage(this.pageNo);
            }
            else if (this.isSearch) {
                nextPage = new SearchPage(this.pageNo);
            }
            else if (this.isWatches) {
                nextPage = new WatchesPage(this.pageNo);
            }
            if (nextPage != null) {
                const spacer = document.createElement('div');
                spacer.style.height = '20px';
                nextPage.gallery.appendChild(spacer);
                const loadingSpinner = new window.FALoadingSpinner(nextPage.gallery);
                loadingSpinner.spinnerThickness = 5;
                loadingSpinner.size = 50;
                loadingSpinner.visible = true;
                try {
                    this.prevFigures = await nextPage.loadPage(this.prevFigures);
                }
                finally {
                    loadingSpinner.visible = false;
                    loadingSpinner.dispose();
                    nextPage.gallery.removeChild(spacer);
                }
            }
        }
    }

    async function showError(error, caption) {
        const message = error instanceof Error ? error.message : String(error);
        await window.FAMessageBox.show(message, caption, window.FAMessageBoxButtons.OK, window.FAMessageBoxIcon.Error);
    }

    class InfiniGallery {
        scanElem;
        galleryManager;
        scanInterval = -1;
        catchErrors = [
            'No figures found',
            'No watches found',
            'Last page reached'
        ];
        constructor() {
            this.scanElem = document.getElementById('footer');
            this.galleryManager = new GalleryManager();
            window.addEventListener('ig-stop-detection', () => {
                this.stopScrollDetection();
            });
        }
        startScrollDetection() {
            this.scanInterval = setInterval(() => {
                // Check if the scan element is visible on the screen
                if (isElementOnScreen(this.scanElem)) {
                    // Stop scroll detection and load the next page
                    this.stopScrollDetection();
                    void this.loadNextPage();
                }
            }, 100);
        }
        stopScrollDetection() {
            clearInterval(this.scanInterval);
        }
        async loadNextPage() {
            try {
                await this.galleryManager.loadNextPage();
                this.startScrollDetection();
            }
            catch (error) {
                this.stopScrollDetection();
                const isEndOfGallery = error instanceof Error && this.catchErrors.includes(error.message);
                if (!isEndOfGallery) {
                    Logger.logError('Error loading next page:', error);
                    await showError(error, scriptName);
                }
            }
        }
    }

    const scriptName = 'FA Infini-Gallery';
    const customSettings = new window.FACustomSettings('Furaffinity Features Settings', `${scriptName} Settings`);
    const showPageSeparatorSetting = customSettings.newSetting(window.FASettingType.Boolean, 'Page Separator');
    showPageSeparatorSetting.description = 'Set wether a Page Separator is shown for each new Page loaded. Default: Show Page Separators';
    showPageSeparatorSetting.defaultValue = true;
    const pageSeparatorTextSetting = customSettings.newSetting(window.FASettingType.Text, 'Page Separator Text');
    pageSeparatorTextSetting.description = 'The Text that is displayed when a new Infini-Gallery Page is loaded (if shown). Number of Page gets inserted instead of: %page% .';
    pageSeparatorTextSetting.defaultValue = 'Infini-Gallery Page: %page%';
    pageSeparatorTextSetting.verifyRegex = /%page%/;
    customSettings.loadSettings();
    const requestHelper = new window.FARequestHelper(2);
    if (customSettings.isFeatureEnabled) {
        const matchList = new window.FAMatchList(customSettings);
        matchList.matches = ['net/gallery', 'net/favorites', 'net/scraps', 'net/browse', 'net/search', 'net/controls/buddylist'];
        matchList.runInIFrame = false;
        if (matchList.hasMatch && anyGalleryExistsOnDOM()) {
            const infiniGallery = new InfiniGallery();
            infiniGallery.startScrollDetection();
        }
    }

    exports.pageSeparatorTextSetting = pageSeparatorTextSetting;
    exports.requestHelper = requestHelper;
    exports.scriptName = scriptName;
    exports.showPageSeparatorSetting = showPageSeparatorSetting;

    return exports;

})({});