Webcomic Autoloader 2.0

Gives you the option to load all the subsequent comic pages on a FurAffinity comic page automatically. Even for pages without given Links

当前为 2023-09-11 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Webcomic Autoloader 2.0
// @namespace   Violentmonkey Scripts
// @match       *://*.furaffinity.net/*
// @require 		https://greasyfork.org/scripts/475041-furaffinity-custom-settings/code/Furaffinity-Custom-Settings.js
// @grant       none
// @version     1.7.0
// @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?v2
// @homepageURL https://greasyfork.org/de/scripts/457759-furaffinity-webcomic-autoloader-2-0
// @supportURL  https://greasyfork.org/de/scripts/457759-furaffinity-webcomic-autoloader-2-0/feedback
// @license     MIT
// ==/UserScript==

// jshint esversion: 8

const matchList = ['net/view' ];

CustomSettings.Name = "Extension Settings";
CustomSettings.Provider = "Midori's Script Settings";
CustomSettings.HeaderName = `${GM_info.script.name} Settings`;
const showSearchButtonSetting = new Setting("Simular Search Button", "Sets wether the search for simular Pages button is show.", SettingTypes.Boolean, "Show Search Button", true);
const loadingSpinSpeedSetting = new Setting("Loading Animation", "Sets the spinning speed of the loading animation in milliseconds.", SettingTypes.Number, "", 100);
CustomSettings.loadSettings();

let settingsCount = 0;

const isSettings = window.location.toString().includes('controls/settings');
let exSettings = JSON.parse(localStorage.getItem("wasettings"));
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);


let lightboxPresent = false;
let currLighboxNo = -1;
let imgCount = 1;

let rootHolder = document.getElementById("submissionImg");
rootHolder.setAttribute('imgno', 0);
rootHolder.onclick = function() {
  lightboxPresent = true;
  currLighboxNo = 0;
  window.addEventListener('keydown', handleArrowKeys);
  let lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]');
  lightbox.addEventListener('click', function() {
    lightboxPresent = false;
    currLighboxNo = -1;
    window.removeEventListener('keydown', handleArrowKeys);
  });
};
let counter = 5;
let showLinks = false;
let startImg = window.location.href;
while (startImg.endsWith("/"))
  startImg = startImg.substring(0, startImg.length - 1);
let pageCounter = 0;
let openedLinks = [document.location.toString()];
let rotation;

function insertAfter(newElement, referenceElement) {
  referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);
}
function insertBreakAfter(referenceElement) {
  let br = document.createElement("br");
  insertAfter(br, referenceElement);
}

function getNextLink(doc) {
  let links = doc.querySelectorAll('a[href]:not([class*="button standard mobile-fix"]), :not([class])');
  let link;
  for (const elem of links) {
    if (elem.href && elem.textContent.toLowerCase().includes("next")) {
      try {
        let currImgCalc = elem.href;
        while (currImgCalc.endsWith("/"))
          currImgCalc = currImgCalc.substring(0, currImgCalc.length - 1);
        currImgCalc = currImgCalc.substring(currImgCalc.lastIndexOf('/'), currImgCalc.length);
        let startImgCalc = startImg.substring(startImg.lastIndexOf('/'), startImg.length);
        if (currImgCalc != startImgCalc && currImgCalc != "/#" && !openedLinks.includes(elem.href)) {
          link = elem.href;
          openedLinks.push(link);
          pageCounter++;
        } else if (openedLinks.includes(elem.href)) {
          return "loopinglink";
        }
      } catch (ex) { console.error(ex); }
    }
  }
  return link;
}

