Open jav library images in a gallery.
// ==UserScript==
// @name Jav Library Gallery Overlay
// @version 1.2
// @description Open jav library images in a gallery.
// @author KeanCollyer12
// @match https://*.javlibrary.com/*
// @grant none
// @run-at document-end
// @namespace https://greasyfork.org/users/1603790
// ==/UserScript==
(function () {
'use strict';
let currentImages = [];
let currentIndex = 0;
let overlay = null;
let imgElement = null;
let openTabBtn = null;
let currentScale = 1;
let offsetX = 0;
let offsetY = 0;
let isDragging = false;
let lastMouseX = 0;
let lastMouseY = 0;
let counterTimeout = null;
function createOverlay() {
if (!overlay) {
overlay = document.createElement('div');
overlay.id = 'dmm-gallery-overlay';
overlay.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
background: rgba(0,0,0,0.95); display: none;
align-items: center; justify-content: center;
z-index: 2147483647; user-select: none;
`;
overlay.innerHTML = `
<div style="position: absolute; top: 15px; right: 25px; color: white; font-size: 32px; cursor: pointer; z-index: 10; padding: 10px;" id="close-btn">✕</div>
<div id="prev-btn" style="position: fixed; left: 20px; top: 50%; transform: translateY(-50%); color: white; font-size: 60px; cursor: pointer; text-shadow: 0 0 15px black; z-index: 10;">‹</div>
<div id="next-btn" style="position: fixed; right: 20px; top: 50%; transform: translateY(-50%); color: white; font-size: 60px; cursor: pointer; text-shadow: 0 0 15px black; z-index: 10;">›</div>
<div id="image-container" style="position: relative; width: 96%; height: 94vh; display: flex; align-items: center; justify-content: center; overflow: hidden;">
<img id="gallery-main-img" style="max-width: 100%; max-height: 100%; object-fit: contain; display: block; cursor: zoom-in;">
</div>
<div id="counter" style="position: absolute; bottom: 25px; color: #ddd; font-size: 19px; font-family: Arial, sans-serif; opacity: 0; transition: opacity 0.4s;"></div>
<a id="open-tab-btn" href="#" target="_blank" style="position: absolute; top: 20px; left: 25px; background: rgba(255,255,255,0.15); color: white; padding: 8px 14px; border-radius: 4px; text-decoration: none; font-size: 14px; display: none; z-index: 10;">
🔗 Open image in new tab
</a>
`;
document.body.appendChild(overlay);
imgElement = overlay.querySelector('#gallery-main-img');
openTabBtn = overlay.querySelector('#open-tab-btn');
overlay.querySelector('#close-btn').addEventListener('click', closeOverlay);
overlay.querySelector('#prev-btn').addEventListener('click', prevImage);
overlay.querySelector('#next-btn').addEventListener('click', nextImage);
imgElement.addEventListener('wheel', handleWheel, { passive: false });
imgElement.addEventListener('dblclick', resetZoom);
imgElement.addEventListener('mousedown', startDrag);
imgElement.addEventListener('error', handleImageError);
document.addEventListener('mousemove', onDrag);
document.addEventListener('mouseup', endDrag);
document.addEventListener('keydown', handleKeydown);
}
return overlay;
}
function handleImageError() {
if (openTabBtn) {
openTabBtn.href = currentImages[currentIndex] || '#';
openTabBtn.style.display = 'inline-block';
}
}
function showCounter() {
const counter = document.getElementById('counter');
counter.textContent = `${currentIndex + 1} / ${currentImages.length}`;
counter.style.opacity = '1';
clearTimeout(counterTimeout);
counterTimeout = setTimeout(() => counter.style.opacity = '0', 2000);
}
function hideCounterImmediately() {
const counter = document.getElementById('counter');
clearTimeout(counterTimeout);
counter.style.opacity = '0';
}
function updateImage() {
if (openTabBtn) openTabBtn.style.display = 'none';
resetZoom();
imgElement.src = currentImages[currentIndex];
showCounter();
}
function nextImage() { currentIndex = (currentIndex + 1) % currentImages.length; updateImage(); }
function prevImage() { currentIndex = (currentIndex - 1 + currentImages.length) % currentImages.length; updateImage(); }
function closeOverlay() {
if (overlay) overlay.style.display = 'none';
document.body.style.overflow = '';
if (openTabBtn) openTabBtn.style.display = 'none';
resetZoom();
}
function applyTransform() {
imgElement.style.transform = currentScale === 1 ? 'none' : `translate(${offsetX}px, ${offsetY}px) scale(${currentScale})`;
}
function zoomAt(clientX, clientY, factor) {
hideCounterImmediately();
const rect = imgElement.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const offsetFromCenterX = clientX - centerX;
const offsetFromCenterY = clientY - centerY;
const oldScale = currentScale;
currentScale = Math.max(0.95, currentScale * factor);
const scaleChange = currentScale / oldScale;
offsetX = (clientX - centerX) - (offsetFromCenterX * scaleChange) + (offsetX * scaleChange);
offsetY = (clientY - centerY) - (offsetFromCenterY * scaleChange) + (offsetY * scaleChange);
applyTransform();
updateCursor();
}
function handleWheel(e) { e.preventDefault(); const factor = e.deltaY < 0 ? 1.05 : 0.952; zoomAt(e.clientX, e.clientY, factor); }
function resetZoom() { currentScale = 1; offsetX = 0; offsetY = 0; applyTransform(); updateCursor(); }
function updateCursor() { imgElement.style.cursor = currentScale > 1.05 ? 'grab' : 'zoom-in'; }
function startDrag(e) { if (currentScale <= 1.05) return; isDragging = true; lastMouseX = e.clientX; lastMouseY = e.clientY; imgElement.style.cursor = 'grabbing'; }
function onDrag(e) { if (!isDragging) return; offsetX += e.clientX - lastMouseX; offsetY += e.clientY - lastMouseY; lastMouseX = e.clientX; lastMouseY = e.clientY; applyTransform(); }
function endDrag() { isDragging = false; updateCursor(); }
function handleKeydown(e) {
if (!overlay || overlay.style.display === 'none') return;
if (e.key === 'Escape') closeOverlay();
if (e.key === 'ArrowRight') nextImage();
if (e.key === 'ArrowLeft') prevImage();
if (e.key === 'ArrowUp') zoomAt(window.innerWidth/2, window.innerHeight/2, 1.05);
if (e.key === 'ArrowDown') zoomAt(window.innerWidth/2, window.innerHeight/2, 0.952);
}
// ====================== URL HELPERS ======================
function decodeRedirectUrl(href) {
try {
if (href.includes('redirect.php?url=')) {
let url = href.split('redirect.php?url=')[1].split('&')[0];
return decodeURIComponent(url);
}
return href;
} catch (e) {
return href;
}
}
function getPreferredUrl(url) {
if (!url) return url;
if (url.includes('dmm.co.jp') || url.includes('awsimgsrc.dmm.co.jp')) {
if (url.includes('jp-')) return url;
return url.replace(/(\d+)\.jpg$/, 'jp-$1.jpg');
}
return url;
}
function openGallery(imagesArray, startIndex = 0) {
currentImages = imagesArray;
currentIndex = startIndex;
const ov = createOverlay();
ov.style.display = 'flex';
document.body.style.overflow = 'hidden';
updateImage();
}
// ====================== MAIN JACKET / COVER GALLERY ======================
function initCoverGallery() {
const jacketImg = document.querySelector('#video_jacket_img');
if (!jacketImg) return;
const coverImages = [];
// Main cover
if (jacketImg.src) coverImages.push(jacketImg.src);
// Backup image from onerror
const onerrorAttr = jacketImg.getAttribute('onerror');
if (onerrorAttr) {
const match = onerrorAttr.match(/https?:\/\/[^\s'")]+/);
if (match && !coverImages.includes(match[0])) {
coverImages.push(match[0]);
}
}
if (coverImages.length === 0) return;
// Make cover image clickable → only cover gallery
jacketImg.style.cursor = 'zoom-in';
jacketImg.addEventListener('click', (e) => {
e.stopImmediatePropagation();
openGallery(coverImages, 0);
});
}
// ====================== PREVIEW THUMBS GALLERY ======================
function initPreviewGallery() {
const container = document.querySelector('.previewthumbs');
if (!container) return;
const links = container.querySelectorAll('a');
const previewImages = Array.from(links).map(link => getPreferredUrl(decodeRedirectUrl(link.href)));
links.forEach((link, i) => {
link.addEventListener('click', e => {
e.preventDefault();
e.stopImmediatePropagation();
openGallery(previewImages, i);
});
});
}
// ====================== COMMENT GALLERIES ======================
function initCommentGalleries() {
const commentContainers = document.querySelectorAll('td.t, .text, .comment, .post');
commentContainers.forEach(container => {
const galleryImages = [];
const anchors = container.querySelectorAll('a[href*="redirect.php"], a[href*=".jpg"], a[href*=".png"], a[href*=".gif"], a[href*=".webp"]');
anchors.forEach(a => {
let fullUrl = decodeRedirectUrl(a.href);
if (fullUrl.match(/\.(jpg|jpeg|png|gif|webp)$/i)) {
fullUrl = getPreferredUrl(fullUrl);
if (!galleryImages.includes(fullUrl)) galleryImages.push(fullUrl);
}
});
if (galleryImages.length === 0) return;
const clickables = container.querySelectorAll('a[href*="redirect.php"], a[href*=".jpg"], a[href*=".png"], img');
clickables.forEach((el) => {
el.style.cursor = 'zoom-in';
el.addEventListener('click', function (e) {
e.preventDefault();
e.stopImmediatePropagation();
let startIdx = 0;
if (el.tagName === 'A') {
let clickedUrl = decodeRedirectUrl(el.href);
clickedUrl = getPreferredUrl(clickedUrl);
startIdx = galleryImages.findIndex(url => url === clickedUrl);
} else if (el.tagName === 'IMG' && el.parentElement?.tagName === 'A') {
let clickedUrl = decodeRedirectUrl(el.parentElement.href);
clickedUrl = getPreferredUrl(clickedUrl);
startIdx = galleryImages.findIndex(url => url === clickedUrl);
}
if (startIdx === -1) startIdx = 0;
openGallery(galleryImages, startIdx);
}, true);
});
});
}
function init() {
initCoverGallery();
initPreviewGallery();
initCommentGalleries();
}
init();
const observer = new MutationObserver(init);
observer.observe(document.body, { childList: true, subtree: true });
})();