FA Infini-Gallery

Makes so that the gallery continues loading the next page when you reach its bottom

// ==UserScript==
// @name        FA Infini-Gallery
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require 	https://update.greasyfork.org/scripts/475041/1267274/Furaffinity-Custom-Settings.js
// @grant       none
// @version     1.9.4
// @author      Midori Dragon
// @description Makes so that the gallery continues loading the next page when you reach its bottom
// @icon        https://www.furaffinity.net/themes/beta/img/banners/fa_logo.png?v2
// @homepageURL https://greasyfork.org/de/scripts/462632-infini-gallery
// @supportURL  https://greasyfork.org/de/scripts/462632-infini-gallery/feedback
// @license     MIT
// ==/UserScript==

// jshint esversion: 8

const matchList = ['net/browse', 'net/gallery', 'net/search', 'net/favorites', 'net/scraps', 'net/msg/pms' ];

CustomSettings.name = "Extension Settings";
CustomSettings.provider = "Midori's Script Settings";
CustomSettings.headerName = `${GM_info.script.name} Settings`;
const showPageSeperatorSetting = CustomSettings.newSetting("Page Seperator", "Sets wether a Page Seperator is shown foreach new Page loaded.", SettingTypes.Boolean, "Show Page Seperators", true);
const showDisableButtonSetting = CustomSettings.newSetting("Disable Button", "Sets wether the disable Infini Gallery button is shown in each Gallery.", SettingTypes.Boolean, "Show disable Infini Gallery button", true);
const newPageTextSetting = CustomSettings.newSetting("New Page Text", "Sets the Text that is displayed when a new Infini-Gallery Page is loaded (if shown). Number of Page gets inserted instead of: %page% .", SettingTypes.Text, "", "Infini-Gallery Page: %page%");
CustomSettings.loadSettings();

const isClassicTheme = document.head.querySelector('script[type="text/javascript"][src*="themes/classic"]') != null;

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

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

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

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

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

const isGallery = window.location.toString().includes('net/gallery');
const isFavorites = window.location.toString().includes('net/favorites');
const isBrowse = window.location.toString().includes('net/browse');
const isScraps = window.location.toString().includes('net/scraps');
const isNotes = window.location.toString().includes('net/msg/pms');

let allowScan = true;
let nextButtons;
let lastNextButton;
let gallery;
let lastLink;
let lastNextPageButton;
let pageCount;

if (isClassicTheme) {
	if (isGallery)
		nextButtons = document.querySelectorAll('a[class*="button-link"][href]');
	else if (isFavorites)
		nextButtons = document.querySelectorAll('a[class*="button-link"][href]');
	else if (isBrowse)
		nextButtons = document.querySelectorAll('button[class*="button"][type="submit"]');
	else if (isScraps)
		nextButtons = document.querySelectorAll('a[class*="button-link"][href]');
	else if (isNotes)
		nextButtons = document.querySelectorAll('a[class*="button-link"][href]');
} else {
	if (isGallery)
		nextButtons = document.querySelectorAll('button[class*="button standard"][type="submit"]');
	else if (isFavorites)
		nextButtons = document.querySelectorAll('a[class*="button mobile-button right"][href]');
	else if (isBrowse)
		nextButtons = document.querySelectorAll('a[class*="button standard"][href]');
	else if (isScraps)
		nextButtons = document.querySelectorAll('form[action][method="get"]');
}
if (!nextButtons || nextButtons.length == 0)
	return;

if (showDisableButtonSetting.value) {
	let navPage;
	if (isClassicTheme)
		navPage = document.querySelector('input[type="button"][class="button toggle_titles"]').parentNode;
	else
		navPage = document.querySelector('userpage-nav-links').querySelector('ul');
	let disableIGButton = document.createElement('button');
	disableIGButton.id = "disableIGButton";
	disableIGButton.type = "button";
	disableIGButton.className = "button standard mobile-fix";
	disableIGButton.textContent = "Disable Infini Gallery";
	if (isClassicTheme)
		disableIGButton.style.marginLeft = "8px";
	else
		disableIGButton.style.marginTop = "8px";
	disableIGButton.style.marginRight = "18px";
	disableIGButton.onclick = function() {
		allowScan = !allowScan;
		if (allowScan) {
			disableIGButton.textContent = "Disable Infini Gallery";
			scan();
		} else
			disableIGButton.textContent = "Enable Infini Gallery";
	};
	navPage.appendChild(disableIGButton);
}

lastNextButton = nextButtons[nextButtons.length - 1];
gallery = document.querySelector('section[id*="gallery"]');
lastLink = window.location.toString();
lastNextPageButton = lastNextButton;
pageCount = 1;
scan();

async function scan() {
  const interval = setInterval(() => {
    if (!allowScan)
      clearInterval(interval);
    if (isElementOnScreen(lastNextButton)) {
      clearInterval(interval);
      loadNextPage();
    }
  }, 100);
}

