Panda Infinite Scroll

Infinite scrolling for galleries in e-hentai and exhentai

Version vom 19.01.2021. Aktuellste Version

Du musst eine Erweiterung wie Tampermonkey, Greasemonkey oder Violentmonkey installieren, um dieses Skript zu installieren.

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

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

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

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

Sie müssten eine Skript Manager Erweiterung installieren damit sie dieses Skript installieren können

(Ich habe schon ein Skript Manager, Lass mich es installieren!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          Panda Infinite Scroll
// @description	  Infinite scrolling for galleries in e-hentai and exhentai
// @name:zh-TW           熊貓無限滾動
// @description:zh-TW    在e-hentai和exhentai頁面內實踐無限滾動,自動加載下一頁
// @name:zh-CN           熊猫无限滚动
// @description:zh-CN    在e-hentai和exhentai页面内实践无限滚动,自动加载下一页
// @icon          https://e-hentai.org/favicon.ico
// @namespace     https://gist.github.com/ytjchan
// @author        ytjchan
// @version       1.1.0
// @include       *://e-hentai.org/*
// @include       *://exhentai.org/*
// @exclude       *://e-hentai.org/g/*
// @exclude       *://e-hentai.org/s/*
// @exclude       *://exhentai.org/g/*
// @exclude       *://exhentai.org/s/*
// ==/UserScript==

// Current page (0-indexed)
let url = new URL(window.location);
if (url.pathname.includes("tag") && isNaN(url.pathname.split("/").slice(-1)[0]))
    url.href += "/0";
url.setPage = (url.pathname.includes("tag"))? 
    page => url.href = url.href.replace(/\/[^\/]*$/, "/"+page) :
    page => url.searchParams.set("page", page);
let currentPage = (url.pathname.includes("tag"))? 
    (parseInt(url.pathname.split("/").slice(-1)[0]) || 0) :
    (parseInt(url.searchParams.get("page")) || 0);
// Restore current page in case of page back
if (document.getElementsByClassName("page-anchor").length !== 0)
    currentPage = Math.max(...Array.from(document.getElementsByClassName("page-anchor")).map(el => parseInt(el.getAttribute("data-page"))));

// Max, min page
let pageBtns = document.querySelectorAll(".ptt td");
let maxPage = parseInt(pageBtns[pageBtns.length-2].textContent) - 1;
let minPage = parseInt(pageBtns[1].textContent) - 1;

// Detect if bottom of page reached
let observer = new IntersectionObserver(fetchNextPage, { threshold: 1.0 });
observer.observe(document.querySelector(".ptb"));

// Alert area
let flex = document.createElement("div");
flex.classList.add("scroll-alert-area");
document.getElementsByTagName("body")[0].appendChild(flex);
createScrollAlert(currentPage);

let inProcess = false;
function fetchNextPage() {
    if (currentPage >= maxPage) {
        observer.disconnect();
        createAlert("Last page reached", 5000, "#0FF");
        return;
    }
    
    if (inProcess) return;
    inProcess = true;

    url.setPage(++currentPage);
    fetch(url.href)
    .then(res => res.text())
    .then(html => {
        let parent = document.querySelector(".gl1t, .itg tr").parentNode;
        let dummy = document.createElement("html");
        dummy.innerHTML = html;
        parent.innerHTML += Array.from(dummy.querySelectorAll(".gl1t, .itg tr")).reduce((prev, el, i) => {
            if (i === 0) {
                el.classList.add("page-anchor");
                el.setAttribute("data-page", currentPage);
                let span = document.createElement("span");
                span.classList.add("page-anchor-display");
                span.innerHTML = `p. ${currentPage+1}`
                el.appendChild(span);
            }
            return prev += el.outerHTML;
        }, "");
        createScrollAlert(currentPage);
        inProcess = false;
    })
    .catch(e => createAlert(e, 5000, "#F00"));
}

function createAlert(msg, timeout, bgColor = "#EEE") {
    let div = document.createElement("div");
    div.textContent = msg;
    div.setAttribute("data-bg", bgColor);
    div.classList.add("scroll-alert")
    div.style = `background-color: ${bgColor}`;
    flex.appendChild(div);
    setTimeout(()=>div.remove(), timeout);
    return div;
}

function scrollToPage(page) {
    if (page > currentPage) {
        alert("Page not loaded yet");
        return;
    }
    try {
        document.querySelector(".page-anchor[data-page='"+page+"']").scrollIntoView(); 
    } catch (e) { // page anchor does not exist
        document.documentElement.scrollTop = 0;
    }
}

 // display clickable page alert as 1-indexed
function createScrollAlert(page) {
    createAlert((page+1)+" / "+(maxPage+1), 5000, "#0F0")
        .addEventListener("click", scrollToPage.bind(null, page)); // display as 1-indexed
}

// Custom CSS for page anchors
let style = document.createElement('style');
style.innerHTML = `
.scroll-alert-area {
    display: flex;
    flex-direction: column;
    align-items: flex-end;
    position: fixed;
    top: 0;
    right: 0;
    z-index: 10;
}

.scroll-alert {
    padding: 0.5em;
    margin-bottom: 0.1em;
    font-size: 1.25em;
    color: #000;
    cursor: pointer;
}

.page-anchor-display {
    position: absolute;
    background-color: #0FF;
    z-index: 10;
    padding: 0.25em;
    color: #000;
    opacity: 0.75;
    transition: 0.2s;
    pointer-events: none;
}

.page-anchor:hover .page-anchor-display {
    opacity: 0;
}
`;
document.getElementsByTagName('head')[0].appendChild(style);