ImageSnatcher

Quickly download images by hovering over them and pressing the S key.

// ==UserScript==
// @name         ImageSnatcher
// @name:es      ImageSnatcher
// @version      1.2.3
// @description  Quickly download images by hovering over them and pressing the S key.
// @description:es Descarga imágenes rápidamente pasando el cursor sobre ellas y presionando la tecla S.
// @author       Adam Jensen
// @match        *://*/*
// @grant        GM_download
// @license      MIT
// @namespace http://tampermonkey.net/
// ==/UserScript==

(function () {
    'use strict';

    const validExtensions = /\.(jpe?g|png|gif|webp|bmp|svg|ico|tiff?|avif|jxl|heic|heif|dds|apng|pjpeg|pjpg|webm)$/i;
    const preprocessedImages = []; // Array to store the last 10 preprocessed images
    const maxHistory = 10; // Maximum number of images to keep in the history
    const downloadQueue = []; // Queue of images to be downloaded
    let isDownloading = false; // Flag to track the download state

    function preprocessImage(target) {
        if (!target) return;

        let imageUrl = null;
        let imageTitle = '';

        if (target.tagName.toLowerCase() === 'img') {
            imageUrl = target.src;
            imageTitle = target.alt.trim() || '';
        } else if (target.style.backgroundImage) {
            imageUrl = target.style.backgroundImage.replace(/url\(["']?([^"']+)["']?\)/, '$1');
            const parentAnchor = target.closest('a');
            if (parentAnchor && parentAnchor.href) {
                imageTitle = parentAnchor.href.split('/').pop().split('?')[0];
            }
        }

        if (!imageTitle) {
            const parentAnchor = target.closest('a');
            if (parentAnchor && parentAnchor.href) {
                imageTitle = parentAnchor.href.split('/').pop().split('?')[0];
            }
        }

        if (!imageTitle) {
            imageTitle = imageUrl ? imageUrl.split('/').pop().split('?')[0] : 'unknown_image';
        }

        imageTitle = imageTitle.replace(/[\/:*?"<>|]/g, '_'); // Make filename safe

        // Ensure the title has a valid extension
        if (!validExtensions.test(imageTitle)) {
            const extensionMatch = imageUrl ? imageUrl.match(validExtensions) : null;
            const extension = extensionMatch ? extensionMatch[0] : '.jpg'; // Default to .jpg
            imageTitle += extension;
        }

        // Check if the image is already in the history
        const existingImage = preprocessedImages.find(img => img.url === imageUrl);
        if (!existingImage) {
            // Add the new image to the history
            preprocessedImages.push({ url: imageUrl, title: imageTitle });
            if (preprocessedImages.length > maxHistory) {
                preprocessedImages.shift(); // Remove the oldest image if history exceeds the limit
            }
            console.log(`Preprocessed image: URL=${imageUrl}, Title=${imageTitle}`);
        }
    }

    // Detect hovered image and preprocess it
    document.addEventListener('mousemove', function (event) {
        if (event.target.tagName.toLowerCase() === 'img' || event.target.style.backgroundImage) {
            preprocessImage(event.target);
        }
    });

    // Change cursor to "loading" during the download
    function setCursorLoading(isLoading) {
        if (isLoading) {
            document.body.style.cursor = 'progress'; // Show loading cursor
        } else {
            document.body.style.cursor = ''; // Restore normal cursor
        }
    }

    // Start downloading the images in the queue
    function processDownloadQueue() {
        if (isDownloading || downloadQueue.length === 0) return; // Don't start a new download if one is already in progress or queue is empty

        const nextImage = downloadQueue.shift(); // Get the next image in the queue
        isDownloading = true; // Set the flag that download is in progress
        setCursorLoading(true); // Change cursor to "loading"

        GM_download({
            url: nextImage.url,
            name: nextImage.title,
            onerror: function (err) {
                console.error('Failed to download the image:', err);
                isDownloading = false; // Reset the download flag
                setCursorLoading(false); // Restore cursor
                processDownloadQueue(); // Continue with the next image in the queue
            },
            onload: function () {
                console.log(`Downloaded image: ${nextImage.title}`);
                isDownloading = false; // Reset the download flag
                setCursorLoading(false); // Restore cursor
                processDownloadQueue(); // Continue with the next image in the queue
            }
        });
    }

    // Handle key press for adding the most recent preprocessed image to the download queue
    document.addEventListener('keydown', function (event) {
        // Ignore the keypress if the target is an input or textarea
        const activeElement = document.activeElement;
        const isInputField = activeElement.tagName.toLowerCase() === 'input' ||
                             activeElement.tagName.toLowerCase() === 'textarea' ||
                             activeElement.isContentEditable;

        if (isInputField || isDownloading) return; // Prevent adding to queue if already downloading

        if (event.key.toLowerCase() === 's' && preprocessedImages.length > 0) {
            const latestImage = preprocessedImages[preprocessedImages.length - 1];
            downloadQueue.push(latestImage); // Add the image to the queue
            console.log(`Added image to queue: ${latestImage.title}`);
            processDownloadQueue(); // Start processing the queue if not already in progress
        }
    });
})();