function loadNextPage(nextLink) {
  if(nextLink) {
    if (nextLink.includes("http:"))
      nextLink = nextLink.replace(/^http:/, "https:");
    let request = new XMLHttpRequest();
    request.open('GET', nextLink, true);

    request.onload = function() {
      if (this.status >= 200 && this.status < 400) {
        // Success!
        parser = new DOMParser();
        let nextPage = parser.parseFromString(this.response, "text/html");
        let nl;
        if (nextPage && nextPage.getElementById("submissionImg")) {
          nl = getNextLink(nextPage);
          imgCount++;
          let img = nextPage.getElementById("submissionImg");
          img.setAttribute('imgno', imgCount - 1);
          img.onclick = function() {
            doLightBox(img);
          };

          rootHolder.parentNode.insertBefore(img, rootHolder.nextSibling);
          rootHolder = rootHolder.nextSibling;

          insertBreakAfter(rootHolder);
          rootHolder = rootHolder.nextSibling;
          if (showLinks) {
            let lnk = document.createElement('a');
            let lnkURL = nextLink;
            lnk.innerHTML = lnkURL;
            lnk.href = lnkURL;
            insertAfter(lnk, rootHolder);
            rootHolder = rootHolder.nextSibling;
            insertBreakAfter(rootHolder);
            rootHolder = rootHolder.nextSibling;
          }
          else {
            let br = document.createElement('br');
            insertAfter(br, rootHolder);
            rootHolder = rootHolder.nextSibling;
          }
        }
        if (nl == "loopinglink") {
          // searchNextSimularPage();
          return;
        } else if (nl)
          loadNextPage(nl);
      } else {
        //We reached our target server, but it returned an error
        console.log("none");
      }
    };

    request.onerror = function() {
      //There was a connection error of some sort
      console.log("error");
    };

    request.send();
  }
}

function loadPages(links, i) {
if (i == links.length)
  return;
let nextLink = links[i];
  if(nextLink) {
    let request = new XMLHttpRequest();
    request.open('GET', nextLink, true);

    request.onload = function() {
      if (this.status >= 200 && this.status < 400) {
        // Success!
        parser = new DOMParser();
        let nextPage = parser.parseFromString(this.response, "text/html");
        if (nextPage && nextPage.getElementById("submissionImg")) {
          imgCount++;
          let img = nextPage.getElementById("submissionImg");
          img.setAttribute('imgno', imgCount - 1);
          img.onclick = function() {
            doLightBox(img);
          };

          rootHolder.parentNode.insertBefore(img, rootHolder.nextSibling);
          rootHolder = rootHolder.nextSibling;

          insertBreakAfter(rootHolder);
          rootHolder = rootHolder.nextSibling;
          if (showLinks) {
            let lnk = document.createElement('a');
            let lnkURL = nextLink;
            lnk.innerHTML = lnkURL;
            lnk.href = lnkURL;
            insertAfter(lnk, rootHolder);
            rootHolder = rootHolder.nextSibling;
            insertBreakAfter(rootHolder);
            rootHolder = rootHolder.nextSibling;
          }
          else {
            let br = document.createElement('br');
            insertAfter(br, rootHolder);
            rootHolder = rootHolder.nextSibling;
          }
        }
        i++;
        loadPages(links, i);
      } else {
        //We reached our target server, but it returned an error
        console.log("none");
      }
    };

    request.onerror = function() {
      //There was a connection error of some sort
      console.log("error");
    };

    request.send();
  }
}

function doLightBox(img) {
  let lightbox = document.createElement('div');
  lightbox.className = 'lightbox lightbox-submission';
  lightbox.onclick = function() {
    document.body.removeChild(lightbox);
    lightboxPresent = false;
    currLighboxNo = -1;
    window.removeEventListener('keydown', handleArrowKeys);
  };
  let lightboxImg = img.cloneNode(false);
  lightboxImg.onclick = function(){};
  lightbox.appendChild(lightboxImg);
  document.body.appendChild(lightbox);
  lightboxPresent = true;
  currLighboxNo = +img.getAttribute('imgno');
  window.addEventListener('keydown', handleArrowKeys);
}

function navigateLightboxLeft() {
  if (currLighboxNo > 0) {
    currLighboxNo--;
    let lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]');
    let lightboxImg = lightbox.querySelector('img');
    let nextImg = document.querySelector('img[imgno="' + currLighboxNo + '"]');
    lightboxImg.src = nextImg.src;
  }
}

