Add 1 files
Browse files- index.html +214 -64
index.html
CHANGED
|
@@ -117,6 +117,16 @@
|
|
| 117 |
z-index: -1;
|
| 118 |
opacity: 0.1;
|
| 119 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
</style>
|
| 121 |
</head>
|
| 122 |
<body>
|
|
@@ -148,6 +158,10 @@
|
|
| 148 |
</div>
|
| 149 |
<p class="text-sm mt-2 text-gray-400">Works with JPG, PNG, GIF, WEBP, SVG, BMP, TIFF, and more</p>
|
| 150 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 151 |
<div class="mt-6">
|
| 152 |
<h3 class="text-xl mb-2">OPTIONS</h3>
|
| 153 |
<div class="flex flex-wrap gap-4">
|
|
@@ -314,6 +328,7 @@
|
|
| 314 |
const confirmDelete = document.getElementById('confirmDelete');
|
| 315 |
const recursiveCheck = document.getElementById('recursiveCheck');
|
| 316 |
const filterCheck = document.getElementById('filterCheck');
|
|
|
|
| 317 |
|
| 318 |
// Event listeners
|
| 319 |
scrapeBtn.addEventListener('click', startScraping);
|
|
@@ -359,76 +374,198 @@
|
|
| 359 |
progressText.textContent = '0%';
|
| 360 |
statusText.textContent = 'Starting the digital heist...';
|
| 361 |
|
| 362 |
-
//
|
| 363 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 364 |
}
|
| 365 |
|
| 366 |
-
function
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 371 |
|
| 372 |
-
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
statusText.textContent = 'Sticking it to the man...';
|
| 381 |
-
} else {
|
| 382 |
-
statusText.textContent = 'Almost there, comrade...';
|
| 383 |
}
|
| 384 |
|
| 385 |
-
if
|
| 386 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
finishScraping();
|
|
|
|
| 388 |
}
|
| 389 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 390 |
}
|
| 391 |
|
| 392 |
function finishScraping() {
|
| 393 |
-
statusText.textContent = 'Done!
|
| 394 |
-
|
| 395 |
-
// Generate mock images (in a real app, these would come from the scraping)
|
| 396 |
-
const mockImages = generateMockImages();
|
| 397 |
-
scrapedImages = mockImages;
|
| 398 |
|
| 399 |
// Display images
|
| 400 |
-
displayImages(
|
| 401 |
|
| 402 |
// Show results section
|
| 403 |
resultsSection.classList.remove('hidden');
|
| 404 |
-
}
|
| 405 |
-
|
| 406 |
-
function generateMockImages() {
|
| 407 |
-
const formats = ['jpg', 'png', 'gif', 'webp', 'svg', 'bmp', 'tiff'];
|
| 408 |
-
const images = [];
|
| 409 |
-
|
| 410 |
-
const count = Math.floor(Math.random() * 50) + 30; // 30-80 images
|
| 411 |
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
images.push({
|
| 418 |
-
url: `https://picsum.photos/${width}/${height}?random=${i}`,
|
| 419 |
-
format: format,
|
| 420 |
-
width: width,
|
| 421 |
-
height: height,
|
| 422 |
-
originalUrl: `https://example.com/images/image${i}.${format}`
|
| 423 |
-
});
|
| 424 |
-
}
|
| 425 |
-
|
| 426 |
-
// Filter out tiny images if option is checked
|
| 427 |
-
if (filterCheck.checked) {
|
| 428 |
-
return images.filter(img => img.width > 100 && img.height > 100);
|
| 429 |
-
}
|
| 430 |
-
|
| 431 |
-
return images;
|
| 432 |
}
|
| 433 |
|
| 434 |
function displayImages(images) {
|
|
@@ -441,7 +578,7 @@
|
|
| 441 |
|
| 442 |
tile.innerHTML = `
|
| 443 |
<div class="relative pb-full">
|
| 444 |
-
<img src="${img.url}" alt="Scraped image" class="absolute h-full w-full object-cover" loading="lazy">
|
| 445 |
</div>
|
| 446 |
<div class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-70 p-2 hidden group-hover:block">
|
| 447 |
<span class="filetype-badge px-2 py-1 text-xs">${img.format.toUpperCase()}</span>
|
|
@@ -549,13 +686,20 @@
|
|
| 549 |
return;
|
| 550 |
}
|
| 551 |
|
| 552 |
-
//
|
| 553 |
alert(`Preparing to download ${selectedImages.length} images...\n\n(In a real app, this would create a ZIP file)`);
|
| 554 |
|
| 555 |
-
//
|
| 556 |
-
|
| 557 |
-
|
| 558 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
}
|
| 560 |
|
| 561 |
function showDeleteConfirmation() {
|
|
@@ -604,11 +748,17 @@
|
|
| 604 |
// In a real app, this would convert images to the selected format
|
| 605 |
alert(`Converting ${selectedImages.length} images to ${selectedFormat.toUpperCase()}...\n\n(In a real app, this would use a conversion API)`);
|
| 606 |
|
| 607 |
-
// Simulate conversion
|
| 608 |
-
|
| 609 |
-
|
| 610 |
-
|
| 611 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 612 |
}
|
| 613 |
|
| 614 |
// Close modals when clicking outside
|
|
|
|
| 117 |
z-index: -1;
|
| 118 |
opacity: 0.1;
|
| 119 |
}
|
| 120 |
+
|
| 121 |
+
/* CORS proxy warning */
|
| 122 |
+
.cors-warning {
|
| 123 |
+
background-color: #f00;
|
| 124 |
+
color: #fff;
|
| 125 |
+
padding: 10px;
|
| 126 |
+
margin-top: 10px;
|
| 127 |
+
border: 2px solid #fff;
|
| 128 |
+
font-size: 14px;
|
| 129 |
+
}
|
| 130 |
</style>
|
| 131 |
</head>
|
| 132 |
<body>
|
|
|
|
| 158 |
</div>
|
| 159 |
<p class="text-sm mt-2 text-gray-400">Works with JPG, PNG, GIF, WEBP, SVG, BMP, TIFF, and more</p>
|
| 160 |
|
| 161 |
+
<div class="cors-warning hidden" id="corsWarning">
|
| 162 |
+
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.
|
| 163 |
+
</div>
|
| 164 |
+
|
| 165 |
<div class="mt-6">
|
| 166 |
<h3 class="text-xl mb-2">OPTIONS</h3>
|
| 167 |
<div class="flex flex-wrap gap-4">
|
|
|
|
| 328 |
const confirmDelete = document.getElementById('confirmDelete');
|
| 329 |
const recursiveCheck = document.getElementById('recursiveCheck');
|
| 330 |
const filterCheck = document.getElementById('filterCheck');
|
| 331 |
+
const corsWarning = document.getElementById('corsWarning');
|
| 332 |
|
| 333 |
// Event listeners
|
| 334 |
scrapeBtn.addEventListener('click', startScraping);
|
|
|
|
| 374 |
progressText.textContent = '0%';
|
| 375 |
statusText.textContent = 'Starting the digital heist...';
|
| 376 |
|
| 377 |
+
// Check if URL is from same origin
|
| 378 |
+
try {
|
| 379 |
+
const urlObj = new URL(url);
|
| 380 |
+
const currentOrigin = new URL(window.location.href).origin;
|
| 381 |
+
|
| 382 |
+
if (urlObj.origin === currentOrigin) {
|
| 383 |
+
// Same origin - we can scrape directly
|
| 384 |
+
corsWarning.classList.add('hidden');
|
| 385 |
+
scrapeSameOrigin(url);
|
| 386 |
+
} else {
|
| 387 |
+
// Different origin - need to use proxy
|
| 388 |
+
corsWarning.classList.remove('hidden');
|
| 389 |
+
scrapeWithProxy(url);
|
| 390 |
+
}
|
| 391 |
+
} catch (e) {
|
| 392 |
+
alert("INVALID URL! TRY AGAIN, COMRADE!");
|
| 393 |
+
progressContainer.classList.add('hidden');
|
| 394 |
+
}
|
| 395 |
}
|
| 396 |
|
| 397 |
+
function scrapeSameOrigin(url) {
|
| 398 |
+
fetch(url)
|
| 399 |
+
.then(response => response.text())
|
| 400 |
+
.then(html => {
|
| 401 |
+
// Parse HTML and extract images
|
| 402 |
+
const parser = new DOMParser();
|
| 403 |
+
const doc = parser.parseFromString(html, 'text/html');
|
| 404 |
+
const images = doc.querySelectorAll('img');
|
| 405 |
+
|
| 406 |
+
// Update progress
|
| 407 |
+
progressFill.style.width = '50%';
|
| 408 |
+
progressText.textContent = '50%';
|
| 409 |
+
statusText.textContent = 'Found ' + images.length + ' images...';
|
| 410 |
+
|
| 411 |
+
// Process images
|
| 412 |
+
processImages(Array.from(images), url);
|
| 413 |
+
})
|
| 414 |
+
.catch(error => {
|
| 415 |
+
console.error('Error:', error);
|
| 416 |
+
statusText.textContent = 'Failed to scrape: ' + error.message;
|
| 417 |
+
setTimeout(() => {
|
| 418 |
+
progressContainer.classList.add('hidden');
|
| 419 |
+
}, 2000);
|
| 420 |
+
});
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
function scrapeWithProxy(url) {
|
| 424 |
+
// Using a CORS proxy - note: in production you'd want your own proxy server
|
| 425 |
+
const proxyUrl = `https://api.allorigins.win/get?url=${encodeURIComponent(url)}`;
|
| 426 |
+
|
| 427 |
+
fetch(proxyUrl)
|
| 428 |
+
.then(response => response.json())
|
| 429 |
+
.then(data => {
|
| 430 |
+
if (data.contents) {
|
| 431 |
+
// Parse HTML and extract images
|
| 432 |
+
const parser = new DOMParser();
|
| 433 |
+
const doc = parser.parseFromString(data.contents, 'text/html');
|
| 434 |
+
const images = doc.querySelectorAll('img');
|
| 435 |
+
|
| 436 |
+
// Update progress
|
| 437 |
+
progressFill.style.width = '50%';
|
| 438 |
+
progressText.textContent = '50%';
|
| 439 |
+
statusText.textContent = 'Found ' + images.length + ' images...';
|
| 440 |
+
|
| 441 |
+
// Process images
|
| 442 |
+
processImages(Array.from(images), url);
|
| 443 |
+
} else {
|
| 444 |
+
throw new Error('Failed to get content through proxy');
|
| 445 |
+
}
|
| 446 |
+
})
|
| 447 |
+
.catch(error => {
|
| 448 |
+
console.error('Error:', error);
|
| 449 |
+
statusText.textContent = 'Failed to scrape: ' + error.message;
|
| 450 |
+
setTimeout(() => {
|
| 451 |
+
progressContainer.classList.add('hidden');
|
| 452 |
+
}, 2000);
|
| 453 |
+
});
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
function processImages(images, baseUrl) {
|
| 457 |
+
const baseUrlObj = new URL(baseUrl);
|
| 458 |
+
const filteredImages = [];
|
| 459 |
+
|
| 460 |
+
// Process each image
|
| 461 |
+
images.forEach((img, index) => {
|
| 462 |
+
let src = img.getAttribute('src') || '';
|
| 463 |
+
let width = img.width || 0;
|
| 464 |
+
let height = img.height || 0;
|
| 465 |
|
| 466 |
+
// Resolve relative URLs
|
| 467 |
+
if (src.startsWith('//')) {
|
| 468 |
+
src = baseUrlObj.protocol + src;
|
| 469 |
+
} else if (src.startsWith('/')) {
|
| 470 |
+
src = baseUrlObj.origin + src;
|
| 471 |
+
} else if (!src.startsWith('http')) {
|
| 472 |
+
src = new URL(src, baseUrl).href;
|
| 473 |
+
}
|
| 474 |
|
| 475 |
+
// Get file extension
|
| 476 |
+
let format = 'unknown';
|
| 477 |
+
const match = src.match(/\.(jpe?g|png|gif|webp|svg|bmp|tiff?|ico)/i);
|
| 478 |
+
if (match) {
|
| 479 |
+
format = match[1].toLowerCase();
|
|
|
|
|
|
|
|
|
|
| 480 |
}
|
| 481 |
|
| 482 |
+
// Check if we should filter small images
|
| 483 |
+
const shouldFilter = filterCheck.checked;
|
| 484 |
+
if (!shouldFilter || (width > 100 && height > 100)) {
|
| 485 |
+
filteredImages.push({
|
| 486 |
+
url: src,
|
| 487 |
+
format: format,
|
| 488 |
+
width: width,
|
| 489 |
+
height: height,
|
| 490 |
+
originalUrl: src
|
| 491 |
+
});
|
| 492 |
+
}
|
| 493 |
+
});
|
| 494 |
+
|
| 495 |
+
// Update progress
|
| 496 |
+
progressFill.style.width = '80%';
|
| 497 |
+
progressText.textContent = '80%';
|
| 498 |
+
statusText.textContent = 'Processing ' + filteredImages.length + ' images...';
|
| 499 |
+
|
| 500 |
+
// Verify which images are accessible
|
| 501 |
+
verifyImageAccess(filteredImages);
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
function verifyImageAccess(images) {
|
| 505 |
+
let verifiedCount = 0;
|
| 506 |
+
const verifiedImages = [];
|
| 507 |
+
|
| 508 |
+
// Function to check image access
|
| 509 |
+
function checkImage(index) {
|
| 510 |
+
if (index >= images.length) {
|
| 511 |
+
// All images checked
|
| 512 |
+
scrapedImages = verifiedImages;
|
| 513 |
finishScraping();
|
| 514 |
+
return;
|
| 515 |
}
|
| 516 |
+
|
| 517 |
+
const img = images[index];
|
| 518 |
+
const testImg = new Image();
|
| 519 |
+
|
| 520 |
+
testImg.onload = function() {
|
| 521 |
+
// Image is accessible
|
| 522 |
+
verifiedImages.push(img);
|
| 523 |
+
verifiedCount++;
|
| 524 |
+
|
| 525 |
+
// Update progress
|
| 526 |
+
const progress = 80 + (20 * verifiedCount / images.length);
|
| 527 |
+
progressFill.style.width = progress + '%';
|
| 528 |
+
progressText.textContent = Math.floor(progress) + '%';
|
| 529 |
+
statusText.textContent = `Verified ${verifiedCount}/${images.length} images...`;
|
| 530 |
+
|
| 531 |
+
// Check next image
|
| 532 |
+
checkImage(index + 1);
|
| 533 |
+
};
|
| 534 |
+
|
| 535 |
+
testImg.onerror = function() {
|
| 536 |
+
// Image not accessible, skip it
|
| 537 |
+
verifiedCount++;
|
| 538 |
+
|
| 539 |
+
// Update progress
|
| 540 |
+
const progress = 80 + (20 * verifiedCount / images.length);
|
| 541 |
+
progressFill.style.width = progress + '%';
|
| 542 |
+
progressText.textContent = Math.floor(progress) + '%';
|
| 543 |
+
statusText.textContent = `Verified ${verifiedCount}/${images.length} images...`;
|
| 544 |
+
|
| 545 |
+
// Check next image
|
| 546 |
+
checkImage(index + 1);
|
| 547 |
+
};
|
| 548 |
+
|
| 549 |
+
testImg.src = img.url;
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
// Start checking images
|
| 553 |
+
checkImage(0);
|
| 554 |
}
|
| 555 |
|
| 556 |
function finishScraping() {
|
| 557 |
+
statusText.textContent = 'Done! Found ' + scrapedImages.length + ' accessible images.';
|
|
|
|
|
|
|
|
|
|
|
|
|
| 558 |
|
| 559 |
// Display images
|
| 560 |
+
displayImages(scrapedImages);
|
| 561 |
|
| 562 |
// Show results section
|
| 563 |
resultsSection.classList.remove('hidden');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 564 |
|
| 565 |
+
// Hide progress after delay
|
| 566 |
+
setTimeout(() => {
|
| 567 |
+
progressContainer.classList.add('hidden');
|
| 568 |
+
}, 2000);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
}
|
| 570 |
|
| 571 |
function displayImages(images) {
|
|
|
|
| 578 |
|
| 579 |
tile.innerHTML = `
|
| 580 |
<div class="relative pb-full">
|
| 581 |
+
<img src="${img.url}" alt="Scraped image" class="absolute h-full w-full object-cover" loading="lazy" onerror="this.parentElement.parentElement.remove()">
|
| 582 |
</div>
|
| 583 |
<div class="absolute bottom-0 left-0 right-0 bg-black bg-opacity-70 p-2 hidden group-hover:block">
|
| 584 |
<span class="filetype-badge px-2 py-1 text-xs">${img.format.toUpperCase()}</span>
|
|
|
|
| 686 |
return;
|
| 687 |
}
|
| 688 |
|
| 689 |
+
// Create a zip file of selected images
|
| 690 |
alert(`Preparing to download ${selectedImages.length} images...\n\n(In a real app, this would create a ZIP file)`);
|
| 691 |
|
| 692 |
+
// For demo purposes, we'll download each image individually
|
| 693 |
+
selectedImages.forEach(index => {
|
| 694 |
+
const img = scrapedImages[index];
|
| 695 |
+
const a = document.createElement('a');
|
| 696 |
+
a.href = img.url;
|
| 697 |
+
a.download = `stolen-image-${index}.${img.format}`;
|
| 698 |
+
a.target = '_blank';
|
| 699 |
+
document.body.appendChild(a);
|
| 700 |
+
a.click();
|
| 701 |
+
document.body.removeChild(a);
|
| 702 |
+
});
|
| 703 |
}
|
| 704 |
|
| 705 |
function showDeleteConfirmation() {
|
|
|
|
| 748 |
// In a real app, this would convert images to the selected format
|
| 749 |
alert(`Converting ${selectedImages.length} images to ${selectedFormat.toUpperCase()}...\n\n(In a real app, this would use a conversion API)`);
|
| 750 |
|
| 751 |
+
// Simulate conversion by changing the format in our data
|
| 752 |
+
selectedImages.forEach(index => {
|
| 753 |
+
scrapedImages[index].format = selectedFormat;
|
| 754 |
+
});
|
| 755 |
+
|
| 756 |
+
// Update the display
|
| 757 |
+
displayImages(scrapedImages);
|
| 758 |
+
|
| 759 |
+
// Show message
|
| 760 |
+
alert(`Conversion to ${selectedFormat.toUpperCase()} complete! Take that, capitalist image standards!`);
|
| 761 |
+
convertModal.classList.add('hidden');
|
| 762 |
}
|
| 763 |
|
| 764 |
// Close modals when clicking outside
|