Infini-Gallery

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

目前為 2023-09-11 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name        Infini-Gallery
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require 	https://greasyfork.org/scripts/475041-furaffinity-custom-settings/code/Furaffinity-Custom-Settings.js
// @grant       none
// @version     1.8.0
// @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 = new Setting("Page Seperator", "Sets wether a Page Seperator is shown foreach new Page loaded.", SettingTypes.Boolean, "Show Page Seperators", true);
const showDisableButtonSetting = new Setting("Disable Button", "Sets wether the disable Infini Gallery button is shown in each Gallery.", SettingTypes.Boolean, "Show disable Infini Gallery button", true);
CustomSettings.loadSettings();

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

const isSettings = window.location.toString().includes('controls/settings');
let exSettings = JSON.parse(localStorage.getItem("igsettings"));
if (exSettings == null)
  exSettings = false;
addExSettings();
if (isSettings) {
  addExSettingsSidebar();
  if (exSettings)
    createSettings();
}

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 (!isSettings) {
  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');
			nextPageDescText.textContent = `${GM_info.script.name} Page: ${pageCount}`;
			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(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(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(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);
  }
}