Auto scroll doujinshi and manga on nhentai so you can jerk off all hands-free.
// ==UserScript==
// @name nhentai Auto Scroller
// @version 3.1.0
// @description Auto scroll doujinshi and manga on nhentai so you can jerk off all hands-free.
// @author LoliEnjoyer
// @match https://nhentai.net/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=nhentai.net
// @grant window.onurlchange
// @grant GM_getValue
// @grant GM_setValue
// @grant GM.getValue
// @grant GM.setValue
// @namespace http://tampermonkey.net/
// ==/UserScript==
(function () {
'use strict';
/*-----------------+
| Manga Reader |
+-----------------*/
const defaultTurnPageDelay = 3.0;
function getScrollSpeed() {
try {
const reader = JSON.parse(localStorage.getItem('reader'));
return reader?.scroll_speed ?? 3;
} catch {
return 3;
}
}
const MangaReader = {
scrollIntervalID: null,
turnPageTimeoutID: null,
continueScrollTimeoutID: null,
start() {
if (this.scrollIntervalID !== null) return;
const scrollingElement = document.scrollingElement || document.documentElement;
const scrollDelay = 5;
this.scrollIntervalID = setInterval(() => {
const scrollLimit = scrollingElement.scrollTopMax;
if (scrollingElement.scrollTop < scrollLimit) {
scrollingElement.scrollBy(0, getScrollSpeed());
} else {
this.stopScroll();
this.turnPage(GM_getValue("turnPageDelay", defaultTurnPageDelay));
}
}, scrollDelay);
},
stopScroll() {
clearInterval(this.scrollIntervalID);
this.scrollIntervalID = null;
},
stop() {
this.stopScroll();
clearTimeout(this.turnPageTimeoutID);
clearTimeout(this.continueScrollTimeoutID);
this.turnPageTimeoutID = null;
this.restartScrollTimeoutID = null;
},
turnPage(delay) {
this.turnPageTimeoutID = setTimeout(() => {
document.querySelector('a.next')?.click();
this.turnPageTimeoutID = null;
}, (delay * 1000));
}
};
/*--------------+
| Reader UI |
+--------------*/
let isAutoScroll = false;
let container = null;
let icon = null;
function updateButtonPosition() {
if (!container) return;
Object.assign(container.style, window.scrollY >= 90
? { position: 'fixed', top: '0' }
: { position: 'absolute', top: '90px' }
);
}
function setAutoScroll(enabled) {
isAutoScroll = enabled;
icon.className = `fa fa-lg ${isAutoScroll ? 'fa-pause' : 'fa-play'}`;
if (isAutoScroll) {
MangaReader.start();
} else {
MangaReader.stop();
}
}
function createUI() {
container = document.createElement('div');
const button = document.createElement('button');
icon = document.createElement('i');
const delayInput = document.createElement('input');
icon.className = 'fa fa-lg fa-play';
Object.assign(container.style, {
position: 'absolute',
top: '90px',
right: '0',
margin: '10px',
zIndex: '100',
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-end',
gap: '6px',
});
Object.assign(button.style, {
width: '46px',
height: '46px',
backgroundColor: '#ed2553',
border: 'none',
borderRadius: '4px',
cursor: 'pointer',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
});
delayInput.type = 'number';
delayInput.value = GM_getValue("turnPageDelay", defaultTurnPageDelay);
delayInput.min = 0.5;
delayInput.step = 0.5;
delayInput.title = 'Page turn delay (sec)';
Object.assign(delayInput.style, {
width: '70px',
padding: '4px 6px',
backgroundColor: '#1a1a1a',
border: '1px solid #ed2553',
borderRadius: '4px',
color: '#fff',
fontSize: '12px',
textAlign: 'center',
});
delayInput.addEventListener('change', () => {
const storedTurnPageDelay = GM_getValue("turnPageDelay", defaultTurnPageDelay);
const val = parseFloat(delayInput.value);
if (isNaN(val)) return storedTurnPageDelay;
if (val >= 0) GM_setValue("turnPageDelay", val);
else delayInput.value = storedTurnPageDelay;
});
button.addEventListener('mouseenter', () => { button.style.backgroundColor = '#ee4972' });
button.addEventListener('mouseleave', () => { button.style.backgroundColor = '#ed2553' });
button.addEventListener('click', () => setAutoScroll(!isAutoScroll));
button.appendChild(icon);
container.appendChild(button);
container.appendChild(delayInput);
document.body.appendChild(container);
window.addEventListener('scroll', updateButtonPosition);
updateButtonPosition();
}
function destroyUI() {
setAutoScroll(false);
container?.remove();
container = null;
icon = null;
window.removeEventListener('scroll', updateButtonPosition);
}
/*------------------+
| Load / Unload |
+------------------*/
let isLoaded = false;
function matchesTargetUrl(url) {
return /^https:\/\/nhentai\.net\/g\/\d+\/\d+\/?$/.test(url);
}
function load() {
if (isLoaded) return;
isLoaded = true;
createUI();
}
function unload() {
if (!isLoaded) return;
isLoaded = false;
destroyUI();
}
if (matchesTargetUrl(location.href)) load();
if (window.onurlchange === null) {
window.addEventListener('urlchange', (info) => {
if (matchesTargetUrl(info.url)) {
if (isLoaded) {
MangaReader.stop();
if (isAutoScroll) MangaReader.continueScrollTimeoutID = setTimeout(() => MangaReader.start(), 3000);
} else {
load();
}
} else {
unload();
}
});
}
})();