Infini-Gallery

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

目前為 2023-05-26 提交的版本,檢視 最新版本

// ==UserScript==
// @name        Infini-Gallery
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @grant       none
// @version     1.6
// @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

//User Options:
let showPageSeperator = JSON.parse(localStorage.getItem("igsetting_1"));
if (showPageSeperator == null || showPageSeperator == undefined)
  showPageSeperator = true;
let showDisableButton = JSON.parse(localStorage.getItem("igsetting_2"));
if (showDisableButton == null || showDisableButton == undefined)
  showDisableButton = true;
const matchList = ['net/browse', 'net/gallery', 'net/search', 'net/favorites', 'net/scraps', 'net/msg/pms' ];

let settingsCount = 0;

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;
if (!matchList.some(x => window.location.toString().includes(x)))
  return;

console.info('%cRunning: Infini-Gallery v1.6', 'color: blue');

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

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 (showDisableButton) {
    let 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";
    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) {
    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 = '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);
  }
}

// ------------------------------ //
// ---------- SETTINGS ---------- //
// ------------------------------ //

async function addExSettings() {
  const settings = document.querySelector('ul[class="navhideonmobile"]').querySelector('a[href="/controls/settings/"]').parentNode;

  if (document.getElementById("extension_settings")) {
    document.getElementById('midori_settings').addEventListener('click', function() { localStorage.setItem("igsettings", true.toString()); });
    return;
  }
  let exSettingsHeader = document.createElement("h3");
  exSettingsHeader.id = "extension_settings";
  exSettingsHeader.textContent = "Extension Settings";
  settings.appendChild(exSettingsHeader);

  let wfsettings = document.createElement("a");
  wfsettings.id = "midori_settings";
  wfsettings.textContent = "Midori's Script Settings";
  wfsettings.style.cursor = "pointer";
  wfsettings.onclick = function() {
    localStorage.setItem("igsettings", true.toString());
    window.location = "https://www.furaffinity.net/controls/settings";
  }
  settings.appendChild(wfsettings);
}

async function addExSettingsSidebar() {
  const settings = document.getElementById('controlpanelnav');

  if (document.getElementById("extension_settings_side")) {
    document.getElementById('midori_settings_side').addEventListener('click', function() { localStorage.setItem("igsettings", true.toString()); });
    return;
  }
  let exSettingsHeader = document.createElement("h3");
  exSettingsHeader.id = "extension_settings_side";
  exSettingsHeader.textContent = "Extension Settings";
  settings.appendChild(exSettingsHeader);

  let wfsettings = document.createElement("a");
  wfsettings.id = "midori_settings_side";
  wfsettings.textContent = "Midori's Script Settings";
  wfsettings.style.cursor = "pointer";
  wfsettings.onclick = function() {
    localStorage.setItem("igsettings", true.toString());
    window.location = "https://www.furaffinity.net/controls/settings";
  }
  settings.appendChild(wfsettings);
}

async function createSettings() {
  localStorage.setItem("igsettings", false.toString());
  const columnPage = document.getElementById("columnpage");
  const content = columnPage.querySelector('div[class="content"]');
  for (const section of content.querySelectorAll('section:not([class="exsettings"])'))
    section.parentNode.removeChild(section);

  const section = document.createElement("section");
  section.className = 'exsettings';
  const headerContainer = document.createElement("div");
  headerContainer.className = "section-header";
  const header = document.createElement("h2");
  header.textContent = "Infini Gallery Settings";
  headerContainer.appendChild(header);
  section.appendChild(headerContainer);
  const bodyContainer = document.createElement("div");
  bodyContainer.className = "section-body";

  // Page Seperator Setting
  const pageSeperatorSetting = createSetting("Page Seperator", "Sets wether a Page Seperator is shown foreach new Page loaded", "boolean", "Show Page Seperators", (target) => {
    showPageSeperator = target.checked;
    localStorage.setItem(target.id, showPageSeperator.toString());
  });
  pageSeperatorSetting.querySelector('[id*="setting"]').checked = showPageSeperator;
  bodyContainer.appendChild(pageSeperatorSetting);

  // Disable Button Setting
  const disableButtonSetting = createSetting("Disable Button", "Sets wether the disable Infini Gallery button is shown in each Gallery", "boolean", "Show disable Infini Gallery button", (target) => {
    showDisableButton = target.checked;
    localStorage.setItem(target.id, showDisableButton.toString());
  });
  disableButtonSetting.querySelector('[id*="setting"]').checked = showDisableButton;
  bodyContainer.appendChild(disableButtonSetting);

  section.appendChild(bodyContainer);
  content.appendChild(section);
}

function createSetting(name, description, type, typeDescription, executeFunction) {
  const settingContainer = document.createElement("div");
  settingContainer.className = "control-panel-item-container";

  const settingName = document.createElement("div");
  settingName.className = "control-panel-item-name";
  const settingNameText = document.createElement("h4");
  settingNameText.textContent = name;
  settingName.appendChild(settingNameText);
  settingContainer.appendChild(settingName);

  const settingDesc = document.createElement("div");
  settingDesc.className = "control-panel-item-description";
  const settingDescText = document.createTextNode(description);
  settingDesc.appendChild(settingDescText);
  settingContainer.appendChild(settingDesc);

  const settingOption = document.createElement("div");
  settingOption.className = "control-panel-item-options";

  if (type === "number") {
    settingsCount++;
    const settingInput = document.createElement("input");
    settingInput.id = "igsetting_" + settingsCount;
    settingInput.type = "text";
    settingInput.className = "textbox";
    settingInput.addEventListener("keydown", (event) => {
      const currentValue = parseInt(settingInput.value) || 0;
      if (event.key === "ArrowUp") {
        settingInput.value = (currentValue + 1).toString();
        executeFunction(settingInput);
      } else if (event.key === "ArrowDown") {
        if (currentValue != 0)
          settingInput.value = (currentValue - 1).toString();
        executeFunction(settingInput);
      }
    });
    settingInput.addEventListener("input", () => {
      settingInput.value = settingInput.value.replace(/[^0-9]/g, "");
      if (settingInput.value < 0)
        settingInput.value = 0;
    });
    settingInput.addEventListener("input", () => executeFunction(settingInput));
    settingOption.appendChild(settingInput);
  } else if (type === "boolean") {
    settingsCount++;
    const settingCheckbox = document.createElement("input");
    settingCheckbox.id = "wfsetting_" + settingsCount;
    settingCheckbox.type = "checkbox";
    settingCheckbox.style.cursor = "pointer";
    settingCheckbox.style.marginRight = "4px";
    settingCheckbox.addEventListener("change", () => executeFunction(settingCheckbox));
    settingOption.appendChild(settingCheckbox);
    const settingOptionLabel = document.createElement("label");
    settingOptionLabel.textContent = typeDescription;
    settingOptionLabel.style.cursor = "pointer";
    settingOptionLabel.addEventListener("click", () => {
      settingCheckbox.checked = !settingCheckbox.checked;
      executeFunction(settingCheckbox);
    });
    settingOption.appendChild(settingOptionLabel);
  } else if (type === "action") {
    settingsCount++;
    const settingButton = document.createElement("button");
    settingButton.id = "wfsetting_" + settingsCount;
    settingButton.type = "button";
    settingButton.className = "button standard mobile-fix";
    settingButton.textContent = typeDescription;
    settingButton.addEventListener("click", () => executeFunction(settingButton));
    settingOption.appendChild(settingButton);
  }

  settingContainer.appendChild(settingOption);

  return settingContainer;
}