function navigateLightboxRight() {
  if (currLighboxNo < imgCount - 1) {
    currLighboxNo++;
    let lightbox = document.body.querySelector('div[class="lightbox lightbox-submission"]');
    let lightboxImg = lightbox.querySelector('img');
    let nextImg = document.querySelector('img[imgno="' + currLighboxNo + '"]');
    lightboxImg.src = nextImg.src;
  }
}

function handleArrowKeys(event) {
  if (event.keyCode === 37) { // left arrow
    navigateLightboxLeft();
  } else if (event.keyCode === 39) { // right arrow
    navigateLightboxRight();
  }
}

function startAutoloader() {
  let ab = document.getElementById("autoloaderButton");
  ab.parentNode.removeChild(ab);
  let checkbox = document.getElementById("linksCheckbox");
  checkbox.parentNode.removeChild(checkbox);
  let label = document.getElementById("showLinksLabel");
  label.parentNode.removeChild(label);

  insertBreakAfter(rootHolder);
  rootHolder = rootHolder.nextSibling;

  loadNextPage(secondPage);
}

async function searchNextSimularPage() {
  let ab = document.getElementById("morePagesSearch");
  if (ab)
    rotation = rotateText(ab, true);
  let checkbox = document.getElementById("linksCheckbox");
  if (checkbox)
    checkbox.parentNode.removeChild(checkbox);
  let label = document.getElementById("showLinksLabel");
  if (label)
  label.parentNode.removeChild(label);

  if (checkbox) {
    insertBreakAfter(rootHolder);
    rootHolder = rootHolder.nextSibling;
  }

  const submissionPage = document.getElementById("submission_page");
  const container = submissionPage.querySelector('div[class="submission-id-sub-container"]');
  let currTitle = container.querySelector('div[class="submission-title"]').querySelector('p').textContent;
  currTitle = generalizeString(currTitle, true, true, true, true, true);
  const author = container.querySelector('a');
  let galleryLink = author.href.substring(0, author.href.length - 1);

  let currentSubmissionId = window.location.toString().substring(0, window.location.toString().length - 1);
  currentSubmissionId = "sid-" + currentSubmissionId.substring(currentSubmissionId.lastIndexOf("/") + 1);

  galleryLink = galleryLink.substring(galleryLink.lastIndexOf("/") + 1);
  galleryLink = "https://www.furaffinity.net/gallery/" + galleryLink;

  let figures = [];
  let currentFigureIndex = -1;

  let j = 0;
  while (currentFigureIndex == -1) {
    j++;
    gallery = await getHTML(galleryLink + "/" + j);
    let figuresNew = gallery.getElementsByTagName("figure");
    if (!figuresNew || figuresNew.length == 0) {
      rotation.stop();
      ab.value = "Nothing found... Search again";
      return false;
    }
    figuresNew = Array.from(figuresNew);
    figures = figures.concat(figuresNew);
    currentFigureIndex = figuresNew.findIndex(figure => figure.id == currentSubmissionId);
    //console.log("j: " + j + "  |  index: " + currentFigureIndex);
  }
  currentFigureIndex = figures.findIndex(figure => figure.id == currentSubmissionId);
  //console.log("total index: " + currentFigureIndex);

  if (currentFigureIndex == 0) {
    rotation.stop();
    ab.value = "Nothing found... Search again";
    return false;
  }

  figures = figures.slice(0, currentFigureIndex);

  let pages = [];
  for (let i = figures.length - 1; i >= 0; i--) {
    const titleElem = figures[i].querySelector('figcaption').querySelector('a');
    const title = generalizeString(titleElem.getAttribute("title"), true, true, true, true, true);
    if (title.includes(currTitle))
      pages.push(titleElem.href);
  }
  if (pages.length == 0) {
    rotation.stop();
    ab.value = "Nothing found... Search again";
    return false;
  }
  else
    loadPages(pages, 0);

  ab.parentNode.removeChild(ab);
  return true;
}

