// ==UserScript==
// @name iStripper features
// @description Replace small images by their full size and add a download button
// @version 1.0.0
// @author BreatFR
// @namespace http://gitlab.com/breatfr
// @match *://*.istripper.com/*
// @require https://cdn.jsdelivr.net/npm/[email protected]/umd/index.min.js
// @copyright 2025, BreatFR (https://breat.fr)
// @icon https://www.istripper.com/favicons/istripper/apple-icon-120x120.png
// @license AGPL-3.0-or-later; https://www.gnu.org/licenses/agpl-3.0.txt
// @grant GM_xmlhttpRequest
// @grant GM_download
// @run-at document-end
// ==/UserScript==
(function() {
'use strict';
const style = document.createElement('style');
style.textContent = `
a.box:hover div.img.icard {
background-position: initial;
}
[src*="https://www.istripper.com/free/sets"] {
transition: transform .2s ease-in-out;
}
a.box:hover [src*="https://www.istripper.com/free/sets"] {
transform: scale(1.1);
transition: transform .2s ease-in-out;
}
.istripper-download-btn {
align-items: center;
background-color: rgba(24, 24, 24, .2);
border: none;
border-radius: .5em;
color: #fff;
cursor: pointer;
display: flex;
flex-direction: column;
font-family: poppins, cursive;
font-size: 1.15rem;
gap: 1em;
justify-content: center;
line-height: 1.5;
padding: .5em 1em;
position: absolute;
transition: background-color .3s ease, box-shadow .3s ease;
z-index: 9999;
}
.istripper-download-btn:hover {
background-color: rgba(255, 80, 80, .85);
box-shadow: 0 0 2em rgba(255, 80, 80, .85);
}
@keyframes spinLoop {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.istripper-btn-icon.spin {
animation: spinLoop 1s linear infinite;
}
@keyframes pulseLoop {
0% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.7; }
100% { transform: scale(1); opacity: 1; }
}
.istripper-btn-icon.pulse {
animation: pulseLoop 1.8s ease-in-out infinite;
}
.istripper-btn-icon {
font-size: 3em;
line-height: 1em;
}
#top {
aspect-ratio: 1 / 1;
background: transparent;
border: none;
bottom: 1em;
cursor: pointer;
font-size: 1.2em;
left: 1em;
position: fixed;
z-index: 9999;
}
`;
document.head.appendChild(style);
function updateImagesAndOverlays() {
let images = document.querySelectorAll('img.illustration');
images.forEach(function(img) {
if (!img.src.includes("_card_full")) {
img.src = img.src.replace(".jpg", "_card_full.jpg");
}
});
let divCollection = document.querySelectorAll('.collection-label');
divCollection.forEach(function(div) {
div.style.pointerEvents = 'none';
});
let divsWithOverlay = document.querySelectorAll('div[data-overlay]');
divsWithOverlay.forEach(function(div) {
div.removeAttribute('data-overlay');
});
let overlayImages = document.querySelectorAll('img.overlay');
overlayImages.forEach(function(img) {
img.parentNode.removeChild(img);
});
let icardDivs = document.querySelectorAll('div.img.icard');
icardDivs.forEach(function(div) {
let backgroundImageURL = div.style.backgroundImage;
if (backgroundImageURL) {
if (!backgroundImageURL.includes("_card_full")) {
div.style.alignItems = 'center';
div.style.backgroundPosition = 'center center';
div.style.backgroundSize = '0';
div.style.display = 'flex';
div.style.justifyContent = 'center';
}
}
});
let icardImg = document.querySelectorAll('div.img.icard > img');
icardImg.forEach(function(img) {
if (!img.src.includes("_card_full") && img.classList.contains("hidden")) {
if (img.src.endsWith(".jpg")) {
img.src = img.src.replace(".jpg", "_card_full.jpg");
img.removeAttribute('class');
img.style.height = "242px";
img.style.opacity = '1';
img.style.pointerEvents = 'auto';
img.style.width = "auto";
} else {
console.log("Image URL does not end with '.jpg':", img.src);
}
}
});
let icardOverlayDivs = document.querySelectorAll('div.icard-overlay');
icardOverlayDivs.forEach(function(div) {
div.style.pointerEvents = 'none';
div.style.position = 'absolute';
div.style.top = '0';
div.style.left = '0';
div.removeAttribute('onmouseover');
});
}
updateImagesAndOverlays();
setInterval(updateImagesAndOverlays, 500);
// Download button
async function downloadImage(url) {
console.log(`[iStripper Collector] Fetching image: ${url}`);
const response = await fetch(url, { mode: 'cors' });
if (!response.ok) throw new Error(`Fetch failed for ${url}`);
const blob = await response.blob();
return { blob };
}
function extractFilename(url) {
const match = url.match(/\/([^\/]+?\.(jpg|jpeg|png|webp|gif))(?=\/|$)/i);
return match ? match[1] : 'image.jpg';
}
function blobToUint8Array(blob) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(new Uint8Array(reader.result));
reader.onerror = reject;
reader.readAsArrayBuffer(blob);
});
}
function setButtonContent(btn, icon, label) {
let iconEl = btn.querySelector('.istripper-btn-icon');
let labelEl = btn.querySelector('.istripper-btn-label');
if (!iconEl) {
iconEl = document.createElement('div');
iconEl.className = 'istripper-btn-icon';
btn.appendChild(iconEl);
}
if (!labelEl) {
labelEl = document.createElement('div');
labelEl.className = 'istripper-btn-label';
btn.appendChild(labelEl);
}
iconEl.textContent = icon;
labelEl.textContent = label;
}
function setIconAnimation(btn, type) {
const icon = btn.querySelector('.istripper-btn-icon');
icon.classList.remove('spin', 'pulse');
void icon.offsetWidth;
if (type) icon.classList.add(type);
}
function updateIconWithAnimation(btn, icon, label, animationClass) {
setButtonContent(btn, icon, label);
requestAnimationFrame(() => setIconAnimation(btn, animationClass));
}
function addDownloadButton() {
const urlMatch = location.href.match(/^https:\/\/www\.istripper\.com\/[^\/]+\/models\/[^\/]+\/[^\/]+$/);
const isPackPage = urlMatch && document.querySelector('img.illustration') && document.querySelectorAll('a.picture').length > 0;
const hasDownloadLink = !!document.querySelector('a > .bt-download');
if (!isPackPage || !hasDownloadLink) return;
console.log('[iStripper Collector] Injecting download button');
const btn = document.createElement('button');
const blackBox = document.querySelector('div.cta.showDetail');
const wrapper = document.querySelector('div[style="background: #f6f6f6;"]');
if (blackBox && wrapper) {
wrapper.style.position = 'relative';
wrapper.appendChild(btn); // injecte d'abord pour que offsetWidth soit dispo
requestAnimationFrame(() => {
const boxRect = blackBox.getBoundingClientRect();
const wrapperRect = wrapper.getBoundingClientRect();
const btnRect = btn.getBoundingClientRect();
const top = boxRect.top - wrapperRect.top - btnRect.height - 32;
const left = boxRect.left - wrapperRect.left + (boxRect.width / 2) - (btnRect.width / 2);
btn.style.top = `${top}px`;
btn.style.left = `${left}px`;
});
} else {
console.warn('[iStripper Collector] Positioning fallback');
document.body.appendChild(btn);
}
btn.className = 'istripper-download-btn';
btn.innerHTML = `
<div class="istripper-btn-icon">📦</div>
<div class="istripper-btn-label">Download images pack + cover</div>
`;
const targetDiv = document.querySelector('div[style="background: #f6f6f6;"]');
if (targetDiv) {
targetDiv.appendChild(btn);
} else {
console.warn('[iStripper Collector] Target div not found, appending to body as fallback');
document.body.appendChild(btn);
}
btn.addEventListener('click', async () => {
btn.disabled = true;
updateIconWithAnimation(btn, '🌀', 'Collecting images...', 'spin');
const coverImg = document.querySelector('img.illustration');
const pictureLinks = Array.from(document.querySelectorAll('a.picture'))
.map(a => a.href)
.filter(href => href.includes('.jpg'));
const allLinks = [];
if (coverImg && coverImg.src) allLinks.push({ url: coverImg.src, name: 'cover' });
pictureLinks.forEach(url => allLinks.push({ url, name: null }));
console.log(`[iStripper Collector] Found ${allLinks.length} images`);
const files = {};
let count = 0;
for (const { url, name } of allLinks) {
try {
const { blob } = await downloadImage(url);
if (!blob || blob.size === 0) {
console.warn(`[iStripper Collector] Skipped empty blob: ${url}`);
continue;
}
const uint8 = await blobToUint8Array(blob);
if (!(uint8 instanceof Uint8Array) || uint8.length === 0) {
console.warn(`[iStripper Collector] Skipped invalid Uint8Array for ${url}`);
continue;
}
const originalName = extractFilename(url);
const finalName = name === 'cover' ? `cover.${originalName.split('.').pop()}` : originalName;
console.log(`[iStripper Collector] Preparing ZIP entry: ${finalName} (${uint8.length} bytes)`);
files[finalName] = uint8;
count++;
updateIconWithAnimation(btn, '📥', `Downloading ${count}/${allLinks.length}`, 'pulse');
} catch (e) {
console.warn(`[iStripper Collector] Failed to download ${url}`, e);
}
}
if (count === 0) {
alert('No images downloaded.');
btn.disabled = false;
updateIconWithAnimation(btn, '📦', 'Download pack + cover', null);
return;
}
updateIconWithAnimation(btn, '📦', `Creating ZIP (${count} images)...`, null);
try {
const zipped = await new Promise((resolve, reject) => {
fflate.zip(files, {}, (err, data) => {
if (err) {
console.error('[ZIP async error]', err);
reject(err);
} else {
resolve(data);
}
});
});
const blob = new Blob([zipped], { type: 'application/zip' });
const zipName = document.title.replace(/[\\/:*?"<>|]/g, '_') || 'istripper_pack';
GM_download({
url: URL.createObjectURL(blob),
name: `${zipName}.zip`,
saveAs: true,
onerror: err => {
console.error('[iStripper Collector] ❌ GM_download failed:', err);
alert('ZIP download failed.');
}
});
} catch (e) {
console.error('[iStripper Collector] ❌ ZIP creation failed:', e);
alert(`ZIP creation failed: ${e?.message || e}`);
}
updateIconWithAnimation(btn, '✅', `${count} images downloaded`, null);
setTimeout(() => {
updateIconWithAnimation(btn, '📦', 'Download pack + cover', null);
btn.disabled = false;
}, 3000);
});
}
addDownloadButton();
// Back to top
const btn = document.createElement('button');
btn.id = 'top';
btn.setAttribute('aria-label', 'Scroll to top');
btn.setAttribute('title', 'Scroll to top');
setButtonContent(btn, '🔝', '')
document.body.appendChild(btn);
const mybutton = document.getElementById("top");
window.onscroll = function () {
scrollFunction();
};
function scrollFunction() {
if (
document.body.scrollTop > 20 ||
document.documentElement.scrollTop > 20
) {
mybutton.style.display = "block";
} else {
mybutton.style.display = "none";
}
}
mybutton.addEventListener("click", backToTop);
function backToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
})();