// ==UserScript==
// @name NHentai Infinite Load
// @namespace http://tampermonkey.net/
// @version 2.1
// @description Seamlessly load more comics as you scroll on nhentai.net, with support for blacklisted content removal. Enhanced performance and reliability.
// @author Hentiedup (original), [Snow2122] (adaptation)
// @license MIT
// @match https://nhentai.net/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @icon https://i.imgur.com/1lihxY2.png
// @noframes
// ==/UserScript==
(() => {
// Configuration constants
const CONFIG = {
SCROLL_THRESHOLD: 100, // Pixels before bottom to trigger load
LOADER_HEIGHT: "355px",
PAGE_PARAM: "page=",
MAX_FETCH_ATTEMPTS: 5,
DEBOUNCE_WAIT: 100, // ms for scroll debounce
RETRY_BASE_DELAY: 1000 // ms for AJAX retry delay
};
// User-configurable settings
const settings = {
infinite_load: GM_getValue("infinite_load", true),
remove_native_blacklisted: GM_getValue("remove_native_blacklisted", true)
};
// Initialize blacklist
const nativeBlacklist = [];
// Global variable to track loading state
let infinite_load_isLoadingNextPage = false;
// Cache frequently used selectors
const $containers = $(".container.index-container, #favcontainer.container, #recent-favorites-container, #related-container");
// Log for debugging
console.log("NHI: Script initialized", { infinite_load: settings.infinite_load, remove_native_blacklisted: settings.remove_native_blacklisted });
// Initialize settings UI
initSettingsUI();
// Apply stylesheets
AddInfiniteLoadStylesheets();
// Handle blacklisted tags and infinite load on page load
if ($containers.length) {
HandleBlacklistedRemoval();
InfiniteLoadHandling();
} else {
console.warn("NHI: No matching containers found, script may not work as expected");
}
// FUNCTIONS
// Settings UI
function initSettingsUI() {
GM_registerMenuCommand("Toggle Infinite Load", () => {
const newValue = !settings.infinite_load;
GM_setValue("infinite_load", newValue);
settings.infinite_load = newValue;
alert(`Infinite Load is now ${newValue ? "enabled" : "disabled"}. Refresh to apply.`);
});
GM_registerMenuCommand("Toggle Blacklist Removal", () => {
const newValue = !settings.remove_native_blacklisted;
GM_setValue("remove_native_blacklisted", newValue);
settings.remove_native_blacklisted = newValue;
alert(`Blacklist Removal is now ${newValue ? "enabled" : "disabled"}. Refresh to apply.`);
});
}
// Blacklist related functions
function HandleBlacklistedRemoval() {
if (settings.remove_native_blacklisted) {
$(".gallery.blacklisted").remove();
console.log("NHI: Removed blacklisted galleries on page load");
if (settings.infinite_load) {
const $scriptWithTags = $("script:not([src])").filter((i, el) => $(el).html().includes("blacklisted_tags"));
const tagsMatch = $scriptWithTags.html()?.match(/blacklisted_tags:\s*\[([^\]]*)\]/);
if (tagsMatch?.[1]) {
try {
nativeBlacklist.push(...JSON.parse(`[${tagsMatch[1]}]`));
console.log("NHI: Parsed blacklisted tags", nativeBlacklist);
} catch {
console.warn("NHI: Failed to parse blacklisted tags, disabling blacklist filtering for infinite load");
settings.remove_native_blacklisted = false;
}
} else {
console.warn("NHI: Blacklisted tags not found, disabling blacklist filtering for infinite load");
settings.remove_native_blacklisted = false;
}
}
}
}
// Infinite load related functions
function AddInfiniteLoadStylesheets() {
if (settings.infinite_load) {
GM_addStyle(`
#NHI_loader_icon {
height: ${CONFIG.LOADER_HEIGHT};
line-height: ${CONFIG.LOADER_HEIGHT};
display: none;
}
#NHI_loader_icon.active {
display: block;
}
#NHI_loader_icon > div {
display: inline-flex;
}
.loader {
color: #ed2553;
font-size: 10px;
width: 1em;
height: 1em;
border-radius: 50%;
position: relative;
text-indent: -9999em;
animation: mulShdSpin 1.3s infinite linear;
transform: translateZ(0);
}
@keyframes mulShdSpin {
0%, 100% {
box-shadow: 0 -3em 0 0.2em,
2em -2em 0 0em, 3em 0 0 -1em,
2em 2em 0 -1em, 0 3em 0 -1em,
-2em 2em 0 -1em, -3em 0 0 -1em,
-2em -2em 0 0;
}
12.5% {
box-shadow: 0 -3em 0 0, 2em -2em 0 0.2em,
3em 0 0 0, 2em 2em 0 -1em, 0 3em 0 -1em,
-2em 2em 0 -1em, -3em 0 0 -1em,
-2em -2em 0 -1em;
}
25% {
box-shadow: 0 -3em 0 -0.5em,
2em -2em 0 0, 3em 0 0 0.2em,
2em 2em 0 0, 0 3em 0 -1em,
-2em 2em 0 -1em, -3em 0 0 -1em,
-2em -2em 0 -1em;
}
37.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em,
3em 0em 0 0, 2em 2em 0 0.2em, 0 3em 0 0em,
-2em 2em 0 -1em, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
50% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em,
3em 0 0 -1em, 2em 2em 0 0em, 0 3em 0 0.2em,
-2em 2em 0 0, -3em 0em 0 -1em, -2em -2em 0 -1em;
}
62.5% {
box-shadow: 0 -3em 0 -1em, 2em -2em 0 -1em,
3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 0,
-2em 2em 0 0.2em, -3em 0 0 0, -2em -2em 0 -1em;
}
75% {
box-shadow: 0em -3em 0 -1em, 2em -2em 0 -1em,
3em 0em 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em,
-2em 2em 0 0, -3em 0em 0 0.2em, -2em -2em 0 0;
}
87.5% {
box-shadow: 0em -3em 0 0, 2em -2em 0 -1em,
3em 0 0 -1em, 2em 2em 0 -1em, 0 3em 0 -1em,
-2em 2em 0 0, -3em 0em 0 0, -2em -2em 0 0.2em;
}
}
`);
console.log("NHI: Applied infinite load stylesheets");
}
}
// Utility functions
function debounce(func, wait) {
let timeout;
return function (...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}
function InfiniteLoadHandling() {
if (settings.infinite_load) {
const $paginator = $(".pagination");
if (!$paginator.length || window.location.pathname === "/favorites/") {
console.warn("NHI: Pagination not found or on favorites page, disabling infinite load");
return;
}
const $lastPageLink = $paginator.find(".last");
if (!$lastPageLink.length) {
console.warn("NHI: Pagination last page link not found, disabling infinite load");
return;
}
const lastPageNum = Number.parseInt($lastPageLink.attr("href")?.split(CONFIG.PAGE_PARAM)[1]);
if (isNaN(lastPageNum)) {
console.warn("NHI: Invalid last page number, disabling infinite load");
return;
}
const queryWithNoPage = window.location.search.replace(/[\?\&]page=\d+/, "").replace(/^\&/, "?");
const finalUrlWithoutPageNum = `${window.location.pathname + queryWithNoPage + (queryWithNoPage.length ? "&" : "?")}page=`;
console.log("NHI: Infinite load initialized", { lastPageNum, finalUrlWithoutPageNum });
// Scroll-based infinite load
$(window).scroll(debounce(() => {
if ($(window).scrollTop() + (window.visualViewport?.height || $(window).height()) >= $(document).height() - CONFIG.SCROLL_THRESHOLD) {
const $currentPage = $(".pagination > .page.current:last");
if (!$currentPage.length) {
console.warn("NHI: Current page not found, skipping load");
return;
}
const loadingPageNum = Number.parseInt($currentPage.attr("href").split(CONFIG.PAGE_PARAM)[1]) + 1;
TryLoadInNextPageComics(loadingPageNum, lastPageNum, finalUrlWithoutPageNum);
}
}, CONFIG.DEBOUNCE_WAIT));
// One-time check for small pages
const doc = document.documentElement;
if (doc.scrollHeight <= doc.clientHeight) {
const $currentPage = $(".pagination > .page.current:last");
if ($currentPage.length) {
const loadingPageNum = Number.parseInt($currentPage.attr("href").split(CONFIG.PAGE_PARAM)[1]) + 1;
if (loadingPageNum <= lastPageNum) {
TryLoadInNextPageComics(loadingPageNum, lastPageNum, finalUrlWithoutPageNum);
}
}
}
}
}
function TryLoadInNextPageComics(pageNumToLoad, lastPageNum, fetchUrlWithoutPageNum, retryNum = 0, maxFetchAttempts = CONFIG.MAX_FETCH_ATTEMPTS) {
if (retryNum === 0 && infinite_load_isLoadingNextPage) {
console.log("NHI: Already loading next page, skipping");
return;
}
if (pageNumToLoad > lastPageNum) {
console.log("NHI: Reached last page, stopping");
return;
}
infinite_load_isLoadingNextPage = true;
const $indexContainer = $(".index-container:not(.advertisement, .index-popular)").first();
if (!$indexContainer.length) {
console.warn("NHI: Index container not found, aborting load");
infinite_load_isLoadingNextPage = false;
return;
}
let $loader = $("#NHI_loader_icon");
if (!$loader.length) {
$loader = $('<div id="NHI_loader_icon" class="gallery"><div><span class="loader"></span></div></div>');
$indexContainer.append($loader);
}
$loader.addClass("active");
console.log(`NHI: Loading page ${pageNumToLoad}`);
$.get({
url: fetchUrlWithoutPageNum + pageNumToLoad,
dataType: "html"
}, (data) => {
const $fragment = $(document.createDocumentFragment());
const $existingCovers = $indexContainer.find(".cover").map((_, el) => $(el).attr("href")).get();
$(data).find("div.gallery").each((i, el) => {
if (settings.remove_native_blacklisted) {
if ($(el).hasClass("blacklisted")) return;
const tags = $(el).attr("data-tags")?.trim().split(" ") || [];
if (nativeBlacklist.some(nblTag => tags.includes(String(nblTag)))) return;
}
const href = $(el).find(".cover").attr("href");
if ($existingCovers.includes(href)) return;
$(el).find("img").attr("src", $(el).find("img").attr("data-src"));
$fragment.append(el);
});
$indexContainer.append($fragment);
console.log(`NHI: Loaded ${$(data).find("div.gallery").length} galleries for page ${pageNumToLoad}`);
const $paginatorItem = $(`.pagination > .page[href$='${CONFIG.PAGE_PARAM}${pageNumToLoad}']`);
if ($paginatorItem.length) {
$paginatorItem.addClass("current");
} else {
$(".pagination > .next").before(`<a href="${fetchUrlWithoutPageNum}${pageNumToLoad}" class="page current">${pageNumToLoad}</a>`);
}
$loader.removeClass("active");
infinite_load_isLoadingNextPage = false;
}).fail((jqXHR, textStatus, errorThrown) => {
if (retryNum < maxFetchAttempts) {
const delay = jqXHR.status === 429 ? Math.pow(2, retryNum) * CONFIG.RETRY_BASE_DELAY : CONFIG.RETRY_BASE_DELAY;
console.log(`NHI: Failed loading page ${pageNumToLoad} - ${textStatus} | ${errorThrown} - retrying in ${delay}ms (retry ${retryNum + 1})`);
setTimeout(() => {
TryLoadInNextPageComics(pageNumToLoad, lastPageNum, fetchUrlWithoutPageNum, retryNum + 1, maxFetchAttempts);
}, delay);
} else {
$loader.removeClass("active");
console.log(`NHI: Failed loading page ${pageNumToLoad} - ${textStatus} | ${errorThrown} - Giving up after ${retryNum} retries`);
infinite_load_isLoadingNextPage = false;
}
});
}
})();