function generalizeString(inputString, textToNumbers, removeSpecialChars, removeNumbers, removeSpaces, removeRoman) {
  let outputString = inputString.toLowerCase();

  if (removeRoman) {
    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"), "");
  }

  if (textToNumbers) {
    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()]);
  }

  if (removeSpecialChars)
    outputString = outputString.replace(/[^a-zA-Z0-9 ]/g, "");

  if (removeNumbers)
    outputString = outputString.replace(/[^a-zA-Z ]/g, "");

  if (removeSpaces)
    outputString = outputString.replace(/\s/g, "");

  return outputString;
}

function rotateText(element, isRotating) {
  const characters = [ "◜", "◠", "◝", "◞", "◡", "◟" ];
  let index = 0;
  let intervalId;

  function updateText() {
    element.value = characters[index];
    index = (index + 1) % characters.length;
  }

  function startRotation() {
    intervalId = setInterval(updateText, loadingSpinSpeedSetting.value);
  }

  function stopRotation() {
    clearInterval(intervalId);
  }

  if (isRotating) {
    startRotation();
  }

  return {
    start: startRotation,
    stop: stopRotation
  }
}

function setShowLinks() {
  if (showLinks)
    showLinks = false;
  else
    showLinks = true;
  let checkbox = document.getElementById("linksCheckbox");
  checkbox.checked = showLinks;
}

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);
  }
}

let secondPage = getNextLink(document);
if(secondPage) {
  let img = document.getElementById("submissionImg");
  insertBreakAfter(rootHolder);
  rootHolder = rootHolder.nextSibling;

  pageCounter++;

  let label = document.createElement('a');
  label.id = "showLinksLabel";
  label.innerHTML = "Show Links";
  label.style.cursor = "pointer";
  label.style.marginBottom = "30px";
  label.style.marginLeft = "5px";
  label.onclick = setShowLinks;
  insertAfter(label, rootHolder);

  let checkbox = document.createElement('input');
  checkbox.value = "Show Links";
  checkbox.type = "checkbox";
  checkbox.id = "linksCheckbox";
  checkbox.className = "checkbox standard mobile-fix";
  checkbox.style.cursor = "pointer";
  checkbox.style.marginBottom = "30px";
  checkbox.style.marginLeft = "20px";
  checkbox.onclick = setShowLinks;
  insertAfter(checkbox, rootHolder);

  let button = document.createElement('input');
  button.value = "Enable Comic Autoloader";
  button.type = "button";
  button.id = "autoloaderButton";
  button.className = "button standard mobile-fix";
  button.style.marginTop = "10px";
  button.style.marginBottom = "20px";
  button.onclick = startAutoloader;
  insertAfter(button, rootHolder);
} else if (showSearchButtonSetting.value) {
  let img = document.getElementById("submissionImg");
  insertBreakAfter(rootHolder);
  rootHolder = rootHolder.nextSibling;

  let label = document.createElement('a');
  label.id = "showLinksLabel";
  label.innerHTML = "Show Links";
  label.style.cursor = "pointer";
  label.style.marginBottom = "30px";
  label.style.marginLeft = "5px";
  label.onclick = setShowLinks;
  insertAfter(label, rootHolder);

  let checkbox = document.createElement('input');
  checkbox.value = "Show Links";
  checkbox.type = "checkbox";
  checkbox.id = "linksCheckbox";
  checkbox.className = "checkbox standard mobile-fix";
  checkbox.style.cursor = "pointer";
  checkbox.style.marginBottom = "30px";
  checkbox.style.marginLeft = "20px";
  checkbox.onclick = setShowLinks;
  insertAfter(checkbox, rootHolder);

  let button = document.createElement('input');
  button.value = "Search for more Pages in Series";
  button.type = "button";
  button.id = "morePagesSearch";
  button.className = "button standard mobile-fix";
  button.style.marginTop = "10px";
  button.style.marginBottom = "20px";
  button.onclick = searchNextSimularPage;
  insertAfter(button, rootHolder);
}