Adds a button to open every gallery on an E-Hentai / ExHentai listing page in background tabs, with a delay. Works in all view modes; click the button again to pause or resume.
// ==UserScript==
// @name Open All Galleries EH
// @namespace open-all-galleries-eh
// @version 1.0.0
// @description Adds a button to open every gallery on an E-Hentai / ExHentai listing page in background tabs, with a delay. Works in all view modes; click the button again to pause or resume.
// @author Claude
// @license MIT
// @icon https://e-hentai.org/favicon.ico
// @match https://e-hentai.org/*
// @match https://exhentai.org/*
// @run-at document-idle
// @noframes
// @grant GM_openInTab
// ==/UserScript==
(function () {
'use strict';
const DELAY_MS = 1000; // delay between opening each tab
const CONFIRM_OVER = 30; // ask before opening more than this many tabs
// Collect gallery URLs from the results container. `.itg` is the list wrapper in
// every view mode (Minimal/Compact tables, Extended/Thumbnail divs); the regex keeps
// only /g/<gid>/<token>/ links, and the Set drops the cover+title duplicate per item.
function galleryUrls() {
const urls = [...document.querySelectorAll('.itg a[href]')]
.map(a => a.href)
.filter(h => /\/g\/\d+\/[0-9a-f]+/.test(h));
return [...new Set(urls)];
}
function openUrl(url) {
if (typeof GM_openInTab === 'function') {
GM_openInTab(url, { active: false, insert: true });
} else {
window.open(url, '_blank'); // requires popups to be allowed for this site
}
}
function createButton() {
const btn = document.createElement('button');
btn.id = 'link-opener-btn';
btn.textContent = 'Open Galleries';
btn.style.cssText =
'position:fixed;top:20px;right:20px;z-index:9999;padding:10px;' +
'background:#4f535b;color:#fff;border:2px solid #fff;border-radius:5px;' +
'cursor:pointer;box-shadow:0 2px 5px rgba(0,0,0,0.5);';
document.body.appendChild(btn);
let state = 'idle'; // idle | running | paused
let queue = [];
let idx = 0; // number of tabs already opened
let timer = null;
function finish() {
state = 'idle';
timer = null;
queue = [];
idx = 0;
btn.textContent = 'Open Galleries';
btn.style.background = '#4CAF50';
}
function openNext() {
openUrl(queue[idx]);
idx++;
if (idx >= queue.length) { finish(); return; }
btn.textContent = 'Opening ' + idx + '/' + queue.length + ' (click to pause)';
timer = setTimeout(openNext, DELAY_MS);
}
function start() {
const urls = galleryUrls();
if (urls.length === 0) {
alert('No galleries found on this page.');
return;
}
const secs = Math.round(urls.length * DELAY_MS / 1000);
if (urls.length > CONFIRM_OVER &&
!confirm('Open ' + urls.length + ' galleries in background tabs?\n' +
'Takes about ' + secs + 's and may trip E-Hentai rate limits.')) {
return;
}
queue = urls;
idx = 0;
state = 'running';
btn.style.background = '#cccccc';
openNext();
}
function pause() {
clearTimeout(timer);
timer = null;
state = 'paused';
btn.textContent = 'Paused ' + idx + '/' + queue.length + ' (click to resume)';
btn.style.background = '#b8860b';
}
function resume() {
state = 'running';
btn.style.background = '#cccccc';
openNext();
}
btn.addEventListener('click', () => {
if (state === 'idle') start();
else if (state === 'running') pause();
else resume();
});
}
if (galleryUrls().length > 0) createButton();
})();