async function loadNextPage() {
  let figures;
  if (isClassicTheme) {
    if (isGallery)
      figures = await getNextPageFiguresGallery();
    else if (isFavorites)
      figures = await getNextPageFiguresGallery();
    else if (isBrowse)
      figures = await getNextPageFiguresGallery();
    else if (isScraps)
      figures = await getNextPageFiguresGallery();
    else if (isNotes)
      figures = await getNextPageFiguresGallery();
  } else {
    if (isGallery)
      figures = await getNextPageFiguresGallery();
    else if (isFavorites)
      figures = await getNextPageFiguresFavorites();
    else if (isBrowse)
      figures = await getNextPageFiguresGallery();
    else if (isScraps)
      figures = await getNextPageFiguresGallery();
  }
  if (!figures || figures.length == 0) {
    lastNextButton.parentNode.removeChild(lastNextButton);
    return;
  }
  pageCount++;
  if (!isNotes) {
		if (showPageSeperatorSetting.value) {
			let nextPageDescContainer = document.createElement('div');
			nextPageDescContainer.className = 'folder-description';
			nextPageDescContainer.style.marginTop = '6px';
			nextPageDescContainer.style.marginBottom = '6px';
			let nextPageDesc = document.createElement('div');
			nextPageDesc.className = 'container-item-top';
			let nextPageDescText = document.createElement('h3');
			const regex = /%page%/g;
  		const pageString = newPageTextSetting.value.replace(regex, pageCount);
			nextPageDescText.textContent = pageString;
			if (isClassicTheme) {
				nextPageDescText.style.color = "#c4e9ff";
				nextPageDescText.style.fontSize = "18px";
				nextPageDescText.style.lineHeight = "22px";
				nextPageDescText.style.fontWeight = "600";
				nextPageDescText.style.paddingTop = "3px";
				nextPageDescText.style.paddingBottom = "2px";
				nextPageDescText.style.paddingRight = "0";
				nextPageDescText.style.paddingLeft = "0";
				nextPageDescText.style.margin = "0";
			}
			nextPageDesc.appendChild(nextPageDescText);
			nextPageDescContainer.appendChild(nextPageDesc);
			gallery.appendChild(nextPageDescContainer);
		}
    for (const figure of figures)
      gallery.appendChild(figure);
    try { window.updateEmbedded(); } catch {} //Embedded Image Viewer Integration
    try { window.updateFastFavoriter(); } catch {} //Fast Favoriter 2 Integration
  } else {
    let table = document.getElementById("notes-list");
    let tbody = table.querySelector(tbody);
    for (const figure of figures)
      tbody.appendChild(figure);
  }

  await scan();
}

async function getNextPageFiguresGallery() {
  const nextLink = await incrementUrlLastNumber(lastLink);
  console.log(`${GM_info.script.name} loading next Page: ${nextLink}`);
  lastLink = nextLink;
  const nextPage = await getHTML(nextLink);
  const figures = nextPage.querySelectorAll('figure[class*="t"]');
  return figures;
}
async function getNextPageFiguresFavorites() {
  const nextLink = lastNextPageButton.href;
  console.log(`${GM_info.script.name} loading next Page: ${nextLink}`);
  lastLink = nextLink;
  const nextPage = await getHTML(nextLink);
  let currNextPageButton = nextPage.querySelectorAll('a[class="button mobile-button right"][href]');
  lastNextPageButton = currNextPageButton[currNextPageButton.length - 1];
  const figures = nextPage.querySelectorAll('figure[class*="t"]');
  return figures;
}
async function getNextPageFiguresNotes() {
  const nextLink = await incrementUrlLastNumber(lastLink);
  console.log(`${GM_info.script.name} loading next Page: ${nextLink}`);
  lastLink = nextLink;
  const nextPage = await getHTML(nextLink);
  const notes = nextPage.querySelectorAll('tr[class*="note"]');
  return notes;
}

async function incrementUrlLastNumber(url) {
  if (url.endsWith('/?'))
    url = url.slice(0, -1);
  if (url.endsWith('/'))
    url = url.slice(0, -1);

  var segments = url.split('/');
  var lastSegment = segments[segments.length - 1];

  var match = lastSegment.match(/^\d+/);

  if (match) {
    var nextNumber = parseInt(match[0]) + 1;
    return url.replace(/\d+$/, nextNumber);
  } else
    return url + '/2';
}


function isElementOnScreen(element) {
  var rect = element.getBoundingClientRect();
  var windowHeight = (window.innerHeight || document.documentElement.clientHeight) * 2;
  return (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0);
}

async function getHTML(url) {
  try {
    const response = await fetch(url);
    const html = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    return doc;
  } catch (error) {
    console.error(error);
  }
}