|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>PUNK IMAGE GRABBER - Stick It To The Man</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
@import url('https://fonts.googleapis.com/css2?family=Rubik+Mono+One&family=VT323&display=swap'); |
|
|
|
|
|
body { |
|
|
background-color: #000; |
|
|
font-family: 'VT323', monospace; |
|
|
color: #fff; |
|
|
overflow-x: hidden; |
|
|
} |
|
|
|
|
|
.title { |
|
|
font-family: 'Rubik Mono One', sans-serif; |
|
|
text-shadow: 3px 3px 0 #f00, -3px -3px 0 #00f; |
|
|
} |
|
|
|
|
|
.graffiti { |
|
|
position: absolute; |
|
|
transform: rotate(-5deg); |
|
|
color: #ff0; |
|
|
font-size: 1.2rem; |
|
|
text-shadow: 2px 2px 0 #000; |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
.graffiti-1 { |
|
|
top: 15%; |
|
|
left: 5%; |
|
|
} |
|
|
|
|
|
.graffiti-2 { |
|
|
top: 65%; |
|
|
right: 5%; |
|
|
} |
|
|
|
|
|
.graffiti-3 { |
|
|
bottom: 10%; |
|
|
left: 10%; |
|
|
} |
|
|
|
|
|
.btn-punk { |
|
|
background: linear-gradient(45deg, #f00, #00f); |
|
|
border: 2px solid #fff; |
|
|
color: white; |
|
|
font-weight: bold; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.btn-punk:hover { |
|
|
transform: scale(1.05); |
|
|
box-shadow: 0 0 15px #f00; |
|
|
} |
|
|
|
|
|
.carousel { |
|
|
background-color: rgba(0, 0, 0, 0.9); |
|
|
border: 3px solid #f00; |
|
|
} |
|
|
|
|
|
.image-tile { |
|
|
border: 2px solid #fff; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.image-tile:hover { |
|
|
border-color: #f00; |
|
|
transform: scale(1.02); |
|
|
} |
|
|
|
|
|
.selected { |
|
|
border: 3px solid #00f; |
|
|
box-shadow: 0 0 15px #00f; |
|
|
} |
|
|
|
|
|
.progress-bar { |
|
|
height: 10px; |
|
|
background-color: #333; |
|
|
} |
|
|
|
|
|
.progress-fill { |
|
|
height: 100%; |
|
|
background: linear-gradient(90deg, #f00, #00f); |
|
|
transition: width 0.3s; |
|
|
} |
|
|
|
|
|
.filetype-badge { |
|
|
background-color: #333; |
|
|
border: 1px solid #fff; |
|
|
} |
|
|
|
|
|
.convert-option { |
|
|
border: 1px solid #fff; |
|
|
transition: all 0.3s; |
|
|
} |
|
|
|
|
|
.convert-option:hover { |
|
|
background-color: #333; |
|
|
border-color: #f00; |
|
|
} |
|
|
|
|
|
.convert-option.selected-format { |
|
|
background-color: #00f; |
|
|
color: #fff; |
|
|
} |
|
|
|
|
|
.spray-paint { |
|
|
position: absolute; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
pointer-events: none; |
|
|
z-index: -1; |
|
|
opacity: 0.1; |
|
|
} |
|
|
|
|
|
|
|
|
.cors-warning { |
|
|
background-color: #f00; |
|
|
color: #fff; |
|
|
padding: 10px; |
|
|
margin-top: 10px; |
|
|
border: 2px solid #fff; |
|
|
font-size: 14px; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div class="spray-paint"> |
|
|
<div style="position: absolute; top: 20%; left: 10%; width: 200px; height: 150px; background-color: #f00; transform: rotate(-15deg);"></div> |
|
|
<div style="position: absolute; top: 60%; right: 15%; width: 180px; height: 120px; background-color: #00f; transform: rotate(10deg);"></div> |
|
|
<div style="position: absolute; bottom: 10%; left: 50%; width: 150px; height: 100px; background-color: #ff0; transform: rotate(-5deg);"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="graffiti graffiti-1">FUCK CAPITALISM</div> |
|
|
<div class="graffiti graffiti-2">TRUMP IS A FASCIST</div> |
|
|
<div class="graffiti graffiti-3">EAT THE RICH</div> |
|
|
|
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
<header class="text-center mb-8"> |
|
|
<h1 class="title text-5xl md:text-6xl mb-4">PUNK IMAGE GRABBER</h1> |
|
|
<p class="text-xl">Steal images like you steal from the bourgeoisie</p> |
|
|
</header> |
|
|
|
|
|
<div class="max-w-3xl mx-auto bg-gray-900 p-6 rounded-lg border-2 border-red-500 mb-8 relative"> |
|
|
<h2 class="text-2xl mb-4">ENTER URL TO SCRAPE</h2> |
|
|
<div class="flex"> |
|
|
<input type="text" id="urlInput" placeholder="https://example.com" class="flex-grow px-4 py-2 bg-black text-white border-2 border-white focus:border-red-500 outline-none"> |
|
|
<button id="scrapeBtn" class="btn-punk px-6 py-2 ml-2"> |
|
|
<i class="fas fa-skull-crossbones mr-2"></i>GRAB 'EM |
|
|
</button> |
|
|
</div> |
|
|
<p class="text-sm mt-2 text-gray-400">Works with JPG, PNG, GIF, WEBP, SVG, BMP, TIFF, and more</p> |
|
|
|
|
|
<div class="cors-warning hidden" id="corsWarning"> |
|
|
WARNING: Due to CORS restrictions, we're using a proxy to scrape this site. Some images may not load properly. For best results, use the browser extension version. |
|
|
</div> |
|
|
|
|
|
<div class="mt-6"> |
|
|
<h3 class="text-xl mb-2">OPTIONS</h3> |
|
|
<div class="flex flex-wrap gap-4"> |
|
|
<label class="flex items-center"> |
|
|
<input type="checkbox" id="recursiveCheck" class="mr-2"> |
|
|
<span>Recursive scraping (Deeper = more dangerous)</span> |
|
|
</label> |
|
|
<label class="flex items-center"> |
|
|
<input type="checkbox" id="filterCheck" class="mr-2" checked> |
|
|
<span>Filter out tiny images</span> |
|
|
</label> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="mt-6 hidden" id="progressContainer"> |
|
|
<div class="flex justify-between mb-2"> |
|
|
<span>Scraping progress...</span> |
|
|
<span id="progressText">0%</span> |
|
|
</div> |
|
|
<div class="progress-bar"> |
|
|
<div id="progressFill" class="progress-fill" style="width: 0%"></div> |
|
|
</div> |
|
|
<div class="mt-2 text-sm" id="statusText">Waiting to start...</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="hidden" id="resultsSection"> |
|
|
<div class="flex justify-between items-center mb-4"> |
|
|
<h2 class="text-3xl">YOUR STOLEN GOODS</h2> |
|
|
<div> |
|
|
<button id="selectAllBtn" class="btn-punk px-4 py-1 mr-2"> |
|
|
<i class="fas fa-check-double mr-1"></i>SELECT ALL |
|
|
</button> |
|
|
<button id="convertBtn" class="btn-punk px-4 py-1 mr-2"> |
|
|
<i class="fas fa-exchange-alt mr-1"></i>CONVERT |
|
|
</button> |
|
|
<button id="downloadBtn" class="btn-punk px-4 py-1 mr-2"> |
|
|
<i class="fas fa-download mr-1"></i>DOWNLOAD |
|
|
</button> |
|
|
<button id="deleteBtn" class="btn-punk bg-red-900 px-4 py-1"> |
|
|
<i class="fas fa-trash mr-1"></i>DELETE |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="imageGrid" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-4 mb-8"> |
|
|
|
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="carouselModal" class="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 hidden"> |
|
|
<div class="carousel w-full max-w-4xl p-4 relative"> |
|
|
<button id="closeCarousel" class="absolute top-2 right-2 text-white text-2xl z-50">×</button> |
|
|
|
|
|
<div class="relative h-96 overflow-hidden mb-4"> |
|
|
<img id="carouselImage" src="" alt="" class="w-full h-full object-contain"> |
|
|
</div> |
|
|
|
|
|
<div class="flex justify-between items-center"> |
|
|
<button id="prevBtn" class="btn-punk px-4 py-1"> |
|
|
<i class="fas fa-arrow-left mr-1"></i>PREV |
|
|
</button> |
|
|
<span id="imageCounter" class="text-xl">1/1</span> |
|
|
<button id="nextBtn" class="btn-punk px-4 py-1"> |
|
|
NEXT <i class="fas fa-arrow-right ml-1"></i> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="convertModal" class="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 hidden"> |
|
|
<div class="carousel w-full max-w-2xl p-6 relative"> |
|
|
<button id="closeConvert" class="absolute top-2 right-2 text-white text-2xl z-50">×</button> |
|
|
|
|
|
<h2 class="text-3xl mb-6">CONVERT TO WHAT?</h2> |
|
|
|
|
|
<div class="grid grid-cols-2 sm:grid-cols-3 gap-4 mb-8"> |
|
|
<div class="convert-option p-4 text-center cursor-pointer" data-format="jpg"> |
|
|
<i class="fas fa-file-image text-4xl mb-2"></i> |
|
|
<div>JPG</div> |
|
|
</div> |
|
|
<div class="convert-option p-4 text-center cursor-pointer" data-format="png"> |
|
|
<i class="fas fa-file-image text-4xl mb-2"></i> |
|
|
<div>PNG</div> |
|
|
</div> |
|
|
<div class="convert-option p-4 text-center cursor-pointer" data-format="webp"> |
|
|
<i class="fas fa-file-image text-4xl mb-2"></i> |
|
|
<div>WEBP</div> |
|
|
</div> |
|
|
<div class="convert-option p-4 text-center cursor-pointer" data-format="gif"> |
|
|
<i class="fas fa-file-image text-4xl mb-2"></i> |
|
|
<div>GIF</div> |
|
|
</div> |
|
|
<div class="convert-option p-4 text-center cursor-pointer" data-format="bmp"> |
|
|
<i class="fas fa-file-image text-4xl mb-2"></i> |
|
|
<div>BMP</div> |
|
|
</div> |
|
|
<div class="convert-option p-4 text-center cursor-pointer" data-format="ico"> |
|
|
<i class="fas fa-file-image text-4xl mb-2"></i> |
|
|
<div>ICO</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="flex justify-center"> |
|
|
<button id="confirmConvertBtn" class="btn-punk px-6 py-2"> |
|
|
<i class="fas fa-magic mr-2"></i>CONVERT THESE BITCHES |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="deleteModal" class="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 hidden"> |
|
|
<div class="carousel w-full max-w-md p-6 relative"> |
|
|
<h2 class="text-3xl mb-6 text-center">DESTROY THE EVIDENCE?</h2> |
|
|
<p class="text-xl mb-8 text-center">This will delete <span id="deleteCount">0</span> selected images permanently.</p> |
|
|
|
|
|
<div class="flex justify-center gap-4"> |
|
|
<button id="cancelDelete" class="btn-punk px-6 py-2"> |
|
|
<i class="fas fa-times mr-2"></i>NO, KEEP 'EM |
|
|
</button> |
|
|
<button id="confirmDelete" class="btn-punk bg-red-900 px-6 py-2"> |
|
|
<i class="fas fa-trash mr-2"></i>BURN IT ALL |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
let scrapedImages = []; |
|
|
let currentCarouselIndex = 0; |
|
|
let selectedImages = []; |
|
|
let selectedFormat = null; |
|
|
|
|
|
|
|
|
const urlInput = document.getElementById('urlInput'); |
|
|
const scrapeBtn = document.getElementById('scrapeBtn'); |
|
|
const progressContainer = document.getElementById('progressContainer'); |
|
|
const progressFill = document.getElementById('progressFill'); |
|
|
const progressText = document.getElementById('progressText'); |
|
|
const statusText = document.getElementById('statusText'); |
|
|
const resultsSection = document.getElementById('resultsSection'); |
|
|
const imageGrid = document.getElementById('imageGrid'); |
|
|
const carouselModal = document.getElementById('carouselModal'); |
|
|
const carouselImage = document.getElementById('carouselImage'); |
|
|
const imageCounter = document.getElementById('imageCounter'); |
|
|
const closeCarousel = document.getElementById('closeCarousel'); |
|
|
const prevBtn = document.getElementById('prevBtn'); |
|
|
const nextBtn = document.getElementById('nextBtn'); |
|
|
const selectAllBtn = document.getElementById('selectAllBtn'); |
|
|
const downloadBtn = document.getElementById('downloadBtn'); |
|
|
const deleteBtn = document.getElementById('deleteBtn'); |
|
|
const convertBtn = document.getElementById('convertBtn'); |
|
|
const convertModal = document.getElementById('convertModal'); |
|
|
const closeConvert = document.getElementById('closeConvert'); |
|
|
const confirmConvertBtn = document.getElementById('confirmConvertBtn'); |
|
|
const deleteModal = document.getElementById('deleteModal'); |
|
|
const deleteCount = document.getElementById('deleteCount'); |
|
|
const cancelDelete = document.getElementById('cancelDelete'); |
|
|
const confirmDelete = document.getElementById('confirmDelete'); |
|
|
const recursiveCheck = document.getElementById('recursiveCheck'); |
|
|
const filterCheck = document.getElementById('filterCheck'); |
|
|
const corsWarning = document.getElementById('corsWarning'); |
|
|
|
|
|
|
|
|
scrapeBtn.addEventListener('click', startScraping); |
|
|
closeCarousel.addEventListener('click', () => carouselModal.classList.add('hidden')); |
|
|
prevBtn.addEventListener('click', showPrevImage); |
|
|
nextBtn.addEventListener('click', showNextImage); |
|
|
selectAllBtn.addEventListener('click', toggleSelectAll); |
|
|
downloadBtn.addEventListener('click', downloadSelected); |
|
|
deleteBtn.addEventListener('click', showDeleteConfirmation); |
|
|
convertBtn.addEventListener('click', () => convertModal.classList.remove('hidden')); |
|
|
closeConvert.addEventListener('click', () => convertModal.classList.add('hidden')); |
|
|
confirmConvertBtn.addEventListener('click', convertImages); |
|
|
cancelDelete.addEventListener('click', () => deleteModal.classList.add('hidden')); |
|
|
confirmDelete.addEventListener('click', deleteSelected); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.convert-option').forEach(option => { |
|
|
option.addEventListener('click', function() { |
|
|
document.querySelectorAll('.convert-option').forEach(opt => { |
|
|
opt.classList.remove('selected-format'); |
|
|
}); |
|
|
this.classList.add('selected-format'); |
|
|
selectedFormat = this.dataset.format; |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
function startScraping() { |
|
|
const url = urlInput.value.trim(); |
|
|
if (!url) { |
|
|
alert("ENTER A URL, YOU REBEL!"); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
scrapedImages = []; |
|
|
selectedImages = []; |
|
|
imageGrid.innerHTML = ''; |
|
|
|
|
|
|
|
|
progressContainer.classList.remove('hidden'); |
|
|
progressFill.style.width = '0%'; |
|
|
progressText.textContent = '0%'; |
|
|
statusText.textContent = 'Starting the digital heist...'; |
|
|
|
|
|
|
|
|
try { |
|
|
const urlObj = new URL(url); |
|
|
const currentOrigin = new URL(window.location.href).origin; |
|
|
|
|
|
if (urlObj.origin === currentOrigin) { |
|
|
|
|
|
corsWarning.classList.add('hidden'); |
|
|
scrapeSameOrigin(url); |
|
|
} else { |
|
|
|
|
|
corsWarning.classList.remove('hidden'); |
|
|
scrapeWithProxy(url); |
|
|
} |
|
|
} catch (e) { |
|
|
alert("INVALID URL! TRY AGAIN, COMRADE!"); |
|
|
progressContainer.classList.add('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
function scrapeSameOrigin(url) { |
|
|
fetch(url) |
|
|
.then(response => response.text()) |
|
|
.then(html => { |
|
|
|
|
|
const parser = new DOMParser(); |
|
|
const doc = parser.parseFromString(html, 'text/html'); |
|
|
const images = doc.querySelectorAll('img'); |
|
|
|
|
|
|
|
|
progressFill.style.width = '50%'; |
|
|
progressText.textContent = '50%'; |
|
|
statusText.textContent = 'Found ' + images.length + ' images...'; |
|
|
|
|
|
|
|
|
processImages(Array.from(images), url); |
|
|
}) |
|
|
.catch(error => { |
|
|
console.error('Error:', error); |
|
|
statusText.textContent = 'Failed to scrape: ' + error.message; |
|
|
setTimeout(() => { |
|
|
progressContainer.classList.add('hidden'); |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
|
|
|
function scrapeWithProxy(url) { |
|
|
|
|
|
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`; |
|
|
|
|
|
fetch(proxyUrl) |
|
|
.then(response => response.json()) |
|
|
.then(data => { |
|
|
if (data.contents) { |
|
|
|
|
|
const parser = new DOMParser(); |
|
|
const doc = parser.parseFromString(data.contents, 'text/html'); |
|
|
const images = doc.querySelectorAll('img'); |
|
|
|
|
|
|
|
|
progressFill.style.width = '50%'; |
|
|
progressText.textContent = '50%'; |
|
|
statusText.textContent = 'Found ' + images.length + ' images...'; |
|
|
|
|
|
|
|
|
processImages(Array.from(images), url); |
|
|
} else { |
|
|
throw new Error('Failed to get content through proxy'); |
|
|
} |
|
|
}) |
|
|
.catch(error => { |
|
|
console.error('Error:', error); |
|
|
statusText.textContent = 'Failed to scrape: ' + error.message; |
|
|
setTimeout(() => { |
|
|
progressContainer.classList.add('hidden'); |
|
|
}, 2000); |
|
|
}); |
|
|
} |
|
|
|
|
|
function processImages(images, baseUrl) { |
|
|
const baseUrlObj = new URL(baseUrl); |
|
|
const filteredImages = []; |
|
|
|
|
|
|
|
|
images.forEach((img, index) => { |
|
|
let src = img.getAttribute('src') || ''; |
|
|
let width = img.width || 0; |
|
|
let height = img.height || 0; |
|
|
|
|
|
|
|
|
if (src.startsWith('//')) { |
|
|
src = baseUrlObj.protocol + src; |
|
|
} else if (src.startsWith('/')) { |
|
|
src = baseUrlObj.origin + src; |
|
|
} else if (!src.startsWith('http')) { |
|
|
src = new URL(src, baseUrl).href; |
|
|
} |
|
|
|
|
|
|
|
|
let format = 'unknown'; |
|
|
const match = src.match(/\.(jpe?g|png|gif|webp|svg|bmp|tiff?|ico)/i); |
|
|
if (match) { |
|
|
format = match[1].toLowerCase(); |
|
|
} |
|
|
|
|
|
|
|
|
const shouldFilter = filterCheck.checked; |
|
|
if (!shouldFilter || (width > 100 && height > 100)) { |
|
|
filteredImages.push({ |
|
|
url: src, |
|
|
format: format, |
|
|
width: width, |
|
|
height: height, |
|
|
originalUrl: src |
|
|
}); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
progressFill.style.width = '80%'; |
|
|
progressText.textContent = '80%'; |
|
|
statusText.textContent = 'Processing ' + filteredImages.length + ' images...'; |
|
|
|
|
|
|
|
|
verifyImageAccess(filteredImages); |
|
|
} |
|
|
|
|
|
function verifyImageAccess(images) { |
|
|
let verifiedCount = 0; |
|
|
const verifiedImages = []; |
|
|
|
|
|
|
|
|
function checkImage(index) { |
|
|
if (index >= images.length) { |
|
|
|
|
|
scrapedImages = verifiedImages; |
|
|
finishScraping(); |
|
|
return; |
|
|
} |
|
|
|
|
|
const img = images[index]; |
|
|
const testImg = new Image(); |
|
|
|
|
|
testImg.onload = function() { |
|
|
|
|
|
verifiedImages.push(img); |
|
|
verifiedCount++; |
|
|
|
|
|
|
|
|
const progress = 80 + (20 * verifiedCount / images.length); |
|
|
progressFill.style.width = progress + '%'; |
|
|
progressText.textContent = Math.floor(progress) + '%'; |
|
|
statusText.textContent = `Verified ${verifiedCount}/${images.length} images...`; |
|
|
|
|
|
|
|
|
checkImage(index + 1); |
|
|
}; |
|
|
|
|
|
testImg.onerror = function() { |
|
|
|
|
|
verifiedCount++; |
|
|
|
|
|
|
|
|
const progress = 80 + (20 * verifiedCount / images.length); |
|
|
progressFill.style.width = progress + '%'; |
|
|
progressText.textContent = Math.floor(progress) + '%'; |
|
|
statusText.textContent = `Verified ${verifiedCount}/${images.length} images...`; |
|
|
|
|
|
|
|
|
checkImage(index + 1); |
|
|
}; |
|
|
|
|
|
testImg.src = img.url; |
|
|
} |
|
|
|
|
|
|
|
|
checkImage(0); |
|
|
} |
|
|
|
|
|
function finishScraping() { |
|
|
statusText.textContent = 'Done! Found ' + scrapedImages.length + ' accessible images.'; |
|
|
|
|
|
|
|
|
displayImages(scrapedImages); |
|
|
|
|
|
|
|
|
resultsSection.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
progressContainer.classList.add('hidden'); |
|
|
}, 2000); |
|
|
} |
|
|
|
|
|
function displayImages(images) { |
|
|
imageGrid.innerHTML = ''; |
|
|
|
|
|
images.forEach((img, index) => { |
|
|
const tile = document.createElement('div'); |
|
|
tile.className = 'image-tile relative group cursor-pointer'; |
|
|
tile.dataset.index = index; |
|
|
|
|
|
tile.innerHTML = ` |
|
|
<div class="relative pb-full"> |
|
|
<img src="${img.url}" alt="Scraped image" class="absolute h-full w-full object-cover" loading="lazy" onerror="this.parentElement.parentElement.remove()"> |
|
|
</div> |
|
|
<div class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-70 p-2 hidden group-hover:block"> |
|
|
<span class="filetype-badge px-2 py-1 text-xs">${img.format.toUpperCase()}</span> |
|
|
<span class="text-xs ml-2">${img.width}x${img.height}</span> |
|
|
</div> |
|
|
<div class="absolute top-2 right-2 hidden group-hover:block"> |
|
|
<button class="select-btn bg-blue-500 text-white rounded-full w-6 h-6 flex items-center justify-center"> |
|
|
<i class="fas fa-check text-xs"></i> |
|
|
</button> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
tile.addEventListener('click', () => { |
|
|
currentCarouselIndex = index; |
|
|
showImageInCarousel(); |
|
|
}); |
|
|
|
|
|
|
|
|
const selectBtn = tile.querySelector('.select-btn'); |
|
|
selectBtn.addEventListener('click', (e) => { |
|
|
e.stopPropagation(); |
|
|
toggleImageSelection(index, tile); |
|
|
}); |
|
|
|
|
|
imageGrid.appendChild(tile); |
|
|
}); |
|
|
} |
|
|
|
|
|
function showImageInCarousel() { |
|
|
if (scrapedImages.length === 0) return; |
|
|
|
|
|
const img = scrapedImages[currentCarouselIndex]; |
|
|
carouselImage.src = img.url; |
|
|
imageCounter.textContent = `${currentCarouselIndex + 1}/${scrapedImages.length}`; |
|
|
carouselModal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function showPrevImage() { |
|
|
if (currentCarouselIndex > 0) { |
|
|
currentCarouselIndex--; |
|
|
} else { |
|
|
currentCarouselIndex = scrapedImages.length - 1; |
|
|
} |
|
|
showImageInCarousel(); |
|
|
} |
|
|
|
|
|
function showNextImage() { |
|
|
if (currentCarouselIndex < scrapedImages.length - 1) { |
|
|
currentCarouselIndex++; |
|
|
} else { |
|
|
currentCarouselIndex = 0; |
|
|
} |
|
|
showImageInCarousel(); |
|
|
} |
|
|
|
|
|
function toggleImageSelection(index, tile = null) { |
|
|
if (!tile) { |
|
|
tile = document.querySelector(`.image-tile[data-index="${index}"]`); |
|
|
} |
|
|
|
|
|
const selectedIndex = selectedImages.indexOf(index); |
|
|
if (selectedIndex === -1) { |
|
|
selectedImages.push(index); |
|
|
tile.classList.add('selected'); |
|
|
} else { |
|
|
selectedImages.splice(selectedIndex, 1); |
|
|
tile.classList.remove('selected'); |
|
|
} |
|
|
|
|
|
updateSelectionCount(); |
|
|
} |
|
|
|
|
|
function toggleSelectAll() { |
|
|
const allSelected = selectedImages.length === scrapedImages.length; |
|
|
|
|
|
if (allSelected) { |
|
|
|
|
|
selectedImages = []; |
|
|
document.querySelectorAll('.image-tile').forEach(tile => { |
|
|
tile.classList.remove('selected'); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
selectedImages = Array.from({ length: scrapedImages.length }, (_, i) => i); |
|
|
document.querySelectorAll('.image-tile').forEach(tile => { |
|
|
tile.classList.add('selected'); |
|
|
}); |
|
|
} |
|
|
|
|
|
updateSelectionCount(); |
|
|
} |
|
|
|
|
|
function updateSelectionCount() { |
|
|
const count = selectedImages.length; |
|
|
if (count > 0) { |
|
|
selectAllBtn.innerHTML = `<i class="fas fa-check-double mr-1"></i>${count === scrapedImages.length ? 'DESELECT ALL' : 'SELECT ALL'}`; |
|
|
} else { |
|
|
selectAllBtn.innerHTML = '<i class="fas fa-check-double mr-1"></i>SELECT ALL'; |
|
|
} |
|
|
} |
|
|
|
|
|
function downloadSelected() { |
|
|
if (selectedImages.length === 0) { |
|
|
alert("SELECT SOME IMAGES FIRST, YOU ANARCHIST!"); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
alert(`Preparing to download ${selectedImages.length} images...\n\n(In a real app, this would create a ZIP file)`); |
|
|
|
|
|
|
|
|
selectedImages.forEach(index => { |
|
|
const img = scrapedImages[index]; |
|
|
const a = document.createElement('a'); |
|
|
a.href = img.url; |
|
|
a.download = `stolen-image-${index}.${img.format}`; |
|
|
a.target = '_blank'; |
|
|
document.body.appendChild(a); |
|
|
a.click(); |
|
|
document.body.removeChild(a); |
|
|
}); |
|
|
} |
|
|
|
|
|
function showDeleteConfirmation() { |
|
|
if (selectedImages.length === 0) { |
|
|
alert("SELECT SOME IMAGES TO DELETE, YOU DESTRUCTIVE PUNK!"); |
|
|
return; |
|
|
} |
|
|
|
|
|
deleteCount.textContent = selectedImages.length; |
|
|
deleteModal.classList.remove('hidden'); |
|
|
} |
|
|
|
|
|
function deleteSelected() { |
|
|
|
|
|
const sortedIndexes = [...selectedImages].sort((a, b) => b - a); |
|
|
|
|
|
|
|
|
sortedIndexes.forEach(index => { |
|
|
scrapedImages.splice(index, 1); |
|
|
}); |
|
|
|
|
|
|
|
|
selectedImages = []; |
|
|
|
|
|
|
|
|
displayImages(scrapedImages); |
|
|
|
|
|
|
|
|
deleteModal.classList.add('hidden'); |
|
|
|
|
|
|
|
|
alert("Images deleted! The evidence is destroyed!"); |
|
|
} |
|
|
|
|
|
function convertImages() { |
|
|
if (selectedImages.length === 0) { |
|
|
alert("SELECT SOME IMAGES TO CONVERT, YOU REBEL!"); |
|
|
return; |
|
|
} |
|
|
|
|
|
if (!selectedFormat) { |
|
|
alert("CHOOSE A FORMAT, YOU ANARCHIST!"); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
alert(`Converting ${selectedImages.length} images to ${selectedFormat.toUpperCase()}...\n\n(In a real app, this would use a conversion API)`); |
|
|
|
|
|
|
|
|
selectedImages.forEach(index => { |
|
|
scrapedImages[index].format = selectedFormat; |
|
|
}); |
|
|
|
|
|
|
|
|
displayImages(scrapedImages); |
|
|
|
|
|
|
|
|
alert(`Conversion to ${selectedFormat.toUpperCase()} complete! Take that, capitalist image standards!`); |
|
|
convertModal.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
window.addEventListener('click', (e) => { |
|
|
if (e.target === carouselModal) { |
|
|
carouselModal.classList.add('hidden'); |
|
|
} |
|
|
if (e.target === convertModal) { |
|
|
convertModal.classList.add('hidden'); |
|
|
} |
|
|
if (e.target === deleteModal) { |
|
|
deleteModal.classList.add('hidden'); |
|
|
} |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=maydayjeffk/batch-image-downloader-and-converter" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |