Panda Infinite Scroll

Infinite scrolling for galleries in e-hentai and exhentai

Versión del día 19/1/2021. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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);